001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.fulcrum.yaafi.interceptor.util;
020    
021    /**
022     * <p><code>StopWatch</code> provides a convenient API for timings.</p>
023     *
024     * <p>To start the watch, call {@link #start()}. At this point you can:</p>
025     * <ul>
026     *  <li>{@link #split()} the watch to get the time whilst the watch continues in the
027     *   background. {@link #unsplit()} will remove the effect of the split. At this point,
028     *   these three options are available again.</li>
029     *  <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch
030     *   to continue. Any time between the suspend and resume will not be counted in
031     *   the total. At this point, these three options are available again.</li>
032     *  <li>{@link #stop()} the watch to complete the timing session.</li>
033     * </ul>
034     *
035     * <p>It is intended that the output methods {@link #toString()} and {@link #getTime()}
036     * should only be called after stop, split or suspend, however a suitable result will
037     * be returned at other points.</p>
038     *
039     * <p>NOTE: As from v2.1, the methods protect against inappropriate calls.
040     * Thus you cannot now call stop before start, resume before suspend or
041     * unsplit before split.</p>
042     *
043     * <p>1. split(), suspend(), or stop() cannot be invoked twice<br />
044     * 2. unsplit() may only be called if the watch has been split()<br />
045     * 3. resume() may only be called if the watch has been suspend()<br />
046     * 4. start() cannot be called twice without calling reset()</p>
047     *
048     * @author Henri Yandell
049     * @author Stephen Colebourne
050     * @since 2.0
051     * @version $Id: StopWatch.java,v 1.1 2005/09/29 13:18:37 sigi Exp $
052     */
053    public class StopWatch {
054    
055        // running states
056        private static final int STATE_UNSTARTED = 0;
057        private static final int STATE_RUNNING   = 1;
058        private static final int STATE_STOPPED   = 2;
059        private static final int STATE_SUSPENDED = 3;
060    
061        // split state
062        private static final int STATE_UNSPLIT = 10;
063        private static final int STATE_SPLIT   = 11;
064    
065        /**
066         *  The current running state of the StopWatch.
067         */
068        private int runningState = STATE_UNSTARTED;
069    
070        /**
071         * Whether the stopwatch has a split time recorded.
072         */
073        private int splitState   = STATE_UNSPLIT;
074    
075        /**
076         * The start time.
077         */
078        private long startTime = -1;
079        /**
080         * The stop time.
081         */
082        private long stopTime = -1;
083    
084        /**
085         * <p>Constructor.</p>
086         */
087        public StopWatch() {
088            // nothing to do
089        }
090    
091        /**
092         * <p>Start the stopwatch.</p>
093         *
094         * <p>This method starts a new timing session, clearing any previous values.</p>
095         *
096         * @throws IllegalStateException if the StopWatch is already running.
097         */
098        public void start()
099        {
100            if (this.runningState == STATE_STOPPED)
101            {
102                throw new IllegalStateException(
103                                "Stopwatch must be reset before being restarted. " );
104            }
105            if (this.runningState != STATE_UNSTARTED)
106            {
107                throw new IllegalStateException( "Stopwatch already started. " );
108            }
109            stopTime = -1;
110            startTime = System.currentTimeMillis();
111            this.runningState = STATE_RUNNING;
112        }
113    
114        /**
115         * <p>Stop the stopwatch.</p>
116         *
117         * <p>This method ends a new timing session, allowing the time to be retrieved.</p>
118         *
119         * @throws IllegalStateException if the StopWatch is not running.
120         */
121        public void stop()
122        {
123            if (this.runningState != STATE_RUNNING
124                            && this.runningState != STATE_SUSPENDED)
125            {
126                throw new IllegalStateException( "Stopwatch is not running. " );
127            }
128            stopTime = System.currentTimeMillis();
129            this.runningState = STATE_STOPPED;
130        }
131    
132        /**
133         * <p>Resets the stopwatch. Stops it if need be. </p>
134         *
135         * <p>This method clears the internal values to allow the object to be reused.</p>
136         */
137        public void reset()
138        {
139            this.runningState = STATE_UNSTARTED;
140            this.splitState = STATE_UNSPLIT;
141            startTime = -1;
142            stopTime = -1;
143        }
144    
145        /**
146         * <p>Split the time.</p>
147         *
148         * <p>This method sets the stop time of the watch to allow a time to be extracted.
149         * The start time is unaffected, enabling {@link #unsplit()} to continue the
150         * timing from the original start point.</p>
151         *
152         * @throws IllegalStateException if the StopWatch is not running.
153         */
154        public void split()
155        {
156            if (this.runningState != STATE_RUNNING)
157            {
158                throw new IllegalStateException( "Stopwatch is not running. " );
159            }
160            stopTime = System.currentTimeMillis();
161            this.splitState = STATE_SPLIT;
162        }
163    
164        /**
165         * <p>Remove a split.</p>
166         *
167         * <p>This method clears the stop time. The start time is unaffected, enabling
168         * timing from the original start point to continue.</p>
169         *
170         * @throws IllegalStateException if the StopWatch has not been split.
171         */
172        public void unsplit()
173        {
174            if (this.splitState != STATE_SPLIT)
175            {
176                throw new IllegalStateException( "Stopwatch has not been split. " );
177            }
178            stopTime = -1;
179            this.splitState = STATE_UNSPLIT;
180        }
181    
182        /**
183         * <p>Suspend the stopwatch for later resumption.</p>
184         *
185         * <p>This method suspends the watch until it is resumed. The watch will not include
186         * time between the suspend and resume calls in the total time.</p>
187         *
188         * @throws IllegalStateException if the StopWatch is not currently running.
189         */
190        public void suspend()
191        {
192            if (this.runningState != STATE_RUNNING)
193            {
194                throw new IllegalStateException(
195                                "Stopwatch must be running to suspend. " );
196            }
197            stopTime = System.currentTimeMillis();
198            this.runningState = STATE_SUSPENDED;
199        }
200    
201        /**
202         * <p>Resume the stopwatch after a suspend.</p>
203         *
204         * <p>This method resumes the watch after it was suspended. The watch will not include
205         * time between the suspend and resume calls in the total time.</p>
206         *
207         * @throws IllegalStateException if the StopWatch has not been suspended.
208         */
209        public void resume()
210        {
211            if (this.runningState != STATE_SUSPENDED)
212            {
213                throw new IllegalStateException(
214                                "Stopwatch must be suspended to resume. " );
215            }
216            startTime += (System.currentTimeMillis() - stopTime);
217            stopTime = -1;
218            this.runningState = STATE_RUNNING;
219        }
220    
221        /**
222         * <p>Get the time on the stopwatch.</p>
223         *
224         * <p>This is either the time between the start and the moment this method
225         * is called, or the amount of time between start and stop.</p>
226         *
227         * @return the time in milliseconds
228         */
229        public long getTime()
230        {
231            if (this.runningState == STATE_STOPPED
232                            || this.runningState == STATE_SUSPENDED)
233            {
234                return this.stopTime - this.startTime;
235            }
236            else if (this.runningState == STATE_UNSTARTED)
237            {
238                return 0;
239            }
240            else if (this.runningState == STATE_RUNNING)
241            {
242                return System.currentTimeMillis() - this.startTime;
243            }
244            throw new RuntimeException( "Illegal running state has occured. " );
245        }
246    
247        /**
248         * <p>Get the split time on the stopwatch.</p>
249         *
250         * <p>This is the time between start and latest split. </p>
251         *
252         * @return the split time in milliseconds
253         *
254         * @throws IllegalStateException if the StopWatch has not yet been split.
255         * @since 2.1
256         */
257        public long getSplitTime()
258        {
259            if (this.splitState != STATE_SPLIT)
260            {
261                throw new IllegalStateException(
262                    "Stopwatch must be split to get the split time. "
263                    );
264            }
265            return this.stopTime - this.startTime;
266        }
267    
268        /**
269         * <p>Gets a summary of the time that the stopwatch recorded as a string.</p>
270         *
271         * @return the time as a String
272         */
273        public String toString() {
274            return getTime()+"ms";
275        }
276    }