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