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}