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