View Javadoc
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 }