package cgeo.geocaching.utils;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Base class for caching objects. Don't mix up with a geocache !
*
* The LeastRecentlyUsedMap is basically a LinkedHashMap which can be configured to have certain modes of operation:
* <ul>
* <li> LRU_CACHE means that the elements are updated in the LinkedList on every get() access,
* so the objects that are dropped are the ones that haven't been used the longest</li>
* <li> BOUNDED means that objects are updated only when they are put,
* so the objects that are dropped are the ones that haven't been written the longest</li>
* </ul>
*/
public abstract class LeastRecentlyUsedMap<K, V> extends LinkedHashMap<K, V> {
private enum OperationModes {
LRU_CACHE, BOUNDED
}
private static final long serialVersionUID = -5077882607489806620L;
private final int maxEntries;
private final OperationModes opMode;
private RemoveHandler<V> removeHandler;
// store the HashMap parameters for serialization, as we can't access the originals in the LinkedHashMap
final int initialCapacity;
final float loadFactor;
protected LeastRecentlyUsedMap(final int maxEntries, final int initialCapacity, final float loadFactor, final OperationModes opMode) {
super(initialCapacity, loadFactor, opMode == OperationModes.LRU_CACHE);
this.initialCapacity = initialCapacity;
this.loadFactor = loadFactor;
this.maxEntries = maxEntries;
this.opMode = opMode;
}
protected LeastRecentlyUsedMap(final int maxEntries, final OperationModes opMode) {
this(maxEntries, 16, 0.75f, opMode);
}
@Override
public V put(final K key, final V value) {
// in case the underlying Map is not running with accessOrder==true, the map won't notice any changes
// of existing keys, so for the normal BOUNDED mode we remove and put the value to get its order updated.
if (opMode == OperationModes.BOUNDED && containsKey(key)) {
// avoid trigger the remove notification
final V oldVal = super.remove(key);
put(key, value);
return oldVal;
}
return super.put(key, value);
}
@Override
protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
return size() > maxEntries;
}
public int getMaxEntries() {
return maxEntries;
}
@Override
public V remove(final Object key) {
final V removed = super.remove(key);
if (removed != null && removeHandler != null) {
removeHandler.onRemove(removed);
}
return removed;
}
/**
* Sets a handler for remove notifications. Currently only one handler
* instance is supported
*
* @param removeHandler
* The new handler to receive notifications or null to remove a handler
*/
public void setRemoveHandler(final RemoveHandler<V> removeHandler) {
this.removeHandler = removeHandler;
}
public static class LruCache<K, V> extends LeastRecentlyUsedMap<K, V> {
private static final long serialVersionUID = 9028478916221334454L;
public LruCache(final int maxEntries, final int initialCapacity, final float loadFactor) {
super(maxEntries, initialCapacity, loadFactor, OperationModes.LRU_CACHE);
}
public LruCache(final int maxEntries) {
super(maxEntries, OperationModes.LRU_CACHE);
}
}
public static class Bounded<K, V> extends LeastRecentlyUsedMap<K, V> {
private static final long serialVersionUID = -1476389304214398315L;
public Bounded(final int maxEntries, final int initialCapacity, final float loadFactor) {
super(maxEntries, initialCapacity, loadFactor, OperationModes.BOUNDED);
}
public Bounded(final int maxEntries) {
super(maxEntries, OperationModes.BOUNDED);
}
}
/**
* Interface for handlers that wish to get notified when items are
* removed from the LRUMap
*
*/
public interface RemoveHandler<V> {
/**
* Method will be called on remove
*
* @param removed
* Item that has been removed
*/
void onRemove(V removed);
}
}