Create Duration class and ExpiringSet#getExpiration (prep for #1073)

- Move expiring collections to util.expiring package
- Change ExpiringSet to remove expired entries during normal calls
This commit is contained in:
ljacqu 2017-02-25 17:25:25 +01:00
parent 4edb4e68c2
commit 72c5cfac68
17 changed files with 226 additions and 18 deletions

View File

@ -5,7 +5,7 @@ import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.util.TimedCounter;
import fr.xephi.authme.util.expiring.TimedCounter;
import javax.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;

View File

@ -5,7 +5,7 @@ import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.ExpiringSet;
import fr.xephi.authme.util.expiring.ExpiringSet;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;

View File

@ -7,8 +7,8 @@ import fr.xephi.authme.message.Messages;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.TimedCounter;
import fr.xephi.authme.util.PlayerUtils;
import fr.xephi.authme.util.expiring.TimedCounter;
import org.bukkit.entity.Player;
import javax.inject.Inject;

View File

@ -4,8 +4,8 @@ import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.SettingsDependent;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.ExpiringMap;
import fr.xephi.authme.util.RandomStringUtils;
import fr.xephi.authme.util.expiring.ExpiringMap;
import javax.inject.Inject;
import java.util.concurrent.TimeUnit;

View File

@ -3,6 +3,7 @@ package fr.xephi.authme.util;
import fr.xephi.authme.ConsoleLogger;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
@ -70,4 +71,36 @@ public final class Utils {
return Runtime.getRuntime().availableProcessors();
}
public static Duration convertMillisToSuitableUnit(long duration) {
TimeUnit targetUnit;
if (duration > 1000L * 60L * 60L * 24L) {
targetUnit = TimeUnit.DAYS;
} else if (duration > 1000L * 60L * 60L) {
targetUnit = TimeUnit.HOURS;
} else if (duration > 1000L * 60L) {
targetUnit = TimeUnit.MINUTES;
} else if (duration > 1000L) {
targetUnit = TimeUnit.SECONDS;
} else {
targetUnit = TimeUnit.MILLISECONDS;
}
return new Duration(targetUnit, duration);
}
public static final class Duration {
private final long duration;
private final TimeUnit unit;
Duration(TimeUnit targetUnit, long durationMillis) {
this(targetUnit, durationMillis, TimeUnit.MILLISECONDS);
}
Duration(TimeUnit targetUnit, long sourceDuration, TimeUnit sourceUnit) {
this.duration = targetUnit.convert(sourceDuration, sourceUnit);
this.unit = targetUnit;
}
}
}

View File

@ -0,0 +1,72 @@
package fr.xephi.authme.util.expiring;
import java.util.concurrent.TimeUnit;
/**
* Represents a duration in time, defined by a time unit and a duration.
*/
public class Duration {
private final long duration;
private final TimeUnit unit;
/**
* Constructor.
*
* @param duration the duration
* @param unit the time unit in which {@code duration} is expressed
*/
public Duration(long duration, TimeUnit unit) {
this.duration = duration;
this.unit = unit;
}
/**
* Creates a Duration object for the given duration and unit in the most suitable time unit.
* For example, {@code createWithSuitableUnit(120, TimeUnit.SECONDS)} will return a Duration
* object of 2 minutes.
* <p>
* This method only considers the time units days, hours, minutes, and seconds for the objects
* it creates. Conversion is done with {@link TimeUnit#convert} and so always rounds the
* results down.
* <p>
* Further examples:
* <code>createWithSuitableUnit(299, TimeUnit.MINUTES); // 4 hours</code>
* <code>createWithSuitableUnit(700, TimeUnit.MILLISECONDS); // 0 seconds</code>
*
* @param sourceDuration the duration
* @param sourceUnit the time unit the duration is expressed in
* @return Duration object using the most suitable time unit
*/
public static Duration createWithSuitableUnit(long sourceDuration, TimeUnit sourceUnit) {
long durationMillis = Math.abs(TimeUnit.MILLISECONDS.convert(sourceDuration, sourceUnit));
TimeUnit targetUnit;
if (durationMillis > 1000L * 60L * 60L * 24L) {
targetUnit = TimeUnit.DAYS;
} else if (durationMillis > 1000L * 60L * 60L) {
targetUnit = TimeUnit.HOURS;
} else if (durationMillis > 1000L * 60L) {
targetUnit = TimeUnit.MINUTES;
} else {
targetUnit = TimeUnit.SECONDS;
}
long durationInTargetUnit = targetUnit.convert(sourceDuration, sourceUnit);
return new Duration(durationInTargetUnit, targetUnit);
}
/**
* @return the duration
*/
public long getDuration() {
return duration;
}
/**
* @return the time unit in which the duration is expressed
*/
public TimeUnit getTimeUnit() {
return unit;
}
}

View File

@ -1,4 +1,4 @@
package fr.xephi.authme.util;
package fr.xephi.authme.util.expiring;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

View File

@ -1,4 +1,4 @@
package fr.xephi.authme.util;
package fr.xephi.authme.util.expiring;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -9,9 +9,10 @@ import java.util.concurrent.TimeUnit;
* has expired, the set will act as if the entry no longer exists. Time starts
* counting after the entry has been inserted.
* <p>
* Internally, expired entries are not cleared automatically. A cleanup can be
* triggered with {@link #removeExpiredEntries()}. Adding an entry that is
* already present effectively resets its expiration.
* Internally, expired entries are not guaranteed to be cleared automatically.
* A cleanup of all expired entries may be triggered with
* {@link #removeExpiredEntries()}. Adding an entry that is already present
* effectively resets its expiration.
*
* @param <E> the type of the entries
*/
@ -47,7 +48,14 @@ public class ExpiringSet<E> {
*/
public boolean contains(E entry) {
Long expiration = entries.get(entry);
return expiration != null && expiration > System.currentTimeMillis();
if (expiration == null) {
return false;
} else if (expiration > System.currentTimeMillis()) {
return true;
} else {
entries.remove(entry);
return false;
}
}
/**
@ -73,6 +81,27 @@ public class ExpiringSet<E> {
entries.entrySet().removeIf(entry -> System.currentTimeMillis() > entry.getValue());
}
/**
* Returns the duration of the entry until it expires (provided it is not removed or re-added).
* If the entry does not exist, -1 is returned.
*
* @param entry the entry whose duration before it expires should be returned
* @param unit the unit in which to return the duration
* @return duration the entry will remain in the set (if there are not modifications)
*/
public long getExpiration(E entry, TimeUnit unit) {
Long expiration = entries.get(entry);
if (expiration == null) {
return -1;
}
long stillPresentMillis = expiration - System.currentTimeMillis();
if (stillPresentMillis < 0) {
entries.remove(entry);
return -1;
}
return unit.convert(stillPresentMillis, TimeUnit.MILLISECONDS);
}
/**
* Sets a new expiration duration. Note that already present entries
* will still make use of the old expiration.

View File

@ -1,4 +1,4 @@
package fr.xephi.authme.util;
package fr.xephi.authme.util.expiring;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

View File

@ -3,7 +3,7 @@ package fr.xephi.authme.data;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.TimedCounter;
import fr.xephi.authme.util.expiring.TimedCounter;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;

View File

@ -3,7 +3,7 @@ package fr.xephi.authme.data;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.ExpiringSet;
import fr.xephi.authme.util.expiring.ExpiringSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;

View File

@ -7,7 +7,7 @@ import fr.xephi.authme.message.Messages;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.TimedCounter;
import fr.xephi.authme.util.expiring.TimedCounter;
import org.bukkit.entity.Player;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@ -6,7 +6,7 @@ import ch.jalu.injector.testing.InjectDelayed;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.SecuritySettings;
import fr.xephi.authme.util.ExpiringMap;
import fr.xephi.authme.util.expiring.ExpiringMap;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;

View File

@ -0,0 +1,43 @@
package fr.xephi.authme.util.expiring;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Test for {@link Duration}.
*/
public class DurationTest {
@Test
public void shouldConvertToAppropriateTimeUnit() {
check(Duration.createWithSuitableUnit(0, TimeUnit.HOURS),
0, TimeUnit.SECONDS);
check(Duration.createWithSuitableUnit(124, TimeUnit.MINUTES),
2, TimeUnit.HOURS);
check(Duration.createWithSuitableUnit(300, TimeUnit.HOURS),
12, TimeUnit.DAYS);
check(Duration.createWithSuitableUnit(60 * 24 * 50 + 8, TimeUnit.MINUTES),
50, TimeUnit.DAYS);
check(Duration.createWithSuitableUnit(1000L * 60 * 60 * 24 * 7 + 3000, TimeUnit.MILLISECONDS),
7, TimeUnit.DAYS);
check(Duration.createWithSuitableUnit(1000L * 60 * 60 * 3 + 1400, TimeUnit.MILLISECONDS),
3, TimeUnit.HOURS);
check(Duration.createWithSuitableUnit(248, TimeUnit.SECONDS),
4, TimeUnit.MINUTES);
}
private static void check(Duration duration, long expectedDuration, TimeUnit expectedUnit) {
assertThat(duration.getTimeUnit(), equalTo(expectedUnit));
assertThat(duration.getDuration(), equalTo(expectedDuration));
}
}

View File

@ -1,4 +1,4 @@
package fr.xephi.authme.util;
package fr.xephi.authme.util.expiring;
import org.junit.Test;

View File

@ -1,9 +1,10 @@
package fr.xephi.authme.util;
package fr.xephi.authme.util.expiring;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@ -88,4 +89,34 @@ public class ExpiringSetTest {
assertThat(set.contains(3), equalTo(false));
assertThat(set.contains(6), equalTo(true));
}
@Test
public void shouldReturnExpiration() {
// given
ExpiringSet<String> set = new ExpiringSet<>(123, TimeUnit.MINUTES);
set.add("my entry");
// when
long expiresInHours = set.getExpiration("my entry", TimeUnit.HOURS);
long expiresInMinutes = set.getExpiration("my entry", TimeUnit.MINUTES);
long unknownExpires = set.getExpiration("bogus", TimeUnit.SECONDS);
// then
assertThat(expiresInHours, equalTo(2L));
assertThat(expiresInMinutes, either(equalTo(122L)).or(equalTo(123L)));
assertThat(unknownExpires, equalTo(-1L));
}
@Test
public void shouldReturnMinusOneForExpiredEntry() {
// given
ExpiringSet<Integer> set = new ExpiringSet<>(-100, TimeUnit.SECONDS);
set.add(23);
// when
long expiresInSeconds = set.getExpiration(23, TimeUnit.SECONDS);
// then
assertThat(expiresInSeconds, equalTo(-1L));
}
}

View File

@ -1,4 +1,4 @@
package fr.xephi.authme.util;
package fr.xephi.authme.util.expiring;
import org.junit.Test;