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