package org.limewire.collection; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * A fixed size HashMap that provides indexed access. The replacement * policy is FIFO and the iteration order is from newest to oldest. * <p> * Adding an already existing element will postpone the ejection of that * element. * <p> * It does not support the null element. */ public class FixedSizeArrayHashMap<K, V> extends HashMap<K, V> implements RandomAccessMap<K, V> { private Buffer<Map.Entry<K, V>> buf; /** Creates a FixedSizeArrayHashMap with the specified maximum capacity. */ public FixedSizeArrayHashMap(int maxCapacity) { this.buf = new Buffer<Map.Entry<K, V>>(maxCapacity); } /** * Creates a new FixedSizeArrayHashMap with the provided maximum capacity * and adds elements from the specified Map. If the capacity is less than * the size of the Map, elements will get ejected with FIFO policy. */ public FixedSizeArrayHashMap(int maxCapacity, Map<? extends K, ? extends V> m) { this.buf = new Buffer<Map.Entry<K, V>>(maxCapacity); putAll(m); } /** * Creates a new FixedSizeArrayHashMap with the maximum capacity of the size * of the provided Map and adds all the elements of that Map. */ public FixedSizeArrayHashMap(Map<? extends K, ? extends V> m) { this(m.size(), m); } public FixedSizeArrayHashMap(int maxCapacity, int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); this.buf = new Buffer<Map.Entry<K, V>>(maxCapacity); } public FixedSizeArrayHashMap(int maxCapacity, int initialCapacity) { super(initialCapacity); this.buf = new Buffer<Map.Entry<K, V>>(maxCapacity); } @Override @SuppressWarnings("unchecked") public Object clone() { FixedSizeArrayHashMap<K, V> newSet = (FixedSizeArrayHashMap<K, V>)super.clone(); try { newSet.buf = buf.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } return newSet; } @Override public void clear() { buf.clear(); super.clear(); } @Override public Set<Map.Entry<K, V>> entrySet() { if(entrySet == null) entrySet = new EntrySet(); return entrySet; } public V getValueAt(int i) { return buf.get(i).getValue(); } public K getKeyAt(int i) { return buf.get(i).getKey(); } public Map.Entry<K, V> getEntryAt(int i) { return buf.get(i); } @Override public V put(K key, V value) { if(key == null || value == null) throw new IllegalArgumentException("null key/value not supported!"); V existing = super.put(key, value); if(existing == null) { // eject oldest element if size reached Map.Entry<K, V> removed = buf.add(new FixedEntry<K, V>(key, value)); if(removed != null) { Object removedValue = super.remove(removed.getKey()); assert removedValue == removed.getValue(); } } else { // refresh this element. FixedEntry<K, V> e = new FixedEntry<K, V>(key, value); boolean removed = buf.remove(e); assert removed; Object ejected = buf.add(e); assert ejected == null; } return existing; } @Override public V remove(Object key) { V removed = super.remove(key); if(removed != null) { boolean success = buf.remove(new FixedEntry<Object, V>(key, removed)); assert success; } return removed; } /** A duplicate entry for storage in the buffer. */ private static class FixedEntry<K, V> implements Map.Entry<K, V> { private final K key; private final V value; FixedEntry(K k, V v) { this.key = k; this.value = v; } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { throw new UnsupportedOperationException(); } @Override public boolean equals(Object obj) { FixedEntry e = (FixedEntry)obj; return key.equals(e.key) && value.equals(e.value); } @Override public String toString() { return key + "=" + value; } } protected Iterator<Map.Entry<K, V>> newEntryIterator() { return new ArrayHashMapEntryIterator(); } private class ArrayHashMapEntryIterator extends UnmodifiableIterator<Map.Entry<K, V>> { private final Iterator<Map.Entry<K,V>> iter = buf.iterator(); public boolean hasNext() { return iter.hasNext(); } public Map.Entry<K, V> next() { Map.Entry<K, V> current = iter.next(); return current; } } private transient volatile Set<Map.Entry<K, V>> entrySet = null; private class EntrySet extends AbstractSet<Map.Entry<K,V>> { @Override public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } @Override public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object key = e.getKey(); Object value = e.getValue(); return containsKey(key) && get(key).equals(value); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public int size() { return FixedSizeArrayHashMap.this.size(); } @Override public void clear() { FixedSizeArrayHashMap.this.clear(); } } //////////////////////////////////////////// // COPIED FROM AbstractMap! /** * Each of these fields are initialized to contain an instance of the * appropriate view the first time this view is requested. The views are * stateless, so there's no reason to create more than one of each. */ private transient volatile Set<K> keySet = null; private transient volatile Collection<V> values = null; /** * Returns a Set view of the keys contained in this map. The Set is * backed by the map, so changes to the map are reflected in the Set, * and vice-versa. (If the map is modified while an iteration over * the Set is in progress, the results of the iteration are undefined.) * The Set supports element removal, which removes the corresponding entry * from the map, via the Iterator.remove, Set.remove, removeAll * retainAll, and clear operations. It does not support the add or * addAll operations.<p> * * This implementation returns a Set that subclasses * AbstractSet. The subclass's iterator method returns a "wrapper * object" over this map's entrySet() iterator. The size method delegates * to this map's size method and the contains method delegates to this * map's containsKey method.<p> * * The Set is created the first time this method is called, * and returned in response to all subsequent calls. No synchronization * is performed, so there is a slight chance that multiple calls to this * method will not all return the same Set. * * @return a Set view of the keys contained in this map. */ @Override public Set<K> keySet() { if (keySet == null) { keySet = new AbstractSet<K>() { @Override public Iterator<K> iterator() { return new Iterator<K>() { private Iterator<Map.Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public K next() { return i.next().getKey(); } public void remove() { i.remove(); } }; } @Override public int size() { return FixedSizeArrayHashMap.this.size(); } @Override public boolean contains(Object k) { return FixedSizeArrayHashMap.this.containsKey(k); } }; } return keySet; } /** * Returns a collection view of the values contained in this map. The * collection is backed by the map, so changes to the map are reflected in * the collection, and vice-versa. (If the map is modified while an * iteration over the collection is in progress, the results of the * iteration are undefined.) The collection supports element removal, * which removes the corresponding entry from the map, via the * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, * <tt>removeAll</tt>, <tt>retainAll</tt> and <tt>clear</tt> operations. * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.<p> * * This implementation returns a collection that subclasses abstract * collection. The subclass's iterator method returns a "wrapper object" * over this map's <tt>entrySet()</tt> iterator. The size method * delegates to this map's size method and the contains method delegates * to this map's containsValue method.<p> * * The collection is created the first time this method is called, and * returned in response to all subsequent calls. No synchronization is * performed, so there is a slight chance that multiple calls to this * method will not all return the same Collection. * * @return a collection view of the values contained in this map. */ @Override public Collection<V> values() { if (values == null) { values = new AbstractCollection<V>() { @Override public Iterator<V> iterator() { return new Iterator<V>() { private Iterator<Map.Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() { return i.hasNext(); } public V next() { return i.next().getValue(); } public void remove() { i.remove(); } }; } @Override public int size() { return FixedSizeArrayHashMap.this.size(); } @Override public boolean contains(Object v) { return FixedSizeArrayHashMap.this.containsValue(v); } }; } return values; } }