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