    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 in nanoseconds.
    209     */
    210    private long startTimeNanos;
    211
    212    /**
    213     * The start time in milliseconds - 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 end time in milliseconds - nanoTime is only for elapsed time so we
    221     * need to also store the currentTimeMillis to maintain the old
    222     * getStartTime API.
    223     */
    224    private long stopTimeMillis;
    225
    226    /**
    227     * The stop time in nanoseconds.
    228     */
    229    private long stopTimeNanos;
    230
    231    /**
    232     * <p>
    233     * Constructor.
    234     * </p>
    235     */
    236    public StopWatch() {
    237        this(null);
    238    }
    239
    240    /**
    241     * <p>
    242     * Constructor.
    243     * </p>
    244     * @param message A message for string presentation.
    245     * @since 3.10
    246     */
    247    public StopWatch(final String message) {
    248        this.message = message;
    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 formatSplitTime() {
    258        return DurationFormatUtils.formatDurationHMS(getSplitTime());
    259    }
    260
    261    /**
    262     * Returns the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
    263     *
    264     * @return the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
    265     * @since 3.10
    266     */
    267    public String formatTime() {
    268        return DurationFormatUtils.formatDurationHMS(getTime());
    269    }
    270
    271    /**
    272     * Gets the message for string presentation.
    273     *
    274     * @return the message for string presentation.
    275     * @since 3.10
    276     */
    277    public String getMessage() {
    278        return message;
    279    }
    280
    281    /**
    282     * <p>
    283     * Gets the <em>elapsed</em> time in nanoseconds.
    284     * </p>
    285     *
    286     * <p>
    287     * This is either the time between the start and the moment this method is called, or the amount of time between
    288     * start and stop.
    289     * </p>
    290     *
    291     * @return the <em>elapsed</em> time in nanoseconds.
    292     * @see System#nanoTime()
    293     * @since 3.0
    294     */
    295    public long getNanoTime() {
    296        if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
    297            return this.stopTimeNanos - this.startTimeNanos;
    298        } else if (this.runningState == State.UNSTARTED) {
    299            return 0;
    300        } else 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     * <p>
    308     * Gets the split time in nanoseconds.
    309     * </p>
    310     *
    311     * <p>
    312     * This is the time between start and latest split.
    313     * </p>
    314     *
    315     * @return the split time in nanoseconds
    316     *
    317     * @throws IllegalStateException
    318     *             if the StopWatch has not yet been split.
    319     * @since 3.0
    320     */
    321    public long getSplitNanoTime() {
    322        if (this.splitState != SplitState.SPLIT) {
    323            throw new IllegalStateException("Stopwatch must be split to get the split time.");
    324        }
    325        return this.stopTimeNanos - this.startTimeNanos;
    326    }
    327
    328    /**
    329     * <p>
    330     * Gets the split time on the stopwatch.
    331     * </p>
    332     *
    333     * <p>
    334     * This is the time between start and latest split.
    335     * </p>
    336     *
    337     * @return the split time in milliseconds
    338     *
    339     * @throws IllegalStateException
    340     *             if the StopWatch has not yet been split.
    341     * @since 2.1
    342     */
    343    public long getSplitTime() {
    344        return getSplitNanoTime() / NANO_2_MILLIS;
    345    }
    346
    347    /**
    348     * Gets the time this stopwatch was started in milliseconds, between the current time and midnight, January 1, 1970
    349     * UTC.
    350     *
    351     * @return the time this stopwatch was started in milliseconds, between the current time and midnight, January 1,
    352     *         1970 UTC.
    353     * @throws IllegalStateException if this StopWatch has not been started
    354     * @since 2.4
    355     */
    356    public long getStartTime() {
    357        if (this.runningState == State.UNSTARTED) {
    358            throw new IllegalStateException("Stopwatch has not been started");
    359        }
    360        // System.nanoTime is for elapsed time
    361        return this.startTimeMillis;
    362    }
    363
    364    /**
    365     * Gets the time this stopwatch was stopped in milliseconds, between the current time and midnight, January 1, 1970
    366     * UTC.
    367     *
    368     * @return the time this stopwatch was started in milliseconds, between the current time and midnight, January 1,
    369     *         1970 UTC.
    370     * @throws IllegalStateException if this StopWatch has not been started
    371     * @since 3.12.0
    372     */
    373    public long getStopTime() {
    374        if (this.runningState == State.UNSTARTED) {
    375            throw new IllegalStateException("Stopwatch has not been started");
    376        }
    377        // System.nanoTime is for elapsed time
    378        return this.stopTimeMillis;
    379    }
    380
    381    /**
    382     * <p>
    383     * Gets the time on the stopwatch.
    384     * </p>
    385     *
    386     * <p>
    387     * This is either the time between the start and the moment this method is called, or the amount of time between
    388     * start and stop.
    389     * </p>
    390     *
    391     * @return the time in milliseconds
    392     */
    393    public long getTime() {
    394        return getNanoTime() / NANO_2_MILLIS;
    395    }
    396
    397    /**
    398     * <p>
    399     * Gets the time in the specified TimeUnit.
    400     * </p>
    401     *
    402     * <p>
    403     * This is either the time between the start and the moment this method is called, or the amount of time between
    404     * start and stop. The resulting time will be expressed in the desired TimeUnit with any remainder rounded down.
    405     * For example, if the specified unit is {@code TimeUnit.HOURS} and the stopwatch time is 59 minutes, then the
    406     * result returned will be {@code 0}.
    407     * </p>
    408     *
    409     * @param timeUnit the unit of time, not null
    410     * @return the time in the specified TimeUnit, rounded down
    411     * @since 3.5
    412     */
    413    public long getTime(final TimeUnit timeUnit) {
    414        return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
    415    }
    416
    417    /**
    418     * <p>
    419     * Returns whether the StopWatch is started. A suspended StopWatch is also started watch.
    420     * </p>
    421     *
    422     * @return boolean If the StopWatch is started.
    423     * @since 3.2
    424     */
    425    public boolean isStarted() {
    426        return runningState.isStarted();
    427    }
    428
    429    /**
    430     * <p>
    431     * Returns whether StopWatch is stopped. The stopwatch which's not yet started and explicitly stopped stopwatch is considered
    432     * as stopped.
    433     * </p>
    434     *
    435     * @return boolean If the StopWatch is stopped.
    436     * @since 3.2
    437     */
    438    public boolean isStopped() {
    439        return runningState.isStopped();
    440    }
    441
    442    /**
    443     * <p>
    444     * Returns whether the StopWatch is suspended.
    445     * </p>
    446     *
    447     * @return boolean
    448     *             If the StopWatch is suspended.
    449     * @since 3.2
    450     */
    451    public boolean isSuspended() {
    452        return runningState.isSuspended();
    453    }
    454
    455    /**
    456     * <p>
    457     * Resets the stopwatch. Stops it if need be.
    458     * </p>
    459     *
    460     * <p>
    461     * This method clears the internal values to allow the object to be reused.
    462     * </p>
    463     */
    464    public void reset() {
    465        this.runningState = State.UNSTARTED;
    466        this.splitState = SplitState.UNSPLIT;
    467    }
    468
    469    /**
    470     * <p>
    471     * Resumes the stopwatch after a suspend.
    472     * </p>
    473     *
    474     * <p>
    475     * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
    476     * resume calls in the total time.
    477     * </p>
    478     *
    479     * @throws IllegalStateException
    480     *             if the StopWatch has not been suspended.
    481     */
    482    public void resume() {
    483        if (this.runningState != State.SUSPENDED) {
    484            throw new IllegalStateException("Stopwatch must be suspended to resume. ");
    485        }
    486        this.startTimeNanos += System.nanoTime() - this.stopTimeNanos;
    487        this.runningState = State.RUNNING;
    488    }
    489
    490    /**
    491     * <p>
    492     * Splits the time.
    493     * </p>
    494     *
    495     * <p>
    496     * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
    497     * enabling {@link #unsplit()} to continue the timing from the original start point.
    498     * </p>
    499     *
    500     * @throws IllegalStateException
    501     *             if the StopWatch is not running.
    502     */
    503    public void split() {
    504        if (this.runningState != State.RUNNING) {
    505            throw new IllegalStateException("Stopwatch is not running. ");
    506        }
    507        this.stopTimeNanos = System.nanoTime();
    508        this.splitState = SplitState.SPLIT;
    509    }
    510
    511    /**
    512     * <p>
    513     * Starts the stopwatch.
    514     * </p>
    515     *
    516     * <p>
    517     * This method starts a new timing session, clearing any previous values.
    518     * </p>
    519     *
    520     * @throws IllegalStateException
    521     *             if the StopWatch is already running.
    522     */
    523    public void start() {
    524        if (this.runningState == State.STOPPED) {
    525            throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
    526        }
    527        if (this.runningState != State.UNSTARTED) {
    528            throw new IllegalStateException("Stopwatch already started. ");
    529        }
    530        this.startTimeNanos = System.nanoTime();
    531        this.startTimeMillis = System.currentTimeMillis();
    532        this.runningState = State.RUNNING;
    533    }
    534
    535    /**
    536     * <p>
    537     * Stops the stopwatch.
    538     * </p>
    539     *
    540     * <p>
    541     * This method ends a new timing session, allowing the time to be retrieved.
    542     * </p>
    543     *
    544     * @throws IllegalStateException
    545     *             if the StopWatch is not running.
    546     */
    547    public void stop() {
    548        if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
    549            throw new IllegalStateException("Stopwatch is not running. ");
    550        }
    551        if (this.runningState == State.RUNNING) {
    552            this.stopTimeNanos = System.nanoTime();
    553            this.stopTimeMillis = System.currentTimeMillis();
    554        }
    555        this.runningState = State.STOPPED;
    556    }
    557
    558    /**
    559     * <p>
    560     * Suspends the stopwatch for later resumption.
    561     * </p>
    562     *
    563     * <p>
    564     * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
    565     * resume calls in the total time.
    566     * </p>
    567     *
    568     * @throws IllegalStateException
    569     *             if the StopWatch is not currently running.
    570     */
    571    public void suspend() {
    572        if (this.runningState != State.RUNNING) {
    573            throw new IllegalStateException("Stopwatch must be running to suspend. ");
    574        }
    575        this.stopTimeNanos = System.nanoTime();
    576        this.stopTimeMillis = System.currentTimeMillis();
    577        this.runningState = State.SUSPENDED;
    578    }
    579
    580    /**
    581     * <p>
    582     * Gets a summary of the split time that the stopwatch recorded as a string.
    583     * </p>
    584     *
    585     * <p>
    586     * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
    587     * </p>
    588     *
    589     * @return the split time as a String
    590     * @since 2.1
    591     * @since 3.10 Returns the prefix {@code "message "} if the message is set.
    592     */
    593    public String toSplitString() {
    594        final String msgStr = Objects.toString(message, StringUtils.EMPTY);
    595        final String formattedTime = formatSplitTime();
    596        return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
    597    }
    598
    599    /**
    600     * <p>
    601     * Gets a summary of the time that the stopwatch recorded as a string.
    602     * </p>
    603     *
    604     * <p>
    605     * The format used is ISO 8601-like, [<i>message</i> ]<i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
    606     * </p>
    607     *
    608     * @return the time as a String
    609     * @since 3.10 Returns the prefix {@code "message "} if the message is set.
    610     */
    611    @Override
    612    public String toString() {
    613        final String msgStr = Objects.toString(message, StringUtils.EMPTY);
    614        final String formattedTime = formatTime();
    615        return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
    616    }
    617
    618    /**
    619     * <p>
    620     * Removes a split.
    621     * </p>
    622     *
    623     * <p>
    624     * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
    625     * continue.
    626     * </p>
    627     *
    628     * @throws IllegalStateException
    629     *             if the StopWatch has not been split.
    630     */
    631    public void unsplit() {
    632        if (this.splitState != SplitState.SPLIT) {
    633            throw new IllegalStateException("Stopwatch has not been split. ");
    634        }
    635        this.splitState = SplitState.UNSPLIT;
    636    }
    637
    638}