package org.limewire.collection;
import java.util.HashMap;
import java.util.Map;
/**
* A mapping that "forgets" keys and values using a FIFO replacement
* policy, much like a cache.
* <p>
* More formally, a <code>ForgetfulHashMap</code> is a sequence of key-value pairs
* [ (K1, V1), ... (KN, VN) ] ordered from youngest to oldest. When
* inserting a new pair, (KN, VN) is discarded if N is greater than
* a size (this threshold is fixed when the table is
* constructed). <b>However, this property may not hold if keys are
* re-mapped to different values</b>; if you need to re-map keys to different
* values, use {@link FixedsizeForgetfulHashMap}.
* <p>
* Technically, this is not a valid subtype of <code>HashMap</code> (since items
* can be removed), but <code>HashMap</code> makes implementation easy.
* <p>
* This class is not thread-safe.
<pre>
ForgetfulHashMap<String, String> fhm = new ForgetfulHashMap<String, String>(2);
fhm.put("myKey1", "Abby");
fhm.put("myKey2", "Bob");
System.out.println(fhm);
fhm.put("myKey3", "Chris");
System.out.println(fhm);
fhm.put("myKey3", "replace");
System.out.println(fhm);
Output:
{myKey2=Bob, myKey1=Abby}
{myKey2=Bob, myKey3=Chris}
{myKey3=replace}
</pre>
*
*/
public class ForgetfulHashMap<K, V> extends HashMap<K, V> {
/* The current implementation is a hashtable for the mapping and
* a queue maintaining the ordering.
*
* The ABSTRACTION FUNCTION is
* [ (queue[next-1], super.get(queue[next-1])), ...,
* (queue[0], super.get(queue[0])),
* (queue[n], super.get(queue[n])), ...,
* (queue[next], super.get(queue[next])) ]
* BUT with null keys and/or values removed. This means that
* not all entries of queue need be valid keys into the map.
*
* The REP. INVARIANT is
* n==queue.length
* {k | map.get(k)!=null} <= {x | queue[i]==x && x!=null}
*
* Here "<=" means "is a subset of". In other words, every key in
* the map must be in the queue, though the opposite need not hold.
*
* Note that you could reduce the number of hashes necessary to
* purge old entries by exposing the rep. of the hashtable and
* keeping a queue of indices, not pointers. In this case, the
* hashtable should use probing, not chaining.
*/
private Object[] queue;
private int next;
private int n;
/**
* Create a new ForgetfulHashMap that holds only the last "size" entries.
*
* @param size the number of entries to hold
* @exception IllegalArgumentException if size is less < 1.
*/
public ForgetfulHashMap(int size) {
if (size < 1)
throw new IllegalArgumentException();
queue=new Object[size];
next=0;
n=size;
}
/**
* @requires key is not in this.
* @modifies this.
* @effects adds the given key value pair to this, removing some
* older mapping if necessary. The return value is undefined; it
* exists solely to conform with the superclass' signature.
*/
@Override
public V put(K key, V value) {
V ret=super.put(key,value);
//Purge oldest entry if we're all full, or if we'll become full
//after adding this entry. It's ok if queue[next] is no longer
//in the map.
if (queue[next]!=null) {
super.remove(queue[next]);
}
//And make (key,value) the newest entry. It is ok
//if key is already in queue.
queue[next]=key;
next++;
if (next>=n) {
next=0;
}
return ret;
}
/** Calls put on all keys in t. See put(Object, Object) for
* specification. */
@Override
public void putAll(Map<? extends K, ? extends V> t) {
for(Map.Entry<? extends K, ? extends V> entry : t.entrySet())
put(entry.getKey(), entry.getValue());
}
}