Ranides Atterwim 9 anos atrás
pai
commit
2df5793365

+ 233 - 0
assira/src/main/java/net/ranides/assira/collection/maps/CacheV2.java

@@ -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;
+        }
+    }
+
+}