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));
        }
    }
}

