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