package com.asteria.utility; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; /** * A utility class that provides functions for measuring the elapsed time * between two different time periods. * <p> * <p> * This class is <b>not</b> intended for use across multiple threads. * * @author lare96 <http://github.com/lare96> */ public final class Stopwatch { /** * The internal cached time that acts as a time stamp. */ private long cachedTime = Stopwatch.currentTime(); /** * The current state of this stopwatch. */ private State state = State.STOPPED; @Override public String toString() { boolean stopped = (state == State.STOPPED); return "STOPWATCH[elasped= " + (stopped ? 0 : elapsedTime()) + "]"; } /** * Gets the current time in {@link TimeUnit#MILLISECONDS}. This method is * more accurate than {@link System#currentTimeMillis()} and does not rely * on the underlying OS. * * @return the current time in milliseconds. */ public static long currentTime() { return TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS); } /** * Sets the internal cached time to {@link Utility#currentTime()}, * effectively making {@link Stopwatch#elapsedTime()} and * {@link Stopwatch#elapsed(long, TimeUnit)} return {@code 0}. If this * stopwatch is in a {@link State#STOPPED} state, invocation of this method * will change it to a {@link State#RUNNING} state. * * @return an instance of this stopwatch. */ public Stopwatch reset() { cachedTime = Stopwatch.currentTime(); state = State.RUNNING; return this; } /** * Sets the internal cached time to {@code 0} effectively putting this * stopwatch in a {@link State#STOPPED} state. * * @return an instance of this stopwatch. */ public Stopwatch stop() { state = State.STOPPED; return this; } /** * Retrieves the elapsed time in {@code unit}. If this stopwatch is stopped * invocation of this method will throw an exception. * * @param unit * the time unit to retrieve the elapsed time in. * @return the elapsed time. * @throws IllegalStateException * if this stopwatch has been stopped. */ public long elapsedTime(TimeUnit unit) { if (state == State.STOPPED) throw new IllegalStateException("The timer has been stopped!"); return unit.convert((Stopwatch.currentTime() - cachedTime), TimeUnit.MILLISECONDS); } /** * Retrieves the elapsed time in {@link TimeUnit#MILLISECONDS}. If this * stopwatch is stopped invocation of this method will throw an exception. * * @return the elapsed time. * @throws IllegalStateException * if this stopwatch has been stopped. */ public long elapsedTime() { return elapsedTime(TimeUnit.MILLISECONDS); } /** * Determines if the elapsed time is greater than {@code time} in * {@code unit}. If this stopwatch is stopped invocation of this method will * automatically return {@code true}. * * @param time * the time to check if greater than the elapsed time. * @param unit * the time unit to has in. * @return {@code true} if the elapsed time has passed or this stopwatch has * been stopped, {@code false} otherwise. */ public boolean elapsed(long time, TimeUnit unit) { if (state == State.STOPPED) return true; return elapsedTime(unit) >= time; } /** * Determines if the elapsed time is greater than {@code time} in * {@link TimeUnit#MILLISECONDS}. If this stopwatch is stopped invocation of * this method will automatically return {@code true}. * * @param time * the time to check if greater than the elapsed time. * @return {@code true} if the elapsed time has passed or this stopwatch has * been stopped, {@code false} otherwise. */ public boolean elapsed(long time) { return elapsed(time, TimeUnit.MILLISECONDS); } /** * Determines if this stopwatch is in a {@link State#STOPPED} state. * * @return {@code true} if this stopwatch is in a stopped state, * {@code false} otherwise. */ public boolean isStopped() { return state == State.STOPPED; } /** * Executes {@code action} if the elapsed time is greater than {@code time} * in {@code unit}. If this stopwatch is stopped invocation of this method * will automatically execute {@code action}. * * @param time * the time to check if greater than the elapsed time. * @param action * the action to execute if satisfied. * @param unit * the time unit to check in. */ public void ifElapsed(long time, Consumer<? super Long> action, TimeUnit unit) { if (state == State.STOPPED) { action.accept((long) 0); return; } long elapsed = elapsedTime(unit); if (elapsed >= time) { action.accept(elapsed); } } /** * Executes {@code action} if the elapsed time is greater than {@code time} * in {@link TimeUnit#MILLISECONDS}. If this stopwatch is stopped invocation * of this method will automatically execute {@code action}. * * @param timePassed * the time to check if greater than the elapsed time. * @param action * the action to execute if satisfied. */ public void ifElapsed(long timePassed, Consumer<? super Long> action) { ifElapsed(timePassed, action, TimeUnit.MILLISECONDS); } /** * The enumerated type representing all possible states of this stopwatch. * * @author lare96 <http://github.com/lare96> */ private enum State { RUNNING, STOPPED } /** * A utility class that provides functions for measuring the elapsed time * between two different time periods. The only difference between this * class and {@link Stopwatch} is that the fields in this class are wrapped * in atomic based classes to ensure thread safety. * <p> * <p> * This class is atomic and is intended for use across multiple threads. * * @author lare96 <http://github.com/lare96> */ public static final class AtomicStopwatch { /** * The internal cached time that acts as a time stamp. */ private final AtomicLong cachedTime = new AtomicLong(Stopwatch.currentTime()); /** * The current state of this stopwatch. */ private final AtomicReference<State> state = new AtomicReference<>(State.STOPPED); @Override public String toString() { boolean stopped = (state.get() == State.STOPPED); return "STOPWATCH[elasped= " + (stopped ? 0 : elapsedTime()) + "]"; } /** * Gets the current time in {@link TimeUnit#MILLISECONDS}. This method * is more accurate than {@link System#currentTimeMillis()} and does not * rely on the underlying OS. * * @return the current time in milliseconds. */ public static long currentTime() { return TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS); } /** * Sets the internal cached time to {@link Utility#currentTime()}, * effectively making {@link Stopwatch#elapsedTime()} and * {@link Stopwatch#elapsed(long, TimeUnit)} return {@code 0}. If this * stopwatch is in a {@link State#STOPPED} state, invocation of this * method will change it to a {@link State#RUNNING} state. * * @return an instance of this stopwatch. */ public AtomicStopwatch reset() { cachedTime.set(Stopwatch.currentTime()); state.set(State.RUNNING); return this; } /** * Sets the internal cached time to {@code 0} effectively putting this * stopwatch in a {@link State#STOPPED} state. * * @return an instance of this stopwatch. */ public AtomicStopwatch stop() { state.set(State.STOPPED); return this; } /** * Retrieves the elapsed time in {@code unit}. If this stopwatch is * stopped invocation of this method will throw an exception. * * @param unit * the time unit to retrieve the elapsed time in. * @return the elapsed time. * @throws IllegalStateException * if this stopwatch has been stopped. */ public long elapsedTime(TimeUnit unit) { if (state.get() == State.STOPPED) throw new IllegalStateException("The timer has been stopped!"); return unit.convert((Stopwatch.currentTime() - cachedTime.get()), TimeUnit.MILLISECONDS); } /** * Retrieves the elapsed time in {@link TimeUnit#MILLISECONDS}. If this * stopwatch is stopped invocation of this method will throw an * exception. * * @return the elapsed time. * @throws IllegalStateException * if this stopwatch has been stopped. */ public long elapsedTime() { return elapsedTime(TimeUnit.MILLISECONDS); } /** * Determines if the elapsed time is greater than {@code time} in * {@code unit}. If this stopwatch is stopped invocation of this method * will automatically return {@code true}. * * @param time * the time to check if greater than the elapsed time. * @param unit * the time unit to has in. * @return {@code true} if the elapsed time has passed or this stopwatch * has been stopped, {@code false} otherwise. */ public boolean elapsed(long time, TimeUnit unit) { if (state.get() == State.STOPPED) return true; return elapsedTime(unit) >= time; } /** * Determines if the elapsed time is greater than {@code time} in * {@link TimeUnit#MILLISECONDS}. If this stopwatch is stopped * invocation of this method will automatically return {@code true}. * * @param time * the time to check if greater than the elapsed time. * @return {@code true} if the elapsed time has passed or this stopwatch * has been stopped, {@code false} otherwise. */ public boolean elapsed(long time) { return elapsed(time, TimeUnit.MILLISECONDS); } /** * Determines if this stopwatch is in a {@link State#STOPPED} state. * * @return {@code true} if this stopwatch is in a stopped state, * {@code false} otherwise. */ public boolean isStopped() { return state.get() == State.STOPPED; } /** * Executes {@code action} if the elapsed time is greater than * {@code time} in {@code unit}. If this stopwatch is stopped invocation * of this method will automatically execute {@code action}. * * @param time * the time to check if greater than the elapsed time. * @param action * the action to execute if satisfied. * @param unit * the time unit to check in. */ public void ifElapsed(long time, Consumer<? super Long> action, TimeUnit unit) { if (state.get() == State.STOPPED) { action.accept((long) 0); return; } long elapsed = elapsedTime(unit); if (elapsed >= time) { action.accept(elapsed); } } /** * Executes {@code action} if the elapsed time is greater than * {@code time} in {@link TimeUnit#MILLISECONDS}. If this stopwatch is * stopped invocation of this method will automatically execute * {@code action}. * * @param timePassed * the time to check if greater than the elapsed time. * @param action * the action to execute if satisfied. */ public void ifElapsed(long timePassed, Consumer<? super Long> action) { ifElapsed(timePassed, action, TimeUnit.MILLISECONDS); } } }