packageorg.rx.cache;import org.rx.common.*;importorg.rx.beans.DateTime;importjava.util.Collections;importjava.util.LinkedHashMap;importjava.util.Map;importjava.util.concurrent.Future;importjava.util.function.Consumer;importjava.util.function.Function;import staticorg.rx.common.Contract.as;import staticorg.rx.common.Contract.require;import staticorg.rx.util.AsyncTask.TaskFactory;public final class LRUCache extendsDisposable {private classCacheItem {publicTV value;private intexpireSeconds;privateDateTime createTime;private ConsumerexpireCallback;public CacheItem(TV value, int expireSeconds, ConsumerexpireCallback) {this.value =value;this.expireSeconds =expireSeconds;this.expireCallback =expireCallback;
refresh();
}public voidrefresh() {if (expireSeconds > -1) {
createTime=DateTime.utcNow();
}
}public voidcallback() {if (expireCallback != null) {
expireCallback.accept(value);
}
}
}private static final Lazy> instance = new Lazy<>(() -> new LRUCache<>(1000, 120));public static LRUCachegetInstance() {returninstance.getValue();
}public static Object getOrStore(String key, Functionsupplier) {
require(key, supplier);return getInstance().getOrAdd(App.cacheKey(key), p ->supplier.apply(key));
}private final Mapcache;private intmaxCapacity;private intexpireSeconds;private ConsumerexpireCallback;privateFuture future;public LRUCache(int maxSize, intexpireSecondsAfterAccess) {this(maxSize, expireSecondsAfterAccess, 8 * 1000, null);
}public LRUCache(int maxSize, int expireSecondsAfterAccess, long checkPeriod, ConsumerremoveCallback) {
cache= Collections.synchronizedMap(new LinkedHashMap(maxSize + 1, .75F, true) {
@Overrideprotected boolean removeEldestEntry(Map.Entryeldest) {boolean remove = size() >maxCapacity;if(remove) {
eldest.getValue().callback();
}returnremove;
}
});
maxCapacity=maxSize;
expireSeconds=expireSecondsAfterAccess;
expireCallback=removeCallback;
future= TaskFactory.schedule(() ->{for (Map.Entry entry : NQuery.of(cache.entrySet()).where(p -> p.getValue().expireSeconds > -1
&& DateTime.utcNow().addSeconds(-p.getValue().expireSeconds).after(p.getValue().createTime))) {
entry.getValue().callback();
cache.remove(entry.getKey());
}
}, checkPeriod);
}
@Overrideprotected voidfreeObjects() {if (future != null) {
future.cancel(true);
}
cache.clear();
}public voidadd(TK key, TV val) {
add(key, val, expireSeconds, expireCallback);
}public void add(TK key, TV val, int expireSecondsAfterAccess, ConsumerremoveCallback) {
require(key);
cache.put(key,newCacheItem(val, expireSecondsAfterAccess, removeCallback));
}public voidremove(TK key) {
remove(key,true);
}public void remove(TK key, booleandestroy) {
require(key);
CacheItem remove=cache.remove(key);if (remove == null) {return;
}
AutoCloseable ac;if (destroy && (ac = as(remove.value, AutoCloseable.class)) != null) {try{
ac.close();
}catch(Exception ex) {
Logger.error(ex,"Auto close error");
}
}
}publicTV get(TK key) {
require(key);
CacheItem item=cache.get(key);if (item == null) {return null;
}
item.refresh();returnitem.value;
}public TV getOrAdd(TK key, Functionsupplier) {
require(key, supplier);
CacheItem item=cache.get(key);if (item == null) {
cache.put(key, item= newCacheItem(supplier.apply(key), expireSeconds, expireCallback));
}else{
item.refresh();
}returnitem.value;
}
}