StopWatch.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.lang3.time;

  18. import java.time.Duration;
  19. import java.time.Instant;
  20. import java.util.Objects;
  21. import java.util.concurrent.TimeUnit;
  22. import java.util.function.Supplier;

  23. import org.apache.commons.lang3.StringUtils;
  24. import org.apache.commons.lang3.function.FailableConsumer;
  25. import org.apache.commons.lang3.function.FailableRunnable;
  26. import org.apache.commons.lang3.function.FailableSupplier;

  27. /**
  28.  * {@link StopWatch} provides a convenient API for timings.
  29.  *
  30.  * <p>
  31.  * To start the watch, call {@link #start()} or {@link StopWatch#createStarted()}. At this point you can:
  32.  * </p>
  33.  * <ul>
  34.  * <li>{@link #split()} the watch to get the time whilst the watch continues in the background. {@link #unsplit()} will remove the effect of the split. At this
  35.  * point, these three options are available again.</li>
  36.  * <li>{@link #suspend()} the watch to pause it. {@link #resume()} allows the watch to continue. Any time between the suspend and resume will not be counted in
  37.  * the total. At this point, these three options are available again.</li>
  38.  * <li>{@link #stop()} the watch to complete the timing session.</li>
  39.  * </ul>
  40.  *
  41.  * <p>
  42.  * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop, split or suspend, however a suitable
  43.  * result will be returned at other points.
  44.  * </p>
  45.  *
  46.  * <p>
  47.  * NOTE: As from v2.1, the methods protect against inappropriate calls. Thus you cannot now call stop before start, resume before suspend or unsplit before
  48.  * split.
  49.  * </p>
  50.  *
  51.  * <ol>
  52.  * <li>{@link #split()}, {@link #suspend()}, or {@link #stop()} cannot be invoked twice</li>
  53.  * <li>{@link #unsplit()} may only be called if the watch has been {@link #split()}</li>
  54.  * <li>{@link #resume()} may only be called if the watch has been {@link #suspend()}</li>
  55.  * <li>{@link #start()} cannot be called twice without calling {@link #reset()}</li>
  56.  * </ol>
  57.  *
  58.  * <p>
  59.  * This class is not thread-safe
  60.  * </p>
  61.  *
  62.  * @see DurationUtils#of(FailableRunnable)
  63.  * @see DurationUtils#of(FailableConsumer)
  64.  * @since 2.0
  65.  */
  66. public class StopWatch {

  67.     /**
  68.      * Enumeration type which indicates the split status of a StopWatch.
  69.      */
  70.     private enum SplitState {
  71.         SPLIT, UNSPLIT
  72.     }

  73.     /**
  74.      * Enumeration type which indicates the status of a StopWatch.
  75.      */
  76.     private enum State {

  77.         RUNNING {
  78.             @Override
  79.             boolean isStarted() {
  80.                 return true;
  81.             }

  82.             @Override
  83.             boolean isStopped() {
  84.                 return false;
  85.             }

  86.             @Override
  87.             boolean isSuspended() {
  88.                 return false;
  89.             }
  90.         },

  91.         STOPPED {
  92.             @Override
  93.             boolean isStarted() {
  94.                 return false;
  95.             }

  96.             @Override
  97.             boolean isStopped() {
  98.                 return true;
  99.             }

  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.          * Tests whether this StopWatch is started. A suspended StopWatch is also started.
  135.          *
  136.          * @return boolean If this StopWatch is started.
  137.          */
  138.         abstract boolean isStarted();

  139.         /**
  140.          * Tests whether this StopWatch is stopped. A StopWatch which is not yet started and explicitly stopped is considered stopped.
  141.          *
  142.          * @return boolean If this StopWatch is stopped.
  143.          */
  144.         abstract boolean isStopped();

  145.         /**
  146.          * Tests whether this StopWatch is suspended.
  147.          *
  148.          * @return boolean If this StopWatch is suspended.
  149.          */
  150.         abstract boolean isSuspended();
  151.     }

  152.     private static final long NANO_2_MILLIS = 1_000_000L;

  153.     /**
  154.      * Creates a StopWatch.
  155.      *
  156.      * @return StopWatch a StopWatch.
  157.      * @since 3.10
  158.      */
  159.     public static StopWatch create() {
  160.         return new StopWatch();
  161.     }

  162.     /**
  163.      * Creates and starts a StopWatch.
  164.      *
  165.      * @return StopWatch a started StopWatch.
  166.      * @since 3.5
  167.      */
  168.     public static StopWatch createStarted() {
  169.         final StopWatch sw = new StopWatch();
  170.         sw.start();
  171.         return sw;
  172.     }

  173.     /**
  174.      * A message for string presentation.
  175.      *
  176.      * @since 3.10
  177.      */
  178.     private final String message;

  179.     /**
  180.      * The current running state of this StopWatch.
  181.      */
  182.     private State runningState = State.UNSTARTED;

  183.     /**
  184.      * Whether this StopWatch has a split time recorded.
  185.      */
  186.     private SplitState splitState = SplitState.UNSPLIT;

  187.     /**
  188.      * The start time in nanoseconds.
  189.      *
  190.      * This field can be removed once we move off of Java 8.
  191.      */
  192.     private long startTimeNanos;

  193.     /**
  194.      * The start Instant.
  195.      * <p>
  196.      * nanoTime is only for elapsed time so we need to also store the currentTimeMillis to maintain the old getStartTime API.
  197.      * </p>
  198.      * <p>
  199.      * On Java 8, Instant has millisecond precision, later versions use nanoseconds.
  200.      * </p>
  201.      */
  202.     private Instant startInstant;

  203.     /**
  204.      * The end Instant.
  205.      * <p>
  206.      * nanoTime is only for elapsed time so we need to also store the currentTimeMillis to maintain the old getStartTime API.
  207.      * </p>
  208.      * <p>
  209.      * On Java 8, Instant has millisecond precision, later versions use nanoseconds.
  210.      * </p>
  211.      */
  212.     private Instant stopInstant;

  213.     /**
  214.      * The stop time in nanoseconds.
  215.      *
  216.      * This field can be removed once we move off of Java 8.
  217.      */
  218.     private long stopTimeNanos;

  219.     /**
  220.      * Constructs a new instance.
  221.      */
  222.     public StopWatch() {
  223.         this(null);
  224.     }

  225.     /**
  226.      * Constructs a new instance.
  227.      *
  228.      * @param message A message for string presentation.
  229.      * @since 3.10
  230.      */
  231.     public StopWatch(final String message) {
  232.         this.message = message;
  233.     }

  234.     /**
  235.      * Formats the split time with {@link DurationFormatUtils#formatDurationHMS}.
  236.      *
  237.      * @return the split time formatted by {@link DurationFormatUtils#formatDurationHMS}.
  238.      * @since 3.10
  239.      */
  240.     public String formatSplitTime() {
  241.         return DurationFormatUtils.formatDurationHMS(getSplitDuration().toMillis());
  242.     }

  243.     /**
  244.      * Formats the time formatted with {@link DurationFormatUtils#formatDurationHMS}.
  245.      *
  246.      * @return the time formatted by {@link DurationFormatUtils#formatDurationHMS}.
  247.      * @since 3.10
  248.      */
  249.     public String formatTime() {
  250.         return DurationFormatUtils.formatDurationHMS(getTime());
  251.     }

  252.     /**
  253.      * Delegates to {@link Supplier#get()} while recording the duration of the call.
  254.      *
  255.      * @param <T>      the type of results supplied by this supplier.
  256.      * @param supplier The supplier to {@link Supplier#get()}.
  257.      * @return a result from the given Supplier.
  258.      * @since 3.18.0
  259.      */
  260.     public <T> T get(final Supplier<T> supplier) {
  261.         startResume();
  262.         try {
  263.             return supplier.get();
  264.         } finally {
  265.             suspend();
  266.         }
  267.     }

  268.     /**
  269.      * Gets the Duration on this StopWatch.
  270.      *
  271.      * <p>
  272.      * This is either the Duration between the start and the moment this method is called, or the Duration between start and stop.
  273.      * </p>
  274.      *
  275.      * @return the Duration.
  276.      * @since 3.16.0
  277.      */
  278.     public Duration getDuration() {
  279.         return Duration.ofNanos(getNanoTime());
  280.     }

  281.     /**
  282.      * Gets the message for string presentation.
  283.      *
  284.      * @return the message for string presentation.
  285.      * @since 3.10
  286.      */
  287.     public String getMessage() {
  288.         return message;
  289.     }

  290.     /**
  291.      * Gets the <em>elapsed</em> time in nanoseconds.
  292.      *
  293.      * <p>
  294.      * This is either the time between the start and the moment this method is called, or the amount of time between start and stop.
  295.      * </p>
  296.      *
  297.      * @return the <em>elapsed</em> time in nanoseconds.
  298.      * @see System#nanoTime()
  299.      * @since 3.0
  300.      */
  301.     public long getNanoTime() {
  302.         switch (runningState) {
  303.         case STOPPED:
  304.         case SUSPENDED:
  305.             return stopTimeNanos - startTimeNanos;
  306.         case UNSTARTED:
  307.             return 0;
  308.         case RUNNING:
  309.             return System.nanoTime() - startTimeNanos;
  310.         default:
  311.             break;
  312.         }
  313.         throw new IllegalStateException("Illegal running state has occurred.");
  314.     }

  315.     /**
  316.      * Gets the split Duration on this StopWatch.
  317.      *
  318.      * <p>
  319.      * This is the Duration between start and latest split.
  320.      * </p>
  321.      *
  322.      * @return the split Duration
  323.      * @throws IllegalStateException if this StopWatch has not yet been split.
  324.      * @since 3.16.0
  325.      */
  326.     public Duration getSplitDuration() {
  327.         return Duration.ofNanos(getSplitNanoTime());
  328.     }

  329.     /**
  330.      * Gets the split time in nanoseconds.
  331.      *
  332.      * <p>
  333.      * This is the time between start and latest split.
  334.      * </p>
  335.      *
  336.      * @return the split time in nanoseconds
  337.      * @throws IllegalStateException if this StopWatch has not yet been split.
  338.      * @since 3.0
  339.      */
  340.     public long getSplitNanoTime() {
  341.         if (splitState != SplitState.SPLIT) {
  342.             throw new IllegalStateException("Stopwatch must be split to get the split time.");
  343.         }
  344.         return stopTimeNanos - startTimeNanos;
  345.     }

  346.     /**
  347.      * Gets the split time on this StopWatch.
  348.      *
  349.      * <p>
  350.      * This is the time between start and latest split.
  351.      * </p>
  352.      *
  353.      * @return the split time in milliseconds
  354.      * @throws IllegalStateException if this StopWatch has not yet been split.
  355.      * @since 2.1
  356.      * @deprecated Use {@link #getSplitDuration()}.
  357.      */
  358.     @Deprecated
  359.     public long getSplitTime() {
  360.         return nanosToMillis(getSplitNanoTime());
  361.     }

  362.     /**
  363.      * Gets the Instant this StopWatch was started, between the current time and midnight, January 1, 1970 UTC.
  364.      *
  365.      * @return the Instant this StopWatch was started, between the current time and midnight, January 1, 1970 UTC.
  366.      * @throws IllegalStateException if this StopWatch has not been started
  367.      * @since 3.16.0
  368.      */
  369.     public Instant getStartInstant() {
  370.         if (runningState == State.UNSTARTED) {
  371.             throw new IllegalStateException("Stopwatch has not been started");
  372.         }
  373.         return startInstant;
  374.     }

  375.     /**
  376.      * Gets the time this StopWatch was started in milliseconds, between the current time and midnight, January 1, 1970 UTC.
  377.      *
  378.      * @return the time this StopWatch was started in milliseconds, between the current time and midnight, January 1, 1970 UTC.
  379.      * @throws IllegalStateException if this StopWatch has not been started
  380.      * @since 2.4
  381.      * @deprecated Use {@link #getStartInstant()}.
  382.      */
  383.     @Deprecated
  384.     public long getStartTime() {
  385.         return getStartInstant().toEpochMilli();
  386.     }

  387.     /**
  388.      * Gets the Instant this StopWatch was stopped, between the current time and midnight, January 1, 1970 UTC.
  389.      *
  390.      * @return the Instant this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
  391.      * @throws IllegalStateException if this StopWatch has not been started
  392.      * @since 3.16.0
  393.      */
  394.     public Instant getStopInstant() {
  395.         if (runningState == State.UNSTARTED) {
  396.             throw new IllegalStateException("Stopwatch has not been started");
  397.         }
  398.         return stopInstant;
  399.     }

  400.     /**
  401.      * Gets the time this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
  402.      *
  403.      * @return the time this StopWatch was stopped in milliseconds, between the current time and midnight, January 1, 1970 UTC.
  404.      * @throws IllegalStateException if this StopWatch has not been started
  405.      * @since 3.12.0
  406.      * @deprecated Use {@link #getStopInstant()}.
  407.      */
  408.     @Deprecated
  409.     public long getStopTime() {
  410.         // stopTimeNanos stores System.nanoTime() for elapsed time
  411.         return getStopInstant().toEpochMilli();
  412.     }

  413.     /**
  414.      * Delegates to {@link FailableSupplier#get()} while recording the duration of the call.
  415.      *
  416.      * @param <T>      the type of results supplied by this supplier.
  417.      * @param <E>      The kind of thrown exception or error.
  418.      * @param supplier The supplier to {@link Supplier#get()}.
  419.      * @return a result from the given Supplier.
  420.      * @throws Throwable if the supplier fails.
  421.      * @since 3.18.0
  422.      */
  423.     public <T, E extends Throwable> T getT(final FailableSupplier<T, E> supplier) throws Throwable {
  424.         startResume();
  425.         try {
  426.             return supplier.get();
  427.         } finally {
  428.             suspend();
  429.         }
  430.     }

  431.     /**
  432.      * Gets the time on this StopWatch.
  433.      *
  434.      * <p>
  435.      * This is either the time between the start and the moment this method is called, or the amount of time between start and stop.
  436.      * </p>
  437.      *
  438.      * @return the time in milliseconds
  439.      * @see #getDuration()
  440.      */
  441.     public long getTime() {
  442.         return nanosToMillis(getNanoTime());
  443.     }

  444.     /**
  445.      * Gets the time in the specified TimeUnit.
  446.      *
  447.      * <p>
  448.      * This is either the time between the start and the moment this method is called, or the amount of time between start and stop. The resulting time will be
  449.      * expressed in the desired TimeUnit with any remainder rounded down. For example, if the specified unit is {@code TimeUnit.HOURS} and this StopWatch time
  450.      * is 59 minutes, then the result returned will be {@code 0}.
  451.      * </p>
  452.      *
  453.      * @param timeUnit the unit of time, not null
  454.      * @return the time in the specified TimeUnit, rounded down
  455.      * @since 3.5
  456.      */
  457.     public long getTime(final TimeUnit timeUnit) {
  458.         return timeUnit.convert(getNanoTime(), TimeUnit.NANOSECONDS);
  459.     }

  460.     /**
  461.      * Tests whether this StopWatch is started. A suspended StopWatch is also started watch.
  462.      *
  463.      * @return boolean If this StopWatch is started.
  464.      * @since 3.2
  465.      */
  466.     public boolean isStarted() {
  467.         return runningState.isStarted();
  468.     }

  469.     /**
  470.      * Tests whether StopWatch is stopped. this StopWatch which's not yet started and explicitly stopped StopWatch is considered as stopped.
  471.      *
  472.      * @return boolean If this StopWatch is stopped.
  473.      * @since 3.2
  474.      */
  475.     public boolean isStopped() {
  476.         return runningState.isStopped();
  477.     }

  478.     /**
  479.      * Tests whether this StopWatch is suspended.
  480.      *
  481.      * @return boolean If this StopWatch is suspended.
  482.      * @since 3.2
  483.      */
  484.     public boolean isSuspended() {
  485.         return runningState.isSuspended();
  486.     }

  487.     /**
  488.      * Converts nanoseconds to milliseconds.
  489.      *
  490.      * @param nanos nanoseconds to convert.
  491.      * @return milliseconds conversion result.
  492.      */
  493.     private long nanosToMillis(final long nanos) {
  494.         return nanos / NANO_2_MILLIS;
  495.     }

  496.     /**
  497.      * Resets this StopWatch. Stops it if need be.
  498.      *
  499.      * <p>
  500.      * This method clears the internal values to allow the object to be reused.
  501.      * </p>
  502.      */
  503.     public void reset() {
  504.         runningState = State.UNSTARTED;
  505.         splitState = SplitState.UNSPLIT;
  506.     }

  507.     /**
  508.      * Resumes this StopWatch after a suspend.
  509.      *
  510.      * <p>
  511.      * This method resumes the watch after it was suspended. The watch will not include time between the suspend and resume calls in the total time.
  512.      * </p>
  513.      *
  514.      * @throws IllegalStateException if this StopWatch has not been suspended.
  515.      */
  516.     public void resume() {
  517.         if (runningState != State.SUSPENDED) {
  518.             throw new IllegalStateException("Stopwatch must be suspended to resume.");
  519.         }
  520.         startTimeNanos += System.nanoTime() - stopTimeNanos;
  521.         runningState = State.RUNNING;
  522.     }

  523.     /**
  524.      * Delegates to {@link Runnable#run()} while recording the duration of the call.
  525.      *
  526.      * @param runnable The runnable to {@link Runnable#run()}.
  527.      * @since 3.18.0
  528.      */
  529.     public void run(final Runnable runnable) {
  530.         startResume();
  531.         try {
  532.             runnable.run();
  533.         } finally {
  534.             suspend();
  535.         }
  536.     }

  537.     /**
  538.      * Delegates to {@link FailableRunnable#run()} while recording the duration of the call.
  539.      *
  540.      * @param <E>      The kind of {@link Throwable}.
  541.      * @param runnable The runnable to {@link FailableRunnable#run()}.
  542.      * @throws Throwable Thrown by {@link FailableRunnable#run()}.
  543.      * @since 3.18.0
  544.      */
  545.     public <E extends Throwable> void runT(final FailableRunnable<E> runnable) throws Throwable {
  546.         startResume();
  547.         try {
  548.             runnable.run();
  549.         } finally {
  550.             suspend();
  551.         }
  552.     }

  553.     /**
  554.      * Splits the time.
  555.      *
  556.      * <p>
  557.      * This method sets the stop time of the watch to allow a time to be extracted. The start time is unaffected, enabling {@link #unsplit()} to continue the
  558.      * timing from the original start point.
  559.      * </p>
  560.      *
  561.      * @throws IllegalStateException if this StopWatch is not running.
  562.      */
  563.     public void split() {
  564.         if (runningState != State.RUNNING) {
  565.             throw new IllegalStateException("Stopwatch is not running.");
  566.         }
  567.         stopTimeNanos = System.nanoTime();
  568.         splitState = SplitState.SPLIT;
  569.     }

  570.     /**
  571.      * Starts this StopWatch.
  572.      *
  573.      * <p>
  574.      * This method starts a new timing session, clearing any previous values.
  575.      * </p>
  576.      *
  577.      * @throws IllegalStateException if this StopWatch is already running.
  578.      */
  579.     public void start() {
  580.         if (runningState == State.STOPPED) {
  581.             throw new IllegalStateException("Stopwatch must be reset before being restarted.");
  582.         }
  583.         if (runningState != State.UNSTARTED) {
  584.             throw new IllegalStateException("Stopwatch already started.");
  585.         }
  586.         startTimeNanos = System.nanoTime();
  587.         startInstant = Instant.now();
  588.         runningState = State.RUNNING;
  589.     }

  590.     /**
  591.      * Starts or resumes this StopWatch.
  592.      */
  593.     private void startResume() {
  594.         if (isStopped()) {
  595.             start();
  596.         } else if (isSuspended()) {
  597.             resume();
  598.         }
  599.     }

  600.     /**
  601.      * Stops this StopWatch.
  602.      *
  603.      * <p>
  604.      * This method ends a new timing session, allowing the time to be retrieved.
  605.      * </p>
  606.      *
  607.      * @throws IllegalStateException if this StopWatch is not running.
  608.      */
  609.     public void stop() {
  610.         if (runningState != State.RUNNING && runningState != State.SUSPENDED) {
  611.             throw new IllegalStateException("Stopwatch is not running.");
  612.         }
  613.         if (runningState == State.RUNNING) {
  614.             stopTimeNanos = System.nanoTime();
  615.             stopInstant = Instant.now();
  616.         }
  617.         runningState = State.STOPPED;
  618.     }

  619.     /**
  620.      * Suspends this StopWatch for later resumption.
  621.      *
  622.      * <p>
  623.      * This method suspends the watch until it is resumed. The watch will not include time between the suspend and resume calls in the total time.
  624.      * </p>
  625.      *
  626.      * @throws IllegalStateException if this StopWatch is not currently running.
  627.      */
  628.     public void suspend() {
  629.         if (runningState != State.RUNNING) {
  630.             throw new IllegalStateException("Stopwatch must be running to suspend.");
  631.         }
  632.         stopTimeNanos = System.nanoTime();
  633.         stopInstant = Instant.now();
  634.         runningState = State.SUSPENDED;
  635.     }

  636.     /**
  637.      * Gets a summary of the split time that this StopWatch recorded as a string.
  638.      *
  639.      * <p>
  640.      * The format used is ISO 8601-like, [<em>message</em> ]<em>hours</em>:<em>minutes</em>:<em>seconds</em>.<em>milliseconds</em>.
  641.      * </p>
  642.      *
  643.      * @return the split time as a String
  644.      * @since 2.1
  645.      * @since 3.10 Returns the prefix {@code "message "} if the message is set.
  646.      */
  647.     public String toSplitString() {
  648.         final String msgStr = Objects.toString(message, StringUtils.EMPTY);
  649.         final String formattedTime = formatSplitTime();
  650.         return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
  651.     }

  652.     /**
  653.      * Gets a summary of the time that this StopWatch recorded as a string.
  654.      *
  655.      * <p>
  656.      * The format used is ISO 8601-like, [<em>message</em> ]<em>hours</em>:<em>minutes</em>:<em>seconds</em>.<em>milliseconds</em>.
  657.      * </p>
  658.      *
  659.      * @return the time as a String
  660.      * @since 3.10 Returns the prefix {@code "message "} if the message is set.
  661.      */
  662.     @Override
  663.     public String toString() {
  664.         final String msgStr = Objects.toString(message, StringUtils.EMPTY);
  665.         final String formattedTime = formatTime();
  666.         return msgStr.isEmpty() ? formattedTime : msgStr + StringUtils.SPACE + formattedTime;
  667.     }

  668.     /**
  669.      * Removes the split.
  670.      *
  671.      * <p>
  672.      * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to continue.
  673.      * </p>
  674.      *
  675.      * @throws IllegalStateException if this StopWatch has not been split.
  676.      */
  677.     public void unsplit() {
  678.         if (splitState != SplitState.SPLIT) {
  679.             throw new IllegalStateException("Stopwatch has not been split.");
  680.         }
  681.         splitState = SplitState.UNSPLIT;
  682.     }

  683. }