package edu.harvard.iq.dataverse.util; import java.util.LinkedHashMap; import java.util.concurrent.locks.ReentrantLock; /** * A thread-safe implementation of a capped-size cache, where the removal is done * based on the "Least Recently Used" strategy. * This implementation allows some scenarios where the removed values are not the * least recently used, in order to provide better performance. * * @author michael * @param <K> Class for the cache keys * @param <V> Class for the cache values */ public class LruCache<K,V> { private final LinkedHashMap<K, V> cache = new LinkedHashMap<>(10, 0.75f, true); private final ReentrantLock cacheLock = new ReentrantLock(); private long maxSize = 128; /** * @param k The key to get * @return The value associated with {@code k}, or {@code null}, if there isn't any. */ public V get( K k ) { try { cacheLock.lock(); return cache.get(k); } finally { cacheLock.unlock(); } } /** * Associates {@code k} with {@code v}. * @param k the key * @param v the value * @return {@code v}, to allow method call chaining. */ public V put( K k, V v ) { try { cacheLock.lock(); cache.put(k, v); shrinkToMaxSize(); return v; } finally { cacheLock.unlock(); } } public long size() { try { cacheLock.lock(); return cache.size(); } finally { cacheLock.unlock(); } } public long getMaxSize() { return maxSize; } public void setMaxSize(long maxSize) { if ( maxSize < 1 ) { throw new IllegalArgumentException("Max cache size can't be less than 1"); } try { cacheLock.lock(); this.maxSize = maxSize; shrinkToMaxSize(); } finally { cacheLock.unlock(); } } public void invalidate() { try { cacheLock.lock(); cache.clear(); } finally { cacheLock.unlock(); } } public void invalidate( K k ) { try { cacheLock.lock(); cache.remove(k); } finally { cacheLock.unlock(); } } private void shrinkToMaxSize() { while( cache.size() > getMaxSize() ) { cache.remove( cache.entrySet().iterator().next().getKey() ); } } }