171 lines
5.3 KiB
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());
|
|
}
|
|
}
|
|
}
|