    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}