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