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
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 1558512 2014-01-15 18:56:57Z britter $
057 */
058public class StopWatch {
059
060    private static final long NANO_2_MILLIS = 1000000L;
061    
062    /**
063     * Enumeration type which indicates the status of stopwatch.
064     */
065    private enum State {
066
067        UNSTARTED {
068            @Override boolean isStarted() { return false; }
069            @Override boolean isStopped() { return true;  }
070            @Override boolean isSuspended() { return false; }
071        },
072        RUNNING {
073            @Override boolean isStarted() { return true; }
074            @Override boolean isStopped() { return false; }
075            @Override boolean isSuspended() { return false; }
076        },
077        STOPPED {
078            @Override boolean isStarted() { return false; }
079            @Override boolean isStopped() { return true; }
080            @Override boolean isSuspended() { return false; }
081        },
082        SUSPENDED {
083            @Override boolean isStarted() { return true; }
084            @Override boolean isStopped() { return false; }
085            @Override  boolean isSuspended() { return true; }
086        };
087
088        /**
089         * <p>
090         * The method is used to find out if the StopWatch is started. A suspended
091         * StopWatch is also started watch.
092         * </p>
093
094         * @return boolean
095         *             If the StopWatch is started.
096         */
097        abstract boolean isStarted();
098
099        /**
100         * <p>
101         * This method is used to find out whether the StopWatch is stopped. The
102         * stopwatch which's not yet started and explicitly stopped stopwatch is
103         * considered as stopped.
104         * </p>
105         *
106         * @return boolean
107         *             If the StopWatch is stopped.
108         */
109        abstract boolean isStopped();
110
111        /**
112         * <p>
113         * This method is used to find out whether the StopWatch is suspended.
114         * </p>
115         *
116         * @return boolean
117         *             If the StopWatch is suspended.
118         */
119        abstract boolean isSuspended();
120    }
121
122    /**
123     * Enumeration type which indicates the split status of stopwatch.
124     */    
125    private enum SplitState {
126        SPLIT,
127        UNSPLIT
128    }
129    /**
130     * The current running state of the StopWatch.
131     */
132    private State runningState = State.UNSTARTED;
133
134    /**
135     * Whether the stopwatch has a split time recorded.
136     */
137    private SplitState splitState = SplitState.UNSPLIT;
138
139    /**
140     * The start time.
141     */
142    private long startTime;
143
144    /**
145     * The start time in Millis - nanoTime is only for elapsed time so we 
146     * need to also store the currentTimeMillis to maintain the old 
147     * getStartTime API.
148     */
149    private long startTimeMillis;
150
151    /**
152     * The stop time.
153     */
154    private long stopTime;
155
156    /**
157     * <p>
158     * Constructor.
159     * </p>
160     */
161    public StopWatch() {
162        super();
163    }
164
165    /**
166     * <p>
167     * Start the stopwatch.
168     * </p>
169     * 
170     * <p>
171     * This method starts a new timing session, clearing any previous values.
172     * </p>
173     * 
174     * @throws IllegalStateException
175     *             if the StopWatch is already running.
176     */
177    public void start() {
178        if (this.runningState == State.STOPPED) {
179            throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
180        }
181        if (this.runningState != State.UNSTARTED) {
182            throw new IllegalStateException("Stopwatch already started. ");
183        }
184        this.startTime = System.nanoTime();
185        this.startTimeMillis = System.currentTimeMillis();
186        this.runningState = State.RUNNING;
187    }
188
189
190    /**
191     * <p>
192     * Stop the stopwatch.
193     * </p>
194     * 
195     * <p>
196     * This method ends a new timing session, allowing the time to be retrieved.
197     * </p>
198     * 
199     * @throws IllegalStateException
200     *             if the StopWatch is not running.
201     */
202    public void stop() {
203        if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
204            throw new IllegalStateException("Stopwatch is not running. ");
205        }
206        if (this.runningState == State.RUNNING) {
207            this.stopTime = System.nanoTime();
208        }
209        this.runningState = State.STOPPED;
210    }
211
212    /**
213     * <p>
214     * Resets the stopwatch. Stops it if need be.
215     * </p>
216     * 
217     * <p>
218     * This method clears the internal values to allow the object to be reused.
219     * </p>
220     */
221    public void reset() {
222        this.runningState = State.UNSTARTED;
223        this.splitState = SplitState.UNSPLIT;
224    }
225
226    /**
227     * <p>
228     * Split the time.
229     * </p>
230     * 
231     * <p>
232     * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
233     * enabling {@link #unsplit()} to continue the timing from the original start point.
234     * </p>
235     * 
236     * @throws IllegalStateException
237     *             if the StopWatch is not running.
238     */
239    public void split() {
240        if (this.runningState != State.RUNNING) {
241            throw new IllegalStateException("Stopwatch is not running. ");
242        }
243        this.stopTime = System.nanoTime();
244        this.splitState = SplitState.SPLIT;
245    }
246
247    /**
248     * <p>
249     * Remove a split.
250     * </p>
251     * 
252     * <p>
253     * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
254     * continue.
255     * </p>
256     * 
257     * @throws IllegalStateException
258     *             if the StopWatch has not been split.
259     */
260    public void unsplit() {
261        if (this.splitState != SplitState.SPLIT) {
262            throw new IllegalStateException("Stopwatch has not been split. ");
263        }
264        this.splitState = SplitState.UNSPLIT;
265    }
266
267    /**
268     * <p>
269     * Suspend the stopwatch for later resumption.
270     * </p>
271     * 
272     * <p>
273     * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
274     * resume calls in the total time.
275     * </p>
276     * 
277     * @throws IllegalStateException
278     *             if the StopWatch is not currently running.
279     */
280    public void suspend() {
281        if (this.runningState != State.RUNNING) {
282            throw new IllegalStateException("Stopwatch must be running to suspend. ");
283        }
284        this.stopTime = System.nanoTime();
285        this.runningState = State.SUSPENDED;
286    }
287
288    /**
289     * <p>
290     * Resume the stopwatch after a suspend.
291     * </p>
292     * 
293     * <p>
294     * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
295     * resume calls in the total time.
296     * </p>
297     * 
298     * @throws IllegalStateException
299     *             if the StopWatch has not been suspended.
300     */
301    public void resume() {
302        if (this.runningState != State.SUSPENDED) {
303            throw new IllegalStateException("Stopwatch must be suspended to resume. ");
304        }
305        this.startTime += System.nanoTime() - this.stopTime;
306        this.runningState = State.RUNNING;
307    }
308
309    /**
310     * <p>
311     * Get the time on the stopwatch.
312     * </p>
313     * 
314     * <p>
315     * This is either the time between the start and the moment this method is called, or the amount of time between
316     * start and stop.
317     * </p>
318     * 
319     * @return the time in milliseconds
320     */
321    public long getTime() {
322        return getNanoTime() / NANO_2_MILLIS;
323    }
324    /**
325     * <p>
326     * Get the time on the stopwatch in nanoseconds.
327     * </p>
328     * 
329     * <p>
330     * This is either the time between the start and the moment this method is called, or the amount of time between
331     * start and stop.
332     * </p>
333     * 
334     * @return the time in nanoseconds
335     * @since 3.0
336     */
337    public long getNanoTime() {
338        if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
339            return this.stopTime - this.startTime;
340        } else if (this.runningState == State.UNSTARTED) {
341            return 0;
342        } else if (this.runningState == State.RUNNING) {
343            return System.nanoTime() - this.startTime;
344        }
345        throw new RuntimeException("Illegal running state has occurred.");
346    }
347
348    /**
349     * <p>
350     * Get the split time on the stopwatch.
351     * </p>
352     * 
353     * <p>
354     * This is the time between start and latest split.
355     * </p>
356     * 
357     * @return the split time in milliseconds
358     * 
359     * @throws IllegalStateException
360     *             if the StopWatch has not yet been split.
361     * @since 2.1
362     */
363    public long getSplitTime() {
364        return getSplitNanoTime() / NANO_2_MILLIS;
365    }
366    /**
367     * <p>
368     * Get the split time on the stopwatch in nanoseconds.
369     * </p>
370     * 
371     * <p>
372     * This is the time between start and latest split.
373     * </p>
374     * 
375     * @return the split time in nanoseconds
376     * 
377     * @throws IllegalStateException
378     *             if the StopWatch has not yet been split.
379     * @since 3.0
380     */
381    public long getSplitNanoTime() {
382        if (this.splitState != SplitState.SPLIT) {
383            throw new IllegalStateException("Stopwatch must be split to get the split time. ");
384        }
385        return this.stopTime - this.startTime;
386    }
387
388    /**
389     * Returns the time this stopwatch was started.
390     * 
391     * @return the time this stopwatch was started
392     * @throws IllegalStateException
393     *             if this StopWatch has not been started
394     * @since 2.4
395     */
396    public long getStartTime() {
397        if (this.runningState == State.UNSTARTED) {
398            throw new IllegalStateException("Stopwatch has not been started");
399        }
400        // System.nanoTime is for elapsed time
401        return this.startTimeMillis;
402    }
403
404    /**
405     * <p>
406     * Gets a summary of the time that the stopwatch recorded as a string.
407     * </p>
408     * 
409     * <p>
410     * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
411     * </p>
412     * 
413     * @return the time as a String
414     */
415    @Override
416    public String toString() {
417        return DurationFormatUtils.formatDurationHMS(getTime());
418    }
419
420    /**
421     * <p>
422     * Gets a summary of the split time that the stopwatch recorded as a string.
423     * </p>
424     * 
425     * <p>
426     * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
427     * </p>
428     * 
429     * @return the split time as a String
430     * @since 2.1
431     */
432    public String toSplitString() {
433        return DurationFormatUtils.formatDurationHMS(getSplitTime());
434    }
435
436    /**
437     * <p>
438     * The method is used to find out if the StopWatch is started. A suspended
439     * StopWatch is also started watch.
440     * </p>
441     *
442     * @return boolean
443     *             If the StopWatch is started.
444     * @since 3.2
445     */
446    public boolean isStarted() {
447        return runningState.isStarted();
448    }
449
450    /**
451     * <p>
452     * This method is used to find out whether the StopWatch is suspended.
453     * </p>
454     *
455     * @return boolean
456     *             If the StopWatch is suspended.
457     * @since 3.2
458     */
459    public boolean isSuspended() {
460        return runningState.isSuspended();
461    }
462
463    /**
464     * <p>
465     * This method is used to find out whether the StopWatch is stopped. The
466     * stopwatch which's not yet started and explicitly stopped stopwatch is
467     * considered as stopped.
468     * </p>
469     *
470     * @return boolean
471     *             If the StopWatch is stopped.
472     * @since 3.2
473     */
474    public boolean isStopped() {
475        return runningState.isStopped();
476    }    
477
478}