/*************************************** * * * JBoss: The OpenSource J2EE WebOS * * * * Distributable under LGPL license. * * See terms of license at gnu.org. * * * ***************************************/ package org.jboss.util.collection; import java.util.Iterator; import java.util.Map; import java.util.AbstractMap; import java.util.HashMap; import java.util.Set; import java.util.AbstractSet; import java.util.NoSuchElementException; import java.lang.ref.WeakReference; import java.lang.ref.ReferenceQueue; /** * This Map will remove entries when the value in the map has been * cleaned from garbage collection * * @version $Revision: 1.2.4.1 $ * @author Bill Burke */ public class WeakValueHashMap extends AbstractMap implements Map { private static class WeakValueRef extends WeakReference { public Object key; private WeakValueRef(Object key, Object val, ReferenceQueue q) { super(val, q); this.key = key; } private static WeakValueRef create(Object key, Object val, ReferenceQueue q) { if (val == null) return null; else return new WeakValueRef(key, val, q); } } public Set entrySet() { processQueue(); return hash.entrySet(); } /* Hash table mapping WeakKeys to values */ private Map hash; /* Reference queue for cleared WeakKeys */ private ReferenceQueue queue = new ReferenceQueue(); /* Remove all invalidated entries from the map, that is, remove all entries whose values have been discarded. */ private void processQueue() { WeakValueRef ref; while ((ref = (WeakValueRef)queue.poll()) != null) { if (ref == (WeakValueRef) hash.get(ref.key)) { // only remove if it is the *exact* same WeakValueRef // hash.remove(ref.key); } } } /* -- Constructors -- */ /** * Constructs a new, empty WeakHashMap with the given * initial capacity and the given load factor. * * @param initialCapacity The initial capacity of the * WeakHashMap * * @param loadFactor The load factor of the WeakHashMap * * @throws IllegalArgumentException If the initial capacity is less than * zero, or if the load factor is * nonpositive */ public WeakValueHashMap(int initialCapacity, float loadFactor) { hash = new HashMap(initialCapacity, loadFactor); } /** * Constructs a new, empty WeakHashMap with the given * initial capacity and the default load factor, which is * 0.75. * * @param initialCapacity The initial capacity of the * WeakHashMap * * @throws IllegalArgumentException If the initial capacity is less than * zero */ public WeakValueHashMap(int initialCapacity) { hash = new HashMap(initialCapacity); } /** * Constructs a new, empty WeakHashMap with the default * initial capacity and the default load factor, which is * 0.75. */ public WeakValueHashMap() { hash = new HashMap(); } /** * Constructs a new WeakHashMap with the same mappings as the * specified Map. The WeakHashMap is created with an * initial capacity of twice the number of mappings in the specified map * or 11 (whichever is greater), and a default load factor, which is * 0.75. * * @param t the map whose mappings are to be placed in this map. * @since 1.3 */ public WeakValueHashMap(Map t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); } /* -- Simple queries -- */ /** * Returns the number of key-value mappings in this map. * Note: In contrast with most implementations of the * Map interface, the time required by this operation is * linear in the size of the map. */ public int size() { processQueue(); return hash.size(); } /** * Returns true if this map contains no key-value mappings. */ public boolean isEmpty() { processQueue(); return hash.isEmpty(); } /** * Returns true if this map contains a mapping for the * specified key. * * @param key The key whose presence in this map is to be tested */ public boolean containsKey(Object key) { processQueue(); return hash.containsKey(key); } /* -- Lookup and modification operations -- */ /** * Returns the value to which this map maps the specified key. * If this map does not contain a value for this key, then return * null. * * @param key The key whose associated value, if any, is to be returned */ public Object get(Object key) { processQueue(); WeakReference ref = (WeakReference)hash.get(key); if (ref != null) return ref.get(); return null; } /** * Updates this map so that the given key maps to the given * value. If the map previously contained a mapping for * key then that mapping is replaced and the previous value is * returned. * * @param key The key that is to be mapped to the given * value * @param value The value to which the given key is to be * mapped * * @return The previous value to which this key was mapped, or * null if if there was no mapping for the key */ public Object put(Object key, Object value) { processQueue(); Object rtn = hash.put(key, WeakValueRef.create(key, value, queue)); if (rtn != null) rtn = ((WeakReference)rtn).get(); return rtn; } /** * Removes the mapping for the given key from this map, if * present. * * @param key The key whose mapping is to be removed * * @return The value to which this key was mapped, or null if * there was no mapping for the key */ public Object remove(Object key) { processQueue(); return hash.remove(key); } /** * Removes all mappings from this map. */ public void clear() { processQueue(); hash.clear(); } }