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