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.concurrent.TimeUnit;
021
022/**
023 * <p>
024 * <code>StopWatch</code> provides a convenient API for timings.
025 * </p>
026 * 
027 * <p>
028 * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
029 * </p>
030 * <ul>
031 * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will
032 * remove the effect of the split. At this point, these three options are available again.</li>
033 * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the
034 * suspend and resume will not be counted in the total. At this point, these three options are available again.</li>
035 * <li>{@link #stop()} the watch to complete the timing session.</li>
036 * </ul>
037 * 
038 * <p>
039 * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop,
040 * split or suspend, however a suitable result will be returned at other points.
041 * </p>
042 * 
043 * <p>
044 * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start,
045 * resume before suspend or unsplit before split.
046 * </p>
047 * 
048 * <p>
049 * 1. split(), suspend(), or stop() cannot be invoked twice<br>
050 * 2. unsplit() may only be called if the watch has been split()<br>
051 * 3. resume() may only be called if the watch has been suspend()<br>
052 * 4. start() cannot be called twice without calling reset()
053 * </p>
054 * 
055 * <p>This class is not thread-safe</p>
056 * 
057 * @since 2.0
058 */
059public class StopWatch {
060
061    private static final long NANO_2_MILLIS = 1000000L;
062
063
064    /**
065     * Provides a started stopwatch for convenience.
066     *
067     * @return StopWatch a stopwatch that's already been started. 
068     *
069     * @since 3.5
070     */
071    public static StopWatch createStarted() {
072        StopWatch sw = new StopWatch();
073        sw.start();
074        return sw;
075    }
076    
077    /**
078     * Enumeration type which indicates the status of stopwatch.
079     */
080    private enum State {
081
082        UNSTARTED {
083            @Override boolean isStarted() { return false; }
084            @Override boolean isStopped() { return true;  }
085            @Override boolean isSuspended() { return false; }
086        },
087        RUNNING {
088            @Override boolean isStarted() { return true; }
089            @Override boolean isStopped() { return false; }
090            @Override boolean isSuspended() { return false; }
091        },
092        STOPPED {
093            @Override boolean isStarted() { return false; }
094            @Override boolean isStopped() { return true; }
095            @Override boolean isSuspended() { return false; }
096        },
097        SUSPENDED {
098            @Override boolean isStarted() { return true; }
099            @Override boolean isStopped() { return false; }
100            @Override  boolean isSuspended() { return true; }
101        };
102
103        /**
104         * <p>
105         * The method is used to find out if the StopWatch is started. A suspended
106         * StopWatch is also started watch.
107         * </p>
108
109         * @return boolean
110         *             If the StopWatch is started.
111         */
112        abstract boolean isStarted();
113
114        /**
115         * <p>
116         * This method is used to find out whether the StopWatch is stopped. The
117         * stopwatch which's not yet started and explicitly stopped stopwatch is
118         * considered as stopped.
119         * </p>
120         *
121         * @return boolean
122         *             If the StopWatch is stopped.
123         */
124        abstract boolean isStopped();
125
126        /**
127         * <p>
128         * This method is used to find out whether the StopWatch is suspended.
129         * </p>
130         *
131         * @return boolean
132         *             If the StopWatch is suspended.
133         */
134        abstract boolean isSuspended();
135    }
136
137    /**
138     * Enumeration type which indicates the split status of stopwatch.
139     */    
140    private enum SplitState {
141        SPLIT,
142        UNSPLIT
143    }
144    /**
145     * The current running state of the StopWatch.
146     */
147    private State runningState = State.UNSTARTED;
148
149    /**
150     * Whether the stopwatch has a split time recorded.
151     */
152    private SplitState splitState = SplitState.UNSPLIT;
153
154    /**
155     * The start time.
156     */
157    private long startTime;
158
159    /**
160     * The start time in Millis - nanoTime is only for elapsed time so we 
161     * need to also store the currentTimeMillis to maintain the old 
162     * getStartTime API.
163     */
164    private long startTimeMillis;
165
166    /**
167     * The stop time.
168     */
169    private long stopTime;
170
171    /**
172     * <p>
173     * Constructor.
174     * </p>
175     */
176    public StopWatch() {
177        super();
178    }
179
180    /**
181     * <p>
182     * Start the stopwatch.
183     * </p>
184     * 
185     * <p>
186     * This method starts a new timing session, clearing any previous values.
187     * </p>
188     * 
189     * @throws IllegalStateException
190     *             if the StopWatch is already running.
191     */
192    public void start() {
193        if (this.runningState == State.STOPPED) {
194            throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
195        }
196        if (this.runningState != State.UNSTARTED) {
197            throw new IllegalStateException("Stopwatch already started. ");
198        }
199        this.startTime = System.nanoTime();
200        this.startTimeMillis = System.currentTimeMillis();
201        this.runningState = State.RUNNING;
202    }
203
204
205    /**
206     * <p>
207     * Stop the stopwatch.
208     * </p>
209     * 
210     * <p>
211     * This method ends a new timing session, allowing the time to be retrieved.
212     * </p>
213     * 
214     * @throws IllegalStateException
215     *             if the StopWatch is not running.
216     */
217    public void stop() {
218        if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) {
219            throw new IllegalStateException("Stopwatch is not running. ");
220        }
221        if (this.runningState == State.RUNNING) {
222            this.stopTime = System.nanoTime();
223        }
224        this.runningState = State.STOPPED;
225    }
226
227    /**
228     * <p>
229     * Resets the stopwatch. Stops it if need be.
230     * </p>
231     * 
232     * <p>
233     * This method clears the internal values to allow the object to be reused.
234     * </p>
235     */
236    public void reset() {
237        this.runningState = State.UNSTARTED;
238        this.splitState = SplitState.UNSPLIT;
239    }
240
241    /**
242     * <p>
243     * Split the time.
244     * </p>
245     * 
246     * <p>
247     * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected,
248     * enabling {@link #unsplit()} to continue the timing from the original start point.
249     * </p>
250     * 
251     * @throws IllegalStateException
252     *             if the StopWatch is not running.
253     */
254    public void split() {
255        if (this.runningState != State.RUNNING) {
256            throw new IllegalStateException("Stopwatch is not running. ");
257        }
258        this.stopTime = System.nanoTime();
259        this.splitState = SplitState.SPLIT;
260    }
261
262    /**
263     * <p>
264     * Remove a split.
265     * </p>
266     * 
267     * <p>
268     * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
269     * continue.
270     * </p>
271     * 
272     * @throws IllegalStateException
273     *             if the StopWatch has not been split.
274     */
275    public void unsplit() {
276        if (this.splitState != SplitState.SPLIT) {
277            throw new IllegalStateException("Stopwatch has not been split. ");
278        }
279        this.splitState = SplitState.UNSPLIT;
280    }
281
282    /**
283     * <p>
284     * Suspend the stopwatch for later resumption.
285     * </p>
286     * 
287     * <p>
288     * This method suspends the watch until it is resumed. The watch will not include time between the suspend and
289     * resume calls in the total time.
290     * </p>
291     * 
292     * @throws IllegalStateException
293     *             if the StopWatch is not currently running.
294     */
295    public void suspend() {
296        if (this.runningState != State.RUNNING) {
297            throw new IllegalStateException("Stopwatch must be running to suspend. ");
298        }
299        this.stopTime = System.nanoTime();
300        this.runningState = State.SUSPENDED;
301    }
302
303    /**
304     * <p>
305     * Resume the stopwatch after a suspend.
306     * </p>
307     * 
308     * <p>
309     * This method resumes the watch after it was suspended. The watch will not include time between the suspend and
310     * resume calls in the total time.
311     * </p>
312     * 
313     * @throws IllegalStateException
314     *             if the StopWatch has not been suspended.
315     */
316    public void resume() {
317        if (this.runningState != State.SUSPENDED) {
318            throw new IllegalStateException("Stopwatch must be suspended to resume. ");
319        }
320        this.startTime += System.nanoTime() - this.stopTime;
321        this.runningState = State.RUNNING;
322    }
323
324    /**
325     * <p>
326     * Get the time on the stopwatch.
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 milliseconds
335     */
336    public long getTime() {
337        return getNanoTime() / NANO_2_MILLIS;
338    }
339
340    /**
341     * <p>
342     * Get the time on the stopwatch in the specified TimeUnit.
343     * </p>
344     * 
345     * <p>
346     * This is either the time between the start and the moment this method is called, or the amount of time between
347     * start and stop. The resulting time will be expressed in the desired TimeUnit with any remainder rounded down.
348     * For example, if the specified unit is {@code TimeUnit.HOURS} and the stopwatch time is 59 minutes, then the
349     * result returned will be {@code 0}.
350     * </p>
351     * 
352     * @param timeUnit the unit of time, not null
353     * @return the time in the specified TimeUnit, rounded down
354     * @since 3.5
355     */
356    public long getTime(final TimeUnit timeUnit) {
357        return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
358    }
359
360    /**
361     * <p>
362     * Get the time on the stopwatch in nanoseconds.
363     * </p>
364     * 
365     * <p>
366     * This is either the time between the start and the moment this method is called, or the amount of time between
367     * start and stop.
368     * </p>
369     * 
370     * @return the time in nanoseconds
371     * @since 3.0
372     */
373    public long getNanoTime() {
374        if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) {
375            return this.stopTime - this.startTime;
376        } else if (this.runningState == State.UNSTARTED) {
377            return 0;
378        } else if (this.runningState == State.RUNNING) {
379            return System.nanoTime() - this.startTime;
380        }
381        throw new RuntimeException("Illegal running state has occurred.");
382    }
383
384    /**
385     * <p>
386     * Get the split time on the stopwatch.
387     * </p>
388     * 
389     * <p>
390     * This is the time between start and latest split.
391     * </p>
392     * 
393     * @return the split time in milliseconds
394     * 
395     * @throws IllegalStateException
396     *             if the StopWatch has not yet been split.
397     * @since 2.1
398     */
399    public long getSplitTime() {
400        return getSplitNanoTime() / NANO_2_MILLIS;
401    }
402    /**
403     * <p>
404     * Get the split time on the stopwatch in nanoseconds.
405     * </p>
406     * 
407     * <p>
408     * This is the time between start and latest split.
409     * </p>
410     * 
411     * @return the split time in nanoseconds
412     * 
413     * @throws IllegalStateException
414     *             if the StopWatch has not yet been split.
415     * @since 3.0
416     */
417    public long getSplitNanoTime() {
418        if (this.splitState != SplitState.SPLIT) {
419            throw new IllegalStateException("Stopwatch must be split to get the split time. ");
420        }
421        return this.stopTime - this.startTime;
422    }
423
424    /**
425     * Returns the time this stopwatch was started.
426     * 
427     * @return the time this stopwatch was started
428     * @throws IllegalStateException
429     *             if this StopWatch has not been started
430     * @since 2.4
431     */
432    public long getStartTime() {
433        if (this.runningState == State.UNSTARTED) {
434            throw new IllegalStateException("Stopwatch has not been started");
435        }
436        // System.nanoTime is for elapsed time
437        return this.startTimeMillis;
438    }
439
440    /**
441     * <p>
442     * Gets a summary of the time that the stopwatch recorded as a string.
443     * </p>
444     * 
445     * <p>
446     * The format used is ISO 8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
447     * </p>
448     * 
449     * @return the time as a String
450     */
451    @Override
452    public String toString() {
453        return DurationFormatUtils.formatDurationHMS(getTime());
454    }
455
456    /**
457     * <p>
458     * Gets a summary of the split time that the stopwatch recorded as a string.
459     * </p>
460     * 
461     * <p>
462     * The format used is ISO 8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
463     * </p>
464     * 
465     * @return the split time as a String
466     * @since 2.1
467     */
468    public String toSplitString() {
469        return DurationFormatUtils.formatDurationHMS(getSplitTime());
470    }
471
472    /**
473     * <p>
474     * The method is used to find out if the StopWatch is started. A suspended
475     * StopWatch is also started watch.
476     * </p>
477     *
478     * @return boolean
479     *             If the StopWatch is started.
480     * @since 3.2
481     */
482    public boolean isStarted() {
483        return runningState.isStarted();
484    }
485
486    /**
487     * <p>
488     * This method is used to find out whether the StopWatch is suspended.
489     * </p>
490     *
491     * @return boolean
492     *             If the StopWatch is suspended.
493     * @since 3.2
494     */
495    public boolean isSuspended() {
496        return runningState.isSuspended();
497    }
498
499    /**
500     * <p>
501     * This method is used to find out whether the StopWatch is stopped. The
502     * stopwatch which's not yet started and explicitly stopped stopwatch is
503     * considered as stopped.
504     * </p>
505     *
506     * @return boolean
507     *             If the StopWatch is stopped.
508     * @since 3.2
509     */
510    public boolean isStopped() {
511        return runningState.isStopped();
512    }    
513
514}