1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.fulcrum.yaafi.interceptor.util;
20
21 /**
22 * <p>
23 * <code>StopWatch</code> provides a convenient API for timings.
24 * </p>
25 *
26 * <p>
27 * To start the watch, call {@link #start()}. At this point you can:
28 * </p>
29 * <ul>
30 * <li>{@link #split()} the watch to get the time whilst the watch continues in
31 * the background. {@link #unsplit()} will remove the effect of the split. At
32 * this point, these three options are available again.</li>
33 * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the
34 * watch to continue. Any time between the suspend and resume will not be
35 * counted in the total. At this point, these three options are available
36 * again.</li>
37 * <li>{@link #stop()} the watch to complete the timing session.</li>
38 * </ul>
39 *
40 * <p>
41 * It is intended that the output methods {@link #toString()} and
42 * {@link #getTime()} should only be called after stop, split or suspend,
43 * however a suitable result will be returned at other points.
44 * </p>
45 *
46 * <p>
47 * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you
48 * cannot now call stop before start, resume before suspend or unsplit before
49 * split.
50 * </p>
51 *
52 * <p>
53 * 1. split(), suspend(), or stop() cannot be invoked twice <br>
54 * 2. unsplit() may only be called if the watch has been split()<br>
55 * 3. resume() may only be called if the watch has been suspend()<br>
56 * 4. start() cannot be called twice without calling reset()
57 * </p>
58 *
59 * @author Henri Yandell
60 * @author Stephen Colebourne
61 * @since 2.0
62 * @version $Id$
63 */
64 public class StopWatch {
65
66 // running states
67 private static final int STATE_UNSTARTED = 0;
68 private static final int STATE_RUNNING = 1;
69 private static final int STATE_STOPPED = 2;
70 private static final int STATE_SUSPENDED = 3;
71
72 // split state
73 private static final int STATE_UNSPLIT = 10;
74 private static final int STATE_SPLIT = 11;
75
76 /**
77 * The current running state of the StopWatch.
78 */
79 private int runningState = STATE_UNSTARTED;
80
81 /**
82 * Whether the stopwatch has a split time recorded.
83 */
84 private int splitState = STATE_UNSPLIT;
85
86 /**
87 * The start time.
88 */
89 private long startTime = -1;
90 /**
91 * The stop time.
92 */
93 private long stopTime = -1;
94
95 /**
96 * <p>
97 * Constructor.
98 * </p>
99 */
100 public StopWatch() {
101 // nothing to do
102 }
103
104 /**
105 * <p>
106 * Start the stopwatch.
107 * </p>
108 *
109 * <p>
110 * This method starts a new timing session, clearing any previous values.
111 * </p>
112 *
113 * @throws IllegalStateException if the StopWatch is already running.
114 */
115 public void start() {
116 if (this.runningState == STATE_STOPPED) {
117 throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
118 }
119 if (this.runningState != STATE_UNSTARTED) {
120 throw new IllegalStateException("Stopwatch already started. ");
121 }
122 stopTime = -1;
123 startTime = System.currentTimeMillis();
124 this.runningState = STATE_RUNNING;
125 }
126
127 /**
128 * <p>
129 * Stop the stopwatch.
130 * </p>
131 *
132 * <p>
133 * This method ends a new timing session, allowing the time to be retrieved.
134 * </p>
135 *
136 * @throws IllegalStateException if the StopWatch is not running.
137 */
138 public void stop() {
139 if (this.runningState != STATE_RUNNING && this.runningState != STATE_SUSPENDED) {
140 throw new IllegalStateException("Stopwatch is not running. ");
141 }
142 stopTime = System.currentTimeMillis();
143 this.runningState = STATE_STOPPED;
144 }
145
146 /**
147 * <p>
148 * Resets the stopwatch. Stops it if need be.
149 * </p>
150 *
151 * <p>
152 * This method clears the internal values to allow the object to be reused.
153 * </p>
154 */
155 public void reset() {
156 this.runningState = STATE_UNSTARTED;
157 this.splitState = STATE_UNSPLIT;
158 startTime = -1;
159 stopTime = -1;
160 }
161
162 /**
163 * <p>
164 * Split the time.
165 * </p>
166 *
167 * <p>
168 * This method sets the stop time of the watch to allow a time to be extracted.
169 * The start time is unaffected, enabling {@link #unsplit()} to continue the
170 * timing from the original start point.
171 * </p>
172 *
173 * @throws IllegalStateException if the StopWatch is not running.
174 */
175 public void split() {
176 if (this.runningState != STATE_RUNNING) {
177 throw new IllegalStateException("Stopwatch is not running. ");
178 }
179 stopTime = System.currentTimeMillis();
180 this.splitState = STATE_SPLIT;
181 }
182
183 /**
184 * <p>
185 * Remove a split.
186 * </p>
187 *
188 * <p>
189 * This method clears the stop time. The start time is unaffected, enabling
190 * timing from the original start point to continue.
191 * </p>
192 *
193 * @throws IllegalStateException if the StopWatch has not been split.
194 */
195 public void unsplit() {
196 if (this.splitState != STATE_SPLIT) {
197 throw new IllegalStateException("Stopwatch has not been split. ");
198 }
199 stopTime = -1;
200 this.splitState = STATE_UNSPLIT;
201 }
202
203 /**
204 * <p>
205 * Suspend the stopwatch for later resumption.
206 * </p>
207 *
208 * <p>
209 * This method suspends the watch until it is resumed. The watch will not
210 * include time between the suspend and resume calls in the total time.
211 * </p>
212 *
213 * @throws IllegalStateException if the StopWatch is not currently running.
214 */
215 public void suspend() {
216 if (this.runningState != STATE_RUNNING) {
217 throw new IllegalStateException("Stopwatch must be running to suspend. ");
218 }
219 stopTime = System.currentTimeMillis();
220 this.runningState = STATE_SUSPENDED;
221 }
222
223 /**
224 * <p>
225 * Resume the stopwatch after a suspend.
226 * </p>
227 *
228 * <p>
229 * This method resumes the watch after it was suspended. The watch will not
230 * include time between the suspend and resume calls in the total time.
231 * </p>
232 *
233 * @throws IllegalStateException if the StopWatch has not been suspended.
234 */
235 public void resume() {
236 if (this.runningState != STATE_SUSPENDED) {
237 throw new IllegalStateException("Stopwatch must be suspended to resume. ");
238 }
239 startTime += System.currentTimeMillis() - stopTime;
240 stopTime = -1;
241 this.runningState = STATE_RUNNING;
242 }
243
244 /**
245 * Get the time on the stopwatch.
246 *
247 * This is either the time between the start and the moment this method is
248 * called, or the amount of time between start and stop.
249 *
250 * @return the time in milliseconds
251 */
252 public long getTime() {
253 if (this.runningState == STATE_STOPPED || this.runningState == STATE_SUSPENDED) {
254 return this.stopTime - this.startTime;
255 } else if (this.runningState == STATE_UNSTARTED) {
256 return 0;
257 } else if (this.runningState == STATE_RUNNING) {
258 return System.currentTimeMillis() - this.startTime;
259 }
260 throw new RuntimeException("Illegal running state has occured. ");
261 }
262
263 /**
264 * Get the split time on the stopwatch.
265 *
266 * This is the time between start and latest split.
267 *
268 * @return the split time in milliseconds
269 * @throws IllegalStateException if the StopWatch has not yet been split.
270 * @since 2.1
271 */
272 public long getSplitTime() {
273 if (this.splitState != STATE_SPLIT) {
274 throw new IllegalStateException("Stopwatch must be split to get the split time. ");
275 }
276 return this.stopTime - this.startTime;
277 }
278
279 /**
280 * Gets a summary of the time that the stopwatch recorded as a string.
281 *
282 * @return the time as a String
283 */
284 public String toString() {
285 return getTime() + "ms";
286 }
287 }