* @return the number of key-value mappings in this map. */ public int size() { // delegate to entrySet, as super.size() also counts WeakValues return entrySet().size(); } /** * Returns true if this map contains no key-value mappings.
* @return true if this map contains no key-value mappings. */ public boolean isEmpty() { return size() == 0; } /** * Returns true if this map contains a mapping for the specified * key.
* @param key key whose presence in this map is to be tested * @return true if this map contains a mapping for the specified * key. */ public boolean containsKey(Object key) { // need to clean up gc'ed values before invoking super method processQueue(); return super.containsKey(key); } /** * Returns true if this map maps one or more keys to the * specified value.
* @param value value whose presence in this map is to be tested * @return true if this map maps one or more keys to this value. */ public boolean containsValue(Object value) { return super.containsValue(WeakValue.create(value)); } /** * Gets the value for the given key.
* @param key key whose associated value, if any, is to be returned * @return the value to which this map maps the specified key. */ public Object get(Object key) { // We don't need to remove garbage collected values here; // if they are garbage collected, the get() method returns null; // the next put() call with the same key removes the old value // automatically so that it can be completely garbage collected return getReferenceObject((WeakReference) super.get(key)); } /** * Puts a new (key,value) into the map.
* @param key key with which the specified value is to be associated. * @param value value to be associated with the specified key. * @return previous value associated with specified key, or null * if there was no mapping for key or the value has been garbage * collected by the garbage collector. */ public Object put(Object key, Object value) { // If the map already contains an equivalent key, the new key // of a (key, value) pair is NOT stored in the map but the new // value only. But as the key is strongly referenced by the // map, it can not be removed from the garbage collector, even // if the key becomes weakly reachable due to the old // value. So, it isn't necessary to remove all garbage // collected values with their keys from the map before the // new entry is made. We only clean up here to distribute // clean up calls on different operations. processQueue(); WeakValue oldValue = (WeakValue)super.put(key, WeakValue.create(key, value, queue)); return getReferenceObject(oldValue); } /** * Removes key and value for the given key.
* @param key key whose mapping is to be removed from the map.
* @return previous value associated with specified key, or null
* if there was no mapping for key or the value has been garbage
* collected by the garbage collector.
*/
public Object remove(Object key) {
return getReferenceObject((WeakReference) super.remove(key));
}
/**
* A convenience method to return the object held by the
* weak reference or null
if it does not exist.
*/
private final Object getReferenceObject(WeakReference ref) {
return (ref == null) ? null : ref.get();
}
/**
* Removes all garbage collected values with their keys from the map.
* Since we don't know how much the ReferenceQueue.poll() operation
* costs, we should not call it every map operation.
*/
private void processQueue() {
WeakValue wv = null;
while ((wv = (WeakValue) this.queue.poll()) != null) {
// "super" is not really necessary but use it
// to be on the safe side
super.remove(wv.key);
}
}
/* -- Helper classes -- */
/**
* We need this special class to keep the backward reference from
* the value to the key, so that we are able to remove the key if
* the value is garbage collected.
*/
private static class WeakValue extends WeakReference {
/**
* It's the same as the key in the map. We need the key to remove
* the value if it is garbage collected.
*/
private Object key;
private WeakValue(Object value) {
super(value);
}
/**
* Creates a new weak reference without adding it to a
* ReferenceQueue.
*/
private static WeakValue create(Object value) {
if (value == null) return null;
else return new WeakValue(value);
}
private WeakValue(Object key, Object value, ReferenceQueue queue) {
super(value, queue);
this.key = key;
}
/**
* Creates a new weak reference and adds it to the given queue.
*/
private static WeakValue create(Object key, Object value,
ReferenceQueue queue) {
if (value == null) return null;
else return new WeakValue(key, value, queue);
}
/**
* A WeakValue is equal to another WeakValue iff they both refer
* to objects that are, in turn, equal according to their own
* equals methods.
*/
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof WeakValue))
return false;
Object ref1 = this.get();
Object ref2 = ((WeakValue) obj).get();
if (ref1 == ref2)
return true;
if ((ref1 == null) || (ref2 == null))
return false;
return ref1.equals(ref2);
}
/**
*
*/
public int hashCode() {
Object ref = this.get();
return (ref == null) ? 0 : ref.hashCode();
}
}
/**
* Internal class for entries. This class wraps/unwraps the
* values of the Entry objects returned from the underlying map.
*/
private class Entry implements Map.Entry {
private Map.Entry ent;
private Object value; /* Strong reference to value, so that the
GC will leave it alone as long as this
Entry exists */
Entry(Map.Entry ent, Object value) {
this.ent = ent;
this.value = value;
}
public Object getKey() {
return ent.getKey();
}
public Object getValue() {
return value;
}
public Object setValue(Object value) {
// This call changes the map. Please see the comment on
// the put method for the correctness remark.
Object oldValue = this.value;
this.value = value;
ent.setValue(WeakValue.create(getKey(), value, queue));
return oldValue;
}
private boolean valEquals(Object o1, Object o2) {
return (o1 == null) ? (o2 == null) : o1.equals(o2);
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry) o;
return (valEquals(ent.getKey(), e.getKey())
&& valEquals(value, e.getValue()));
}
public int hashCode() {
Object k;
return ((((k = ent.getKey()) == null) ? 0 : k.hashCode())
^ ((value == null) ? 0 : value.hashCode()));
}
}
/**
* Internal class for entry sets to unwrap/wrap WeakValues stored
* in the map.
*/
private class EntrySet extends AbstractSet {
public Iterator iterator() {
// remove garbage collected elements
processQueue();
return new Iterator() {
Iterator hashIterator = hashEntrySet.iterator();
Entry next = null;
public boolean hasNext() {
if (hashIterator.hasNext()) {
// since we removed garbage collected elements,
// we can simply return the next entry.
Map.Entry ent = (Map.Entry) hashIterator.next();
WeakValue wv = (WeakValue) ent.getValue();
Object v = (wv == null) ? null : wv.get();
next = new Entry(ent, v);
return true;
}
return false;
}
public Object next() {
if ((next == null) && !hasNext())
throw new NoSuchElementException();
Entry e = next;
next = null;
return e;
}
public void remove() {
hashIterator.remove();
}
};
}
public boolean isEmpty() {
return !(iterator().hasNext());
}
public int size() {
int j = 0;
for (Iterator i = iterator(); i.hasNext(); i.next()) j++;
return j;
}
public boolean remove(Object o) {
if (!(o instanceof Map.Entry)) return false;
Map.Entry e = (Map.Entry) o;
Object ek = e.getKey();
Object ev = e.getValue();
Object hv = WeakValueHashMap.this.get(ek);
if (hv == null) {
// if the map's value is null, we have to check, if the
// entry's value is null and the map contains the key
if ((ev == null) && WeakValueHashMap.this.containsKey(ek)) {
WeakValueHashMap.this.remove(ek);
return true;
} else {
return false;
}
// otherwise, simply compare the values
} else if (hv.equals(ev)) {
WeakValueHashMap.this.remove(ek);
return true;
}
return false;
}
public int hashCode() {
int h = 0;
for (Iterator i = hashEntrySet.iterator(); i.hasNext(); ) {
Map.Entry ent = (Map.Entry) i.next();
Object k;
WeakValue wv = (WeakValue) ent.getValue();
if (wv == null) continue;
h += ((((k = ent.getKey()) == null) ? 0 : k.hashCode())
^ wv.hashCode());
}
return h;
}
}
// internal helper variable, because we can't access
// entrySet from the superclass inside the EntrySet class
private Set hashEntrySet = null;
// stores the EntrySet instance
private Set entrySet = null;
/**
* Returns a Set
view of the mappings in this map.
* @return a Set
view of the mappings in this map.
*/
public Set entrySet() {
if (entrySet == null) {
hashEntrySet = super.entrySet();
entrySet = new EntrySet();
}
return entrySet;
}
// stores the value collection
private transient Collection values = null;
/**
* Returns a Collection
view of the values contained
* in this map.
* @return a Collection
view of the values contained
* in this map.
*/
public Collection values() {
// delegates to entrySet, because super method returns
// WeakValues instead of value objects
if (values == null) {
values = new AbstractCollection() {
public Iterator iterator() {
return new Iterator() {
private Iterator i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public Object next() {
return ((Entry)i.next()).getValue();
}
public void remove() {
i.remove();
}
};
}
public int size() {
return WeakValueHashMap.this.size();
}
public boolean contains(Object v) {
return WeakValueHashMap.this.containsValue(v);
}
};
}
return values;
}
}