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