package iamrescue.routing.util; import java.util.Map; import java.util.Set; import javolution.util.FastMap; public class LeastRecentlyUsedMap<K, V> { private Map<K, LinkedValue<K, V>> map; private LinkedValue<K, V> mostRecentlyAccessed; private LinkedValue<K, V> leastRecentlyAccessed; private int maxCapacity; /** * Creates a new cache with the given maximum capacity and initial capacity. * * @param initialCapacity * Initial capacity * @param maxCapacity * Maximum capacity */ public LeastRecentlyUsedMap(int initialCapacity, int maxCapacity) { map = new FastMap<K, LinkedValue<K, V>>(initialCapacity); setMaximumCapacity(maxCapacity); } /** * Creates a new cache with the given maximum capacity. * * @param maxCapacity * Maximum capacity */ public LeastRecentlyUsedMap(int maxCapacity) { this(maxCapacity, maxCapacity); } /** * Returns the current key set. * * @return The key set. */ public Set<K> keySet() { return map.keySet(); } /** * The maximum capacity. * * @return The max capacity. */ public int getMaxCapacity() { return maxCapacity; } /** * * @return Size of the cache. */ public int size() { return map.size(); } /** * Adds a new element. If max Capacity is exceeded, the least recently * accessed entries are discarded. * * @param key * The key * @param value * The value */ public void put(K key, V value) { // Safely remove any previous ones. LinkedValue<K, V> old = map.remove(key); if (old != null) { removeFromChain(old); } LinkedValue<K, V> linkedValue = new LinkedValue<K, V>(key, value); map.put(key, linkedValue); makeMostRecent(linkedValue); checkSize(); } /** * Makes given value the most recent entry, fixing all links. * * @param value * The new most recent entry */ private void makeMostRecent(LinkedValue<K, V> value) { if (mostRecentlyAccessed == null) { mostRecentlyAccessed = value; assert (leastRecentlyAccessed == null); leastRecentlyAccessed = value; } if (!mostRecentlyAccessed.equals(value)) { value.previousValue = mostRecentlyAccessed; mostRecentlyAccessed.nextValue = value; mostRecentlyAccessed = value; } } /** * Removes links to/from neighbours of this value. * * @param toRemove * The value to remove. */ private void removeFromChain(LinkedValue<K, V> toRemove) { if (toRemove.equals(mostRecentlyAccessed)) { mostRecentlyAccessed = toRemove.previousValue; } if (toRemove.equals(leastRecentlyAccessed)) { leastRecentlyAccessed = toRemove.nextValue; } LinkedValue<K, V> previous = toRemove.previousValue; LinkedValue<K, V> next = toRemove.nextValue; if (previous != null) { previous.nextValue = next; } if (next != null) { next.previousValue = previous; } toRemove.nextValue = null; toRemove.previousValue = null; } public V get(K key) { LinkedValue<K, V> linkedValue = map.get(key); V value; if (linkedValue == null) { value = null; } else { value = linkedValue.value; if (!mostRecentlyAccessed.equals(linkedValue)) { removeFromChain(linkedValue); makeMostRecent(linkedValue); } } return value; } /** * Checks the size of the map and removes the least recently accessed * elements when necessary. */ private void checkSize() { while (map.size() > maxCapacity) { K leastRecentlyKey = leastRecentlyAccessed.key; leastRecentlyAccessed.nextValue.previousValue = null; map.remove(leastRecentlyKey); leastRecentlyAccessed = leastRecentlyAccessed.nextValue; } } /** * Sets the maximum capacity for this cache. If more elements than this are * added, the least recently accessed ones are discarded. * * @param maxCapacity * The maximum capacity. */ public void setMaximumCapacity(int maxCapacity) { if (maxCapacity <= 0) { throw new IllegalArgumentException("Capacity must be at least 1"); } this.maxCapacity = maxCapacity; checkSize(); } /** * Removes the entry with the given key. * * @param key * The key to remove. * @return The value associated with this key (or null if none). */ public V remove(K key) { LinkedValue<K, V> value = map.remove(key); if (value != null) { LinkedValue<K, V> next = value.nextValue; LinkedValue<K, V> previous = value.previousValue; if (next != null) { next.previousValue = previous; } else { assert value.equals(mostRecentlyAccessed); mostRecentlyAccessed = previous; } if (previous != null) { previous.nextValue = next; } else { assert value.equals(leastRecentlyAccessed); leastRecentlyAccessed = next; } } else { return null; } return value.value; } private static class LinkedValue<K, V> { private V value; private K key; private LinkedValue<K, V> nextValue; private LinkedValue<K, V> previousValue; public LinkedValue(K key, V value) { this.key = key; this.value = value; } } /** * Clears the map */ public void clear() { map.clear(); mostRecentlyAccessed = null; leastRecentlyAccessed = null; } }