Java Tutorial/Development/Stop Watch — различия между версиями

Материал из Java эксперт
Перейти к: навигация, поиск
м (1 версия)
 
(нет различий)

Текущая версия на 15:28, 31 мая 2010

Provides the programatic analog of a physical stop watch

/* Copyright (c) 2001-2009, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * Provides the programatic analog of a physical stop watch. 
 *
 * The watch can be started, stopped and zeroed and can be queried for
 * elapsed running time.  The watch accumulates elapsed time over starts
 * and stops such that only the time actually spent running is recorded.
 * If the watch is zeroed, then the accumulated time is discarded and
 * the watch starts again with zero acumulated time. 
 *
 * @author boucherb@users
 * @version 1.7.2
 * @since 1.7.2
 */
public class StopWatch {
    /**
     * The last time this object made the transition
     * from stopped to running state, as reported
     * by System.currentTimeMillis().
     */
    private long startTime;
    private long lastStart;
    /**
     * The accumulated running time of this object since
     * it was last zeroed.
     */
    private long total;
    /** Flags if this object is started or stopped. */
    boolean running = false;
    /** Creates, zeros, and starts a new StopWatch */
    public StopWatch() {
        this(true);
    }
    /** Creates, zeros, and starts a new StopWatch */
    public StopWatch(boolean start) {
        if (start) {
            start();
        }
    }
    /**
     * Retrieves the accumulated time this object has spent running since
     * it was last zeroed.
     * @return the accumulated time this object has spent running since
     * it was last zeroed.
     */
    public long elapsedTime() {
        if (running) {
            return total + System.currentTimeMillis() - startTime;
        } else {
            return total;
        }
    }
    /**
     * Retrieves the accumulated time this object has spent running since
     * it was last started.
     * @return the accumulated time this object has spent running since
     * it was last started.
     */
    public long currentElapsedTime() {
        if (running) {
            return System.currentTimeMillis() - startTime;
        } else {
            return 0;
        }
    }
    /** Zeros accumulated running time and restarts this object. */
    public void zero() {
        total = 0;
        start();
    }
    /**
     * Ensures that this object is in the running state.  If this object is not
     * running, then the call has the effect of setting the <code>startTime</code>
     * attribute to the current value of System.currentTimeMillis() and setting
     * the <code>running</code> attribute to <code>true</code>.
     */
    public void start() {
        startTime = System.currentTimeMillis();
        running   = true;
    }
    /**
     * Ensures that this object is in the stopped state.  If this object is
     * in the running state, then this has the effect of adding to the
     * <code>total</code> attribute the elapsed time since the last transition
     * from stopped to running state and sets the <code>running</code> attribute
     * to false. If this object is not in the running state, this call has no
     * effect.
     */
    public void stop() {
        if (running) {
            total   += System.currentTimeMillis() - startTime;
            running = false;
        }
    }
    public void mark() {
        stop();
        start();
    }
    /**
     * Retrieves prefix + " in " + elapsedTime() + " ms."
     * @param prefix The string to use as a prefix
     * @return prefix + " in " + elapsedTime() + " ms."
     */
    public String elapsedTimeToMessage(String prefix) {
        return prefix + " in " + elapsedTime() + " ms.";
    }
    /**
     * Retrieves prefix + " in " + elapsedTime() + " ms."
     * @param prefix The string to use as a prefix
     * @return prefix + " in " + elapsedTime() + " ms."
     */
    public String currentElapsedTimeToMessage(String prefix) {
        return prefix + " in " + currentElapsedTime() + " ms.";
    }
    /**
     * Retrieves the internal state of this object, as a String.
     *
     * The retreived value is:
     *
     * <pre>
     *    super.toString() +
     *    "[running=" +
     *    running +
     *    ", startTime=" +
     *    startTime +
     *    ", total=" +
     *    total + "]";
     * </pre>
     * @return the state of this object, as a String
     */
    public String toString() {
        return super.toString() + "[running=" + running + ", startTime="
               + startTime + ", total=" + total + "]";
    }
}





Simple stop watch

/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;
/**
 * Simple stop watch, allowing for timing of a number of tasks,
 * exposing total running time and running time for each named task.
 *
 * Conceals use of <code>System.currentTimeMillis()</code>, improving the
 * readability of application code and reducing the likelihood of calculation errors.
 *
 * Note that this object is not designed to be thread-safe and does not
 * use synchronization.
 *
 * This class is normally used to verify performance during proof-of-concepts
 * and in development, rather than as part of production applications.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since May 2, 2001
 */
public class StopWatch {
  /**
   * Identifier of this stop watch.
   * Handy when we have output from multiple stop watches
   * and need to distinguish between them in log or console output.
   */
  private final String id;
  private boolean keepTaskList = true;
  /** List of TaskInfo objects */
  private final List taskList = new LinkedList();
  /** Start time of the current task */
  private long startTimeMillis;
  /** Is the stop watch currently running? */
  private boolean running;
  /** Name of the current task */
  private String currentTaskName;
  private TaskInfo lastTaskInfo;
  private int taskCount;
  /** Total running time */
  private long totalTimeMillis;

  /**
   * Construct a new stop watch. Does not start any task.
   */
  public StopWatch() {
    this.id = "";
  }
  /**
   * Construct a new stop watch with the given id.
   * Does not start any task.
   * @param id identifier for this stop watch.
   * Handy when we have output from multiple stop watches
   * and need to distinguish between them.
   */
  public StopWatch(String id) {
    this.id = id;
  }
  /**
   * Determine whether the TaskInfo array is built over time. Set this to
   * "false" when using a StopWatch for millions of intervals, or the task
   * info structure will consume excessive memory. Default is "true".
   */
  public void setKeepTaskList(boolean keepTaskList) {
    this.keepTaskList = keepTaskList;
  }

  /**
   * Start an unnamed task. The results are undefined if {@link #stop()}
   * or timing methods are called without invoking this method.
   * @see #stop()
   */
  public void start() throws IllegalStateException {
    start("");
  }
  /**
   * Start a named task. The results are undefined if {@link #stop()}
   * or timing methods are called without invoking this method.
   * @param taskName the name of the task to start
   * @see #stop()
   */
  public void start(String taskName) throws IllegalStateException {
    if (this.running) {
      throw new IllegalStateException("Can"t start StopWatch: it"s already running");
    }
    this.startTimeMillis = System.currentTimeMillis();
    this.running = true;
    this.currentTaskName = taskName;
  }
  /**
   * Stop the current task. The results are undefined if timing
   * methods are called without invoking at least one pair
   * {@link #start()} / {@link #stop()} methods.
   * @see #start()
   */
  public void stop() throws IllegalStateException {
    if (!this.running) {
      throw new IllegalStateException("Can"t stop StopWatch: it"s not running");
    }
    long lastTime = System.currentTimeMillis() - this.startTimeMillis;
    this.totalTimeMillis += lastTime;
    this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
    if (this.keepTaskList) {
      this.taskList.add(lastTaskInfo);
    }
    ++this.taskCount;
    this.running = false;
    this.currentTaskName = null;
  }
  /**
   * Return whether the stop watch is currently running.
   */
  public boolean isRunning() {
    return this.running;
  }

  /**
   * Return the time taken by the last task.
   */
  public long getLastTaskTimeMillis() throws IllegalStateException {
    if (this.lastTaskInfo == null) {
      throw new IllegalStateException("No tests run: can"t get last interval");
    }
    return this.lastTaskInfo.getTimeMillis();
  }
  /**
   * Return the total time in milliseconds for all tasks.
   */
  public long getTotalTimeMillis() {
    return totalTimeMillis;
  }
  /**
   * Return the total time in seconds for all tasks.
   */
  public double getTotalTimeSeconds() {
    return totalTimeMillis / 1000.0;
  }
  /**
   * Return the number of tasks timed.
   */
  public int getTaskCount() {
    return taskCount;
  }
  /**
   * Return an array of the data for tasks performed.
   */
  public TaskInfo[] getTaskInfo() {
    if (!this.keepTaskList) {
      throw new UnsupportedOperationException("Task info is not being kept!");
    }
    return (TaskInfo[]) this.taskList.toArray(new TaskInfo[this.taskList.size()]);
  }

  /**
   * Return a short description of the total running time.
   */
  public String shortSummary() {
    return "StopWatch "" + this.id + "": running time (millis) = " + getTotalTimeMillis();
  }
  /**
   * Return a string with a table describing all tasks performed.
   * For custom reporting, call getTaskInfo() and use the task info directly.
   */
  public String prettyPrint() {
    StringBuffer sb = new StringBuffer(shortSummary());
    sb.append("\n");
    if (!this.keepTaskList) {
      sb.append("No task info kept");
    }
    else {
      TaskInfo[] tasks = getTaskInfo();
      sb.append("-----------------------------------------\n");
      sb.append("ms     %     Task name\n");
      sb.append("-----------------------------------------\n");
      NumberFormat nf = NumberFormat.getNumberInstance();
      nf.setMinimumIntegerDigits(5);
      nf.setGroupingUsed(false);
      NumberFormat pf = NumberFormat.getPercentInstance();
      pf.setMinimumIntegerDigits(3);
      pf.setGroupingUsed(false);
      for (int i = 0; i < tasks.length; i++) {
        sb.append(nf.format(tasks[i].getTimeMillis()) + "  ");
        sb.append(pf.format(tasks[i].getTimeSeconds() / getTotalTimeSeconds()) + "  ");
        sb.append(tasks[i].getTaskName() + "\n");
      }
    }
    return sb.toString();
  }
  /**
   * Return an informative string describing all tasks performed
   * For custom reporting, call <code>getTaskInfo()</code> and use the task info directly.
   */
  public String toString() {
    StringBuffer sb = new StringBuffer(shortSummary());
    if (this.keepTaskList) {
      TaskInfo[] tasks = getTaskInfo();
      for (int i = 0; i < tasks.length; i++) {
        sb.append("; [" + tasks[i].getTaskName() + "] took " + tasks[i].getTimeMillis());
        long percent = Math.round((100.0 * tasks[i].getTimeSeconds()) / getTotalTimeSeconds());
        sb.append(" = " + percent + "%");
      }
    }
    else {
      sb.append("; no task info kept");
    }
    return sb.toString();
  }

  /**
   * Inner class to hold data about one task executed within the stop watch.
   */
  public static class TaskInfo {
    private final String taskName;
    private final long timeMillis;
    private TaskInfo(String taskName, long timeMillis) {
      this.taskName = taskName;
      this.timeMillis = timeMillis;
    }
    /**
     * Return the name of this task.
     */
    public String getTaskName() {
      return taskName;
    }
    /**
     * Return the time in milliseconds this task took.
     */
    public long getTimeMillis() {
      return timeMillis;
    }
    /**
     * Return the time in seconds this task took.
     */
    public double getTimeSeconds() {
      return timeMillis / 1000.0;
    }
  }
}





Some simple stop watch.

/*
 * Copyright 2000,2005 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * Please see COPYING for the complete licence.
 */
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
/*
 * Ein Klasse, die Zeitmessungen aufnimmt und diese in Relation zueineander
 * setzt. Zum Start der Zeitmessung Methode start kurzer Beschreibung was
 * gemessen wird als Parameter aufrufen. Das Ende der Zeitmessung wird durch
 * stop angezeigt. Es kann nur eine Zeitmessung gleichzeitig stattfinden. Die
 * Ausgabe ist nicht sortiert, gibt aber die relativen Unterschiede in der
 * Dauer der einzelnen Messungen an. Die Messung mit der laengsten Dauer ist
 * der Referenzwert (1.0).
 */
/**
 * Some simple stop watch. It allows to measure multiple time periods
 * and prints them. Usage: call start(comment) and stop() for
 * each period of time.
 *
 * @author 
 */
public class TimeMeasure {
    protected final static double RESOLUTION = 100.0;
    /**
     * List of measurements.
     */
    protected final ArrayList measures;
    /**
     * Message formatter
     */
    protected final MessageFormat formatter;
    /**
     * the current time measurement.
     */
    protected Measure current;
    /**
     * Simple TimeMesaure with default format.
     */
    public TimeMeasure() {
        this(new MessageFormat("{0}\t: {1}\t {2}x\n"));
    }
    /**
     * A new TimeMeasure which reports in a specific format. The
     * format is a standard MessageFormat with the following variables:
     * <ul>
     * <li><code>{0}</code> the measurement comment</li>
     * <li><code>{1}</code> the time it took</li>
     * <li><code>{2}</code> how many times this is faster than the
     * slowest measurement</li>
     * </ul>
     */
    public TimeMeasure(MessageFormat formatter) {
        this.measures = new ArrayList();
        this.formatter = formatter;
    }
    /**
     * Reset of all Measurements.
     */
    public void reset() {
        measures.clear();
    }
    /*
     * Startet eine neue Messsung.
     * @param comment  Die Beschreibung der Messung.
     */
    public void start(String comment) {
        current = new Measure(comment);
    }
    /*
     * Startet eine neue Messsung.
     * @param comment  Die Beschreibung der Messung.
    public Object generate(String comment) {
        current = new Measure();
        actual.rument = comment;
        measures.add(actual);
        return actual;
    }
     */
    /*
     * Addiert eine Messsung zu einer bestehenden.
     * @param comment  Die Beschreibung der Messung.
    public void addToMeasure(Object measure) {
        int index = measures.indexOf(measure);
        if ( index<0 ) {
            System.err.println("Measure does not exists " + measure);
            actual = null;
            return;
        }
        actual = (Measure)measures.get(index);
        measures.remove(index);
        actual.start = System.currentTimeMillis();
    }
     */
    /**
     * stop current time measurement and store it.
     */
    public void stop() {
        if (current != null) {
            current.stop();
            measures.add(current);
            current = null;
        }
    }
    /**
     * determines the time duration of the longest or shortest time interval.
     *
     * @param findShortest boolean "true", if we are looking for the shortest
     *                     time interval; "false" if we are looking for the
     *                     longest.
     */
    private long findReferenceValue(boolean findShortest) {
        long result = findShortest ? Long.MAX_VALUE : -1;
        Iterator it = measures.iterator();
        while (it.hasNext()) {
            Measure m = (Measure) it.next();
            result = (findShortest
                    ? Math.min(result, m.getDuration())
                    : Math.max(result, m.getDuration()));
        }
        return result;
    }
    public String print() {
        return print(false);
    }
    /**
     * creates a formatted output (using the MessageFormat) of all
     * results. The output is sorted in the in the sequence the time
     * measurements took place.
     * Writes the relative time to either the shortest or the longest
     * time interval.
     *
     * @param shortestIsReference boolean true, if the shortest time interval
     *                            is the reference value (1.0). False, if the
     *                            longest is the reference.
     */
    public String print(boolean shortestIsReference) {
        StringBuilder result = new StringBuilder();
        long reference = findReferenceValue(shortestIsReference);
        Iterator it = measures.iterator();
        while (it.hasNext()) {
            Measure m = (Measure) it.next();
            String factor = " -- ";
            long duration = m.getDuration();
            if (reference > 0) {
                long tmp = (long) ((duration * RESOLUTION) / reference);
                factor = String.valueOf(tmp / RESOLUTION);
            }
            Object[] args = {m.getComment(), (duration + "ms"), factor};
            result.append(formatter.format(args));
        }
        return result.toString();
    }
    public String toString() {
        return print();
    }
    /**
     * A class to store one period of time.
     */
    private final static class Measure {
        /**
         * start time.
         */
        private final long start;
        /**
         * stop time.
         */
        private long stop;
        /**
         * Die Gesamtdauer der Messung
         */
        private long duration;
        /**
         * Description.
         */
        private String comment;
        public Measure(String comment) {
            start = System.currentTimeMillis();
            this.rument = comment;
        }
        public void stop() {
            stop = System.currentTimeMillis();
            duration = stop - start;
        }
        public long getDuration() { return duration; }
        public String getComment() { return comment; }
    }
}





Stop Watch

import java.io.PrintWriter;
/**
 * Prints time durations; used for development purposes only.
 * 
 * No threads or system resources are harmed in the making of a stop watch. A
 * stop watch simply remembers the start time (and elapsed time when paused) and
 * prints the total &quot;running&quot; time when either {@link #mark} or
 * {@link #stop} is called.
 * 
 * {@link #stop} doesn"t really stop anything. You can call stop as many times
 * as you like and it will print the time elapsed since start. It would be easy
 * to change this behavior, but I haven"t needed to.
 * 
 * @author Jim Menard, 
 */
public class StopWatch {
  protected String name;
  protected long t0;
  protected long elapsedTime;
  protected PrintWriter out;
  public StopWatch() {
    this(null, null);
  }
  public StopWatch(String name) {
    this(name, null);
  }
  public StopWatch(PrintWriter out) {
    this(null, out);
  }
  public StopWatch(String name, PrintWriter out) {
    this.name = name;
    if (out == null)
      this.out = new PrintWriter(System.err);
    else
      this.out = out;
    elapsedTime = -1L; // So we can tell if we are ever started
  }
  /**
   * Remembers the current time and prints a message.
   */
  public void start() {
    start(true);
  }
  /**
   * Remembers the current time and prints a message if requested.
   * 
   * @param printStarting
   *          if <code>true</code> and this stop watch has a name, print a
   *          message
   */
  public void start(boolean printStarting) {
    if (t0 != 0)
      System.err.println("(warning: StopWatch already started; resetting)");
    if (printStarting && name != null)
      System.err.println("starting " + name);
    elapsedTime = 0;
    t0 = System.currentTimeMillis();
  }
  /**
   * Pauses the stop watch.
   */
  public void pause() {
    long now = System.currentTimeMillis();
    elapsedTime += now - t0;
    t0 = 0;
  }
  /**
   * Resumes the stop watch.
   */
  public void resume() {
    t0 = System.currentTimeMillis();
  }
  /**
   * Prints the current elapsed time without stopping.
   */
  public void mark() {
    stop(null, true);
  }
  /**
   * Prints the current elapsed time without stopping, along with the stop watch
   * name if <var>printMark</var> is <code>true</code>.
   * 
   * @param printMark
   *          if <code>true</code>, the stop watch name will be printed
   */
  public void mark(boolean printMark) {
    stop(null, printMark);
  }
  /**
   * Prints the current elapsed time without stopping, along with the stop watch
   * name and <var>msg</var>.
   * 
   * @param msg
   *          a message to print
   */
  public void mark(String msg) {
    stop(msg, true);
  }
  /**
   * Prints the current elapsed time without stopping, along with, along with
   * the stop watch name if <var>printMark</var> is <code>true</code> and the
   * <var>msg</var> if it"s not <code>null</code>.
   * 
   * @param msg
   *          a message to print
   * @param printMark
   *          if <code>true</code>, the stop watch name will be printed
   */
  public void mark(String msg, boolean printMark) {
    stop(msg, printMark);
  }
  /**
   * Stops the stop watch and prints the name of this stop watch and the current
   * elapsed time.
   */
  public void stop() {
    stop(null, true);
  }
  /**
   * Stops the stop watch and prints the name of this stop watch, <var>msg</var>
   * if non-<code>null</code>, and the current elapsed time.
   * 
   * @param msg
   *          a message to print; may be <code>null</code>
   */
  public void stop(String msg) {
    stop(msg, true);
  }
  /**
   * Prints the current elapsed time, along with the stop watch name if
   * <var>printMark</var> is <code>true</code> and the <var>msg</var> if
   * it"s not <code>null</code>.
   * 
   * @param msg
   *          a message to print; may be <code>null</code>
   * @param printName
   *          if <code>true</code>, the stop watch name will be printed
   */
  public void stop(String msg, boolean printName) {
    long now = System.currentTimeMillis();
    if (elapsedTime == -1) {
      System.err.println("(StopWatch" + (name != null ? (" \"" + name + """) : "")
          + " was stopped without ever being started)");
      return;
    }
    long total = elapsedTime;
    if (t0 != 0)
      total += now - t0;
    String separator = null;
    if (printName && name != null) {
      System.err.print(name);
      separator = ": ";
    }
    if (msg != null) {
      if (separator != null)
        System.err.print(" ");
      System.err.print("(" + msg + ")");
      separator = ": ";
    }
    if (separator != null)
      System.err.print(separator);
    System.err.println("" + (total / 1000.0) + " seconds");
  }
}





StopWatch provides a convenient API for timings.

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

/**
 * 
 * <code>StopWatch</code> provides a convenient API for timings.
 * 
 * 
 * 
 * To start the watch, call {@link #start()}. At this point you can:
 * 
 * <ul>
 * <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 point, these three options are available again.</li>
 * <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 the total. At this point, these three options are available again.</li>
 * <li>{@link #stop()} the watch to complete the timing session.</li>
 * </ul>
 * 
 * 
 * It is intended that the output methods {@link #toString()} and {@link #getTime()} should only be called after stop,
 * split or suspend, however a suitable result will be returned at other points.
 * 
 * 
 * 
 * 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 split.
 * 
 * 
 * 
 * 1. split(), suspend(), or stop() cannot be invoked twice<br />
 * 2. unsplit() may only be called if the watch has been split()<br />
 * 3. resume() may only be called if the watch has been suspend()<br />
 * 4. start() cannot be called twice without calling reset()
 * 
 * 
 * @author Stephen Colebourne
 * @since 2.0
 * @version $Id: StopWatch.java 583608 2007-10-10 20:38:41Z ggregory $
 */
public class StopWatch {
    // running states
    private static final int STATE_UNSTARTED = 0;
    private static final int STATE_RUNNING = 1;
    private static final int STATE_STOPPED = 2;
    private static final int STATE_SUSPENDED = 3;
    // split state
    private static final int STATE_UNSPLIT = 10;
    private static final int STATE_SPLIT = 11;
    /**
     * The current running state of the StopWatch.
     */
    private int runningState = STATE_UNSTARTED;
    /**
     * Whether the stopwatch has a split time recorded.
     */
    private int splitState = STATE_UNSPLIT;
    /**
     * The start time.
     */
    private long startTime = -1;
    /**
     * The stop time.
     */
    private long stopTime = -1;
    /**
     * 
     * Constructor.
     * 
     */
    public StopWatch() {
        super();
    }
    /**
     * 
     * Start the stopwatch.
     * 
     * 
     * 
     * This method starts a new timing session, clearing any previous values.
     * 
     * 
     * @throws IllegalStateException
     *             if the StopWatch is already running.
     */
    public void start() {
        if (this.runningState == STATE_STOPPED) {
            throw new IllegalStateException("Stopwatch must be reset before being restarted. ");
        }
        if (this.runningState != STATE_UNSTARTED) {
            throw new IllegalStateException("Stopwatch already started. ");
        }
        this.stopTime = -1;
        this.startTime = System.currentTimeMillis();
        this.runningState = STATE_RUNNING;
    }
    /**
     * 
     * Stop the stopwatch.
     * 
     * 
     * 
     * This method ends a new timing session, allowing the time to be retrieved.
     * 
     * 
     * @throws IllegalStateException
     *             if the StopWatch is not running.
     */
    public void stop() {
        if (this.runningState != STATE_RUNNING && this.runningState != STATE_SUSPENDED) {
            throw new IllegalStateException("Stopwatch is not running. ");
        }
        if (this.runningState == STATE_RUNNING) {
            this.stopTime = System.currentTimeMillis();
        }
        this.runningState = STATE_STOPPED;
    }
    /**
     * 
     * Resets the stopwatch. Stops it if need be.
     * 
     * 
     * 
     * This method clears the internal values to allow the object to be reused.
     * 
     */
    public void reset() {
        this.runningState = STATE_UNSTARTED;
        this.splitState = STATE_UNSPLIT;
        this.startTime = -1;
        this.stopTime = -1;
    }
    /**
     * 
     * Split the time.
     * 
     * 
     * 
     * 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 timing from the original start point.
     * 
     * 
     * @throws IllegalStateException
     *             if the StopWatch is not running.
     */
    public void split() {
        if (this.runningState != STATE_RUNNING) {
            throw new IllegalStateException("Stopwatch is not running. ");
        }
        this.stopTime = System.currentTimeMillis();
        this.splitState = STATE_SPLIT;
    }
    /**
     * 
     * Remove a split.
     * 
     * 
     * 
     * This method clears the stop time. The start time is unaffected, enabling timing from the original start point to
     * continue.
     * 
     * 
     * @throws IllegalStateException
     *             if the StopWatch has not been split.
     */
    public void unsplit() {
        if (this.splitState != STATE_SPLIT) {
            throw new IllegalStateException("Stopwatch has not been split. ");
        }
        this.stopTime = -1;
        this.splitState = STATE_UNSPLIT;
    }
    /**
     * 
     * Suspend the stopwatch for later resumption.
     * 
     * 
     * 
     * 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.
     * 
     * 
     * @throws IllegalStateException
     *             if the StopWatch is not currently running.
     */
    public void suspend() {
        if (this.runningState != STATE_RUNNING) {
            throw new IllegalStateException("Stopwatch must be running to suspend. ");
        }
        this.stopTime = System.currentTimeMillis();
        this.runningState = STATE_SUSPENDED;
    }
    /**
     * 
     * Resume the stopwatch after a suspend.
     * 
     * 
     * 
     * 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.
     * 
     * 
     * @throws IllegalStateException
     *             if the StopWatch has not been suspended.
     */
    public void resume() {
        if (this.runningState != STATE_SUSPENDED) {
            throw new IllegalStateException("Stopwatch must be suspended to resume. ");
        }
        this.startTime += (System.currentTimeMillis() - this.stopTime);
        this.stopTime = -1;
        this.runningState = STATE_RUNNING;
    }
    /**
     * 
     * Get the time on the stopwatch.
     * 
     * 
     * 
     * This is either the time between the start and the moment this method is called, or the amount of time between
     * start and stop.
     * 
     * 
     * @return the time in milliseconds
     */
    public long getTime() {
        if (this.runningState == STATE_STOPPED || this.runningState == STATE_SUSPENDED) {
            return this.stopTime - this.startTime;
        } else if (this.runningState == STATE_UNSTARTED) {
            return 0;
        } else if (this.runningState == STATE_RUNNING) {
            return System.currentTimeMillis() - this.startTime;
        }
        throw new RuntimeException("Illegal running state has occured. ");
    }
    /**
     * 
     * Get the split time on the stopwatch.
     * 
     * 
     * 
     * This is the time between start and latest split.
     * 
     * 
     * @return the split time in milliseconds
     * 
     * @throws IllegalStateException
     *             if the StopWatch has not yet been split.
     * @since 2.1
     */
    public long getSplitTime() {
        if (this.splitState != STATE_SPLIT) {
            throw new IllegalStateException("Stopwatch must be split to get the split time. ");
        }
        return this.stopTime - this.startTime;
    }
    /**
     * Returns the time this stopwatch was started.
     * 
     * @return the time this stopwatch was started
     * @throws IllegalStateException
     *             if this StopWatch has not been started
     * @since 2.4
     */
    public long getStartTime() {
        if (this.runningState == STATE_UNSTARTED) {
            throw new IllegalStateException("Stopwatch has not been started");
        }
        return this.startTime;
    }
    /**
     * 
     * Gets a summary of the time that the stopwatch recorded as a string.
     * 
     * 
     * 
     * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
     * 
     * 
     * @return the time as a String
     */
    public String toString() {
        return new Date(getTime()).toString();
    }
    /**
     * 
     * Gets a summary of the split time that the stopwatch recorded as a string.
     * 
     * 
     * 
     * The format used is ISO8601-like, <i>hours</i>:<i>minutes</i>:<i>seconds</i>.<i>milliseconds</i>.
     * 
     * 
     * @return the split time as a String
     * @since 2.1
     */
    public String toSplitString() {
        return new Date(getSplitTime()).toString();
    }
}