You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by nf...@apache.org on 2025/03/21 20:58:46 UTC
(camel) branch essobedo/CAMEL-21888/no-longer-compress-changes created (now fa1ef4553c5)
This is an automated email from the ASF dual-hosted git repository.
nfilotto pushed a change to branch essobedo/CAMEL-21888/no-longer-compress-changes
in repository https://gitbox.apache.org/repos/asf/camel.git
at fa1ef4553c5 CAMEL-21888: camel-core - Compress changes only if needed
This branch includes the following new commits:
new fa1ef4553c5 CAMEL-21888: camel-core - Compress changes only if needed
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(camel) 01/01: CAMEL-21888: camel-core - Compress changes only if needed
Posted by nf...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
nfilotto pushed a commit to branch essobedo/CAMEL-21888/no-longer-compress-changes
in repository https://gitbox.apache.org/repos/asf/camel.git
commit fa1ef4553c557254e66422ef98c08dc05b586f84
Author: Nicolas Filotto <ni...@qlik.com>
AuthorDate: Fri Mar 21 21:58:12 2025 +0100
CAMEL-21888: camel-core - Compress changes only if needed
---
.../camel/support/cache/SimpleLRUCacheTest.java | 142 +++++++++++
.../apache/camel/support/cache/SimpleLRUCache.java | 270 +++++++++++++++++----
2 files changed, 366 insertions(+), 46 deletions(-)
diff --git a/core/camel-core/src/test/java/org/apache/camel/support/cache/SimpleLRUCacheTest.java b/core/camel-core/src/test/java/org/apache/camel/support/cache/SimpleLRUCacheTest.java
index cb1a17ee60d..58e44a4b215 100644
--- a/core/camel-core/src/test/java/org/apache/camel/support/cache/SimpleLRUCacheTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/support/cache/SimpleLRUCacheTest.java
@@ -17,6 +17,7 @@
package org.apache.camel.support.cache;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -60,9 +61,12 @@ class SimpleLRUCacheTest {
assertNull(map.put("1", "One"));
assertEquals(1, map.size());
assertEquals(1, map.getQueueSize());
+ assertEquals("One", map.get("1"));
map.entrySet().iterator().next().setValue("bar");
assertEquals(1, map.size());
assertEquals(2, map.getQueueSize());
+ assertEquals("bar", map.get("1"));
+ assertThrows(NullPointerException.class, () -> map.entrySet().iterator().next().setValue(null));
}
@Test
@@ -79,6 +83,80 @@ class SimpleLRUCacheTest {
map.put("1", "B");
assertEquals(1, map.size());
assertEquals(2, map.getQueueSize());
+ map.put("2", "A");
+ assertEquals(2, map.size());
+ assertEquals(3, map.getQueueSize());
+ map.put("3", "A");
+ assertEquals(3, map.size());
+ assertEquals(4, map.getQueueSize());
+ map.put("4", "A");
+ assertEquals(3, map.size());
+ assertEquals(3, map.getQueueSize());
+ }
+
+ @Test
+ void size() {
+ assertEquals(0, map.size());
+ assertNull(map.put("1", "One"));
+ assertEquals(1, map.size());
+ }
+
+ @Test
+ void isEmpty() {
+ assertTrue(map.isEmpty());
+ assertNull(map.put("1", "One"));
+ assertFalse(map.isEmpty());
+ map.remove("1");
+ assertTrue(map.isEmpty());
+ }
+
+ @Test
+ void containsKey() {
+ assertFalse(map.containsKey("1"));
+ assertNull(map.put("1", "One"));
+ assertTrue(map.containsKey("1"));
+ map.remove("1");
+ assertFalse(map.containsKey("1"));
+ assertThrows(NullPointerException.class, () -> map.containsKey(null));
+ }
+
+ @Test
+ void containsValue() {
+ assertFalse(map.containsValue("One"));
+ assertNull(map.put("1", "One"));
+ assertTrue(map.containsValue("One"));
+ map.remove("1");
+ assertFalse(map.containsValue("One"));
+ assertThrows(NullPointerException.class, () -> map.containsValue(null));
+ }
+
+ @Test
+ void remove() {
+ assertTrue(map.isEmpty());
+ map.remove("1");
+ assertTrue(map.isEmpty());
+ assertNull(map.put("1", "One"));
+ assertFalse(map.isEmpty());
+ map.remove("1");
+ assertTrue(map.isEmpty());
+ assertThrows(NullPointerException.class, () -> map.remove(null));
+ }
+
+ @Test
+ void removeWithValue() {
+ assertTrue(map.isEmpty());
+ map.remove("1", "One");
+ assertTrue(map.isEmpty());
+ assertNull(map.put("1", "One"));
+ assertFalse(map.isEmpty());
+ map.remove("1", "Two");
+ assertFalse(map.isEmpty());
+ map.remove("2", "One");
+ assertFalse(map.isEmpty());
+ map.remove("1", "One");
+ assertTrue(map.isEmpty());
+ assertThrows(NullPointerException.class, () -> map.remove(null, "A"));
+ assertThrows(NullPointerException.class, () -> map.remove("A", null));
}
@Test
@@ -101,6 +179,54 @@ class SimpleLRUCacheTest {
assertEquals(1, consumed.size());
assertTrue(map.containsKey("2"));
assertEquals("Two v2", map.get("2"));
+ assertThrows(NullPointerException.class, () -> map.put("A", null));
+ assertThrows(NullPointerException.class, () -> map.put(null, "A"));
+ }
+
+ @Test
+ void putAll() {
+ assertEquals(0, map.size());
+ Map<String, String> data = new LinkedHashMap<>();
+ data.put("1", "One");
+ data.put("2", "Two");
+ data.put("3", "Three");
+ map.putAll(data);
+ assertEquals(3, map.size());
+ assertEquals(0, consumed.size());
+ data.clear();
+ data.put("4", "Four");
+ data.put("5", "Five");
+ map.putAll(data);
+ assertEquals(3, map.size());
+ assertEquals(2, consumed.size());
+ assertFalse(map.containsKey("1"));
+ assertFalse(map.containsKey("2"));
+ assertTrue(consumed.contains("One"));
+ assertTrue(consumed.contains("Two"));
+ assertThrows(NullPointerException.class, () -> map.putAll(null));
+ }
+
+ @Test
+ void clear() {
+ assertEquals(0, map.size());
+ map.putAll(Map.of("1", "One", "2", "Two", "3", "Three"));
+ assertEquals(3, map.size());
+ map.clear();
+ assertEquals(0, map.size());
+ }
+
+ @Test
+ void replaceAll() {
+ map.replaceAll((k, v) -> v + " v2");
+ assertEquals(0, map.size());
+ map.putAll(Map.of("1", "One", "2", "Two", "3", "Three"));
+ assertEquals(3, map.size());
+ map.replaceAll((k, v) -> v + " v2");
+ assertEquals(3, map.size());
+ assertEquals("One v2", map.get("1"));
+ assertEquals("Two v2", map.get("2"));
+ assertEquals("Three v2", map.get("3"));
+ assertThrows(NullPointerException.class, () -> map.replaceAll(null));
}
@Test
@@ -128,6 +254,8 @@ class SimpleLRUCacheTest {
assertEquals(2, consumed.size());
assertFalse(map.containsKey("2"));
assertTrue(consumed.contains("Two"));
+ assertThrows(NullPointerException.class, () -> map.putIfAbsent("A", null));
+ assertThrows(NullPointerException.class, () -> map.putIfAbsent(null, "A"));
}
@Test
@@ -164,6 +292,8 @@ class SimpleLRUCacheTest {
assertEquals("Five", map.computeIfAbsent("5", k -> null));
assertEquals(3, map.size());
assertEquals(2, consumed.size());
+ assertThrows(NullPointerException.class, () -> map.computeIfAbsent(null, k -> null));
+ assertThrows(NullPointerException.class, () -> map.computeIfAbsent("A", null));
}
@Test
@@ -189,6 +319,8 @@ class SimpleLRUCacheTest {
assertEquals(2, map.size());
assertEquals(0, consumed.size());
assertFalse(map.containsKey("1"));
+ assertThrows(NullPointerException.class, () -> map.computeIfPresent(null, (k, v) -> null));
+ assertThrows(NullPointerException.class, () -> map.computeIfPresent("A", null));
}
@Test
@@ -215,6 +347,8 @@ class SimpleLRUCacheTest {
assertEquals(2, map.size());
assertEquals(1, consumed.size());
assertFalse(map.containsKey("2"));
+ assertThrows(NullPointerException.class, () -> map.compute(null, (k, v) -> null));
+ assertThrows(NullPointerException.class, () -> map.compute("A", null));
}
@Test
@@ -238,6 +372,9 @@ class SimpleLRUCacheTest {
assertNull(map.merge("2", "V2", (v1, v2) -> null));
assertEquals(2, map.size());
assertEquals(1, consumed.size());
+ assertThrows(NullPointerException.class, () -> map.merge("A", "B", null));
+ assertThrows(NullPointerException.class, () -> map.merge("A", null, (v1, v2) -> null));
+ assertThrows(NullPointerException.class, () -> map.merge(null, "A", (v1, v2) -> null));
}
@Test
@@ -259,6 +396,8 @@ class SimpleLRUCacheTest {
assertEquals("Three v2", map.get("3"));
assertEquals(3, map.size());
assertEquals(0, consumed.size());
+ assertThrows(NullPointerException.class, () -> map.replace("A", null));
+ assertThrows(NullPointerException.class, () -> map.replace(null, "A"));
}
@Test
@@ -285,6 +424,9 @@ class SimpleLRUCacheTest {
assertEquals("Three v2", map.get("3"));
assertEquals(3, map.size());
assertEquals(0, consumed.size());
+ assertThrows(NullPointerException.class, () -> map.replace("A", "B", null));
+ assertThrows(NullPointerException.class, () -> map.replace("A", null, "B"));
+ assertThrows(NullPointerException.class, () -> map.replace(null, "A", "B"));
}
@Test
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/cache/SimpleLRUCache.java b/core/camel-support/src/main/java/org/apache/camel/support/cache/SimpleLRUCache.java
index 7c1b76e601a..9a0510a5839 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/cache/SimpleLRUCache.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/cache/SimpleLRUCache.java
@@ -16,7 +16,7 @@
*/
package org.apache.camel.support.cache;
-import java.util.Collections;
+import java.util.Collection;
import java.util.Deque;
import java.util.HashSet;
import java.util.Map;
@@ -25,12 +25,14 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.stream.Collectors;
/**
* {@code SimpleLRUCache} is a simple implementation of a cache of type Least Recently Used . The implementation doesn't
@@ -40,7 +42,7 @@ import java.util.function.Function;
* @param <K> type of the key
* @param <V> type of the value
*/
-public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
+public class SimpleLRUCache<K, V> implements Map<K, V> {
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
@@ -54,7 +56,7 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
/**
* The lock to prevent the addition of changes during the swap of queue of changes.
*/
- private final ReadWriteLock swapLock = new ReentrantReadWriteLock();
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* The maximum cache size.
*/
@@ -62,17 +64,20 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
/**
* The last changes recorded.
*/
- private final AtomicReference<Deque<Entry<K, V>>> lastChanges = new AtomicReference<>(new ConcurrentLinkedDeque<>());
+ private final AtomicReference<Deque<Entry<K, ValueHolder<V>>>> lastChanges
+ = new AtomicReference<>(new ConcurrentLinkedDeque<>());
/**
* The function to call when an entry is evicted.
*/
private final Consumer<V> evict;
+ private final AtomicLong sequence = new AtomicLong();
+ private final Map<K, ValueHolder<V>> delegate;
public SimpleLRUCache(int initialCapacity, int maximumCacheSize, Consumer<V> evicted) {
- super(initialCapacity, DEFAULT_LOAD_FACTOR);
if (maximumCacheSize <= 0) {
throw new IllegalArgumentException("The maximum cache size must be greater than 0");
}
+ this.delegate = new ConcurrentHashMap<>(initialCapacity, DEFAULT_LOAD_FACTOR);
this.maximumCacheSize = maximumCacheSize;
this.evict = Objects.requireNonNull(evicted);
}
@@ -84,20 +89,66 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
* @param mappingFunction the mapping function to apply.
* @return the result of the mapping function.
*/
- private V addChange(OperationContext<K, V> context, Function<? super K, ? extends V> mappingFunction) {
+ private ValueHolder<V> addChange(OperationContext<K, V> context, Function<? super K, ? extends V> mappingFunction) {
K key = context.key;
V value = mappingFunction.apply(key);
if (value == null) {
return null;
}
- Entry<K, V> entry = Map.entry(key, value);
- swapLock.readLock().lock();
- try {
- lastChanges.get().add(entry);
- } finally {
- swapLock.readLock().unlock();
+ ValueHolder<V> holder = newValue(value);
+ lastChanges.get().add(Map.entry(key, holder));
+ return holder;
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return delegate.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ if (value == null)
+ throw new NullPointerException();
+ return delegate.values().stream()
+ .map(ValueHolder::get)
+ .anyMatch(v -> Objects.equals(v, value));
+ }
+
+ @Override
+ public V get(Object key) {
+ return extractValue(delegate.get(key));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean remove(Object key, Object value) {
+ if (key == null || value == null) {
+ throw new NullPointerException();
+ }
+ K keyK = (K) key;
+ try (OperationContext<K, V> context = new OperationContext<>(this, keyK)) {
+ delegate.compute(
+ keyK,
+ (k, v) -> {
+ V extractedValue = extractValue(v);
+ if (Objects.equals(value, extractedValue)) {
+ context.result = extractedValue;
+ return null;
+ }
+ return v;
+ });
+ return context.result != null;
}
- return value;
}
@Override
@@ -106,26 +157,31 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
throw new NullPointerException();
}
try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
- super.compute(
+ delegate.compute(
key,
(k, v) -> {
- context.result = v;
+ context.result = extractValue(v);
return addChange(context, x -> value);
});
return context.result;
}
}
+ @Override
+ public V remove(Object key) {
+ return extractValue(delegate.remove(key));
+ }
+
@Override
public V putIfAbsent(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException();
}
try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
- super.compute(
+ delegate.compute(
key,
(k, v) -> {
- context.result = v;
+ context.result = extractValue(v);
if (v != null) {
return v;
}
@@ -141,7 +197,7 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
throw new NullPointerException();
}
try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
- return super.computeIfAbsent(key, k -> addChange(context, mappingFunction));
+ return extractValue(delegate.computeIfAbsent(key, k -> addChange(context, mappingFunction)));
}
}
@@ -151,7 +207,8 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
throw new NullPointerException();
}
try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
- return super.computeIfPresent(key, (k, v) -> addChange(context, x -> remappingFunction.apply(x, v)));
+ return extractValue(delegate.computeIfPresent(key,
+ (k, v) -> addChange(context, x -> remappingFunction.apply(x, extractValue(v)))));
}
}
@@ -161,7 +218,8 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
throw new NullPointerException();
}
try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
- return super.compute(key, (k, v) -> addChange(context, x -> remappingFunction.apply(x, v)));
+ return extractValue(
+ delegate.compute(key, (k, v) -> addChange(context, x -> remappingFunction.apply(x, extractValue(v)))));
}
}
@@ -171,12 +229,12 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
throw new NullPointerException();
}
try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
- return super.compute(
+ return extractValue(delegate.compute(
key,
(k, oldValue) -> {
- V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
+ V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue.get(), value);
return addChange(context, x -> newValue);
- });
+ }));
}
}
@@ -186,12 +244,13 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
throw new NullPointerException();
}
try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
- super.computeIfPresent(
+ delegate.computeIfPresent(
key,
(k, v) -> {
- if (Objects.equals(oldValue, v)) {
- context.result = addChange(context, x -> newValue);
- return context.result;
+ if (Objects.equals(oldValue, extractValue(v))) {
+ ValueHolder<V> result = addChange(context, x -> newValue);
+ context.result = extractValue(result);
+ return result;
}
return v;
});
@@ -205,10 +264,10 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
throw new NullPointerException();
}
try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
- super.computeIfPresent(
+ delegate.computeIfPresent(
key,
(k, v) -> {
- context.result = v;
+ context.result = extractValue(v);
return addChange(context, x -> value);
});
return context.result;
@@ -222,16 +281,48 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
}
}
+ @Override
+ public void clear() {
+ lock.writeLock().lock();
+ try {
+ delegate.clear();
+ lastChanges.getAndSet(new ConcurrentLinkedDeque<>());
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return delegate.keySet();
+ }
+
+ @Override
+ public Collection<V> values() {
+ return delegate.values().stream().map(ValueHolder::get).toList();
+ }
+
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+ if (function == null) {
+ throw new NullPointerException();
+ }
for (Entry<? extends K, ? extends V> e : entrySet()) {
- replace(e.getKey(), e.getValue(), function.apply(e.getKey(), e.getValue()));
+ K key = e.getKey();
+ V value = e.getValue();
+ try (OperationContext<K, V> context = new OperationContext<>(this, key)) {
+ delegate.computeIfPresent(
+ key,
+ (k, v) -> addChange(context, x -> function.apply(x, value)));
+ }
}
}
@Override
public Set<Entry<K, V>> entrySet() {
- return Collections.unmodifiableSet(super.entrySet());
+ return delegate.entrySet().stream()
+ .map(entry -> new CacheEntry<>(this, entry.getKey(), entry.getValue().get()))
+ .collect(Collectors.toUnmodifiableSet());
}
/**
@@ -272,24 +363,29 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
/**
* @return the oldest existing change.
*/
- private Entry<K, V> nextOldestChange() {
+ private Entry<K, ValueHolder<V>> nextOldestChange() {
return lastChanges.get().poll();
}
/**
- * Removes duplicates from the queue of changes.
+ * Removes duplicates from the queue of changes if the queue is full.
*/
- private void compressChanges() {
- Deque<Entry<K, V>> newChanges = new ConcurrentLinkedDeque<>();
- Deque<Entry<K, V>> currentChanges;
- swapLock.writeLock().lock();
+ private void compressChangesIfNeeded() {
+ Deque<Entry<K, ValueHolder<V>>> newChanges;
+ Deque<Entry<K, ValueHolder<V>>> currentChanges;
+ lock.writeLock().lock();
try {
- currentChanges = lastChanges.getAndSet(newChanges);
+ if (isQueueFull()) {
+ newChanges = new ConcurrentLinkedDeque<>();
+ currentChanges = lastChanges.getAndSet(newChanges);
+ } else {
+ return;
+ }
} finally {
- swapLock.writeLock().unlock();
+ lock.writeLock().unlock();
}
Set<K> keys = new HashSet<>();
- Entry<K, V> entry;
+ Entry<K, ValueHolder<V>> entry;
while ((entry = currentChanges.pollLast()) != null) {
if (keys.add(entry.getKey())) {
newChanges.addFirst(entry);
@@ -297,6 +393,14 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
}
}
+ private ValueHolder<V> newValue(V value) {
+ return new ValueHolder<>(sequence.incrementAndGet(), value);
+ }
+
+ private V extractValue(ValueHolder<V> holder) {
+ return holder == null ? null : holder.get();
+ }
+
/**
* The internal context of all write operations.
*/
@@ -317,21 +421,21 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
OperationContext(SimpleLRUCache<K, V> cache, K key) {
this.cache = cache;
this.key = key;
+ cache.lock.readLock().lock();
}
@Override
public void close() {
+ cache.lock.readLock().unlock();
if (cache.evictionNeeded() && cache.eviction.compareAndSet(false, true)) {
try {
do {
- cache.compressChanges();
+ cache.compressChangesIfNeeded();
if (cache.isCacheFull()) {
- Entry<K, V> oldest = cache.nextOldestChange();
- if (oldest != null && cache.remove(oldest.getKey(), oldest.getValue())) {
- cache.evict.accept(oldest.getValue());
+ Entry<K, ValueHolder<V>> oldest = cache.nextOldestChange();
+ if (oldest != null && cache.delegate.remove(oldest.getKey(), oldest.getValue())) {
+ cache.evict.accept(oldest.getValue().get());
}
- } else {
- break;
}
} while (cache.evictionNeeded());
} finally {
@@ -340,4 +444,78 @@ public class SimpleLRUCache<K, V> extends ConcurrentHashMap<K, V> {
}
}
}
+
+ /**
+ * A cache value holder that is identified by its id represented by the revision number.
+ *
+ * @param <V> the type of the value
+ */
+ private static class ValueHolder<V> {
+ private final long revision;
+ private final V value;
+
+ ValueHolder(long revision, V value) {
+ this.revision = revision;
+ this.value = value;
+ }
+
+ V get() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ValueHolder<?> that = (ValueHolder<?>) o;
+ return revision == that.revision;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(revision);
+ }
+ }
+
+ /**
+ * A modifiable cache entry.
+ *
+ * @param <K> the type of the key
+ * @param <V> the type of the value
+ */
+ private static class CacheEntry<K, V> implements Entry<K, V> {
+
+ private final K key;
+ private V val;
+ /**
+ * The underlying cache.
+ */
+ private final SimpleLRUCache<K, V> cache;
+
+ CacheEntry(SimpleLRUCache<K, V> cache, K key, V value) {
+ this.cache = cache;
+ this.key = key;
+ this.val = value;
+ }
+
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ @Override
+ public V getValue() {
+ return val;
+ }
+
+ @Override
+ public V setValue(V value) {
+ if (value == null)
+ throw new NullPointerException();
+ V v = val;
+ val = value;
+ cache.put(key, value);
+ return v;
+ }
+ }
}