Last active
April 23, 2016 14:06
-
-
Save SocraticPhoenix/f3d74641db94c3fc531ab2a97ae7010e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package your.namespace.here; | |
import java.util.Map; | |
import java.util.concurrent.ConcurrentHashMap; | |
/** | |
* The Stopwatch class represents a mapping of versatile timing entries, each of which can be independently stopped, started, or examined. | |
* | |
* @param <T> The type of key to use for the timing entries | |
*/ | |
public class Stopwatch<T> { | |
private Map<T, Entry> counts; | |
private boolean initiallyStarted; | |
/** | |
* Creates a new Stopwatch that's timing entries are automatically started whenever they are created. | |
*/ | |
public Stopwatch() { | |
this(true); | |
} | |
/** | |
* Creates a new Stopwatch that's timing entries are either started or stopped whenever they are created. | |
* | |
* @param initiallyStarted Whether or not to start a timing entry when it is created. | |
*/ | |
public Stopwatch(boolean initiallyStarted) { | |
this.counts = new ConcurrentHashMap<>(); | |
this.initiallyStarted = initiallyStarted; | |
} | |
/** | |
* Converts milliseconds to a human readable string | |
* | |
* @param millis The milliseconds to convert | |
* @param roundMillis Whether or not to round milliseconds to seconds | |
* @return A human readable string | |
*/ | |
public static String toTimeString(long millis, boolean roundMillis) { | |
if (millis == 0|| (millis < 500 && millis >= 0 && roundMillis)) { //Handle the 0 case | |
return roundMillis ? "0 Seconds" : "0 Milliseconds"; | |
} | |
long seconds = millis / 1000; //TOTAL number of seconds | |
long minutes = seconds / 60; //TOTAL number of minutes | |
long hours = minutes / 60; //TOTAL number of hours | |
millis = millis - (seconds * 1000); //Remove the milliseconds taken care of by the number of seconds | |
seconds = seconds - (minutes * 60); //Remove the seconds taken care of by the number of minutes | |
minutes = minutes - (60 * hours); //Remove the minutes taken care of by the number of hours | |
//If we're rounding milliseconds, add stuff to seconds. | |
if (roundMillis) { | |
if (millis >= 500) { | |
seconds++; | |
} else if (millis <= -500) { | |
seconds--; | |
} | |
millis = 0; | |
} | |
StringBuilder builder = new StringBuilder(); | |
if (hours != 0) { //Are their hours present? | |
builder.append(hours).append(hours == 1 ? " Hour" : " Hours"); //Append hours and "Hours" | |
builder.append(containsNonZero(1, minutes, seconds, millis) ? " and" : ""); //If there are remaining things to append, append "and" | |
} | |
if (minutes != 0) { //Are their minutes present? | |
builder.append(hours != 0 && !containsNonZero(1, minutes, seconds, millis) ? ", " : (hours != 0 ? " " : "")).append(minutes).append(minutes == 1 ? " Minute" : " Minutes"); //Append minutes and "Minutes" | |
builder.append(containsNonZero(1, seconds, millis) ? " and" : ""); //If there are remaining things to append, append "and" | |
} | |
if (seconds != 0) { //Are their seconds present? | |
builder.append((minutes != 0 || hours != 0) && !containsNonZero(1, seconds, millis) ? ", " : (minutes != 0 || hours != 0 ? " " : "")).append(seconds).append(seconds == 1 ? " Second" : " Seconds"); //Append seconds and "Seconds" | |
builder.append(containsNonZero(1, millis) ? " and" : ""); //If there are remaining things to append, append "and" | |
} | |
if (millis != 0) { //Are their milliseconds present? | |
builder.append(minutes != 0 || hours != 0 || seconds != 0 ? " " : "").append(millis).append(millis == 1 ? " Millisecond" : " Milliseconds"); //Append milliseconds and "Milliseconds" | |
} | |
return builder.toString(); | |
} | |
/** | |
* Returns true if <i>toCheck</i> contains exactly <i>amount</i> non-zero numbers. | |
* | |
* @param amount The desired amount of non-zero numbers. | |
* @param toCheck The numbers to check | |
* @return True if the desired amount of non-zeroes were found, false otherwise. | |
*/ | |
private static boolean containsNonZero(int amount, long... toCheck) { | |
int count = 0; | |
for (long l : toCheck) { | |
if (l != 0) { | |
count++; | |
} | |
if (count > amount) { | |
break; | |
} | |
} | |
return count == amount; | |
} | |
/** | |
* Creates a new timing entry under the specified id. | |
* | |
* @param id The id of the timing entry. | |
*/ | |
public void create(T id) { | |
this.counts.put(id, new Entry(!this.initiallyStarted)); | |
} | |
/** | |
* Removes a timing entry under the specified id, if it exists. | |
* | |
* @param id The id of the timing entry. | |
*/ | |
public void remove(T id) { | |
this.counts.remove(id); | |
} | |
/** | |
* Determines whether or not there is a timing entry registered under the specified id. | |
* | |
* @param id The id of the timing entry. | |
* @return True if the timing entry exists, false otherwise. | |
*/ | |
public boolean contains(T id) { | |
return this.counts.containsKey(id); | |
} | |
/** | |
* Gets an existing timing entry, or creates one if it does not exist. | |
* | |
* @param id The id of the timing entry. | |
* @return The new or pre-existing timing entry. | |
*/ | |
public Stopwatch.Entry getOrCreate(T id) { | |
if (!this.counts.containsKey(id)) { | |
this.create(id); | |
} | |
return this.counts.get(id); | |
} | |
/** | |
* Fetches the elapsed time of the timing entry under the specified id. | |
* | |
* <p>The entry is created if it does not exist.</p> | |
* @param id The id of the timing entry. | |
* @return The elapsed time of the timing entry. | |
*/ | |
public long time(T id) { | |
return this.getOrCreate(id).time(); | |
} | |
/** | |
* Stops the timing entry under the specified id. | |
* | |
* <p>The entry is created if it does not exist.</p> | |
* @param id The id of the timing entry. | |
*/ | |
public void stop(T id) { | |
this.getOrCreate(id).stop(); | |
} | |
/** | |
* Starts the timing entry under the specified id. | |
* | |
* <p>The entry is created if it does not exist.</p> | |
* @param id The id of the timing entry. | |
*/ | |
public void start(T id) { | |
this.getOrCreate(id).start(); | |
} | |
/** | |
* Fetches the time until the timing entry the specified id will reach the specified time elapsed. | |
* <p>If the specified time entry has more elapsed time than specified, negative or zero time will be returned</p> | |
* <p>The entry is created if it does not exist.</p> | |
* @param id The id of the timing entry. | |
* @param elapsed The time elapsed to consider. | |
* @return The time until the entry will reach the specified elapsed time. | |
*/ | |
public long until(T id, long elapsed) { | |
return this.getOrCreate(id).until(elapsed); | |
} | |
/** | |
* Fetches the elapsed time of the timing entry under the specified id and returns a human readable representation of it. | |
* | |
* <p>The entry is created if it does not exist.</p> | |
* @param id The id of the timing entry. | |
* @return The human readable elapsed time of the timing entry. | |
*/ | |
public String humanReadableTime(T id) { | |
return this.getOrCreate(id).humanReadableTime(); | |
} | |
/** | |
* Fetches the time until the timing entry the specified id will reach the specified time elapsed and returns a human readable representation if it. | |
* <p>If the specified time entry has more elapsed time than specified, negative or zero time will be returned</p> | |
* <p>The entry is created if it does not exist.</p> | |
* @param id The id of the timing entry. | |
* @param elapsed The time elapsed to consider. | |
* @return The human readable time until the entry will reach the specified elapsed time. | |
*/ | |
public String humanReadableUntil(T id, long elapsed) { | |
return this.getOrCreate(id).humanReadableUntil(elapsed); | |
} | |
/** | |
* Fetches the elapsed time of the timing entry under the specified id and returns a human readable representation of it. | |
* | |
* <p>The entry is created if it does not exist.</p> | |
* @param id The id of the timing entry. | |
* @param roundMillis Whether or not to round milliseconds when creating the human readable string. | |
* @return The human readable elapsed time of the timing entry. | |
*/ | |
public String humanReadableTime(T id, boolean roundMillis) { | |
return this.getOrCreate(id).humanReadableTime(roundMillis); | |
} | |
/** | |
* Fetches the time until the timing entry the specified id will reach the specified time elapsed and returns a human readable representation if it. | |
* <p>If the specified time entry has more elapsed time than specified, negative or zero time will be returned</p> | |
* <p>The entry is created if it does not exist.</p> | |
* @param id The id of the timing entry. | |
* @param elapsed The time elapsed to consider. | |
* @param roundMillis Whether or not to round milliseconds when creating the human readable string. | |
* @return The human readable time until the entry will reach the specified elapsed time. | |
*/ | |
public String humanReadableUntil(T id, long elapsed, boolean roundMillis) { | |
return this.getOrCreate(id).humanReadableUntil(elapsed, roundMillis); | |
} | |
/** | |
* The Stopwatch.Entry class represents a single Stopwatch timing entry. This class takes care of the actual counting and mathematical calculations. | |
*/ | |
public static class Entry { | |
private long startTime; | |
private long stoppedTimeElapsed; | |
private long stopTime; | |
private boolean stopped; | |
/** | |
* Creates a new Stopwatch.Entry, with the specified initial stopped value. | |
* | |
* @param stopped Whether or not the Stopwatch.Entry starts when it is created. | |
*/ | |
public Entry(boolean stopped) { | |
this.startTime = current(); | |
this.stoppedTimeElapsed = 0; | |
this.stopTime = 0; | |
this.stopped = stopped; | |
if (stopped) { | |
this.stopTime = current(); | |
} | |
} | |
/** | |
* Creates a new Stopwatch.Entry that starts immediately. | |
*/ | |
public Entry() { | |
this(false); | |
} | |
/** | |
* Pauses this Stopwatch.Entry. This method only has an effect if the Stopwatch.Entry is not currently paused. | |
*/ | |
public void stop() { | |
if (!stopped) { | |
this.stopped = true; | |
this.stopTime = current(); | |
} | |
} | |
/** | |
* Starts this Stopwatch.Entry. This method only has an effect if the Stopwatch.Entry is not currently started. | |
*/ | |
public void start() { | |
if (stopped) { | |
this.stopped = false; | |
this.stoppedTimeElapsed = this.stoppedTimeElapsed + (current() - stopTime); | |
} | |
} | |
/** | |
* Fetches the time elapsed since this Stopwatch.Entry was created, <b>without taking into account pauses.</b> | |
* | |
* @return The time elapsed since this Stopwatch.Entry was created. | |
*/ | |
public long rawTime() { | |
return current() - this.startTime; | |
} | |
/** | |
* Fetches the time elapsed since this Stopwatch.Entry was created, taking into account pauses. | |
* | |
* @return The time elapsed since this Stopwatch.Entry was created. | |
*/ | |
public long time() { | |
if (stopped) { | |
long current = current(); | |
this.stoppedTimeElapsed = this.stoppedTimeElapsed + (current - stopTime); | |
this.stopTime = current; | |
} | |
return (current() - this.startTime) - stoppedTimeElapsed; | |
} | |
/** | |
* Fetches the amount of time until this.time() will return <i>elapsed</i> | |
* | |
* @param elapsed The target elapsed time | |
* @return The amount of time until this.time() reaches <i>elapsed</i>, or a time < 0 if this.time() is greater than elapsed. | |
*/ | |
public long until(long elapsed) { | |
return elapsed - this.time(); | |
} | |
/** | |
* Fetches the time elapsed since this Stopwatch.Entry was created, taking into account pauses, and returns it as a human readable string. | |
* | |
* @return The human readable time elapsed since this Stopwatch.Entry was created. | |
*/ | |
public String humanReadableTime() { | |
return Stopwatch.toTimeString(this.time(), false); | |
} | |
/** | |
* Fetches the amount of time until this.time() will return <i>elapsed</i>, and returns it as a human readable string. | |
* | |
* @param elapsed The target elapsed time | |
* @return The amount of time until this.time() reaches <i>elapsed</i>, or a time < 0 if this.time() is greater than elapsed. | |
*/ | |
public String humanReadableUntil(long elapsed) { | |
return Stopwatch.toTimeString(this.until(elapsed), false); | |
} | |
/** | |
* Fetches the time elapsed since this Stopwatch.Entry was created, taking into account pauses, and returns it as a human readable string. | |
* | |
* @param roundMillis Whether or not to round milliseconds when creating the human readable string. | |
* @return The human readable time elapsed since this Stopwatch.Entry was created. | |
*/ | |
public String humanReadableTime(boolean roundMillis) { | |
return Stopwatch.toTimeString(this.time(), roundMillis); | |
} | |
/** | |
* Fetches the amount of time until this.time() will return <i>elapsed</i>, and returns it as a human readable string. | |
* | |
* @param roundMillis Whether or not to round milliseconds when creating the human readable string. | |
* @param elapsed The target elapsed time | |
* @return The amount of time until this.time() reaches <i>elapsed</i>, or a time < 0 if this.time() is greater than elapsed. | |
*/ | |
public String humanReadableUntil(long elapsed, boolean roundMillis) { | |
return Stopwatch.toTimeString(this.until(elapsed), roundMillis); | |
} | |
/** | |
* Convenience method for quickly accessing System.currentTimeMillis() | |
* | |
* @return System.currentTimeMillis() | |
*/ | |
private long current() { | |
return System.currentTimeMillis(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment