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