apply new code formatter and save action
This commit is contained in:
@@ -15,144 +15,143 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CollectionUtils {
|
||||
|
||||
public interface Compare<T> {
|
||||
|
||||
public boolean test(T valueA);
|
||||
public interface Compare<T> {
|
||||
|
||||
public static <T, V> Compare<T> compare(Function<? super T, ? extends V> keyExtractor, V value) {
|
||||
Objects.requireNonNull(keyExtractor);
|
||||
return t -> Objects.equals(keyExtractor.apply(t), value);
|
||||
}
|
||||
public boolean test(T valueA);
|
||||
|
||||
default Compare<T> thenCompare(Compare<? super T> other) {
|
||||
Objects.requireNonNull(other);
|
||||
return t -> {
|
||||
final boolean res = test(t);
|
||||
return res ? other.test(t) : false;
|
||||
};
|
||||
}
|
||||
|
||||
default <V> Compare<T> thenCompare(Function<T, ? extends V> keyExtractor, V value) {
|
||||
return thenCompare(compare(keyExtractor, value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static <T> List<T> copySort(Collection<? extends T> collection, Comparator<T> comparator){
|
||||
final List<T> result = new ArrayList<T>(collection);
|
||||
Collections.sort(result, comparator);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, R extends T> void mapInPlace(final List<T> list, final Function<T, R> mapper) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
final T value = list.get(i);
|
||||
final T newValue = mapper.apply(value);
|
||||
list.set(i, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T, R> List<R> map(final Collection<T> list, final Function<T, R> mapper) {
|
||||
final List<R> result = new ArrayList<>(list.size());
|
||||
|
||||
for (final T t : list) {
|
||||
result.add(mapper.apply(t));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, R> List<R> map(final T[] input, final Function<T, R> mapper) {
|
||||
return Stream.of(input).map(mapper).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <O extends Collection<R>, T, R> O map(final Collection<T> input, final O result,
|
||||
final Function<T, R> mapper) {
|
||||
|
||||
for (final T t : input) {
|
||||
final R e = mapper.apply(t);
|
||||
result.add(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, V> Map<T, V> createMapFromValues(final Iterable<V> iterable, final Function<V, T> keyMapper) {
|
||||
final Map<T, V> result = new HashMap<>();
|
||||
|
||||
for (final V value : iterable) {
|
||||
final T key = keyMapper.apply(value);
|
||||
|
||||
result.put(key, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <KEY, VALUE> Map<KEY, VALUE> createMapFromKeys(final Iterable<KEY> iterable,
|
||||
final Function<KEY, VALUE> valueMapper) {
|
||||
final Map<KEY, VALUE> result = new HashMap<>();
|
||||
|
||||
for (final KEY key : iterable) {
|
||||
final VALUE value = valueMapper.apply(key);
|
||||
|
||||
result.put(key, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> filter(final Collection<T> collection, final Predicate<T> predicate) {
|
||||
return collection.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T> int indexOf(final List<T> list, final Predicate<T> predicate) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (predicate.test(list.get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static <T> boolean contains(Collection<T> collection, final Compare<T> compare) {
|
||||
for (T t : collection) {
|
||||
boolean found = compare.test(t);
|
||||
if (found ) {
|
||||
return true;
|
||||
public static <T, V> Compare<T> compare(Function<? super T, ? extends V> keyExtractor, V value) {
|
||||
Objects.requireNonNull(keyExtractor);
|
||||
return t -> Objects.equals(keyExtractor.apply(t), value);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T> long count(Collection<T> collection, final Compare<T> compare) {
|
||||
long count = 0;
|
||||
for (T t : collection) {
|
||||
boolean found = compare.test(t);
|
||||
if (found ) {
|
||||
count++;
|
||||
|
||||
default Compare<T> thenCompare(Compare<? super T> other) {
|
||||
Objects.requireNonNull(other);
|
||||
return t -> {
|
||||
final boolean res = test(t);
|
||||
return res ? other.test(t) : false;
|
||||
};
|
||||
}
|
||||
|
||||
default <V> Compare<T> thenCompare(Function<T, ? extends V> keyExtractor, V value) {
|
||||
return thenCompare(compare(keyExtractor, value));
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static <V, T extends Collection<V>> T removeAll(final T collection, final T remove,
|
||||
final Supplier<T> generator) {
|
||||
public static <T> List<T> copySort(Collection<? extends T> collection, Comparator<T> comparator) {
|
||||
final List<T> result = new ArrayList<T>(collection);
|
||||
Collections.sort(result, comparator);
|
||||
return result;
|
||||
}
|
||||
|
||||
final T result = generator.get();
|
||||
result.addAll(collection);
|
||||
result.removeAll(remove);
|
||||
return result;
|
||||
}
|
||||
public static <T, R extends T> void mapInPlace(final List<T> list, final Function<T, R> mapper) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
final T value = list.get(i);
|
||||
final T newValue = mapper.apply(value);
|
||||
list.set(i, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static <V, T extends Collection<V>> T retainAll(final T collection, final T retain,
|
||||
final Supplier<T> generator) {
|
||||
public static <T, R> List<R> map(final Collection<T> list, final Function<T, R> mapper) {
|
||||
final List<R> result = new ArrayList<>(list.size());
|
||||
|
||||
final T result = generator.get();
|
||||
result.addAll(collection);
|
||||
result.retainAll(retain);
|
||||
return result;
|
||||
}
|
||||
for (final T t : list) {
|
||||
result.add(mapper.apply(t));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, R> List<R> map(final T[] input, final Function<T, R> mapper) {
|
||||
return Stream.of(input).map(mapper).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <O extends Collection<R>, T, R> O map(final Collection<T> input, final O result,
|
||||
final Function<T, R> mapper) {
|
||||
|
||||
for (final T t : input) {
|
||||
final R e = mapper.apply(t);
|
||||
result.add(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T, V> Map<T, V> createMapFromValues(final Iterable<V> iterable, final Function<V, T> keyMapper) {
|
||||
final Map<T, V> result = new HashMap<>();
|
||||
|
||||
for (final V value : iterable) {
|
||||
final T key = keyMapper.apply(value);
|
||||
|
||||
result.put(key, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <KEY, VALUE> Map<KEY, VALUE> createMapFromKeys(final Iterable<KEY> iterable,
|
||||
final Function<KEY, VALUE> valueMapper) {
|
||||
final Map<KEY, VALUE> result = new HashMap<>();
|
||||
|
||||
for (final KEY key : iterable) {
|
||||
final VALUE value = valueMapper.apply(key);
|
||||
|
||||
result.put(key, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> filter(final Collection<T> collection, final Predicate<T> predicate) {
|
||||
return collection.stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static <T> int indexOf(final List<T> list, final Predicate<T> predicate) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (predicate.test(list.get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static <T> boolean contains(Collection<T> collection, final Compare<T> compare) {
|
||||
for (T t : collection) {
|
||||
boolean found = compare.test(t);
|
||||
if (found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T> long count(Collection<T> collection, final Compare<T> compare) {
|
||||
long count = 0;
|
||||
for (T t : collection) {
|
||||
boolean found = compare.test(t);
|
||||
if (found) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static <V, T extends Collection<V>> T removeAll(final T collection, final T remove,
|
||||
final Supplier<T> generator) {
|
||||
|
||||
final T result = generator.get();
|
||||
result.addAll(collection);
|
||||
result.removeAll(remove);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <V, T extends Collection<V>> T retainAll(final T collection, final T retain,
|
||||
final Supplier<T> generator) {
|
||||
|
||||
final T result = generator.get();
|
||||
result.addAll(collection);
|
||||
result.retainAll(retain);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import java.time.ZoneOffset;
|
||||
|
||||
public class DateUtils {
|
||||
|
||||
public static OffsetDateTime getDate(final int year, final int month, final int day, final int hour,
|
||||
final int minute, final int second) {
|
||||
public static OffsetDateTime getDate(final int year, final int month, final int day, final int hour,
|
||||
final int minute, final int second) {
|
||||
|
||||
final OffsetDateTime result = OffsetDateTime.of(year, month, day, hour, minute, second, 0, ZoneOffset.UTC);
|
||||
return result;
|
||||
}
|
||||
final OffsetDateTime result = OffsetDateTime.of(year, month, day, hour, minute, second, 0, ZoneOffset.UTC);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static OffsetDateTime nowInUtc() {
|
||||
return OffsetDateTime.now(ZoneOffset.UTC);
|
||||
}
|
||||
public static OffsetDateTime nowInUtc() {
|
||||
return OffsetDateTime.now(ZoneOffset.UTC);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,98 +4,98 @@ import java.text.MessageFormat;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Preconditions {
|
||||
public static void checkEven(final long value, final String message) {
|
||||
if (value % 2 != 0) {
|
||||
throw new IllegalStateException(message + ". Was: " + value);
|
||||
}
|
||||
}
|
||||
public static void checkEven(final long value, final String message) {
|
||||
if (value % 2 != 0) {
|
||||
throw new IllegalStateException(message + ". Was: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args
|
||||
*/
|
||||
public static void checkGreater(final long a, final long b, final String message, final Object... args) {
|
||||
/**
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args
|
||||
*/
|
||||
public static void checkGreater(final long a, final long b, final String message, final Object... args) {
|
||||
|
||||
if (a <= b) {
|
||||
throw new IllegalStateException(MessageFormat.format(message, args) + " Expected: " + a + " > " + b);
|
||||
}
|
||||
}
|
||||
if (a <= b) {
|
||||
throw new IllegalStateException(MessageFormat.format(message, args) + " Expected: " + a + " > " + b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args
|
||||
* @throws IllegalStateException if {@code a} is not greater or equal to
|
||||
* {@code b}
|
||||
*/
|
||||
public static void checkGreaterOrEqual(final long a, final long b, final String message, final Object... args) {
|
||||
if (a < b) {
|
||||
throw new IllegalStateException(MessageFormat.format(message, args) + " Expected: " + a + " >= " + b);
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param a
|
||||
* @param b
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args
|
||||
* @throws IllegalStateException if {@code a} is not greater or equal to
|
||||
* {@code b}
|
||||
*/
|
||||
public static void checkGreaterOrEqual(final long a, final long b, final String message, final Object... args) {
|
||||
if (a < b) {
|
||||
throw new IllegalStateException(MessageFormat.format(message, args) + " Expected: " + a + " >= " + b);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkSmaller(final long a, final long b, final String message, final Object... args) {
|
||||
if (a >= b) {
|
||||
throw new IllegalStateException(MessageFormat.format(message, args) + " Expected: " + a + " < " + b);
|
||||
}
|
||||
}
|
||||
public static void checkSmaller(final long a, final long b, final String message, final Object... args) {
|
||||
if (a >= b) {
|
||||
throw new IllegalStateException(MessageFormat.format(message, args) + " Expected: " + a + " < " + b);
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkEqual(final Object actual, final Object expected) {
|
||||
checkEqual(actual, expected, "expected {0} is equal to {1}", actual, expected);
|
||||
}
|
||||
public static void checkEqual(final Object actual, final Object expected) {
|
||||
checkEqual(actual, expected, "expected {0} is equal to {1}", actual, expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given values are equal. The check is done with
|
||||
* {@link Objects#equals(Object, Object)}
|
||||
*
|
||||
* @param actual the actual value
|
||||
* @param expected the expected value
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args arguments for the message
|
||||
* @throws IllegalStateException if {@code actual} is not equal to
|
||||
* {@code expected}
|
||||
*/
|
||||
public static void checkEqual(final Object actual, final Object expected, final String message,
|
||||
final Object... args) {
|
||||
if (!Objects.equals(actual, expected)) {
|
||||
throw new IllegalStateException(
|
||||
MessageFormat.format(message, args) + " Expected: " + actual + " equals " + expected);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check that the given values are equal. The check is done with
|
||||
* {@link Objects#equals(Object, Object)}
|
||||
*
|
||||
* @param actual the actual value
|
||||
* @param expected the expected value
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args arguments for the message
|
||||
* @throws IllegalStateException if {@code actual} is not equal to
|
||||
* {@code expected}
|
||||
*/
|
||||
public static void checkEqual(final Object actual, final Object expected, final String message,
|
||||
final Object... args) {
|
||||
if (!Objects.equals(actual, expected)) {
|
||||
throw new IllegalStateException(
|
||||
MessageFormat.format(message, args) + " Expected: " + actual + " equals " + expected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given value is true.
|
||||
*
|
||||
* @param actual must be true
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args arguments for the message
|
||||
* @throws IllegalStateException if {@code actual} is not true
|
||||
*/
|
||||
public static void checkTrue(final boolean actual, final String message, final Object... args) {
|
||||
checkEqual(actual, true, message, args);
|
||||
}
|
||||
/**
|
||||
* Check that the given value is true.
|
||||
*
|
||||
* @param actual must be true
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args arguments for the message
|
||||
* @throws IllegalStateException if {@code actual} is not true
|
||||
*/
|
||||
public static void checkTrue(final boolean actual, final String message, final Object... args) {
|
||||
checkEqual(actual, true, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given value is false.
|
||||
*
|
||||
* @param actual must be false
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args arguments for the message
|
||||
* @throws IllegalStateException if {@code actual} is not false
|
||||
*/
|
||||
public static void checkFalse(final boolean actual, final String message, final Object... args) {
|
||||
checkEqual(actual, false, message, args);
|
||||
}
|
||||
/**
|
||||
* Check that the given value is false.
|
||||
*
|
||||
* @param actual must be false
|
||||
* @param message formatted with {@link MessageFormat}
|
||||
* @param args arguments for the message
|
||||
* @throws IllegalStateException if {@code actual} is not false
|
||||
*/
|
||||
public static void checkFalse(final boolean actual, final String message, final Object... args) {
|
||||
checkEqual(actual, false, message, args);
|
||||
}
|
||||
|
||||
public static void checkNull(final Object actual, final String message, final Object... args) {
|
||||
if (actual != null) {
|
||||
throw new IllegalStateException(MessageFormat.format(message, args));
|
||||
}
|
||||
}
|
||||
public static void checkNull(final Object actual, final String message, final Object... args) {
|
||||
if (actual != null) {
|
||||
throw new IllegalStateException(MessageFormat.format(message, args));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,32 +4,32 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LRUCache<K, V> {
|
||||
private final LinkedHashMap<K, V> cache;
|
||||
private final LinkedHashMap<K, V> cache;
|
||||
|
||||
public LRUCache(final int maxEntries) {
|
||||
this.cache = new LinkedHashMap<>(16, 0.75f, true) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public LRUCache(final int maxEntries) {
|
||||
this.cache = new LinkedHashMap<>(16, 0.75f, true) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
|
||||
return size() > maxEntries;
|
||||
}
|
||||
};
|
||||
}
|
||||
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
|
||||
return size() > maxEntries;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public V put(final K key, final V value) {
|
||||
return cache.put(key, value);
|
||||
}
|
||||
public V put(final K key, final V value) {
|
||||
return cache.put(key, value);
|
||||
}
|
||||
|
||||
public V get(final K key) {
|
||||
return cache.get(key);
|
||||
}
|
||||
public V get(final K key) {
|
||||
return cache.get(key);
|
||||
}
|
||||
|
||||
public V remove(final K key) {
|
||||
return cache.remove(key);
|
||||
}
|
||||
public V remove(final K key) {
|
||||
return cache.remove(key);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return cache.size();
|
||||
}
|
||||
public int size() {
|
||||
return cache.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class RuntimeExcecutionException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -3626851728980513527L;
|
||||
private static final long serialVersionUID = -3626851728980513527L;
|
||||
|
||||
public RuntimeExcecutionException(final ExecutionException e) {
|
||||
super(e);
|
||||
}
|
||||
public RuntimeExcecutionException(final ExecutionException e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,380 +26,380 @@ import org.testng.annotations.Test;
|
||||
@Test
|
||||
public class HotEntryCacheTest {
|
||||
|
||||
static {
|
||||
Configurator.setRootLevel(Level.TRACE);
|
||||
}
|
||||
static {
|
||||
Configurator.setRootLevel(Level.TRACE);
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HotEntryCacheTest.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HotEntryCacheTest.class);
|
||||
|
||||
private int cacheId = 0;
|
||||
private int cacheId = 0;
|
||||
|
||||
@Test(invocationCount = 1)
|
||||
public void testRemovalListenerCalledOnExpire() throws InterruptedException {
|
||||
@Test(invocationCount = 1)
|
||||
public void testRemovalListenerCalledOnExpire() throws InterruptedException {
|
||||
|
||||
LOGGER.info("");
|
||||
LOGGER.info("");
|
||||
LOGGER.info("start: testRemovalListenerCalledOnExpire");
|
||||
LOGGER.info("");
|
||||
LOGGER.info("");
|
||||
LOGGER.info("start: testRemovalListenerCalledOnExpire");
|
||||
|
||||
final long originalMinSleepPeriod = HotEntryCache.getMinSleepPeriod();
|
||||
final long originalMaxSleepPeriod = HotEntryCache.getMaxSleepPeriod();
|
||||
try {
|
||||
final String key = "key";
|
||||
final String value = "value";
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final long originalMinSleepPeriod = HotEntryCache.getMinSleepPeriod();
|
||||
final long originalMaxSleepPeriod = HotEntryCache.getMaxSleepPeriod();
|
||||
try {
|
||||
final String key = "key";
|
||||
final String value = "value";
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofMillis(1), 10,
|
||||
"cache-" + ++cacheId);
|
||||
HotEntryCache.setMinSleepPeriod(1);
|
||||
HotEntryCache.setMaxSleepPeriod(2);
|
||||
cache.addListener((k, v) -> {
|
||||
Assert.assertEquals(k, key);
|
||||
Assert.assertEquals(v, value);
|
||||
latch.countDown();
|
||||
});
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofMillis(1), 10,
|
||||
"cache-" + ++cacheId);
|
||||
HotEntryCache.setMinSleepPeriod(1);
|
||||
HotEntryCache.setMaxSleepPeriod(2);
|
||||
cache.addListener((k, v) -> {
|
||||
Assert.assertEquals(k, key);
|
||||
Assert.assertEquals(v, value);
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
cache.put(key, value);
|
||||
final boolean listenerCalled = latch.await(100, TimeUnit.MILLISECONDS);
|
||||
Assert.assertTrue(listenerCalled, "removal listener called");
|
||||
} finally {
|
||||
HotEntryCache.setMinSleepPeriod(originalMinSleepPeriod);
|
||||
HotEntryCache.setMaxSleepPeriod(originalMaxSleepPeriod);
|
||||
}
|
||||
}
|
||||
cache.put(key, value);
|
||||
final boolean listenerCalled = latch.await(100, TimeUnit.MILLISECONDS);
|
||||
Assert.assertTrue(listenerCalled, "removal listener called");
|
||||
} finally {
|
||||
HotEntryCache.setMinSleepPeriod(originalMinSleepPeriod);
|
||||
HotEntryCache.setMaxSleepPeriod(originalMaxSleepPeriod);
|
||||
}
|
||||
}
|
||||
|
||||
public void testPutAndGet() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
public void testPutAndGet() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final String replacedNull = cache.put("key", "value1");
|
||||
Assert.assertEquals(replacedNull, null);
|
||||
final String replacedNull = cache.put("key", "value1");
|
||||
Assert.assertEquals(replacedNull, null);
|
||||
|
||||
final String cachedValue1 = cache.get("key");
|
||||
Assert.assertEquals(cachedValue1, "value1");
|
||||
final String cachedValue1 = cache.get("key");
|
||||
Assert.assertEquals(cachedValue1, "value1");
|
||||
|
||||
final String replacedValue1 = cache.put("key", "value2");
|
||||
Assert.assertEquals(replacedValue1, "value1");
|
||||
final String replacedValue1 = cache.put("key", "value2");
|
||||
Assert.assertEquals(replacedValue1, "value1");
|
||||
|
||||
final String cachedValue2 = cache.get("key");
|
||||
Assert.assertEquals(cachedValue2, "value2");
|
||||
}
|
||||
final String cachedValue2 = cache.get("key");
|
||||
Assert.assertEquals(cachedValue2, "value2");
|
||||
}
|
||||
|
||||
public void testPutTouches() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final Duration timeToLive = Duration.ofSeconds(10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
|
||||
public void testPutTouches() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final Duration timeToLive = Duration.ofSeconds(10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
|
||||
|
||||
cache.put("key", "value1");
|
||||
cache.put("key", "value1");
|
||||
|
||||
clock.plusSeconds(2);
|
||||
cache.updateTime();
|
||||
clock.plusSeconds(2);
|
||||
cache.updateTime();
|
||||
|
||||
cache.put("key", "value2");
|
||||
cache.put("key", "value2");
|
||||
|
||||
clock.plus(timeToLive.minusSeconds(1));
|
||||
cache.triggerEvictionAndWait();
|
||||
// at this point the entry would have been evicted it it was not touched by the
|
||||
// second put.
|
||||
clock.plus(timeToLive.minusSeconds(1));
|
||||
cache.triggerEvictionAndWait();
|
||||
// at this point the entry would have been evicted it it was not touched by the
|
||||
// second put.
|
||||
|
||||
final String cachedValue2 = cache.get("key");
|
||||
Assert.assertEquals(cachedValue2, "value2");
|
||||
final String cachedValue2 = cache.get("key");
|
||||
Assert.assertEquals(cachedValue2, "value2");
|
||||
|
||||
clock.plus(timeToLive.plusSeconds(1));
|
||||
// time elapsed since the last put: timeToLive +1s
|
||||
cache.triggerEvictionAndWait();
|
||||
clock.plus(timeToLive.plusSeconds(1));
|
||||
// time elapsed since the last put: timeToLive +1s
|
||||
cache.triggerEvictionAndWait();
|
||||
|
||||
final String cachedValue1_evicted = cache.get("key");
|
||||
Assert.assertEquals(cachedValue1_evicted, null);
|
||||
}
|
||||
final String cachedValue1_evicted = cache.get("key");
|
||||
Assert.assertEquals(cachedValue1_evicted, null);
|
||||
}
|
||||
|
||||
public void testGetTouches() throws Exception {
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final Duration timeToLive = Duration.ofSeconds(10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
|
||||
public void testGetTouches() throws Exception {
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final Duration timeToLive = Duration.ofSeconds(10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
|
||||
|
||||
cache.put("key", "value1");
|
||||
cache.put("key", "value1");
|
||||
|
||||
// skip forward in time, but do not yet trigger eviction
|
||||
clock.plus(timeToLive.plusMillis(1));
|
||||
cache.updateTime();
|
||||
// skip forward in time, but do not yet trigger eviction
|
||||
clock.plus(timeToLive.plusMillis(1));
|
||||
cache.updateTime();
|
||||
|
||||
cache.get("key"); // will touch the entry
|
||||
cache.get("key"); // will touch the entry
|
||||
|
||||
cache.triggerEvictionAndWait(); // if get didn't touch, then this will evict the entry
|
||||
cache.triggerEvictionAndWait(); // if get didn't touch, then this will evict the entry
|
||||
|
||||
final String cachedValue1 = cache.get("key");
|
||||
Assert.assertEquals(cachedValue1, "value1");
|
||||
}
|
||||
final String cachedValue1 = cache.get("key");
|
||||
Assert.assertEquals(cachedValue1, "value1");
|
||||
}
|
||||
|
||||
public void testEvictionByBackgroundThread() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final Duration timeToLive = Duration.ofSeconds(10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
|
||||
public void testEvictionByBackgroundThread() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final Duration timeToLive = Duration.ofSeconds(10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
|
||||
|
||||
final CompletableFuture<String> evictionEventFuture = new CompletableFuture<>();
|
||||
cache.addListener((key, value) -> {
|
||||
evictionEventFuture.complete(value);
|
||||
});
|
||||
final CompletableFuture<String> evictionEventFuture = new CompletableFuture<>();
|
||||
cache.addListener((key, value) -> {
|
||||
evictionEventFuture.complete(value);
|
||||
});
|
||||
|
||||
cache.put("key", "value1");
|
||||
cache.put("key", "value1");
|
||||
|
||||
clock.plus(timeToLive.minusSeconds(1));
|
||||
cache.updateTime();
|
||||
clock.plus(timeToLive.minusSeconds(1));
|
||||
cache.updateTime();
|
||||
|
||||
cache.put("key2", "value2");
|
||||
clock.plus(Duration.ofSeconds(1).plusMillis(1));
|
||||
cache.triggerEvictionAndWait();
|
||||
cache.put("key2", "value2");
|
||||
clock.plus(Duration.ofSeconds(1).plusMillis(1));
|
||||
cache.triggerEvictionAndWait();
|
||||
|
||||
final String evictedValue1 = evictionEventFuture.get(5, TimeUnit.MINUTES); // enough time for debugging
|
||||
Assert.assertEquals(evictedValue1, "value1");
|
||||
}
|
||||
final String evictedValue1 = evictionEventFuture.get(5, TimeUnit.MINUTES); // enough time for debugging
|
||||
Assert.assertEquals(evictedValue1, "value1");
|
||||
}
|
||||
|
||||
public void testRemove() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
LOGGER.info("");
|
||||
LOGGER.info("");
|
||||
LOGGER.info("start: testRemove");
|
||||
public void testRemove() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
LOGGER.info("");
|
||||
LOGGER.info("");
|
||||
LOGGER.info("start: testRemove");
|
||||
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final List<String> removedValues = new ArrayList<>();
|
||||
cache.addListener((key, value) -> removedValues.add(value));
|
||||
final List<String> removedValues = new ArrayList<>();
|
||||
cache.addListener((key, value) -> removedValues.add(value));
|
||||
|
||||
cache.put("key", "value1");
|
||||
cache.put("key", "value1");
|
||||
|
||||
final String removedValue = cache.remove("key");
|
||||
Assert.assertEquals(removedValue, "value1");
|
||||
final String removedValue = cache.remove("key");
|
||||
Assert.assertEquals(removedValue, "value1");
|
||||
|
||||
Assert.assertEquals(removedValues, Arrays.asList("value1"));
|
||||
Assert.assertEquals(removedValues, Arrays.asList("value1"));
|
||||
|
||||
Assert.assertEquals(cache.get("key"), null);
|
||||
}
|
||||
Assert.assertEquals(cache.get("key"), null);
|
||||
}
|
||||
|
||||
public void testClear() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
public void testClear() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final List<String> removedValues = new ArrayList<>();
|
||||
cache.addListener((key, value) -> removedValues.add(value));
|
||||
final List<String> removedValues = new ArrayList<>();
|
||||
cache.addListener((key, value) -> removedValues.add(value));
|
||||
|
||||
cache.put("key1", "value1");
|
||||
cache.put("key2", "value2");
|
||||
cache.put("key1", "value1");
|
||||
cache.put("key2", "value2");
|
||||
|
||||
cache.clear();
|
||||
cache.clear();
|
||||
|
||||
Assert.assertEquals(cache.get("key1"), null);
|
||||
Assert.assertEquals(cache.get("key2"), null);
|
||||
Assert.assertEquals(cache.get("key1"), null);
|
||||
Assert.assertEquals(cache.get("key2"), null);
|
||||
|
||||
Assert.assertEquals(removedValues, Arrays.asList("value1", "value2"));
|
||||
}
|
||||
Assert.assertEquals(removedValues, Arrays.asList("value1", "value2"));
|
||||
}
|
||||
|
||||
public void testForEachTouches() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final Duration timeToLive = Duration.ofSeconds(10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
|
||||
public void testForEachTouches() throws InterruptedException, ExecutionException, TimeoutException {
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final Duration timeToLive = Duration.ofSeconds(10);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10, clock);
|
||||
|
||||
final CompletableFuture<String> evictionEventFuture = new CompletableFuture<>();
|
||||
cache.addListener((key, value) -> {
|
||||
evictionEventFuture.complete(value);
|
||||
});
|
||||
final CompletableFuture<String> evictionEventFuture = new CompletableFuture<>();
|
||||
cache.addListener((key, value) -> {
|
||||
evictionEventFuture.complete(value);
|
||||
});
|
||||
|
||||
// add value
|
||||
cache.put("key", "value1");
|
||||
// add value
|
||||
cache.put("key", "value1");
|
||||
|
||||
// seek, so that it is almost evicted
|
||||
clock.plus(timeToLive.minusMillis(1));
|
||||
cache.updateTime();
|
||||
// seek, so that it is almost evicted
|
||||
clock.plus(timeToLive.minusMillis(1));
|
||||
cache.updateTime();
|
||||
|
||||
// the for each should touch the entries
|
||||
cache.forEach(s -> {
|
||||
/* no-op */});
|
||||
// the for each should touch the entries
|
||||
cache.forEach(s -> {
|
||||
/* no-op */});
|
||||
|
||||
// seek again
|
||||
clock.plus(timeToLive.minusMillis(1));
|
||||
cache.triggerEvictionAndWait();
|
||||
// seek again
|
||||
clock.plus(timeToLive.minusMillis(1));
|
||||
cache.triggerEvictionAndWait();
|
||||
|
||||
// if the touch didn't happen, then the value is now evicted
|
||||
Assert.assertEquals(evictionEventFuture.isDone(), false);
|
||||
// if the touch didn't happen, then the value is now evicted
|
||||
Assert.assertEquals(evictionEventFuture.isDone(), false);
|
||||
|
||||
// seek again, so that the entry will get evicted
|
||||
clock.plus(timeToLive.minusMillis(1));
|
||||
cache.triggerEvictionAndWait();
|
||||
// seek again, so that the entry will get evicted
|
||||
clock.plus(timeToLive.minusMillis(1));
|
||||
cache.triggerEvictionAndWait();
|
||||
|
||||
Assert.assertEquals(cache.get("key"), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that
|
||||
* {@link HotEntryCache#putIfAbsent(Object, java.util.function.Function)
|
||||
* putIfAbsent} is atomic by calling
|
||||
* {@link HotEntryCache#putIfAbsent(Object, java.util.function.Function)
|
||||
* putIfAbsent} in two threads and asserting that the supplier was only called
|
||||
* once.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testPutIfAbsentIsAtomic() throws Exception {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final ExecutorService pool = Executors.newCachedThreadPool();
|
||||
try {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
final String key = "key";
|
||||
final String valueA = "A";
|
||||
final String valueB = "B";
|
||||
|
||||
pool.submit(() -> {
|
||||
cache.putIfAbsent(key, k -> {
|
||||
latch.countDown();
|
||||
sleep(TimeUnit.MILLISECONDS, 20);
|
||||
return valueA;
|
||||
});
|
||||
return null;
|
||||
});
|
||||
pool.submit(() -> {
|
||||
waitFor(latch);
|
||||
cache.putIfAbsent(key, k -> valueB);
|
||||
return null;
|
||||
});
|
||||
|
||||
pool.shutdown();
|
||||
pool.awaitTermination(1, TimeUnit.MINUTES);
|
||||
|
||||
final String actual = cache.get(key);
|
||||
Assert.assertEquals(actual, valueA);
|
||||
} finally {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
public void testPutIfAbsentReturnsExistingValue() throws Exception {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final String key = "key";
|
||||
final String valueA = "A";
|
||||
final String valueB = "B";
|
||||
|
||||
cache.put(key, valueA);
|
||||
|
||||
final String returnedByPutIfAbsent = cache.putIfAbsent(key, k -> valueB);
|
||||
Assert.assertEquals(returnedByPutIfAbsent, valueA);
|
||||
|
||||
final String actualInCache = cache.get(key);
|
||||
Assert.assertEquals(actualInCache, valueA);
|
||||
}
|
||||
|
||||
public void testPutIfAbsentDoesNotAddNull() throws Exception {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final String key = "key";
|
||||
final String returnedByPutIfAbsent = cache.putIfAbsent(key, k -> null);
|
||||
Assert.assertNull(returnedByPutIfAbsent, null);
|
||||
|
||||
final String actualInCache = cache.get(key);
|
||||
Assert.assertEquals(actualInCache, null);
|
||||
}
|
||||
|
||||
public void testMaxSizeIsRespected() {
|
||||
final int maxSize = 10;
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofHours(1), maxSize, clock);
|
||||
final Set<String> removedKeys = new LinkedHashSet<>();
|
||||
cache.addListener((key, value) -> removedKeys.add(key));
|
||||
|
||||
// fill the cache
|
||||
int count = 0;
|
||||
for (count = 0; count < maxSize; count++) {
|
||||
|
||||
cache.put("key" + count, "value" + count);
|
||||
clock.plus(2L, ChronoUnit.MILLIS);
|
||||
cache.updateTime();
|
||||
}
|
||||
Assert.assertEquals(cache.size(), maxSize, "cache is full");
|
||||
Assert.assertEquals(removedKeys, List.of(), "removed keys at point A");
|
||||
|
||||
// add an item to a full cache -> the oldest 20% of the entries will be evicted
|
||||
// before the new entry is added
|
||||
cache.put("key" + count, "value" + count);
|
||||
clock.plus(2L, ChronoUnit.MILLIS);
|
||||
cache.updateTime();
|
||||
count++;
|
||||
|
||||
Assert.assertEquals(cache.size(), maxSize - 1, "cache was full, 20% (2 items) were removed and one added");
|
||||
Assert.assertEquals(removedKeys, Set.of("key0", "key1"), "removed keys at point B");
|
||||
}
|
||||
|
||||
public void testEvictionDueToSizeLimitDoesNotRemoveMoreThan20Percent() {
|
||||
final int maxSize = 10;
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofHours(1), maxSize, clock);
|
||||
final Set<String> removedKeys = new LinkedHashSet<>();
|
||||
cache.addListener((key, value) -> removedKeys.add(key));
|
||||
|
||||
// fill the cache
|
||||
int count = 0;
|
||||
for (count = 0; count < maxSize; count++) {
|
||||
// all entries get the same eviction time due to the fixed clock
|
||||
cache.put("key" + count, "value" + count);
|
||||
}
|
||||
Assert.assertEquals(cache.size(), maxSize, "cache is full");
|
||||
Assert.assertEquals(removedKeys, List.of(), "removed keys at point A");
|
||||
|
||||
// add an item to a full cache -> the oldest 20% of the entries will be evicted
|
||||
// before the new entry is added
|
||||
cache.put("key" + count, "value" + count);
|
||||
count++;
|
||||
|
||||
Assert.assertEquals(cache.size(), maxSize - 1, "cache was full, 20% (2 items) were removed and one added");
|
||||
Assert.assertEquals(removedKeys.size(), (int) (maxSize * 0.2), "number of removed keys at point B");
|
||||
}
|
||||
|
||||
private void sleep(final TimeUnit timeUnit, final long timeout) {
|
||||
try {
|
||||
timeUnit.sleep(timeout);
|
||||
} catch (final InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitFor(final CountDownLatch latch) {
|
||||
try {
|
||||
latch.await(1, TimeUnit.MINUTES);
|
||||
} catch (final InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(final String[] args) throws InterruptedException {
|
||||
|
||||
Configurator.setRootLevel(Level.TRACE);
|
||||
|
||||
final Duration timeToLive = Duration.ofSeconds(1);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10);
|
||||
|
||||
cache.addListener((key, value) -> {
|
||||
System.out.println(Instant.now() + " evicting: " + key + " -> " + value);
|
||||
});
|
||||
cache.put("key", "value that is touched");
|
||||
for (int i = 0; i < 20; i++) {
|
||||
|
||||
System.out.println(Instant.now() + " putting value" + i);
|
||||
cache.put("key" + i, "value" + i);
|
||||
cache.put("key", "value that is touched" + i);
|
||||
TimeUnit.MILLISECONDS.sleep(450);
|
||||
}
|
||||
|
||||
for (int i = 20; i < 23; i++) {
|
||||
System.out.println(Instant.now() + " putting value" + i);
|
||||
cache.put("key" + i, "value" + i);
|
||||
TimeUnit.MILLISECONDS.sleep(Duration.ofSeconds(5).plusMillis(10).toMillis());
|
||||
}
|
||||
|
||||
TimeUnit.MILLISECONDS.sleep(Duration.ofSeconds(5).plusMillis(10).toMillis());
|
||||
|
||||
for (int i = 23; i < 27; i++) {
|
||||
System.out.println(Instant.now() + " putting value" + i);
|
||||
cache.put("key" + i, "value" + i);
|
||||
TimeUnit.MILLISECONDS.sleep(Duration.ofSeconds(5).plusMillis(10).toMillis());
|
||||
}
|
||||
|
||||
TimeUnit.SECONDS.sleep(300);
|
||||
}
|
||||
Assert.assertEquals(cache.get("key"), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that
|
||||
* {@link HotEntryCache#putIfAbsent(Object, java.util.function.Function)
|
||||
* putIfAbsent} is atomic by calling
|
||||
* {@link HotEntryCache#putIfAbsent(Object, java.util.function.Function)
|
||||
* putIfAbsent} in two threads and asserting that the supplier was only called
|
||||
* once.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testPutIfAbsentIsAtomic() throws Exception {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final ExecutorService pool = Executors.newCachedThreadPool();
|
||||
try {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
final String key = "key";
|
||||
final String valueA = "A";
|
||||
final String valueB = "B";
|
||||
|
||||
pool.submit(() -> {
|
||||
cache.putIfAbsent(key, k -> {
|
||||
latch.countDown();
|
||||
sleep(TimeUnit.MILLISECONDS, 20);
|
||||
return valueA;
|
||||
});
|
||||
return null;
|
||||
});
|
||||
pool.submit(() -> {
|
||||
waitFor(latch);
|
||||
cache.putIfAbsent(key, k -> valueB);
|
||||
return null;
|
||||
});
|
||||
|
||||
pool.shutdown();
|
||||
pool.awaitTermination(1, TimeUnit.MINUTES);
|
||||
|
||||
final String actual = cache.get(key);
|
||||
Assert.assertEquals(actual, valueA);
|
||||
} finally {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
public void testPutIfAbsentReturnsExistingValue() throws Exception {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final String key = "key";
|
||||
final String valueA = "A";
|
||||
final String valueB = "B";
|
||||
|
||||
cache.put(key, valueA);
|
||||
|
||||
final String returnedByPutIfAbsent = cache.putIfAbsent(key, k -> valueB);
|
||||
Assert.assertEquals(returnedByPutIfAbsent, valueA);
|
||||
|
||||
final String actualInCache = cache.get(key);
|
||||
Assert.assertEquals(actualInCache, valueA);
|
||||
}
|
||||
|
||||
public void testPutIfAbsentDoesNotAddNull() throws Exception {
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofSeconds(10), 10);
|
||||
|
||||
final String key = "key";
|
||||
final String returnedByPutIfAbsent = cache.putIfAbsent(key, k -> null);
|
||||
Assert.assertNull(returnedByPutIfAbsent, null);
|
||||
|
||||
final String actualInCache = cache.get(key);
|
||||
Assert.assertEquals(actualInCache, null);
|
||||
}
|
||||
|
||||
public void testMaxSizeIsRespected() {
|
||||
final int maxSize = 10;
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofHours(1), maxSize, clock);
|
||||
final Set<String> removedKeys = new LinkedHashSet<>();
|
||||
cache.addListener((key, value) -> removedKeys.add(key));
|
||||
|
||||
// fill the cache
|
||||
int count = 0;
|
||||
for (count = 0; count < maxSize; count++) {
|
||||
|
||||
cache.put("key" + count, "value" + count);
|
||||
clock.plus(2L, ChronoUnit.MILLIS);
|
||||
cache.updateTime();
|
||||
}
|
||||
Assert.assertEquals(cache.size(), maxSize, "cache is full");
|
||||
Assert.assertEquals(removedKeys, List.of(), "removed keys at point A");
|
||||
|
||||
// add an item to a full cache -> the oldest 20% of the entries will be evicted
|
||||
// before the new entry is added
|
||||
cache.put("key" + count, "value" + count);
|
||||
clock.plus(2L, ChronoUnit.MILLIS);
|
||||
cache.updateTime();
|
||||
count++;
|
||||
|
||||
Assert.assertEquals(cache.size(), maxSize - 1, "cache was full, 20% (2 items) were removed and one added");
|
||||
Assert.assertEquals(removedKeys, Set.of("key0", "key1"), "removed keys at point B");
|
||||
}
|
||||
|
||||
public void testEvictionDueToSizeLimitDoesNotRemoveMoreThan20Percent() {
|
||||
final int maxSize = 10;
|
||||
final ModifiableFixedTimeClock clock = new ModifiableFixedTimeClock();
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(Duration.ofHours(1), maxSize, clock);
|
||||
final Set<String> removedKeys = new LinkedHashSet<>();
|
||||
cache.addListener((key, value) -> removedKeys.add(key));
|
||||
|
||||
// fill the cache
|
||||
int count = 0;
|
||||
for (count = 0; count < maxSize; count++) {
|
||||
// all entries get the same eviction time due to the fixed clock
|
||||
cache.put("key" + count, "value" + count);
|
||||
}
|
||||
Assert.assertEquals(cache.size(), maxSize, "cache is full");
|
||||
Assert.assertEquals(removedKeys, List.of(), "removed keys at point A");
|
||||
|
||||
// add an item to a full cache -> the oldest 20% of the entries will be evicted
|
||||
// before the new entry is added
|
||||
cache.put("key" + count, "value" + count);
|
||||
count++;
|
||||
|
||||
Assert.assertEquals(cache.size(), maxSize - 1, "cache was full, 20% (2 items) were removed and one added");
|
||||
Assert.assertEquals(removedKeys.size(), (int) (maxSize * 0.2), "number of removed keys at point B");
|
||||
}
|
||||
|
||||
private void sleep(final TimeUnit timeUnit, final long timeout) {
|
||||
try {
|
||||
timeUnit.sleep(timeout);
|
||||
} catch (final InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitFor(final CountDownLatch latch) {
|
||||
try {
|
||||
latch.await(1, TimeUnit.MINUTES);
|
||||
} catch (final InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(final String[] args) throws InterruptedException {
|
||||
|
||||
Configurator.setRootLevel(Level.TRACE);
|
||||
|
||||
final Duration timeToLive = Duration.ofSeconds(1);
|
||||
final HotEntryCache<String, String> cache = new HotEntryCache<>(timeToLive, 10);
|
||||
|
||||
cache.addListener((key, value) -> {
|
||||
System.out.println(Instant.now() + " evicting: " + key + " -> " + value);
|
||||
});
|
||||
cache.put("key", "value that is touched");
|
||||
for (int i = 0; i < 20; i++) {
|
||||
|
||||
System.out.println(Instant.now() + " putting value" + i);
|
||||
cache.put("key" + i, "value" + i);
|
||||
cache.put("key", "value that is touched" + i);
|
||||
TimeUnit.MILLISECONDS.sleep(450);
|
||||
}
|
||||
|
||||
for (int i = 20; i < 23; i++) {
|
||||
System.out.println(Instant.now() + " putting value" + i);
|
||||
cache.put("key" + i, "value" + i);
|
||||
TimeUnit.MILLISECONDS.sleep(Duration.ofSeconds(5).plusMillis(10).toMillis());
|
||||
}
|
||||
|
||||
TimeUnit.MILLISECONDS.sleep(Duration.ofSeconds(5).plusMillis(10).toMillis());
|
||||
|
||||
for (int i = 23; i < 27; i++) {
|
||||
System.out.println(Instant.now() + " putting value" + i);
|
||||
cache.put("key" + i, "value" + i);
|
||||
TimeUnit.MILLISECONDS.sleep(Duration.ofSeconds(5).plusMillis(10).toMillis());
|
||||
}
|
||||
|
||||
TimeUnit.SECONDS.sleep(300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,87 +12,87 @@ import java.time.temporal.TemporalUnit;
|
||||
* useful in tests, so that you can explicitly set the time.
|
||||
*/
|
||||
public class ModifiableFixedTimeClock extends Clock implements Serializable {
|
||||
private static final long serialVersionUID = 1955332545617873736L;
|
||||
private Instant instant;
|
||||
private final ZoneId zone;
|
||||
private static final long serialVersionUID = 1955332545617873736L;
|
||||
private Instant instant;
|
||||
private final ZoneId zone;
|
||||
|
||||
public ModifiableFixedTimeClock() {
|
||||
this(Instant.now());
|
||||
}
|
||||
public ModifiableFixedTimeClock() {
|
||||
this(Instant.now());
|
||||
}
|
||||
|
||||
public ModifiableFixedTimeClock(final Instant fixedInstant) {
|
||||
this(fixedInstant, ZoneId.systemDefault());
|
||||
}
|
||||
public ModifiableFixedTimeClock(final Instant fixedInstant) {
|
||||
this(fixedInstant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public ModifiableFixedTimeClock(final Instant fixedInstant, final ZoneId zone) {
|
||||
this.instant = fixedInstant;
|
||||
this.zone = zone;
|
||||
}
|
||||
public ModifiableFixedTimeClock(final Instant fixedInstant, final ZoneId zone) {
|
||||
this.instant = fixedInstant;
|
||||
this.zone = zone;
|
||||
}
|
||||
|
||||
public void setTime(final Instant instant) {
|
||||
this.instant = instant;
|
||||
}
|
||||
public void setTime(final Instant instant) {
|
||||
this.instant = instant;
|
||||
}
|
||||
|
||||
public void plus(final TemporalAmount amountToAdd) {
|
||||
instant = instant.plus(amountToAdd);
|
||||
}
|
||||
public void plus(final TemporalAmount amountToAdd) {
|
||||
instant = instant.plus(amountToAdd);
|
||||
}
|
||||
|
||||
public void plus(final long amountToAdd, final TemporalUnit unit) {
|
||||
instant = instant.plus(amountToAdd, unit);
|
||||
}
|
||||
public void plus(final long amountToAdd, final TemporalUnit unit) {
|
||||
instant = instant.plus(amountToAdd, unit);
|
||||
}
|
||||
|
||||
public void plusMillis(final long millisToAdd) {
|
||||
instant = instant.plusMillis(millisToAdd);
|
||||
}
|
||||
public void plusMillis(final long millisToAdd) {
|
||||
instant = instant.plusMillis(millisToAdd);
|
||||
}
|
||||
|
||||
public void plusNanos(final long nanosToAdd) {
|
||||
instant = instant.plusNanos(nanosToAdd);
|
||||
}
|
||||
public void plusNanos(final long nanosToAdd) {
|
||||
instant = instant.plusNanos(nanosToAdd);
|
||||
}
|
||||
|
||||
public void plusSeconds(final long secondsToAdd) {
|
||||
instant = instant.plusSeconds(secondsToAdd);
|
||||
}
|
||||
public void plusSeconds(final long secondsToAdd) {
|
||||
instant = instant.plusSeconds(secondsToAdd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZoneId getZone() {
|
||||
return zone;
|
||||
}
|
||||
@Override
|
||||
public ZoneId getZone() {
|
||||
return zone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clock withZone(final ZoneId zone) {
|
||||
if (zone.equals(this.zone)) {
|
||||
return this;
|
||||
}
|
||||
return new ModifiableFixedTimeClock(instant, zone);
|
||||
}
|
||||
@Override
|
||||
public Clock withZone(final ZoneId zone) {
|
||||
if (zone.equals(this.zone)) {
|
||||
return this;
|
||||
}
|
||||
return new ModifiableFixedTimeClock(instant, zone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long millis() {
|
||||
return instant.toEpochMilli();
|
||||
}
|
||||
@Override
|
||||
public long millis() {
|
||||
return instant.toEpochMilli();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant instant() {
|
||||
return instant;
|
||||
}
|
||||
@Override
|
||||
public Instant instant() {
|
||||
return instant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj instanceof ModifiableFixedTimeClock) {
|
||||
final ModifiableFixedTimeClock other = (ModifiableFixedTimeClock) obj;
|
||||
return instant.equals(other.instant) && zone.equals(other.zone);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj instanceof ModifiableFixedTimeClock) {
|
||||
final ModifiableFixedTimeClock other = (ModifiableFixedTimeClock) obj;
|
||||
return instant.equals(other.instant) && zone.equals(other.zone);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return instant.hashCode() ^ zone.hashCode();
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return instant.hashCode() ^ zone.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FixedClock[" + instant + "," + zone + "]";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FixedClock[" + instant + "," + zone + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user