package freenet.support;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import freenet.support.Logger.LogLevel;
/**
* An LRU map from K to V. That is, when a mapping is added, it is
* pushed to the top of the queue, even if it was already present, and
* pop/peek operate from the bottom of the queue i.e. the least recently
* pushed. The caller must implement any size limit needed.
* FIXME most callers should be switched to LinkedHashMap.
* Does not support null keys.
* @param <K> The key type.
* @param <V> The value type.
*/
public class LRUMap<K, V> {
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
/** We use our own DoublyLinkedList implementation because it improves
* performance to be able to inherit from and refer to QItem's directly. */
private final DoublyLinkedListImpl<QItem<K, V>> list = new DoublyLinkedListImpl<QItem<K, V>>();
private final Map<K, QItem<K, V>> hash;
public LRUMap() {
hash = new HashMap<K, QItem<K, V>>();
}
/** Takes an arbitrary map */
private LRUMap(Map<K, QItem<K, V>> map) {
hash = map;
}
/** Create a LRUMap that is safe to use with keys that can be
* controlled by an attacker. Meaning one based on a TreeMap, not a
* HashMap (think hash collision DoS's). */
public static<K extends Comparable<K>,V> LRUMap<K,V> createSafeMap() {
return new LRUMap<K,V>(new TreeMap<K, QItem<K,V>>());
}
/** Create a LRUMap that is safe to use with keys that can be
* controlled by an attacker. Meaning one based on a TreeMap, not a
* HashMap (think hash collision DoS's). */
public static<K,V> LRUMap<K,V> createSafeMap(Comparator<K> comparator) {
return new LRUMap<K,V>(new TreeMap<K, QItem<K,V>>(comparator));
}
/**
* push()ing an object that is already in
* the queue moves that object to the most
* recently used position, but doesn't add
* a duplicate entry in the queue.
*/
public final synchronized V push(K key, V value) {
if(key == null)
throw new NullPointerException();
V old = null;
QItem<K,V> insert = hash.get(key);
if (insert == null) {
insert = new QItem<K, V>(key, value);
hash.put(key,insert);
} else {
old = insert.value;
insert.value = value;
list.remove(insert);
}
if(logMINOR)
Logger.minor(this, "Pushed "+insert+" ( "+key+ ' ' +value+" )");
list.unshift(insert);
return old;
}
/**
* @return Least recently pushed key.
*/
public final synchronized K popKey() {
if ( list.size() > 0 ) {
return hash.remove(list.pop().obj).obj;
} else {
return null;
}
}
/**
* @return Least recently pushed value.
*/
public final synchronized V popValue() {
if ( list.size() > 0 ) {
return hash.remove(list.pop().obj).value;
} else {
return null;
}
}
public final synchronized V peekValue() {
if ( list.size() > 0 ) {
return hash.get(list.tail().obj).value;
} else {
return null;
}
}
public final synchronized K peekKey() {
if ( list.size() > 0 ) {
return hash.get(list.tail().obj).obj;
} else {
return null;
}
}
public final int size() {
return list.size();
}
public final synchronized boolean removeKey(K key) {
if(key == null)
throw new NullPointerException();
QItem<K,V> i = (hash.remove(key));
if(i != null) {
list.remove(i);
return true;
} else {
return false;
}
}
/**
* Check if this queue contains obj
* @param obj Object to match
* @return true if this queue contains obj.
*/
public final synchronized boolean containsKey(K key) {
if(key == null)
throw new NullPointerException();
return hash.containsKey(key);
}
/**
* Note that this does not automatically promote the key. You have
* to do that by hand with push(key, value).
*/
public final synchronized V get(K key) {
if(key == null)
throw new NullPointerException();
QItem<K,V> q = hash.get(key);
if(q == null) return null;
return q.value;
}
public Enumeration<K> keys() {
return new ItemEnumeration();
}
public Enumeration<V> values() {
return new ValuesEnumeration();
}
private class ItemEnumeration implements Enumeration<K> {
private Enumeration<QItem<K, V>> source = list.reverseElements();
@Override
public boolean hasMoreElements() {
synchronized(LRUMap.this) {
return source.hasMoreElements();
}
}
@Override
public K nextElement() {
synchronized(LRUMap.this) {
return source.nextElement().obj;
}
}
}
private class ValuesEnumeration implements Enumeration<V> {
private Enumeration<QItem<K, V>> source = list.reverseElements();
@Override
public boolean hasMoreElements() {
synchronized(LRUMap.this) {
return source.hasMoreElements();
}
}
@Override
public V nextElement() {
synchronized(LRUMap.this) {
return source.nextElement().value;
}
}
}
public static class QItem<K, V> extends DoublyLinkedListImpl.Item<QItem<K, V>> {
public K obj;
public V value;
public QItem(K key, V val) {
this.obj = key;
this.value = val;
}
@Override
public String toString() {
return super.toString()+": "+obj+ ' ' +value;
}
}
public boolean isEmpty() {
return list.isEmpty();
}
/**
* Note that unlike the java.util versions, this will not reallocate (hence it doesn't return),
* so pass in an appropriately big array, and make sure you hold the lock!
* @param entries
* @return
*/
public synchronized void valuesToArray(V[] entries) {
Enumeration<V> values = values();
int i=0;
while(values.hasMoreElements())
entries[i++] = values.nextElement();
}
public synchronized void clear() {
list.clear();
hash.clear();
}
}