View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.lang3.time;
19  
20  /**
21   * <p>
22   * <code>StopWatch</code> provides a convenient API for timings.
23   * </p>
24   * 
25   * <p>
26   * To start the watch, call {@link #start()}. At this point you can:
27   * </p>
28   * <ul>
29   * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will
30   * remove the effect of the split. At this point, these three options are available again.</li>
31   * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the
32   * suspend and resume will not be counted in the total. At this point, these three options are available again.</li>
33   * <li>{@link #stop()} the watch to complete the timing session.</li>
34   * </ul>
35   * 
36   * <p>
37   * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop,
38   * split or suspend, however a suitable result will be returned at other points.
39   * </p>
40   * 
41   * <p>
42   * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start,
43   * resume before suspend or unsplit before split.
44   * </p>
45   * 
46   * <p>
47   * 1. split(), suspend(), or stop() cannot be invoked twice<br>
48   * 2. unsplit() may only be called if the watch has been split()<br>
49   * 3. resume() may only be called if the watch has been suspend()<br>
50   * 4. start() cannot be called twice without calling reset()
51   * </p>
52   * 
53   * <p>This class is not thread-safe</p>
54   * 
55   * @since 2.0
56   * @version $Id: StopWatch.java 1583482 2014-03-31 22:54:57Z niallp $
57   */
58  public class StopWatch {
59  
60      private static final long NANO_2_MILLIS = 1000000L;
61      
62      /**
63       * Enumeration type which indicates the status of stopwatch.
64       */
65      private enum State {
66  
67          UNSTARTED {
68              @Override boolean isStarted() { return false; }
69              @Override boolean isStopped() { return true;  }
70              @Override boolean isSuspended() { return false; }
71          },
72          RUNNING {
73              @Override boolean isStarted() { return true; }
74              @Override boolean isStopped() { return false; }
75              @Override boolean isSuspended() { return false; }
76          },
77          STOPPED {
78              @Override boolean isStarted() { return false; }
79              @Override boolean isStopped() { return true; }
80              @Override boolean isSuspended() { return false; }
81          },
82          SUSPENDED {
83              @Override boolean isStarted() { return true; }
84              @Override boolean isStopped() { return false; }
85              @Override  boolean isSuspended() { return true; }
86          };
87  
88          /**
89           * <p>
90           * The method is used to find out if the StopWatch is started. A suspended
91           * StopWatch is also started watch.
92           * </p>
93  
94           * @return boolean
95           *             If the StopWatch is started.
96           */
97          abstract boolean isStarted();
98  
99          /**
100          * <p>
101          * This method is used to find out whether the StopWatch is stopped. The
102          * stopwatch which's not yet started and explicitly stopped stopwatch is
103          * considered as stopped.
104          * </p>
105          *
106          * @return boolean
107          *             If the StopWatch is stopped.
108          */
109         abstract boolean isStopped();
110 
111         /**
112          * <p>
113          * This method is used to find out whether the StopWatch is suspended.
114          * </p>
115          *
116          * @return boolean
117          *             If the StopWatch is suspended.
118          */
119         abstract boolean isSuspended();
120     }
121 
122     /**
123      * Enumeration type which indicates the split status of stopwatch.
124      */    
125     private enum SplitState {
126         SPLIT,
127         UNSPLIT
128     }
129     /**
130      * The current running state of the StopWatch.
131      */
132     private State runningState = State.UNSTARTED;
133 
134     /**
135      * Whether the stopwatch has a split time recorded.
136      */
137     private SplitState splitState = SplitState.UNSPLIT;
138 
139     /**
140      * The start time.
141      */
142     private long startTime;
143 
144     /**
145      * The start time in Millis - nanoTime is only for elapsed time so we 
146      * need to also store the currentTimeMillis to maintain the old 
147      * getStartTime API.
148      */
149     private long startTimeMillis;
150 
151     /**
152      * The stop time.
153      */
154     private long stopTime;
155 
156     /**
157      * <p>
158      * Constructor.
159      * </p>
160      */
161     public StopWatch() {
162         super();
163     }
164 
165     /**
166      * <p>
167      * Start the stopwatch.
168      * </p>
169      * 
170      * <p>
171      * This method starts a new timing session, clearing any previous values.
172      * </p>
173      * 
174      * @throws IllegalStateException
175      *             if the StopWatch is already running.
176      */
177     public void start() {
178         if (this.runningState == State.STOPPED) {
179             throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
180         }
181         if (this.runningState != State.UNSTARTED) {
182             throw new IllegalStateException("Stopwatch already started. ");
183         }
184         this.startTime = System.nanoTime();
185         this.startTimeMillis = System.currentTimeMillis();
186         this.runningState = State.RUNNING;
187     }
188 
189 
190     /**
191      * <p>
192      * Stop the stopwatch.
193      * </p>
194      * 
195      * <p>
196      * This method ends a new timing session, allowing the time to be retrieved.
197      * </p>
198      * 
199      * @throws IllegalStateException
200      *             if the StopWatch is not running.
201      */
202     public void stop() {
203         if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
204             throw new IllegalStateException("Stopwatch is not running. ");
205         }
206         if (this.runningState == State.RUNNING) {
207             this.stopTime = System.nanoTime();
208         }
209         this.runningState = State.STOPPED;
210     }
211 
212     /**
213      * <p>
214      * Resets the stopwatch. Stops it if need be.
215      * </p>
216      * 
217      * <p>
218      * This method clears the internal values to allow the object to be reused.
219      * </p>
220      */
221     public void reset() {
222         this.runningState = State.UNSTARTED;
223         this.splitState = SplitState.UNSPLIT;
224     }
225 
226     /**
227      * <p>
228      * Split the time.
229      * </p>
230      * 
231      * <p>
232      * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
233      * enabling {@link #unsplit()} to continue the timing from the original start point.
234      * </p>
235      * 
236      * @throws IllegalStateException
237      *             if the StopWatch is not running.
238      */
239     public void split() {
240         if (this.runningState != State.RUNNING) {
241             throw new IllegalStateException("Stopwatch is not running. ");
242         }
243         this.stopTime = System.nanoTime();
244         this.splitState = SplitState.SPLIT;
245     }
246 
247     /**
248      * <p>
249      * Remove a split.
250      * </p>
251      * 
252      * <p>
253      * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
254      * continue.
255      * </p>
256      * 
257      * @throws IllegalStateException
258      *             if the StopWatch has not been split.
259      */
260     public void unsplit() {
261         if (this.splitState != SplitState.SPLIT) {
262             throw new IllegalStateException("Stopwatch has not been split. ");
263         }
264         this.splitState = SplitState.UNSPLIT;
265     }
266 
267     /**
268      * <p>
269      * Suspend the stopwatch for later resumption.
270      * </p>
271      * 
272      * <p>
273      * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
274      * resume calls in the total time.
275      * </p>
276      * 
277      * @throws IllegalStateException
278      *             if the StopWatch is not currently running.
279      */
280     public void suspend() {
281         if (this.runningState != State.RUNNING) {
282             throw new IllegalStateException("Stopwatch must be running to suspend. ");
283         }
284         this.stopTime = System.nanoTime();
285         this.runningState = State.SUSPENDED;
286     }
287 
288     /**
289      * <p>
290      * Resume the stopwatch after a suspend.
291      * </p>
292      * 
293      * <p>
294      * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
295      * resume calls in the total time.
296      * </p>
297      * 
298      * @throws IllegalStateException
299      *             if the StopWatch has not been suspended.
300      */
301     public void resume() {
302         if (this.runningState != State.SUSPENDED) {
303             throw new IllegalStateException("Stopwatch must be suspended to resume. ");
304         }
305         this.startTime += System.nanoTime() - this.stopTime;
306         this.runningState = State.RUNNING;
307     }
308 
309     /**
310      * <p>
311      * Get the time on the stopwatch.
312      * </p>
313      * 
314      * <p>
315      * This is either the time between the start and the moment this method is called, or the amount of time between
316      * start and stop.
317      * </p>
318      * 
319      * @return the time in milliseconds
320      */
321     public long getTime() {
322         return getNanoTime() / NANO_2_MILLIS;
323     }
324     /**
325      * <p>
326      * Get the time on the stopwatch in nanoseconds.
327      * </p>
328      * 
329      * <p>
330      * This is either the time between the start and the moment this method is called, or the amount of time between
331      * start and stop.
332      * </p>
333      * 
334      * @return the time in nanoseconds
335      * @since 3.0
336      */
337     public long getNanoTime() {
338         if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
339             return this.stopTime - this.startTime;
340         } else if (this.runningState == State.UNSTARTED) {
341             return 0;
342         } else if (this.runningState == State.RUNNING) {
343             return System.nanoTime() - this.startTime;
344         }
345         throw new RuntimeException("Illegal running state has occurred.");
346     }
347 
348     /**
349      * <p>
350      * Get the split time on the stopwatch.
351      * </p>
352      * 
353      * <p>
354      * This is the time between start and latest split.
355      * </p>
356      * 
357      * @return the split time in milliseconds
358      * 
359      * @throws IllegalStateException
360      *             if the StopWatch has not yet been split.
361      * @since 2.1
362      */
363     public long getSplitTime() {
364         return getSplitNanoTime() / NANO_2_MILLIS;
365     }
366     /**
367      * <p>
368      * Get the split time on the stopwatch in nanoseconds.
369      * </p>
370      * 
371      * <p>
372      * This is the time between start and latest split.
373      * </p>
374      * 
375      * @return the split time in nanoseconds
376      * 
377      * @throws IllegalStateException
378      *             if the StopWatch has not yet been split.
379      * @since 3.0
380      */
381     public long getSplitNanoTime() {
382         if (this.splitState != SplitState.SPLIT) {
383             throw new IllegalStateException("Stopwatch must be split to get the split time. ");
384         }
385         return this.stopTime - this.startTime;
386     }
387 
388     /**
389      * Returns the time this stopwatch was started.
390      * 
391      * @return the time this stopwatch was started
392      * @throws IllegalStateException
393      *             if this StopWatch has not been started
394      * @since 2.4
395      */
396     public long getStartTime() {
397         if (this.runningState == State.UNSTARTED) {
398             throw new IllegalStateException("Stopwatch has not been started");
399         }
400         // System.nanoTime is for elapsed time
401         return this.startTimeMillis;
402     }
403 
404     /**
405      * <p>
406      * Gets a summary of the time that the stopwatch recorded as a string.
407      * </p>
408      * 
409      * <p>
410      * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
411      * </p>
412      * 
413      * @return the time as a String
414      */
415     @Override
416     public String toString() {
417         return DurationFormatUtils.formatDurationHMS(getTime());
418     }
419 
420     /**
421      * <p>
422      * Gets a summary of the split time that the stopwatch recorded as a string.
423      * </p>
424      * 
425      * <p>
426      * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
427      * </p>
428      * 
429      * @return the split time as a String
430      * @since 2.1
431      */
432     public String toSplitString() {
433         return DurationFormatUtils.formatDurationHMS(getSplitTime());
434     }
435 
436     /**
437      * <p>
438      * The method is used to find out if the StopWatch is started. A suspended
439      * StopWatch is also started watch.
440      * </p>
441      *
442      * @return boolean
443      *             If the StopWatch is started.
444      * @since 3.2
445      */
446     public boolean isStarted() {
447         return runningState.isStarted();
448     }
449 
450     /**
451      * <p>
452      * This method is used to find out whether the StopWatch is suspended.
453      * </p>
454      *
455      * @return boolean
456      *             If the StopWatch is suspended.
457      * @since 3.2
458      */
459     public boolean isSuspended() {
460         return runningState.isSuspended();
461     }
462 
463     /**
464      * <p>
465      * This method is used to find out whether the StopWatch is stopped. The
466      * stopwatch which's not yet started and explicitly stopped stopwatch is
467      * considered as stopped.
468      * </p>
469      *
470      * @return boolean
471      *             If the StopWatch is stopped.
472      * @since 3.2
473      */
474     public boolean isStopped() {
475         return runningState.isStopped();
476     }    
477 
478 }