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