001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.lang3.time;
019
020import java.time.Duration;
021import java.time.Instant;
022import java.util.Objects;
023import java.util.concurrent.TimeUnit;
024
025import org.apache.commons.lang3.StringUtils;
026import org.apache.commons.lang3.function.FailableConsumer;
027import org.apache.commons.lang3.function.FailableRunnable;
028
029/**
030 * {@link StopWatch} provides a convenient API for timings.
031 *
032 * <p>
033 * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
034 * </p>
035 * <ul>
036 * <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
037 * point, these three options are available again.</li>
038 * <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
039 * the total. At this point, these three options are available again.</li>
040 * <li>{@link #stop()} the watch to complete the timing session.</li>
041 * </ul>
042 *
043 * <p>
044 * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop, split or suspend, however a suitable
045 * result will be returned at other points.
046 * </p>
047 *
048 * <p>
049 * 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
050 * split.
051 * </p>
052 *
053 * <ol>
054 * <li>{@link #split()}, {@link #suspend()}, or {@link #stop()} cannot be invoked twice</li>
055 * <li>{@link #unsplit()} may only be called if the watch has been {@link #split()}</li>
056 * <li>{@link #resume()} may only be called if the watch has been {@link #suspend()}</li>
057 * <li>{@link #start()} cannot be called twice without calling {@link #reset()}</li>
058 * </ol>
059 *
060 * <p>
061 * This class is not thread-safe
062 * </p>
063 *
064 * @see DurationUtils#of(FailableRunnable)
065 * @see DurationUtils#of(FailableConsumer)
066 *
067 * @since 2.0
068 */
069public class StopWatch {
070
071    /**
072     * Enumeration type which indicates the split status of a StopWatch.
073     */
074    private enum SplitState {
075        SPLIT, UNSPLIT
076    }
077
078    /**
079     * Enumeration type which indicates the status of a StopWatch.
080     */
081    private enum State {
082
083        RUNNING {
084            @Override
085            boolean isStarted() {
086                return true;
087            }
088
089            @Override
090            boolean isStopped() {
091                return false;
092            }
093
094            @Override
095            boolean isSuspended() {
096                return false;
097            }
098        },
099
100        STOPPED {
101            @Override
102            boolean isStarted() {
103                return false;
104            }
105
106            @Override
107            boolean isStopped() {
108                return true;
109            }
110
111            @Override
112            boolean isSuspended() {
113                return false;
114            }
115        },
116
117        SUSPENDED {
118            @Override
119            boolean isStarted() {
120                return true;
121            }
122
123            @Override
124            boolean isStopped() {
125                return false;
126            }
127
128            @Override
129            boolean isSuspended() {
130                return true;
131            }
132        },
133
134        UNSTARTED {
135            @Override
136            boolean isStarted() {
137                return false;
138            }
139
140            @Override
141            boolean isStopped() {
142                return true;
143            }
144
145            @Override
146            boolean isSuspended() {
147                return false;
148            }
149        };
150
151        /**
152         * Tests whether the StopWatch is started. A suspended StopWatch is also started.
153         *
154         * @return boolean If the StopWatch is started.
155         */
156        abstract boolean isStarted();
157
158        /**
159         * Tests whether the StopWatch is stopped. A StopWatch which is not yet started and explicitly stopped is considered stopped.
160         *
161         * @return boolean If the StopWatch is stopped.
162         */
163        abstract boolean isStopped();
164
165        /**
166         * Tests whether the StopWatch is suspended.
167         *
168         * @return boolean If the StopWatch is suspended.
169         */
170        abstract boolean isSuspended();
171    }
172
173    private static final long NANO_2_MILLIS = 1000000L;
174
175    /**
176     * Creates a StopWatch.
177     *
178     * @return StopWatch a StopWatch.
179     *
180     * @since 3.10
181     */
182    public static StopWatch create() {
183        return new StopWatch();
184    }
185
186    /**
187     * Creates and starts a StopWatch.
188     *
189     * @return StopWatch a started StopWatch.
190     *
191     * @since 3.5
192     */
193    public static StopWatch createStarted() {
194        final StopWatch sw = new StopWatch();
195        sw.start();
196        return sw;
197    }
198
199    /**
200     * A message for string presentation.
201     *
202     * @since 3.10
203     */
204    private final String message;
205
206    /**
207     * The current running state of the StopWatch.
208     */
209    private State runningState = State.UNSTARTED;
210
211    /**
212     * Whether the StopWatch has a split time recorded.
213     */
214    private SplitState splitState = SplitState.UNSPLIT;
215
216    /**
217     * The start time in nanoseconds.
218     *
219     * This field can be removed once we move off of Java 8.
220     */
221    private long startTimeNanos;
222
223    /**
224     * The start Instant.
225     * <p>
226     * nanoTime is only for elapsed time so we need to also store the currentTimeMillis to maintain the old getStartTime API.
227     * </p>
228     * <p>
229     * On Java 8, Instant has millisecond precision, only later versions use nanoseconds.
230     * </p>
231     */
232    private Instant startInstant;
233
234    /**
235     * The end Instant.
236     * <p>
237     * nanoTime is only for elapsed time so we need to also store the currentTimeMillis to maintain the old getStartTime API.
238     * </p>
239     * <p>
240     * On Java 8, Instant has millisecond precision, only later versions use nanoseconds.
241     * </p>
242     */
243    private Instant stopInstant;
244
245    /**
246     * The stop time in nanoseconds.
247     *
248     * This field can be removed once we move off of Java 8.
249     */
250    private long stopTimeNanos;
251
252    /**
253     * Constructs a new instance.
254     */
255    public StopWatch() {
256        this(null);
257    }
258
259    /**
260     * Constructs a new instance.
261     *
262     * @param message A message for string presentation.
263     * @since 3.10
264     */
265    public StopWatch(final String message) {
266        this.message = message;
267    }
268
269    /**
270     * Formats the split time with {@link DurationFormatUtils#formatDurationHMS}.
271     *
272     * @return the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
273     * @since 3.10
274     */
275    public String formatSplitTime() {
276        return DurationFormatUtils.formatDurationHMS(getSplitDuration().toMillis());
277    }
278
279    /**
280     * Formats the time formatted with {@link DurationFormatUtils#formatDurationHMS}.
281     *
282     * @return the time formatted by {@link DurationFormatUtils#formatDurationHMS}.
283     * @since 3.10
284     */
285    public String formatTime() {
286        return DurationFormatUtils.formatDurationHMS(getTime());
287    }
288
289    /**
290     * Gets the Duration on the StopWatch.
291     *
292     * <p>
293     * This is either the Duration between the start and the moment this method is called, or the Duration between start and stop.
294     * </p>
295     *
296     * @return the Duration.
297     * @since 3.16.0
298     */
299    public Duration getDuration() {
300        return Duration.ofNanos(getNanoTime());
301    }
302
303    /**
304     * Gets the message for string presentation.
305     *
306     * @return the message for string presentation.
307     * @since 3.10
308     */
309    public String getMessage() {
310        return message;
311    }
312
313    /**
314     * Gets the <em>elapsed</em> time in nanoseconds.
315     *
316     * <p>
317     * This is either the time between the start and the moment this method is called, or the amount of time between start and stop.
318     * </p>
319     *
320     * @return the <em>elapsed</em> time in nanoseconds.
321     * @see System#nanoTime()
322     * @since 3.0
323     */
324    public long getNanoTime() {
325        if (runningState == State.STOPPED || runningState == State.SUSPENDED) {
326            return stopTimeNanos - startTimeNanos;
327        }
328        if (runningState == State.UNSTARTED) {
329            return 0;
330        }
331        if (runningState == State.RUNNING) {
332            return System.nanoTime() - startTimeNanos;
333        }
334        throw new IllegalStateException("Illegal running state has occurred.");
335    }
336
337    /**
338     * Gets the split Duration on the StopWatch.
339     *
340     * <p>
341     * This is the Duration between start and latest split.
342     * </p>
343     *
344     * @return the split Duration
345     *
346     * @throws IllegalStateException if the StopWatch has not yet been split.
347     * @since 3.16.0
348     */
349    public Duration getSplitDuration() {
350        return Duration.ofNanos(getSplitNanoTime());
351    }
352
353    /**
354     * Gets the split time in nanoseconds.
355     *
356     * <p>
357     * This is the time between start and latest split.
358     * </p>
359     *
360     * @return the split time in nanoseconds
361     *
362     * @throws IllegalStateException if the StopWatch has not yet been split.
363     * @since 3.0
364     */
365    public long getSplitNanoTime() {
366        if (splitState != SplitState.SPLIT) {
367            throw new IllegalStateException("Stopwatch must be split to get the split time.");
368        }
369        return stopTimeNanos - startTimeNanos;
370    }
371
372    /**
373     * Gets the split time on the StopWatch.
374     *
375     * <p>
376     * This is the time between start and latest split.
377     * </p>
378     *
379     * @return the split time in milliseconds
380     *
381     * @throws IllegalStateException if the StopWatch has not yet been split.
382     * @since 2.1
383     * @deprecated Use {@link #getSplitDuration()}.
384     */
385    @Deprecated
386    public long getSplitTime() {
387        return nanosToMillis(getSplitNanoTime());
388    }
389
390    /**
391     * Gets the Instant this StopWatch was started, between the current time and midnight, January 1, 1970 UTC.
392     *
393     * @return the Instant this StopWatch was started, between the current time and midnight, January 1, 1970 UTC.
394     * @throws IllegalStateException if this StopWatch has not been started
395     * @since 3.16.0
396     */
397    public Instant getStartInstant() {
398        return Instant.ofEpochMilli(getStartTime());
399    }
400
401    /**
402     * Gets the time this StopWatch was started in milliseconds, between the current time and midnight, January 1, 1970 UTC.
403     *
404     * @return the time this StopWatch was started in milliseconds, between the current time and midnight, January 1, 1970 UTC.
405     * @throws IllegalStateException if this StopWatch has not been started
406     * @since 2.4
407     * @deprecated Use {@link #getStartInstant()}.
408     */
409    @Deprecated
410    public long getStartTime() {
411        if (runningState == State.UNSTARTED) {
412            throw new IllegalStateException("Stopwatch has not been started");
413        }
414        // stopTimeNanos stores System.nanoTime() for elapsed time
415        return startInstant.toEpochMilli();
416    }
417
418    /**
419     * Gets the Instant this StopWatch was stopped, between the current time and midnight, January 1, 1970 UTC.
420     *
421     * @return the Instant this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
422     * @throws IllegalStateException if this StopWatch has not been started
423     * @since 3.16.0
424     */
425    public Instant getStopInstant() {
426        return Instant.ofEpochMilli(getStopTime());
427    }
428
429    /**
430     * Gets the time this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
431     *
432     * @return the time this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
433     * @throws IllegalStateException if this StopWatch has not been started
434     * @since 3.12.0
435     * @deprecated Use {@link #getStopInstant()}.
436     */
437    @Deprecated
438    public long getStopTime() {
439        if (runningState == State.UNSTARTED) {
440            throw new IllegalStateException("Stopwatch has not been started");
441        }
442        // stopTimeNanos stores System.nanoTime() for elapsed time
443        return stopInstant.toEpochMilli();
444    }
445
446    /**
447     * Gets the time on the StopWatch.
448     *
449     * <p>
450     * This is either the time between the start and the moment this method is called, or the amount of time between start and stop.
451     * </p>
452     *
453     * @return the time in milliseconds
454     * @deprecated Use {@link #getDuration()}.
455     */
456    @Deprecated
457    public long getTime() {
458        return nanosToMillis(getNanoTime());
459    }
460
461    /**
462     * Gets the time in the specified TimeUnit.
463     *
464     * <p>
465     * 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
466     * 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
467     * 59 minutes, then the result returned will be {@code 0}.
468     * </p>
469     *
470     * @param timeUnit the unit of time, not null
471     * @return the time in the specified TimeUnit, rounded down
472     * @since 3.5
473     */
474    public long getTime(final TimeUnit timeUnit) {
475        return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
476    }
477
478    /**
479     * Tests whether the StopWatch is started. A suspended StopWatch is also started watch.
480     *
481     * @return boolean If the StopWatch is started.
482     * @since 3.2
483     */
484    public boolean isStarted() {
485        return runningState.isStarted();
486    }
487
488    /**
489     * Tests whether StopWatch is stopped. The StopWatch which's not yet started and explicitly stopped StopWatch is considered as stopped.
490     *
491     * @return boolean If the StopWatch is stopped.
492     * @since 3.2
493     */
494    public boolean isStopped() {
495        return runningState.isStopped();
496    }
497
498    /**
499     * Tests whether the StopWatch is suspended.
500     *
501     * @return boolean If the StopWatch is suspended.
502     * @since 3.2
503     */
504    public boolean isSuspended() {
505        return runningState.isSuspended();
506    }
507
508    /**
509     * Converts nanoseconds to milliseconds.
510     *
511     * @param nanos nanoseconds to convert.
512     * @return milliseconds conversion result.
513     */
514    private long nanosToMillis(final long nanos) {
515        return nanos / NANO_2_MILLIS;
516    }
517
518    /**
519     * Resets the StopWatch. Stops it if need be.
520     *
521     * <p>
522     * This method clears the internal values to allow the object to be reused.
523     * </p>
524     */
525    public void reset() {
526        runningState = State.UNSTARTED;
527        splitState = SplitState.UNSPLIT;
528    }
529
530    /**
531     * Resumes the StopWatch after a suspend.
532     *
533     * <p>
534     * 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.
535     * </p>
536     *
537     * @throws IllegalStateException if the StopWatch has not been suspended.
538     */
539    public void resume() {
540        if (runningState != State.SUSPENDED) {
541            throw new IllegalStateException("Stopwatch must be suspended to resume. ");
542        }
543        startTimeNanos += System.nanoTime() - stopTimeNanos;
544        runningState = State.RUNNING;
545    }
546
547    /**
548     * Splits the time.
549     *
550     * <p>
551     * 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
552     * timing from the original start point.
553     * </p>
554     *
555     * @throws IllegalStateException if the StopWatch is not running.
556     */
557    public void split() {
558        if (runningState != State.RUNNING) {
559            throw new IllegalStateException("Stopwatch is not running. ");
560        }
561        stopTimeNanos = System.nanoTime();
562        splitState = SplitState.SPLIT;
563    }
564
565    /**
566     * Starts the StopWatch.
567     *
568     * <p>
569     * This method starts a new timing session, clearing any previous values.
570     * </p>
571     *
572     * @throws IllegalStateException if the StopWatch is already running.
573     */
574    public void start() {
575        if (runningState == State.STOPPED) {
576            throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
577        }
578        if (runningState != State.UNSTARTED) {
579            throw new IllegalStateException("Stopwatch already started. ");
580        }
581        startTimeNanos = System.nanoTime();
582        startInstant = Instant.now();
583        runningState = State.RUNNING;
584    }
585
586    /**
587     * Stops the StopWatch.
588     *
589     * <p>
590     * This method ends a new timing session, allowing the time to be retrieved.
591     * </p>
592     *
593     * @throws IllegalStateException if the StopWatch is not running.
594     */
595    public void stop() {
596        if (runningState != State.RUNNING && runningState != State.SUSPENDED) {
597            throw new IllegalStateException("Stopwatch is not running. ");
598        }
599        if (runningState == State.RUNNING) {
600            stopTimeNanos = System.nanoTime();
601            stopInstant = Instant.now();
602        }
603        runningState = State.STOPPED;
604    }
605
606    /**
607     * Suspends the StopWatch for later resumption.
608     *
609     * <p>
610     * 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.
611     * </p>
612     *
613     * @throws IllegalStateException if the StopWatch is not currently running.
614     */
615    public void suspend() {
616        if (runningState != State.RUNNING) {
617            throw new IllegalStateException("Stopwatch must be running to suspend. ");
618        }
619        stopTimeNanos = System.nanoTime();
620        stopInstant = Instant.now();
621        runningState = State.SUSPENDED;
622    }
623
624    /**
625     * Gets a summary of the split time that the StopWatch recorded as a string.
626     *
627     * <p>
628     * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
629     * </p>
630     *
631     * @return the split time as a String
632     * @since 2.1
633     * @since 3.10 Returns the prefix {@code "message "} if the message is set.
634     */
635    public String toSplitString() {
636        final String msgStr = Objects.toString(message, StringUtils.EMPTY);
637        final String formattedTime = formatSplitTime();
638        return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
639    }
640
641    /**
642     * Gets a summary of the time that the StopWatch recorded as a string.
643     *
644     * <p>
645     * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
646     * </p>
647     *
648     * @return the time as a String
649     * @since 3.10 Returns the prefix {@code "message "} if the message is set.
650     */
651    @Override
652    public String toString() {
653        final String msgStr = Objects.toString(message, StringUtils.EMPTY);
654        final String formattedTime = formatTime();
655        return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
656    }
657
658    /**
659     * Removes a split.
660     *
661     * <p>
662     * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to continue.
663     * </p>
664     *
665     * @throws IllegalStateException if the StopWatch has not been split.
666     */
667    public void unsplit() {
668        if (splitState != SplitState.SPLIT) {
669            throw new IllegalStateException("Stopwatch has not been split. ");
670        }
671        splitState = SplitState.UNSPLIT;
672    }
673
674}