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