    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
    018    package org.apache.commons.lang3.time;
    019
    020    /**
    021     * <p>
    022     * <code>StopWatch</code> provides a convenient API for timings.
    023     * </p>
    024     *
    025     * <p>
    026     * To start the watch, call {@link #start()}. At this point you can:
    027     * </p>
    028     * <ul>
    029     * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will
    030     * remove the effect of the split. At this point, these three options are available again.</li>
    031     * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the
    032     * suspend and resume will not be counted in the total. At this point, these three options are available again.</li>
    033     * <li>{@link #stop()} the watch to complete the timing session.</li>
    034     * </ul>
    035     *
    036     * <p>
    037     * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop,
    038     * split or suspend, however a suitable result will be returned at other points.
    039     * </p>
    040     *
    041     * <p>
    042     * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start,
    043     * resume before suspend or unsplit before split.
    044     * </p>
    045     *
    046     * <p>
    047     * 1. split(), suspend(), or stop() cannot be invoked twice<br />
    048     * 2. unsplit() may only be called if the watch has been split()<br />
    049     * 3. resume() may only be called if the watch has been suspend()<br />
    050     * 4. start() cannot be called twice without calling reset()
    051     * </p>
    052     *
    053     * <p>This class is not thread-safe</p>
    054     *
    055     * @author Apache Software Foundation
    056     * @since 2.0
    057     * @version $Id: StopWatch.java 959577 2010-07-01 09:48:23Z sebb $
    058     */
    059    public class StopWatch {
    060
    061        private static final long NANO_2_MILLIS = 1000000L;
    062
    063        // running states
    064        private static final int STATE_UNSTARTED = 0;
    065
    066        private static final int STATE_RUNNING = 1;
    067
    068        private static final int STATE_STOPPED = 2;
    069
    070        private static final int STATE_SUSPENDED = 3;
    071
    072        // split state
    073        private static final int STATE_UNSPLIT = 10;
    074
    075        private static final int STATE_SPLIT = 11;
    076
    077        /**
    078         * The current running state of the StopWatch.
    079         */
    080        private int runningState = STATE_UNSTARTED;
    081
    082        /**
    083         * Whether the stopwatch has a split time recorded.
    084         */
    085        private int splitState = STATE_UNSPLIT;
    086
    087        /**
    088         * The start time.
    089         */
    090        private long startTime;
    091
    092        /**
    093         * The start time in Millis - nanoTime is only for elapsed time so we
    094         * need to also store the currentTimeMillis to maintain the old
    095         * getStartTime API.
    096         */
    097        private long startTimeMillis;
    098
    099        /**
    100         * The stop time.
    101         */
    102        private long stopTime;
    103
    104        /**
    105         * <p>
    106         * Constructor.
    107         * </p>
    108         */
    109        public StopWatch() {
    110            super();
    111        }
    112
    113        /**
    114         * <p>
    115         * Start the stopwatch.
    116         * </p>
    117         *
    118         * <p>
    119         * This method starts a new timing session, clearing any previous values.
    120         * </p>
    121         *
    122         * @throws IllegalStateException
    123         *             if the StopWatch is already running.
    124         */
    125        public void start() {
    126            if (this.runningState == STATE_STOPPED) {
    127                throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
    128            }
    129            if (this.runningState != STATE_UNSTARTED) {
    130                throw new IllegalStateException("Stopwatch already started. ");
    131            }
    132            this.startTime = System.nanoTime();
    133            this.startTimeMillis = System.currentTimeMillis();
    134            this.runningState = STATE_RUNNING;
    135        }
    136
    137        /**
    138         * <p>
    139         * Stop the stopwatch.
    140         * </p>
    141         *
    142         * <p>
    143         * This method ends a new timing session, allowing the time to be retrieved.
    144         * </p>
    145         *
    146         * @throws IllegalStateException
    147         *             if the StopWatch is not running.
    148         */
    149        public void stop() {
    150            if (this.runningState != STATE_RUNNING && this.runningState != STATE_SUSPENDED) {
    151                throw new IllegalStateException("Stopwatch is not running. ");
    152            }
    153            if (this.runningState == STATE_RUNNING) {
    154                this.stopTime = System.nanoTime();
    155            }
    156            this.runningState = STATE_STOPPED;
    157        }
    158
    159        /**
    160         * <p>
    161         * Resets the stopwatch. Stops it if need be.
    162         * </p>
    163         *
    164         * <p>
    165         * This method clears the internal values to allow the object to be reused.
    166         * </p>
    167         */
    168        public void reset() {
    169            this.runningState = STATE_UNSTARTED;
    170            this.splitState = STATE_UNSPLIT;
    171        }
    172
    173        /**
    174         * <p>
    175         * Split the time.
    176         * </p>
    177         *
    178         * <p>
    179         * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
    180         * enabling {@link #unsplit()} to continue the timing from the original start point.
    181         * </p>
    182         *
    183         * @throws IllegalStateException
    184         *             if the StopWatch is not running.
    185         */
    186        public void split() {
    187            if (this.runningState != STATE_RUNNING) {
    188                throw new IllegalStateException("Stopwatch is not running. ");
    189            }
    190            this.stopTime = System.nanoTime();
    191            this.splitState = STATE_SPLIT;
    192        }
    193
    194        /**
    195         * <p>
    196         * Remove a split.
    197         * </p>
    198         *
    199         * <p>
    200         * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
    201         * continue.
    202         * </p>
    203         *
    204         * @throws IllegalStateException
    205         *             if the StopWatch has not been split.
    206         */
    207        public void unsplit() {
    208            if (this.splitState != STATE_SPLIT) {
    209                throw new IllegalStateException("Stopwatch has not been split. ");
    210            }
    211            this.splitState = STATE_UNSPLIT;
    212        }
    213
    214        /**
    215         * <p>
    216         * Suspend the stopwatch for later resumption.
    217         * </p>
    218         *
    219         * <p>
    220         * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
    221         * resume calls in the total time.
    222         * </p>
    223         *
    224         * @throws IllegalStateException
    225         *             if the StopWatch is not currently running.
    226         */
    227        public void suspend() {
    228            if (this.runningState != STATE_RUNNING) {
    229                throw new IllegalStateException("Stopwatch must be running to suspend. ");
    230            }
    231            this.stopTime = System.nanoTime();
    232            this.runningState = STATE_SUSPENDED;
    233        }
    234
    235        /**
    236         * <p>
    237         * Resume the stopwatch after a suspend.
    238         * </p>
    239         *
    240         * <p>
    241         * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
    242         * resume calls in the total time.
    243         * </p>
    244         *
    245         * @throws IllegalStateException
    246         *             if the StopWatch has not been suspended.
    247         */
    248        public void resume() {
    249            if (this.runningState != STATE_SUSPENDED) {
    250                throw new IllegalStateException("Stopwatch must be suspended to resume. ");
    251            }
    252            this.startTime += (System.nanoTime() - this.stopTime);
    253            this.runningState = STATE_RUNNING;
    254        }
    255
    256        /**
    257         * <p>
    258         * Get the time on the stopwatch.
    259         * </p>
    260         *
    261         * <p>
    262         * This is either the time between the start and the moment this method is called, or the amount of time between
    263         * start and stop.
    264         * </p>
    265         *
    266         * @return the time in milliseconds
    267         */
    268        public long getTime() {
    269            return getNanoTime() / NANO_2_MILLIS;
    270        }
    271        /**
    272         * <p>
    273         * Get the time on the stopwatch in nanoseconds.
    274         * </p>
    275         *
    276         * <p>
    277         * This is either the time between the start and the moment this method is called, or the amount of time between
    278         * start and stop.
    279         * </p>
    280         *
    281         * @return the time in nanoseconds
    282         * @since 3.0
    283         */
    284        public long getNanoTime() {
    285            if (this.runningState == STATE_STOPPED || this.runningState == STATE_SUSPENDED) {
    286                return this.stopTime - this.startTime;
    287            } else if (this.runningState == STATE_UNSTARTED) {
    288                return 0;
    289            } else if (this.runningState == STATE_RUNNING) {
    290                return System.nanoTime() - this.startTime;
    291            }
    292            throw new RuntimeException("Illegal running state has occured. ");
    293        }
    294
    295        /**
    296         * <p>
    297         * Get the split time on the stopwatch.
    298         * </p>
    299         *
    300         * <p>
    301         * This is the time between start and latest split.
    302         * </p>
    303         *
    304         * @return the split time in milliseconds
    305         *
    306         * @throws IllegalStateException
    307         *             if the StopWatch has not yet been split.
    308         * @since 2.1
    309         */
    310        public long getSplitTime() {
    311            return getSplitNanoTime() / NANO_2_MILLIS;
    312        }
    313        /**
    314         * <p>
    315         * Get the split time on the stopwatch in nanoseconds.
    316         * </p>
    317         *
    318         * <p>
    319         * This is the time between start and latest split.
    320         * </p>
    321         *
    322         * @return the split time in nanoseconds
    323         *
    324         * @throws IllegalStateException
    325         *             if the StopWatch has not yet been split.
    326         * @since 3.0
    327         */
    328        public long getSplitNanoTime() {
    329            if (this.splitState != STATE_SPLIT) {
    330                throw new IllegalStateException("Stopwatch must be split to get the split time. ");
    331            }
    332            return this.stopTime - this.startTime;
    333        }
    334
    335        /**
    336         * Returns the time this stopwatch was started.
    337         *
    338         * @return the time this stopwatch was started
    339         * @throws IllegalStateException
    340         *             if this StopWatch has not been started
    341         * @since 2.4
    342         */
    343        public long getStartTime() {
    344            if (this.runningState == STATE_UNSTARTED) {
    345                throw new IllegalStateException("Stopwatch has not been started");
    346            }
    347            // System.nanoTime is for elapsed time
    348            return this.startTimeMillis;
    349        }
    350
    351        /**
    352         * <p>
    353         * Gets a summary of the time that the stopwatch recorded as a string.
    354         * </p>
    355         *
    356         * <p>
    357         * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
    358         * </p>
    359         *
    360         * @return the time as a String
    361         */
    362        @Override
    363        public String toString() {
    364            return DurationFormatUtils.formatDurationHMS(getTime());
    365        }
    366
    367        /**
    368         * <p>
    369         * Gets a summary of the split time that the stopwatch recorded as a string.
    370         * </p>
    371         *
    372         * <p>
    373         * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
    374         * </p>
    375         *
    376         * @return the split time as a String
    377         * @since 2.1
    378         */
    379        public String toSplitString() {
    380            return DurationFormatUtils.formatDurationHMS(getSplitTime());
    381        }
    382
    383    }