|
@@ -0,0 +1,233 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
|
|
+ * @license WTFPL
|
|
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
|
|
+ */
|
|
|
|
|
+package net.ranides.assira.collection.maps;
|
|
|
|
|
+
|
|
|
|
|
+import java.lang.ref.Reference;
|
|
|
|
|
+import java.lang.ref.ReferenceQueue;
|
|
|
|
|
+import java.lang.ref.SoftReference;
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+import java.time.Instant;
|
|
|
|
|
+import java.util.LinkedHashMap;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
+import java.util.function.Function;
|
|
|
|
|
+import java.util.function.Supplier;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
|
|
+ */
|
|
|
|
|
+public abstract class CacheV2<K,V> {
|
|
|
|
|
+
|
|
|
|
|
+ protected static final ReferenceQueue QUEUE = new ReferenceQueue<>();
|
|
|
|
|
+
|
|
|
|
|
+ static {
|
|
|
|
|
+ new Thread(CacheV2::cleanup,"assira cache cleanup").start();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected final Function<K,V> supplier;
|
|
|
|
|
+
|
|
|
|
|
+ protected CacheV2(Function<K, V> supplier) {
|
|
|
|
|
+ this.supplier = supplier;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static <K,V> CacheV2<K,V> getInstance(Function<K,V> supplier) {
|
|
|
|
|
+ return new SimpleCache<K, V>(supplier);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static <K,V> CacheV2<K,V> getInstance(int capacity, Function<K,V> supplier) {
|
|
|
|
|
+ return new FixedCache<K, V>(capacity, supplier);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static <K,V> CacheV2<K,V> getInstance(Duration ttl, Function<K,V> supplier) {
|
|
|
|
|
+ return new TTLCache<K, V>(ttl, supplier);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public final V get(K key) {
|
|
|
|
|
+ return get(key, supplier);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public final V get(K key, Function<K,V> function) {
|
|
|
|
|
+ return get(key, () -> function.apply(key));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public final V get(K key, Supplier<V> function) {
|
|
|
|
|
+ Reference<V> ref = getReference(key);
|
|
|
|
|
+ if(null == ref) {
|
|
|
|
|
+ return put(key, function.get());
|
|
|
|
|
+ }
|
|
|
|
|
+ V ret = ref.get();
|
|
|
|
|
+ if(null == ret) {
|
|
|
|
|
+ return put(key, function.get());
|
|
|
|
|
+ }
|
|
|
|
|
+ return ret;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected abstract Reference<V> getReference(K key);
|
|
|
|
|
+
|
|
|
|
|
+ protected abstract V put(K key, V value);
|
|
|
|
|
+
|
|
|
|
|
+ protected abstract void remove(K key);
|
|
|
|
|
+
|
|
|
|
|
+ public abstract void clear();
|
|
|
|
|
+
|
|
|
|
|
+ protected static void cleanup() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ KRef ref;
|
|
|
|
|
+ while ( null != (ref = (KRef)QUEUE.remove()) ) {
|
|
|
|
|
+ ref.src.remove(ref.key);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch(InterruptedException cause) {
|
|
|
|
|
+ // shutdown
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static final class SimpleCache<K,V> extends CacheV2<K,V> {
|
|
|
|
|
+
|
|
|
|
|
+ private final Map<K,KRef<K,V>> map = new ConcurrentHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ public SimpleCache(Function<K, V> supplier) {
|
|
|
|
|
+ super(supplier);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected V put(K key, V value) {
|
|
|
|
|
+ map.put(key, new KRef<K, V>(this, key, value));
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Reference<V> getReference(K key) {
|
|
|
|
|
+ return map.get(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected void remove(K key) {
|
|
|
|
|
+ map.remove(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void clear() {
|
|
|
|
|
+ map.clear();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static final class FixedCache<K,V> extends CacheV2<K,V> {
|
|
|
|
|
+
|
|
|
|
|
+ private final Map<K,KRef<K,V>> map;
|
|
|
|
|
+
|
|
|
|
|
+ public FixedCache(int capacity, Function<K, V> supplier) {
|
|
|
|
|
+ super(supplier);
|
|
|
|
|
+ this.map = new FHashMap<>(capacity);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected V put(K key, V value) {
|
|
|
|
|
+ synchronized (map) {
|
|
|
|
|
+ map.put(key, new KRef<K, V>(this, key, value));
|
|
|
|
|
+ }
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Reference<V> getReference(K key) {
|
|
|
|
|
+ synchronized (map) {
|
|
|
|
|
+ return map.get(key);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected void remove(K key) {
|
|
|
|
|
+ synchronized (map) {
|
|
|
|
|
+ map.remove(key);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void clear() {
|
|
|
|
|
+ synchronized (map) {
|
|
|
|
|
+ map.clear();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static final class TTLCache<K,V> extends CacheV2<K,V> {
|
|
|
|
|
+
|
|
|
|
|
+ private final Duration ttl;
|
|
|
|
|
+
|
|
|
|
|
+ private final Map<K,KRef<K,V>> map = new ConcurrentHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ public TTLCache(Duration ttl, Function<K, V> supplier) {
|
|
|
|
|
+ super(supplier);
|
|
|
|
|
+ this.ttl = ttl;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected V put(K key, V value) {
|
|
|
|
|
+ map.put(key, new TRef<K, V>(this, key, value, ttl));
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Reference<V> getReference(K key) {
|
|
|
|
|
+ return map.get(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected void remove(K key) {
|
|
|
|
|
+ map.remove(key);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void clear() {
|
|
|
|
|
+ map.clear();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static class KRef<K,V> extends SoftReference<V> {
|
|
|
|
|
+
|
|
|
|
|
+ public final CacheV2<K,V> src;
|
|
|
|
|
+ public final K key;
|
|
|
|
|
+
|
|
|
|
|
+ public KRef(CacheV2<K,V> src, K key, V value) {
|
|
|
|
|
+ super(value, QUEUE);
|
|
|
|
|
+ this.src = src;
|
|
|
|
|
+ this.key = key;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static class TRef<K,V> extends KRef<K,V> {
|
|
|
|
|
+
|
|
|
|
|
+ public Instant time;
|
|
|
|
|
+
|
|
|
|
|
+ public TRef(CacheV2<K,V> src, K key, V value, Duration ttl) {
|
|
|
|
|
+ super(src, key, value);
|
|
|
|
|
+ this.time = Instant.now().plus(ttl);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public V get() {
|
|
|
|
|
+ return time.isBefore(Instant.now()) ? null : super.get();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static class FHashMap<K,V> extends LinkedHashMap<K,V> {
|
|
|
|
|
+
|
|
|
|
|
+ private final int maxsize;
|
|
|
|
|
+
|
|
|
|
|
+ public FHashMap(int maxsize) {
|
|
|
|
|
+ super(Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, true);
|
|
|
|
|
+ this.maxsize = maxsize;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
|
|
|
|
+ return this.size() > maxsize;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|