Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; 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.NoSuchElementException; import java.util.Set; /** * A WeakValueHashMap is implemented as a HashMap that maps keys to * WeakValues. Because we don't have access to the innards of the * HashMap, we have to wrap/unwrap value objects with WeakValues on * every operation. Fortunately WeakValues are small, short-lived * objects, so the added allocation overhead is tolerable. This * implementaton directly extends java.util.HashMap. * * @author Markus Fuchs * @see java.util.HashMap * @see java.lang.ref.WeakReference */ public class WeakValueHashMap extends HashMap { /* Reference queue for cleared WeakValues */ private ReferenceQueue queue = new ReferenceQueue(); /** * Returns the number of key-value mappings in this map.<p> * @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 <tt>true</tt> if this map contains no key-value mappings.<p> * @return <tt>true</tt> if this map contains no key-value mappings. */ public boolean isEmpty() { return size() == 0; } /** * Returns <tt>true</tt> if this map contains a mapping for the specified * key.<p> * @param key key whose presence in this map is to be tested * @return <tt>true</tt> 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 <tt>true</tt> if this map maps one or more keys to the * specified value.<p> * @param value value whose presence in this map is to be tested * @return <tt>true</tt> 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.<p> * @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.<p> * @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.<p> * @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 <code>null</code> 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 <code>Set</code> view of the mappings in this map.<p> * @return a <code>Set</code> 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 <code>Collection</code> view of the values contained * in this map.<p> * @return a <code>Collection</code> 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; } }