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