| /* |
| * 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. |
| */ |
| |
| package org.apache.jdo.util; |
| |
| 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; |
| } |
| |
| } |