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