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  import java.util.concurrent.TimeUnit;
21  
22  /**
23   * <p>
24   * <code>StopWatch</code> provides a convenient API for timings.
25   * </p>
26   *
27   * <p>
28   * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
29   * </p>
30   * <ul>
31   * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will
32   * remove the effect of the split. At this point, these three options are available again.</li>
33   * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the
34   * suspend and resume will not be counted in the total. At this point, these three options are available again.</li>
35   * <li>{@link #stop()} the watch to complete the timing session.</li>
36   * </ul>
37   *
38   * <p>
39   * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop,
40   * split or suspend, however a suitable result will be returned at other points.
41   * </p>
42   *
43   * <p>
44   * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start,
45   * resume before suspend or unsplit before split.
46   * </p>
47   *
48   * <p>
49   * 1. split(), suspend(), or stop() cannot be invoked twice<br>
50   * 2. unsplit() may only be called if the watch has been split()<br>
51   * 3. resume() may only be called if the watch has been suspend()<br>
52   * 4. start() cannot be called twice without calling reset()
53   * </p>
54   *
55   * <p>This class is not thread-safe</p>
56   *
57   * @since 2.0
58   */
59  public class StopWatch {
60  
61      private static final long NANO_2_MILLIS = 1000000L;
62  
63  
64      /**
65       * Provides a started stopwatch for convenience.
66       *
67       * @return StopWatch a stopwatch that's already been started.
68       *
69       * @since 3.5
70       */
71      public static StopWatch createStarted() {
72          final StopWatch sw = new StopWatch();
73          sw.start();
74          return sw;
75      }
76  
77      /**
78       * Enumeration type which indicates the status of stopwatch.
79       */
80      private enum State {
81  
82          UNSTARTED {
83              @Override
84              boolean isStarted() {
85                  return false;
86              }
87              @Override
88              boolean isStopped() {
89                  return true;
90              }
91              @Override
92              boolean isSuspended() {
93                  return false;
94              }
95          },
96          RUNNING {
97              @Override
98              boolean isStarted() {
99                  return true;
100             }
101             @Override
102             boolean isStopped() {
103                 return false;
104             }
105             @Override
106             boolean isSuspended() {
107                 return false;
108             }
109         },
110         STOPPED {
111             @Override
112             boolean isStarted() {
113                 return false;
114             }
115             @Override
116             boolean isStopped() {
117                 return true;
118             }
119             @Override
120             boolean isSuspended() {
121                 return false;
122             }
123         },
124         SUSPENDED {
125             @Override
126             boolean isStarted() {
127                 return true;
128             }
129             @Override
130             boolean isStopped() {
131                 return false;
132             }
133             @Override
134             boolean isSuspended() {
135                 return true;
136             }
137         };
138 
139         /**
140          * <p>
141          * The method is used to find out if the StopWatch is started. A suspended
142          * StopWatch is also started watch.
143          * </p>
144 
145          * @return boolean
146          *             If the StopWatch is started.
147          */
148         abstract boolean isStarted();
149 
150         /**
151          * <p>
152          * This method is used to find out whether the StopWatch is stopped. The
153          * stopwatch which's not yet started and explicitly stopped stopwatch is
154          * considered as stopped.
155          * </p>
156          *
157          * @return boolean
158          *             If the StopWatch is stopped.
159          */
160         abstract boolean isStopped();
161 
162         /**
163          * <p>
164          * This method is used to find out whether the StopWatch is suspended.
165          * </p>
166          *
167          * @return boolean
168          *             If the StopWatch is suspended.
169          */
170         abstract boolean isSuspended();
171     }
172 
173     /**
174      * Enumeration type which indicates the split status of stopwatch.
175      */
176     private enum SplitState {
177         SPLIT,
178         UNSPLIT
179     }
180     /**
181      * The current running state of the StopWatch.
182      */
183     private State runningState = State.UNSTARTED;
184 
185     /**
186      * Whether the stopwatch has a split time recorded.
187      */
188     private SplitState splitState = SplitState.UNSPLIT;
189 
190     /**
191      * The start time.
192      */
193     private long startTime;
194 
195     /**
196      * The start time in Millis - nanoTime is only for elapsed time so we
197      * need to also store the currentTimeMillis to maintain the old
198      * getStartTime API.
199      */
200     private long startTimeMillis;
201 
202     /**
203      * The stop time.
204      */
205     private long stopTime;
206 
207     /**
208      * <p>
209      * Constructor.
210      * </p>
211      */
212     public StopWatch() {
213         super();
214     }
215 
216     /**
217      * <p>
218      * Start the stopwatch.
219      * </p>
220      *
221      * <p>
222      * This method starts a new timing session, clearing any previous values.
223      * </p>
224      *
225      * @throws IllegalStateException
226      *             if the StopWatch is already running.
227      */
228     public void start() {
229         if (this.runningState == State.STOPPED) {
230             throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
231         }
232         if (this.runningState != State.UNSTARTED) {
233             throw new IllegalStateException("Stopwatch already started. ");
234         }
235         this.startTime = System.nanoTime();
236         this.startTimeMillis = System.currentTimeMillis();
237         this.runningState = State.RUNNING;
238     }
239 
240 
241     /**
242      * <p>
243      * Stop the stopwatch.
244      * </p>
245      *
246      * <p>
247      * This method ends a new timing session, allowing the time to be retrieved.
248      * </p>
249      *
250      * @throws IllegalStateException
251      *             if the StopWatch is not running.
252      */
253     public void stop() {
254         if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
255             throw new IllegalStateException("Stopwatch is not running. ");
256         }
257         if (this.runningState == State.RUNNING) {
258             this.stopTime = System.nanoTime();
259         }
260         this.runningState = State.STOPPED;
261     }
262 
263     /**
264      * <p>
265      * Resets the stopwatch. Stops it if need be.
266      * </p>
267      *
268      * <p>
269      * This method clears the internal values to allow the object to be reused.
270      * </p>
271      */
272     public void reset() {
273         this.runningState = State.UNSTARTED;
274         this.splitState = SplitState.UNSPLIT;
275     }
276 
277     /**
278      * <p>
279      * Split the time.
280      * </p>
281      *
282      * <p>
283      * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
284      * enabling {@link #unsplit()} to continue the timing from the original start point.
285      * </p>
286      *
287      * @throws IllegalStateException
288      *             if the StopWatch is not running.
289      */
290     public void split() {
291         if (this.runningState != State.RUNNING) {
292             throw new IllegalStateException("Stopwatch is not running. ");
293         }
294         this.stopTime = System.nanoTime();
295         this.splitState = SplitState.SPLIT;
296     }
297 
298     /**
299      * <p>
300      * Remove a split.
301      * </p>
302      *
303      * <p>
304      * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
305      * continue.
306      * </p>
307      *
308      * @throws IllegalStateException
309      *             if the StopWatch has not been split.
310      */
311     public void unsplit() {
312         if (this.splitState != SplitState.SPLIT) {
313             throw new IllegalStateException("Stopwatch has not been split. ");
314         }
315         this.splitState = SplitState.UNSPLIT;
316     }
317 
318     /**
319      * <p>
320      * Suspend the stopwatch for later resumption.
321      * </p>
322      *
323      * <p>
324      * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
325      * resume calls in the total time.
326      * </p>
327      *
328      * @throws IllegalStateException
329      *             if the StopWatch is not currently running.
330      */
331     public void suspend() {
332         if (this.runningState != State.RUNNING) {
333             throw new IllegalStateException("Stopwatch must be running to suspend. ");
334         }
335         this.stopTime = System.nanoTime();
336         this.runningState = State.SUSPENDED;
337     }
338 
339     /**
340      * <p>
341      * Resume the stopwatch after a suspend.
342      * </p>
343      *
344      * <p>
345      * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
346      * resume calls in the total time.
347      * </p>
348      *
349      * @throws IllegalStateException
350      *             if the StopWatch has not been suspended.
351      */
352     public void resume() {
353         if (this.runningState != State.SUSPENDED) {
354             throw new IllegalStateException("Stopwatch must be suspended to resume. ");
355         }
356         this.startTime += System.nanoTime() - this.stopTime;
357         this.runningState = State.RUNNING;
358     }
359 
360     /**
361      * <p>
362      * Get the time on the stopwatch.
363      * </p>
364      *
365      * <p>
366      * This is either the time between the start and the moment this method is called, or the amount of time between
367      * start and stop.
368      * </p>
369      *
370      * @return the time in milliseconds
371      */
372     public long getTime() {
373         return getNanoTime() / NANO_2_MILLIS;
374     }
375 
376     /**
377      * <p>
378      * Get the time on the stopwatch in the specified TimeUnit.
379      * </p>
380      *
381      * <p>
382      * This is either the time between the start and the moment this method is called, or the amount of time between
383      * start and stop. The resulting time will be expressed in the desired TimeUnit with any remainder rounded down.
384      * For example, if the specified unit is {@code TimeUnit.HOURS} and the stopwatch time is 59 minutes, then the
385      * result returned will be {@code 0}.
386      * </p>
387      *
388      * @param timeUnit the unit of time, not null
389      * @return the time in the specified TimeUnit, rounded down
390      * @since 3.5
391      */
392     public long getTime(final TimeUnit timeUnit) {
393         return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
394     }
395 
396     /**
397      * <p>
398      * Get the time on the stopwatch in nanoseconds.
399      * </p>
400      *
401      * <p>
402      * This is either the time between the start and the moment this method is called, or the amount of time between
403      * start and stop.
404      * </p>
405      *
406      * @return the time in nanoseconds
407      * @since 3.0
408      */
409     public long getNanoTime() {
410         if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
411             return this.stopTime - this.startTime;
412         } else if (this.runningState == State.UNSTARTED) {
413             return 0;
414         } else if (this.runningState == State.RUNNING) {
415             return System.nanoTime() - this.startTime;
416         }
417         throw new RuntimeException("Illegal running state has occurred.");
418     }
419 
420     /**
421      * <p>
422      * Get the split time on the stopwatch.
423      * </p>
424      *
425      * <p>
426      * This is the time between start and latest split.
427      * </p>
428      *
429      * @return the split time in milliseconds
430      *
431      * @throws IllegalStateException
432      *             if the StopWatch has not yet been split.
433      * @since 2.1
434      */
435     public long getSplitTime() {
436         return getSplitNanoTime() / NANO_2_MILLIS;
437     }
438     /**
439      * <p>
440      * Get the split time on the stopwatch in nanoseconds.
441      * </p>
442      *
443      * <p>
444      * This is the time between start and latest split.
445      * </p>
446      *
447      * @return the split time in nanoseconds
448      *
449      * @throws IllegalStateException
450      *             if the StopWatch has not yet been split.
451      * @since 3.0
452      */
453     public long getSplitNanoTime() {
454         if (this.splitState != SplitState.SPLIT) {
455             throw new IllegalStateException("Stopwatch must be split to get the split time. ");
456         }
457         return this.stopTime - this.startTime;
458     }
459 
460     /**
461      * Returns the time this stopwatch was started.
462      *
463      * @return the time this stopwatch was started
464      * @throws IllegalStateException
465      *             if this StopWatch has not been started
466      * @since 2.4
467      */
468     public long getStartTime() {
469         if (this.runningState == State.UNSTARTED) {
470             throw new IllegalStateException("Stopwatch has not been started");
471         }
472         // System.nanoTime is for elapsed time
473         return this.startTimeMillis;
474     }
475 
476     /**
477      * <p>
478      * Gets a summary of the time that the stopwatch recorded as a string.
479      * </p>
480      *
481      * <p>
482      * The format used is ISO 8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
483      * </p>
484      *
485      * @return the time as a String
486      */
487     @Override
488     public String toString() {
489         return DurationFormatUtils.formatDurationHMS(getTime());
490     }
491 
492     /**
493      * <p>
494      * Gets a summary of the split time that the stopwatch recorded as a string.
495      * </p>
496      *
497      * <p>
498      * The format used is ISO 8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
499      * </p>
500      *
501      * @return the split time as a String
502      * @since 2.1
503      */
504     public String toSplitString() {
505         return DurationFormatUtils.formatDurationHMS(getSplitTime());
506     }
507 
508     /**
509      * <p>
510      * The method is used to find out if the StopWatch is started. A suspended
511      * StopWatch is also started watch.
512      * </p>
513      *
514      * @return boolean
515      *             If the StopWatch is started.
516      * @since 3.2
517      */
518     public boolean isStarted() {
519         return runningState.isStarted();
520     }
521 
522     /**
523      * <p>
524      * This method is used to find out whether the StopWatch is suspended.
525      * </p>
526      *
527      * @return boolean
528      *             If the StopWatch is suspended.
529      * @since 3.2
530      */
531     public boolean isSuspended() {
532         return runningState.isSuspended();
533     }
534 
535     /**
536      * <p>
537      * This method is used to find out whether the StopWatch is stopped. The
538      * stopwatch which's not yet started and explicitly stopped stopwatch is
539      * considered as stopped.
540      * </p>
541      *
542      * @return boolean
543      *             If the StopWatch is stopped.
544      * @since 3.2
545      */
546     public boolean isStopped() {
547         return runningState.isStopped();
548     }
549 
550 }