2022-07-20 13:18:57 +02:00
|
|
|
|
package fr.pandacube.lib.util;
|
2020-11-02 23:23:41 +01:00
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
2022-07-28 01:13:35 +02:00
|
|
|
|
/**
|
|
|
|
|
* Utility class to track and limit the amount of a specific value for a specified amount of duration.
|
|
|
|
|
*
|
|
|
|
|
* An exemple of application is for rolling expense limit of a debit card: you cannot expense more that {@code $X}
|
|
|
|
|
* during a rolling period of {@code $Y} time.
|
|
|
|
|
*
|
|
|
|
|
* Here is an example usage of this class:
|
|
|
|
|
* <pre>
|
|
|
|
|
* AmountPerTimeLimiter instance = new AmountPerTimeLimiter(X, Y);
|
|
|
|
|
* void tryExpense(double amount) {
|
|
|
|
|
* if (instance.canAdd(amount)) {
|
|
|
|
|
* // do the action (here, it’s the expense)
|
|
|
|
|
* instance.add(amount);
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
* </pre>
|
|
|
|
|
*/
|
2020-11-02 23:23:41 +01:00
|
|
|
|
public class AmountPerTimeLimiter {
|
|
|
|
|
private final double maxAmount;
|
|
|
|
|
private final long duration;
|
|
|
|
|
|
|
|
|
|
private List<Entry> entries = new ArrayList<>();
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new instance of {@link AmountPerTimeLimiter}
|
|
|
|
|
* @param maximumAmount the maximum amount possible in the specified interval
|
|
|
|
|
* @param timeInterval the interval in milliseconds
|
|
|
|
|
*/
|
|
|
|
|
public AmountPerTimeLimiter(double maximumAmount, long timeInterval) {
|
|
|
|
|
maxAmount = maximumAmount;
|
|
|
|
|
duration = timeInterval;
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compute and returns the amount considered for the current time interval.
|
|
|
|
|
* @return the amount considered.
|
|
|
|
|
*/
|
2020-11-02 23:23:41 +01:00
|
|
|
|
public synchronized double getAmountSinceDuration() {
|
|
|
|
|
long currT = System.currentTimeMillis();
|
|
|
|
|
entries = entries.stream()
|
|
|
|
|
.filter(e -> e.time > currT - duration)
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
|
return entries.stream()
|
|
|
|
|
.mapToDouble(e -> e.amount)
|
|
|
|
|
.sum();
|
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register the provided amount into this limiter, at the current system time.
|
|
|
|
|
* @param amount the amount added.
|
|
|
|
|
*/
|
2020-11-02 23:23:41 +01:00
|
|
|
|
public synchronized void add(double amount) {
|
|
|
|
|
long currT = System.currentTimeMillis();
|
2022-07-28 01:13:35 +02:00
|
|
|
|
if (!entries.isEmpty() && entries.get(entries.size()-1).time == currT)
|
2020-11-02 23:23:41 +01:00
|
|
|
|
entries.get(entries.size()-1).amount += amount;
|
|
|
|
|
else
|
2022-07-28 01:13:35 +02:00
|
|
|
|
entries.add(new Entry(currT, amount));
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determine if the provided amount can be added into the limiter without exceeding the limit.
|
|
|
|
|
* @param amount the amount to test.
|
|
|
|
|
* @return if it’s possible to add that amount now.
|
|
|
|
|
*/
|
2020-11-02 23:23:41 +01:00
|
|
|
|
public boolean canAdd(double amount) {
|
|
|
|
|
return getAmountSinceDuration() + amount < maxAmount;
|
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets the amount that can be added right now into the limiter.
|
|
|
|
|
* @return the maximum amount that can be added.
|
|
|
|
|
*/
|
2020-11-02 23:23:41 +01:00
|
|
|
|
public double getRemainingForNow() {
|
|
|
|
|
return Math.max(0, maxAmount - getAmountSinceDuration());
|
|
|
|
|
}
|
2022-07-28 01:13:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class Entry {
|
|
|
|
|
private final long time;
|
|
|
|
|
private double amount;
|
|
|
|
|
public Entry(long t, double a) {
|
|
|
|
|
time = t; amount = a;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-02 23:23:41 +01:00
|
|
|
|
}
|