ProjectAres/Util/core/src/main/java/tc/oc/commons/core/util/PunchClock.java

171 lines
5.3 KiB
Java

package tc.oc.commons.core.util;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.google.common.base.Predicate;
import com.google.common.collect.Sets;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Tracks the presence and absence of multiple objects over some time span.
*
* All times are calculated relative to the time source passed to the constructor,
* which must provide the current elapsed time at any moment. The elapsed time must
* be monotonically increasing i.e. it must never decrease, but it can otherwise
* increase at any rate or not at all.
*/
public class PunchClock<T> {
// Time reference for all calculations
private final Supplier<Duration> timeSource;
// Most recent punch in/out time
private final Map<T, Duration> inTimes = new HashMap<>();
private final Map<T, Duration> outTimes = new HashMap<>();
// Cumulative punched in/out time, EXCLUDING any current interval
private final Map<T, Duration> cumulativePresence = new DefaultMapAdapter<>(Duration.ZERO);
// Maximum continuous punched out interval, EXCLUDING any current one
private final Map<T, Duration> maxContinuousAbsence = new DefaultMapAdapter<>(Duration.ZERO);
// Time of first punch-in
private final Map<T, Duration> firstPresence = new HashMap<>();
public PunchClock(Supplier<Duration> timeSource) {
this.timeSource = checkNotNull(timeSource);
}
public Duration getElapsed() {
return timeSource.get();
}
/**
* Time since the given key last punched in, or zero if the key has never punched in
*/
public Duration getContinuousPresence(T key) {
Duration inTime = inTimes.get(key);
if(inTime == null) {
return Duration.ZERO;
} else {
return getElapsed().minus(inTime);
}
}
/**
* Time since the given key last punched out, zero if they are currently punched in,
* current time if they have never punched in or out
*/
public Duration getContinuousAbsence(T key) {
Duration outTime = outTimes.get(key);
if(outTime != null) {
return getElapsed().minus(outTime);
} else if(inTimes.containsKey(key)) {
return Duration.ZERO;
} else {
return getElapsed();
}
}
public Duration getMaxContinuousAbsence(T key) {
return TimeUtils.max(maxContinuousAbsence.get(key), getContinuousAbsence(key));
}
/**
* Total time that the given key has ever been punched in
*/
public Duration getCumulativePresence(T key) {
return cumulativePresence.get(key).plus(getContinuousPresence(key));
}
public double getCumulativePresencePercent(T key) {
return (double) getCumulativePresence(key).toMillis() / getElapsed().toMillis();
}
/**
* Total time that the given key has ever not been punched in
*/
public Duration getCumulativeAbsence(T key) {
return getElapsed().minus(getCumulativePresence(key));
}
public double getCumulativeAbsencePercent(T key) {
return (double) getCumulativeAbsence(key).toMillis() / getElapsed().toMillis();
}
public @Nullable Duration getFirstPresence(T key) {
Duration t = firstPresence.get(key);
if(t != null && Comparables.lessThan(t, getElapsed())) {
return t;
} else {
return null;
}
}
public boolean isPresent(T key) {
return inTimes.containsKey(key);
}
public boolean isAbsent(T key) {
return !isPresent(key);
}
public Set<T> getAllPresent() {
return inTimes.keySet();
}
public Set<T> getAllWithPresence() {
return Sets.union(cumulativePresence.keySet(), inTimes.keySet());
}
public Set<T> getAllWithNonZeroPresence() {
final Duration elapsed = getElapsed();
if(Comparables.greaterThan(elapsed, Duration.ZERO)) {
return Sets.union(
cumulativePresence.keySet(),
Sets.filter(
inTimes.keySet(),
new Predicate<T>() {
@Override public boolean apply(@Nullable T key) {
return Comparables.greaterThan(getElapsed(), inTimes.get(key));
}
}
)
);
} else {
return Collections.emptySet();
}
}
private void setOrClear(Map<T, Duration> map, T key, Duration time) {
if(Comparables.greaterThan(time, Duration.ZERO)) {
map.put(key, time);
} else {
map.remove(key);
}
}
public void punchIn(T key) {
if(!isPresent(key)) {
setOrClear(maxContinuousAbsence, key, getMaxContinuousAbsence(key));
outTimes.remove(key);
inTimes.put(key, getElapsed());
if(!firstPresence.containsKey(key)) firstPresence.put(key, getElapsed());
}
}
public void punchOut(T key) {
if(isPresent(key)) {
setOrClear(cumulativePresence, key, getCumulativePresence(key));
inTimes.remove(key);
outTimes.put(key, getElapsed());
}
}
}