diff --git a/orm/src/main/java/com/zfoo/orm/util/LazyCache.java b/orm/src/main/java/com/zfoo/orm/util/LazyCache.java index 5caee4078..89d2d1829 100644 --- a/orm/src/main/java/com/zfoo/orm/util/LazyCache.java +++ b/orm/src/main/java/com/zfoo/orm/util/LazyCache.java @@ -1,5 +1,6 @@ package com.zfoo.orm.util; +import com.zfoo.protocol.model.Pair; import com.zfoo.scheduler.util.TimeUtils; import java.util.concurrent.ConcurrentHashMap; @@ -14,22 +15,49 @@ */ public class LazyCache { - private static class ValueCache { + private static class CacheValue { public volatile V value; public volatile long expireTime; } + public static enum RemovalCause { + /** + * The entry was manually removed by the user. This can result from the user invoking any of the + * following methods on the cache or map view. + * remove() + */ + EXPLICIT, + + /** + * The entry itself was not actually removed, but its value was replaced by the user. This can + * result from the user invoking any of the following methods on the cache or map view. + * put() + */ + REPLACED, + + + /** + * The entry's expiration timestamp has passed. + */ + EXPIRED, + + /** + * The entry was evicted due to size constraints. + */ + SIZE; + } - private AtomicLong expireCheckTimeAtomic; - private volatile long minExpireTime; + + private int maximumSize; private long expireAfterAccessMillis; private long expireCheckInterval; - private int maximumSize; - private ConcurrentMap> cacheMap; - private BiConsumer removeCallback = (k, v) -> { + private AtomicLong expireCheckTimeAtomic; + private volatile long minExpireTime; + private ConcurrentMap> cacheMap; + private BiConsumer, RemovalCause> removeCallback = (pair, removalCause) -> { }; - public LazyCache(int maximumSize, long expireAfterAccessMillis, long expireCheckIntervalMillis, BiConsumer removeCallback) { + public LazyCache(int maximumSize, long expireAfterAccessMillis, long expireCheckIntervalMillis, BiConsumer, RemovalCause> removeCallback) { this.maximumSize = maximumSize; this.expireAfterAccessMillis = expireAfterAccessMillis; this.expireCheckInterval = expireCheckIntervalMillis; @@ -44,10 +72,13 @@ public LazyCache(int maximumSize, long expireAfterAccessMillis, long expireCheck * If the cache previously contained a value associated with the key, the old value is replaced by the new value. */ public void put(K key, V value) { - var valueCache = new ValueCache(); - valueCache.value = value; - valueCache.expireTime = TimeUtils.now(); - cacheMap.put(key, valueCache); + var cacheValue = new CacheValue(); + cacheValue.value = value; + cacheValue.expireTime = TimeUtils.now(); + var oldCacheValue = cacheMap.put(key, cacheValue); + if (oldCacheValue != null) { + removeCallback.accept(new Pair<>(key, oldCacheValue.value), RemovalCause.REPLACED); + } checkMaximumSize(); checkExpire(); } @@ -55,29 +86,43 @@ public void put(K key, V value) { public V get(K key) { checkExpire(); - var valueCache = cacheMap.get(key); - if (valueCache == null) { + var cacheValue = cacheMap.get(key); + if (cacheValue == null) { return null; } - if (valueCache.expireTime < TimeUtils.now()) { - remove(key); + if (cacheValue.expireTime < TimeUtils.now()) { + remove(key, RemovalCause.EXPIRED); return null; } - valueCache.expireTime = TimeUtils.now() + expireAfterAccessMillis; - return valueCache.value; + cacheValue.expireTime = TimeUtils.now() + expireAfterAccessMillis; + return cacheValue.value; } public void remove(K key) { + remove(key, RemovalCause.EXPLICIT); + } + + public void remove(K key, RemovalCause removalCause) { if (key == null) { return; } - var valueCache = cacheMap.remove(key); - if (valueCache != null) { - removeCallback.accept(key, valueCache.value); + var cacheValue = cacheMap.remove(key); + if (cacheValue != null) { + removeCallback.accept(new Pair<>(key, cacheValue.value), removalCause); + } + } + + public void forEach(BiConsumer biConsumer) { + for (var entry : cacheMap.entrySet()) { + biConsumer.accept(entry.getKey(), entry.getValue().value); } } + public int size() { + return cacheMap.size(); + } + // ----------------------------------------------------------------------------------------------------------------- private void checkMaximumSize() { @@ -93,7 +138,7 @@ private void checkMaximumSize() { } } this.minExpireTime = minTimestamp; - remove(minKey); + remove(minKey, RemovalCause.SIZE); checkMaximumSize(); } @@ -107,7 +152,7 @@ private void checkExpire() { for (var entry : cacheMap.entrySet()) { var expireTime = entry.getValue().expireTime; if (expireTime < now) { - remove(entry.getKey()); + remove(entry.getKey(), RemovalCause.EXPIRED); } if (expireTime < minTimestamp) { minTimestamp = expireTime; diff --git a/orm/src/test/java/com/zfoo/orm/cache/LazyCacheTest.java b/orm/src/test/java/com/zfoo/orm/cache/LazyCacheTest.java index aecc8b45c..ca574547b 100644 --- a/orm/src/test/java/com/zfoo/orm/cache/LazyCacheTest.java +++ b/orm/src/test/java/com/zfoo/orm/cache/LazyCacheTest.java @@ -1,6 +1,7 @@ package com.zfoo.orm.cache; import com.zfoo.orm.util.LazyCache; +import com.zfoo.protocol.model.Pair; import com.zfoo.protocol.util.StringUtils; import com.zfoo.protocol.util.ThreadUtils; import com.zfoo.scheduler.util.TimeUtils; @@ -15,10 +16,10 @@ @Ignore public class LazyCacheTest { - private static final BiConsumer myRemoveCallback = new BiConsumer() { + private static final BiConsumer, LazyCache.RemovalCause> myRemoveCallback = new BiConsumer, LazyCache.RemovalCause>() { @Override - public void accept(Integer key, String value) { - System.out.println(StringUtils.format("remove key:[{}] value:[{}]", key, value)); + public void accept(Pair pair, LazyCache.RemovalCause removalCause) { + System.out.println(StringUtils.format("remove key:[{}] value:[{}] removalCause:[{}]", pair.getKey(), pair.getValue(), removalCause)); } };