package com.limegroup.gnutella.util; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * A mapping that "forgets" keys and values using a FIFO replacement * policy, much like a cache.<p> * * More formally, a ForgetfulHashMap 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 * some threshold. This threshold is fixed when the table is * constructed. <b>However, this property may not hold if keys are * remapped to different values</b>; if you need this, use * FixedsizeForgetfulHashmap.<p> * * Technically, this not a valid subtype of HashMap, but it makes * implementation really easy. =) Also, ForgetfulHashMap is <b>not * synchronized</b>. */ public class ForgetfulHashMap extends HashMap { /* 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. */ public Object put(Object key, Object value) { Object 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. */ public void putAll(Map t) { Iterator iter=t.keySet().iterator(); while (iter.hasNext()) { Object key=iter.next(); put(key,t.get(key)); } } }