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 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 1583482 2014-03-31 22:54:57Z niallp $ 057 */ 058public class StopWatch { 059 060 private static final long NANO_2_MILLIS = 1000000L; 061 062 /** 063 * Enumeration type which indicates the status of stopwatch. 064 */ 065 private enum State { 066 067 UNSTARTED { 068 @Override boolean isStarted() { return false; } 069 @Override boolean isStopped() { return true; } 070 @Override boolean isSuspended() { return false; } 071 }, 072 RUNNING { 073 @Override boolean isStarted() { return true; } 074 @Override boolean isStopped() { return false; } 075 @Override boolean isSuspended() { return false; } 076 }, 077 STOPPED { 078 @Override boolean isStarted() { return false; } 079 @Override boolean isStopped() { return true; } 080 @Override boolean isSuspended() { return false; } 081 }, 082 SUSPENDED { 083 @Override boolean isStarted() { return true; } 084 @Override boolean isStopped() { return false; } 085 @Override boolean isSuspended() { return true; } 086 }; 087 088 /** 089 * <p> 090 * The method is used to find out if the StopWatch is started. A suspended 091 * StopWatch is also started watch. 092 * </p> 093 094 * @return boolean 095 * If the StopWatch is started. 096 */ 097 abstract boolean isStarted(); 098 099 /** 100 * <p> 101 * This method is used to find out whether the StopWatch is stopped. The 102 * stopwatch which's not yet started and explicitly stopped stopwatch is 103 * considered as stopped. 104 * </p> 105 * 106 * @return boolean 107 * If the StopWatch is stopped. 108 */ 109 abstract boolean isStopped(); 110 111 /** 112 * <p> 113 * This method is used to find out whether the StopWatch is suspended. 114 * </p> 115 * 116 * @return boolean 117 * If the StopWatch is suspended. 118 */ 119 abstract boolean isSuspended(); 120 } 121 122 /** 123 * Enumeration type which indicates the split status of stopwatch. 124 */ 125 private enum SplitState { 126 SPLIT, 127 UNSPLIT 128 } 129 /** 130 * The current running state of the StopWatch. 131 */ 132 private State runningState = State.UNSTARTED; 133 134 /** 135 * Whether the stopwatch has a split time recorded. 136 */ 137 private SplitState splitState = SplitState.UNSPLIT; 138 139 /** 140 * The start time. 141 */ 142 private long startTime; 143 144 /** 145 * The start time in Millis - nanoTime is only for elapsed time so we 146 * need to also store the currentTimeMillis to maintain the old 147 * getStartTime API. 148 */ 149 private long startTimeMillis; 150 151 /** 152 * The stop time. 153 */ 154 private long stopTime; 155 156 /** 157 * <p> 158 * Constructor. 159 * </p> 160 */ 161 public StopWatch() { 162 super(); 163 } 164 165 /** 166 * <p> 167 * Start the stopwatch. 168 * </p> 169 * 170 * <p> 171 * This method starts a new timing session, clearing any previous values. 172 * </p> 173 * 174 * @throws IllegalStateException 175 * if the StopWatch is already running. 176 */ 177 public void start() { 178 if (this.runningState == State.STOPPED) { 179 throw new IllegalStateException("Stopwatch must be reset before being restarted. "); 180 } 181 if (this.runningState != State.UNSTARTED) { 182 throw new IllegalStateException("Stopwatch already started. "); 183 } 184 this.startTime = System.nanoTime(); 185 this.startTimeMillis = System.currentTimeMillis(); 186 this.runningState = State.RUNNING; 187 } 188 189 190 /** 191 * <p> 192 * Stop the stopwatch. 193 * </p> 194 * 195 * <p> 196 * This method ends a new timing session, allowing the time to be retrieved. 197 * </p> 198 * 199 * @throws IllegalStateException 200 * if the StopWatch is not running. 201 */ 202 public void stop() { 203 if (this.runningState != State.RUNNING && this.runningState != State.SUSPENDED) { 204 throw new IllegalStateException("Stopwatch is not running. "); 205 } 206 if (this.runningState == State.RUNNING) { 207 this.stopTime = System.nanoTime(); 208 } 209 this.runningState = State.STOPPED; 210 } 211 212 /** 213 * <p> 214 * Resets the stopwatch. Stops it if need be. 215 * </p> 216 * 217 * <p> 218 * This method clears the internal values to allow the object to be reused. 219 * </p> 220 */ 221 public void reset() { 222 this.runningState = State.UNSTARTED; 223 this.splitState = SplitState.UNSPLIT; 224 } 225 226 /** 227 * <p> 228 * Split the time. 229 * </p> 230 * 231 * <p> 232 * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected, 233 * enabling {@link #unsplit()} to continue the timing from the original start point. 234 * </p> 235 * 236 * @throws IllegalStateException 237 * if the StopWatch is not running. 238 */ 239 public void split() { 240 if (this.runningState != State.RUNNING) { 241 throw new IllegalStateException("Stopwatch is not running. "); 242 } 243 this.stopTime = System.nanoTime(); 244 this.splitState = SplitState.SPLIT; 245 } 246 247 /** 248 * <p> 249 * Remove a split. 250 * </p> 251 * 252 * <p> 253 * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to 254 * continue. 255 * </p> 256 * 257 * @throws IllegalStateException 258 * if the StopWatch has not been split. 259 */ 260 public void unsplit() { 261 if (this.splitState != SplitState.SPLIT) { 262 throw new IllegalStateException("Stopwatch has not been split. "); 263 } 264 this.splitState = SplitState.UNSPLIT; 265 } 266 267 /** 268 * <p> 269 * Suspend the stopwatch for later resumption. 270 * </p> 271 * 272 * <p> 273 * This method suspends the watch until it is resumed. The watch will not include time between the suspend and 274 * resume calls in the total time. 275 * </p> 276 * 277 * @throws IllegalStateException 278 * if the StopWatch is not currently running. 279 */ 280 public void suspend() { 281 if (this.runningState != State.RUNNING) { 282 throw new IllegalStateException("Stopwatch must be running to suspend. "); 283 } 284 this.stopTime = System.nanoTime(); 285 this.runningState = State.SUSPENDED; 286 } 287 288 /** 289 * <p> 290 * Resume the stopwatch after a suspend. 291 * </p> 292 * 293 * <p> 294 * This method resumes the watch after it was suspended. The watch will not include time between the suspend and 295 * resume calls in the total time. 296 * </p> 297 * 298 * @throws IllegalStateException 299 * if the StopWatch has not been suspended. 300 */ 301 public void resume() { 302 if (this.runningState != State.SUSPENDED) { 303 throw new IllegalStateException("Stopwatch must be suspended to resume. "); 304 } 305 this.startTime += System.nanoTime() - this.stopTime; 306 this.runningState = State.RUNNING; 307 } 308 309 /** 310 * <p> 311 * Get the time on the stopwatch. 312 * </p> 313 * 314 * <p> 315 * This is either the time between the start and the moment this method is called, or the amount of time between 316 * start and stop. 317 * </p> 318 * 319 * @return the time in milliseconds 320 */ 321 public long getTime() { 322 return getNanoTime() / NANO_2_MILLIS; 323 } 324 /** 325 * <p> 326 * Get the time on the stopwatch in nanoseconds. 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 nanoseconds 335 * @since 3.0 336 */ 337 public long getNanoTime() { 338 if (this.runningState == State.STOPPED || this.runningState == State.SUSPENDED) { 339 return this.stopTime - this.startTime; 340 } else if (this.runningState == State.UNSTARTED) { 341 return 0; 342 } else if (this.runningState == State.RUNNING) { 343 return System.nanoTime() - this.startTime; 344 } 345 throw new RuntimeException("Illegal running state has occurred."); 346 } 347 348 /** 349 * <p> 350 * Get the split time on the stopwatch. 351 * </p> 352 * 353 * <p> 354 * This is the time between start and latest split. 355 * </p> 356 * 357 * @return the split time in milliseconds 358 * 359 * @throws IllegalStateException 360 * if the StopWatch has not yet been split. 361 * @since 2.1 362 */ 363 public long getSplitTime() { 364 return getSplitNanoTime() / NANO_2_MILLIS; 365 } 366 /** 367 * <p> 368 * Get the split time on the stopwatch in nanoseconds. 369 * </p> 370 * 371 * <p> 372 * This is the time between start and latest split. 373 * </p> 374 * 375 * @return the split time in nanoseconds 376 * 377 * @throws IllegalStateException 378 * if the StopWatch has not yet been split. 379 * @since 3.0 380 */ 381 public long getSplitNanoTime() { 382 if (this.splitState != SplitState.SPLIT) { 383 throw new IllegalStateException("Stopwatch must be split to get the split time. "); 384 } 385 return this.stopTime - this.startTime; 386 } 387 388 /** 389 * Returns the time this stopwatch was started. 390 * 391 * @return the time this stopwatch was started 392 * @throws IllegalStateException 393 * if this StopWatch has not been started 394 * @since 2.4 395 */ 396 public long getStartTime() { 397 if (this.runningState == State.UNSTARTED) { 398 throw new IllegalStateException("Stopwatch has not been started"); 399 } 400 // System.nanoTime is for elapsed time 401 return this.startTimeMillis; 402 } 403 404 /** 405 * <p> 406 * Gets a summary of the time that the stopwatch recorded as a string. 407 * </p> 408 * 409 * <p> 410 * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>. 411 * </p> 412 * 413 * @return the time as a String 414 */ 415 @Override 416 public String toString() { 417 return DurationFormatUtils.formatDurationHMS(getTime()); 418 } 419 420 /** 421 * <p> 422 * Gets a summary of the split time that the stopwatch recorded as a string. 423 * </p> 424 * 425 * <p> 426 * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>. 427 * </p> 428 * 429 * @return the split time as a String 430 * @since 2.1 431 */ 432 public String toSplitString() { 433 return DurationFormatUtils.formatDurationHMS(getSplitTime()); 434 } 435 436 /** 437 * <p> 438 * The method is used to find out if the StopWatch is started. A suspended 439 * StopWatch is also started watch. 440 * </p> 441 * 442 * @return boolean 443 * If the StopWatch is started. 444 * @since 3.2 445 */ 446 public boolean isStarted() { 447 return runningState.isStarted(); 448 } 449 450 /** 451 * <p> 452 * This method is used to find out whether the StopWatch is suspended. 453 * </p> 454 * 455 * @return boolean 456 * If the StopWatch is suspended. 457 * @since 3.2 458 */ 459 public boolean isSuspended() { 460 return runningState.isSuspended(); 461 } 462 463 /** 464 * <p> 465 * This method is used to find out whether the StopWatch is stopped. The 466 * stopwatch which's not yet started and explicitly stopped stopwatch is 467 * considered as stopped. 468 * </p> 469 * 470 * @return boolean 471 * If the StopWatch is stopped. 472 * @since 3.2 473 */ 474 public boolean isStopped() { 475 return runningState.isStopped(); 476 } 477 478}