|
|
@@ -6,250 +6,30 @@
|
|
|
*/
|
|
|
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;
|
|
|
|
|
|
-import net.ranides.assira.system.RuntimeUtils;
|
|
|
-
|
|
|
/**
|
|
|
* @author Ranides Atterwim <ranides@gmail.com>
|
|
|
*/
|
|
|
-public abstract class Cache<K,V> {
|
|
|
-
|
|
|
- protected static final ReferenceQueue QUEUE = new ReferenceQueue<>();
|
|
|
-
|
|
|
- protected static final boolean USE_THREAD = RuntimeUtils.getProperty("assira.cache.no-threads", "false").equals("false");
|
|
|
-
|
|
|
- static {
|
|
|
- if(USE_THREAD) {
|
|
|
- new Thread(Cache::cleanupThread, "assira cache cleanup").start();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- protected final Function<K,V> supplier;
|
|
|
-
|
|
|
- protected Cache(Function<K, V> supplier) {
|
|
|
- this.supplier = supplier;
|
|
|
- }
|
|
|
-
|
|
|
- public static <K,V> Cache<K,V> getInstance(Function<K,V> supplier) {
|
|
|
- return new SimpleCache<K, V>(supplier);
|
|
|
- }
|
|
|
-
|
|
|
- public static <K,V> Cache<K,V> getInstance(int capacity, Function<K,V> supplier) {
|
|
|
- return new FixedCache<K, V>(capacity, supplier);
|
|
|
- }
|
|
|
-
|
|
|
- public static <K,V> Cache<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 = iget(key);
|
|
|
- if(null == ref) {
|
|
|
- cleanup();
|
|
|
- return iput(key, function.get());
|
|
|
- }
|
|
|
- V ret = ref.get();
|
|
|
- if(null == ret) {
|
|
|
- cleanup();
|
|
|
- return iput(key, function.get());
|
|
|
- }
|
|
|
- return ret;
|
|
|
- }
|
|
|
-
|
|
|
- public final void clear() {
|
|
|
- iclear();
|
|
|
- cleanup();
|
|
|
- }
|
|
|
-
|
|
|
- protected abstract Reference<V> iget(K key);
|
|
|
-
|
|
|
- protected abstract V iput(K key, V value);
|
|
|
-
|
|
|
- protected abstract void iremove(K key);
|
|
|
-
|
|
|
- protected abstract void iclear();
|
|
|
-
|
|
|
- protected static void cleanup() {
|
|
|
- if(!USE_THREAD) {
|
|
|
- KRef ref;
|
|
|
- while ( null != (ref = (KRef)QUEUE.poll()) ) {
|
|
|
- ref.src.iremove(ref.key);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- protected static void cleanupThread() {
|
|
|
- try {
|
|
|
- while (true) {
|
|
|
- KRef ref = (KRef) QUEUE.remove();
|
|
|
- ref.src.iremove(ref.key);
|
|
|
- }
|
|
|
- } catch (InterruptedException cause) {
|
|
|
- // shutdown
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static final class SimpleCache<K,V> extends Cache<K,V> {
|
|
|
+public interface Cache<K,V> {
|
|
|
|
|
|
- private final Map<K,KRef<K,V>> map = new ConcurrentHashMap<>();
|
|
|
-
|
|
|
- public SimpleCache(Function<K, V> supplier) {
|
|
|
- super(supplier);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected V iput(K key, V value) {
|
|
|
- map.put(key, new KRef<K, V>(this, key, value));
|
|
|
- return value;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Reference<V> iget(K key) {
|
|
|
- return map.get(key);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected void iremove(K key) {
|
|
|
- map.remove(key);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void iclear() {
|
|
|
- map.clear();
|
|
|
- }
|
|
|
+ static <K,V> Cache<K,V> getInstance() {
|
|
|
+ return CacheMap.getInstance(e -> { throw new UnsupportedOperationException(); });
|
|
|
}
|
|
|
|
|
|
- private static final class FixedCache<K,V> extends Cache<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 iput(K key, V value) {
|
|
|
- synchronized (map) {
|
|
|
- map.put(key, new KRef<K, V>(this, key, value));
|
|
|
- }
|
|
|
- return value;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Reference<V> iget(K key) {
|
|
|
- synchronized (map) {
|
|
|
- return map.get(key);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected void iremove(K key) {
|
|
|
- synchronized (map) {
|
|
|
- map.remove(key);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void iclear() {
|
|
|
- synchronized (map) {
|
|
|
- map.clear();
|
|
|
- }
|
|
|
- }
|
|
|
+ static <K,V> Cache<K,V> getInstance(int capacity) {
|
|
|
+ return CacheMap.getInstance(capacity, e -> { throw new UnsupportedOperationException(); });
|
|
|
}
|
|
|
|
|
|
- private static final class TTLCache<K,V> extends Cache<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 iput(K key, V value) {
|
|
|
- map.put(key, new TRef<K, V>(this, key, value, ttl));
|
|
|
- return value;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public Reference<V> iget(K key) {
|
|
|
- return map.get(key);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- protected void iremove(K key) {
|
|
|
- map.remove(key);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void iclear() {
|
|
|
- map.clear();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private static class KRef<K,V> extends SoftReference<V> {
|
|
|
-
|
|
|
- public final Cache<K,V> src;
|
|
|
- public final K key;
|
|
|
-
|
|
|
- public KRef(Cache<K,V> src, K key, V value) {
|
|
|
- super(value, QUEUE);
|
|
|
- this.src = src;
|
|
|
- this.key = key;
|
|
|
- }
|
|
|
+ static <K,V> Cache<K,V> getInstance(Duration ttl) {
|
|
|
+ return CacheMap.getInstance(ttl, e -> { throw new UnsupportedOperationException(); });
|
|
|
}
|
|
|
|
|
|
- private static class TRef<K,V> extends KRef<K,V> {
|
|
|
-
|
|
|
- public Instant time;
|
|
|
-
|
|
|
- public TRef(Cache<K,V> src, K key, V value, Duration ttl) {
|
|
|
- super(src, key, value);
|
|
|
- this.time = Instant.now().plus(ttl);
|
|
|
- }
|
|
|
+ V get(K key, Function<K,V> function);
|
|
|
|
|
|
- @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;
|
|
|
- }
|
|
|
- }
|
|
|
+ V get(K key, Supplier<V> function);
|
|
|
|
|
|
+ void clear();
|
|
|
}
|