All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.collect.ReferenceMap Maven / Gradle / Ivy

Go to download

Google Collections Library is a suite of new collections and collection-related goodness for Java 5.0

The newest version!
/*
 * Copyright (C) 2007 Google Inc.
 *
 * Licensed 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 com.google.common.collect;

import com.google.common.base.FinalizableSoftReference;
import com.google.common.base.FinalizableWeakReference;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.ReferenceType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * A {@code ConcurrentMap} implementation that internally utilizes your choice
 * of strong, soft or weak references for its keys and for its values. As soon
 * as any key or value is reclaimed by the garbage collector, the corresponding
 * entry automatically disappears from the map.
 *
 * 

All nine possible combinations of reference types are supported, although * using strong keys with strong values provides no benefit over using a {@code * Map} or {@code ConcurrentMap} directly. This implementation does not permit * null keys or values. * *

Note: because garbage collection happens concurrently to your * program, it follows that this map is always subject to concurrent * modifications, whether or not the caller exposes it to multiple application * threads. The usual caveats about the reliability of methods such as {@link * #size} and {@link Map#equals} apply; for example, {@link #size} may be * observed to remain unchanged for a short time after an entry was reclaimed. * *

To determine equality to a key, this implementation uses * {@link Object#equals} for strong references, and identity-based equality for * soft and weak references. In other words, for a map with weak or soft key * references, {@link #get} returns {@code null} when passed an object that * equals a map key, but isn't the same instance. This behavior is similar to * the way {@link IdentityHashMap} handles key lookups. However, to determine * value equality, as occurs when {@link #containsValue} is called, the * {@code ReferenceMap} always uses {@code equals}, regardless of the value * reference type. * *

Note: {@code new ReferenceMap(WEAK, STRONG)} is very nearly a * drop-in replacement for {@link WeakHashMap}, but improves upon this by using * only identity-based equality for keys. When possible, {@code ReferenceMap} * should be preferred over the JDK collection, for its concurrency and greater * flexibility. * *

Though this class implements {@link Serializable}, serializing reference * maps with weak or soft references leads to unpredictable results. * * @author Bob Lee * @author Kevin Bourrillion */ public final class ReferenceMap extends AbstractMap implements ConcurrentMap, Serializable { private final ReferenceStrategy keyStrategy; private final ReferenceStrategy valueStrategy; /* * The keys in this map are either of type K, SoftReference, or * WeakReference, depending on the chosen keyReferenceType; and likewise * for the values. */ private transient ConcurrentMap delegate; /** * Constructs an empty instance, using the given reference types for keys and * values. */ public ReferenceMap( ReferenceType keyReferenceType, ReferenceType valueReferenceType) { this(keyReferenceType, valueReferenceType, new ConcurrentHashMap()); } /** * Constructs an empty instance, using the given backing map and the given * reference types for keys and values. */ public ReferenceMap(ReferenceType keyReferenceType, ReferenceType valueReferenceType, ConcurrentMap backingMap) { checkArgument(keyReferenceType != ReferenceType.PHANTOM, "Phantom references are not supported."); checkArgument(valueReferenceType != ReferenceType.PHANTOM, "Phantom references are not supported."); checkArgument(backingMap.isEmpty(), "The backing map must be empty."); keyStrategy = ReferenceStrategy.forType(keyReferenceType); valueStrategy = ReferenceStrategy.forType(valueReferenceType); delegate = backingMap; } /* * Specifying Javadoc for many classes so the AbstractMap Javadoc, which * includes incorrect implementation details, is not displayed. */ // Query Operations /** * Returns the number of key-value mappings in this map. */ @Override public int size() { return delegate.size(); } /** * Returns {@code true} if this map contains no key-value mappings. */ @Override public boolean isEmpty() { return delegate.isEmpty(); } /** * Returns {@code true} if this map contains a mapping for the specified key. */ @Override public boolean containsKey(Object key) { Object keyDummy = keyStrategy.getDummyFor(key); return delegate.containsKey(keyDummy); } /** * Returns {@code true} if this map maps one or more keys to the specified * value. */ @Override public boolean containsValue(Object value) { checkNotNull(value); for (Object valueReference : delegate.values()) { if (value.equals(dereferenceValue(valueReference))) { return true; } } return false; } /** * Returns the value to which the specified key is mapped, or {@code null} if * this map contains no mapping for the key. */ @Override public V get(Object key) { Object keyDummy = keyStrategy.getDummyFor(key); Object valueReference = delegate.get(keyDummy); return dereferenceValue(valueReference); } // Modification Operations /** * Associates the specified value with the specified key in this map. */ @Override public V put(K key, V value) { Object keyReference = referenceKey(key); Object valueReference = referenceValue(keyReference, value); return dereferenceValue(delegate.put(keyReference, valueReference)); } public V putIfAbsent(K key, V value) { Object keyReference = referenceKey(key); Object valueReference = referenceValue(keyReference, value); Object existingValueReference; V existingValue; do { existingValueReference = delegate.putIfAbsent(keyReference, valueReference); existingValue = dereferenceValue(existingValueReference); } while (isPartiallyReclaimed(existingValueReference, existingValue)); return existingValue; } public V replace(K key, V value) { Object keyReference = referenceKey(key); Object valueReference = referenceValue(keyReference, value); // Ensure that the existing value is not collected. do { Object existingValueReference; V existingValue; do { existingValueReference = delegate.get(keyReference); /* * This method as a side-effect will proactively call * finalizeReference() as necessary, which prevents this loop from * spinning for a long time. */ existingValue = dereferenceValue(existingValueReference); } while (isPartiallyReclaimed(existingValueReference, existingValue)); if (existingValueReference == null) { return null; // nothing to replace } if (delegate.replace( keyReference, existingValueReference, valueReference)) { // existingValue didn't expire since we still have a reference to it return existingValue; } } while (true); } public boolean replace(K key, V oldValue, V newValue) { /* * It's surprising how much simpler this, the "more-discriminating" form of * replace(), is to implement than the other, "less-discriminating" form. * The difference is that, because we have a strong reference to * 'oldValue', we know that it can't have been garbage collected and so we * can skip all the logic that handles that case. */ Object keyReference = referenceKey(key); Object oldValueDummy = valueStrategy.getDummyFor(oldValue); Object newValueReference = referenceValue(keyReference, newValue); return delegate.replace(keyReference, oldValueDummy, newValueReference); } /** * Returns {@code true} if the specified value reference has been garbage * collected. The value behind the reference is also passed in, rather than * queried inside this method, to ensure that the return statement of this * method will still hold true after it has returned (that is, a value * reference exists outside of this method which will prevent that value from * being garbage collected). A {@code false} result may indicate either that * the value has been fully reclaimed, or that it has not been reclaimed at * all. * * @param valueReference the value reference to be tested * @param value the object referenced by {@code valueReference} * @return {@code true} if {@code valueReference} is non-null and {@code * value} is null */ private static boolean isPartiallyReclaimed( Object valueReference, Object value) { return (valueReference != null) && (value == null); } /** * Removes the mapping for a key from this map if it is present. */ @Override public V remove(Object key) { Object keyDummy = keyStrategy.getDummyFor(key); Object valueReference = delegate.remove(keyDummy); return dereferenceValue(valueReference); } public boolean remove(Object key, Object value) { Object keyDummy = keyStrategy.getDummyFor(key); Object valueDummy = valueStrategy.getDummyFor(value); return delegate.remove(keyDummy, valueDummy); } // Bulk Operations // Inherit putAll() from AbstractMap /** * Removes all of the mappings from this map. */ @Override public void clear() { delegate.clear(); } // Views // Inherit keySet() and values() from AbstractMap private transient EntrySet entrySet; /** * {@inheritDoc} * *

Note: Regardless of the choice of key and value reference types, * an entry in the entry set always has strong references to both key and * value. You should avoid any lingering strong references to {@code Entry} * objects. */ @Override public Set> entrySet() { EntrySet es = entrySet; return (es == null) ? (entrySet = new EntrySet()) : es; } private class EntrySet extends AbstractSet> { @Override public int size() { return ReferenceMap.this.size(); } @Override public boolean isEmpty() { return ReferenceMap.this.isEmpty(); } @Override public boolean contains(Object object) { checkNotNull(object); if (!(object instanceof Map.Entry)) { return false; } Map.Entry entry = (Map.Entry) object; Object key = entry.getKey(); Object value = entry.getValue(); return key != null && value != null && value.equals(get(key)); } @Override public Iterator> iterator() { return new EntryIterator(); } /* * Note: the superclass toArray() methods assume that size() gives a correct * answer, which ours does not. */ @Override public Object[] toArray() { return snapshot().toArray(); } @Override public T[] toArray(T[] array) { checkNotNull(array); return snapshot().toArray(array); } /* * We'd love to use 'new ArrayList(this)' or 'list.addAll(this)', but either * of these would recurse back to us again! */ private List> snapshot() { List> list = Lists.newArrayListWithExpectedSize(size()); for (Map.Entry entry : this) { list.add(entry); } return list; } @Override public boolean remove(Object object) { checkNotNull(object); if (object instanceof Map.Entry) { Map.Entry entry = (Map.Entry) object; return ReferenceMap.this.remove(entry.getKey(), entry.getValue()); } return false; } @Override public void clear() { ReferenceMap.this.clear(); } } private class EntryIterator extends AbstractRemovableIterator> { Iterator> delegateEntries = delegate.entrySet().iterator(); @Override protected Map.Entry computeNext() { while (delegateEntries.hasNext()) { Map.Entry entry = delegateEntries.next(); Object reference = entry.getKey(); @SuppressWarnings("unchecked") final K key = (K) keyStrategy.dereferenceKey(reference); if (key != null) { final V value = dereferenceValue(entry.getValue()); if (value != null) { return new AbstractMapEntry() { V currentValue = value; @Override public K getKey() { return key; } @Override public V getValue() { return currentValue; } @Override public V setValue(V newValue) { put(key, newValue); try { return currentValue; } finally { currentValue = newValue; } } }; } } // Otherwise, skip over this partially-GC'ed entry. } return endOfData(); } /* * On a typical Map this would produce a ConcurrentModificationException, * but we're using a ConcurrentMap, which of course has no problem. */ @Override public void remove(Map.Entry entry) { ReferenceMap.this.remove(entry.getKey(), entry.getValue()); } } // Serialization /* * "Override" default serialization so that we serialize the wrapped values * themselves (of type K and V), since serializing References would be absurd. */ private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // referenceType fields out.writeInt(size()); for (Map.Entry entry : entrySet()) { out.writeObject(entry.getKey()); out.writeObject(entry.getValue()); } out.writeObject(null); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // sets the referenceType fields int approximateSize = in.readInt(); delegate = new ConcurrentHashMap( Maps.capacity(approximateSize)); while (true) { @SuppressWarnings("unchecked") // see writeObject() K key = (K) in.readObject(); if (key == null) { break; } @SuppressWarnings("unchecked") // see writeObject() V value = (V) in.readObject(); put(key, value); } } // The rest of the file is all private machinery private enum ReferenceStrategy { DIRECT { @Override Object referenceKey(ReferenceMap map, Object key) { return key; } @Override Object referenceValue( ReferenceMap map, Object keyReference, Object value) { return value; } @Override Object dereferenceKey(Object reference) { return reference; } @Override Object dereferenceValue(Object object) { return object; } @Override Object getDummyFor(Object object) { return checkNotNull(object); } }, WRAP_IN_SOFT { @Override Object referenceKey(ReferenceMap map, Object key) { return map.new SoftKeyReference(key); } @Override Object referenceValue( ReferenceMap map, Object keyReference, Object value) { return map.new SoftValueReference(keyReference, value); } }, WRAP_IN_WEAK { @Override Object referenceKey(ReferenceMap map, Object key) { return map.new WeakKeyReference(key); } @Override Object referenceValue( ReferenceMap map, Object keyReference, Object value) { return map.new WeakValueReference(keyReference, value); } }; abstract Object referenceKey(ReferenceMap map, Object key); abstract Object referenceValue( ReferenceMap map, Object keyReference, Object value); Object dereferenceKey(Object reference) { return ((Reference) reference).get(); } Object dereferenceValue(Object object) { InternalReference reference = (InternalReference) object; Object value = reference.get(); /* * It's important that we proactively try to finalize the referent, rather * rather than waiting on the queue, in particular because of the * do/while loop in replace(). */ if (value == null) { reference.finalizeReferent(); // The old value was garbage collected. } return value; } Object getDummyFor(Object object) { return new DummyReference(checkNotNull(object)); } static ReferenceStrategy forType(ReferenceType type) { switch (checkNotNull(type)) { case STRONG: return ReferenceStrategy.DIRECT; case SOFT: return ReferenceStrategy.WRAP_IN_SOFT; case WEAK: return ReferenceStrategy.WRAP_IN_WEAK; default: throw new AssertionError(); } } } /* * Marker interface to differentiate external and internal references. Also * duplicates finalizeReferent() and Reference.get() for internal use. */ private interface InternalReference { void finalizeReferent(); Object get(); } private class SoftKeyReference extends FinalizableSoftReference implements InternalReference { final int hashCode; SoftKeyReference(Object key) { super(key); hashCode = System.identityHashCode(key); } public void finalizeReferent() { delegate.remove(this); } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object object) { return referenceEquals(this, object); } } private class SoftValueReference extends FinalizableSoftReference implements InternalReference { final Object keyReference; SoftValueReference(Object keyReference, Object value) { super(value); this.keyReference = keyReference; } public void finalizeReferent() { delegate.remove(keyReference, this); } @Override public int hashCode() { // It's hard to define a useful hash code, so we're careful not to use it. throw new AssertionError("don't hash me"); } @Override public boolean equals(Object obj) { return referenceEquals(this, obj); } } /* * WeakKeyReference/WeakValueReference are absolutely identical to * SoftKeyReference/SoftValueReference except for which classes they extend. */ private class WeakKeyReference extends FinalizableWeakReference implements InternalReference { final int hashCode; WeakKeyReference(Object key) { super(key); hashCode = System.identityHashCode(key); } public void finalizeReferent() { delegate.remove(this); } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object object) { return referenceEquals(this, object); } } private class WeakValueReference extends FinalizableWeakReference implements InternalReference { final Object keyReference; WeakValueReference(Object keyReference, Object value) { super(value); this.keyReference = keyReference; } public void finalizeReferent() { delegate.remove(keyReference, this); } @Override public int hashCode() { // It's hard to define a useful hash code, so we're careful not to use it. throw new AssertionError("don't hash me"); } @Override public boolean equals(Object obj) { return referenceEquals(this, obj); } } /* * A short-lived object that is contrived for the purpose of passing to * methods of the backing map such as get(), remove(), containsKey(), and * replace() (all parameters but the last). That is, it's an object suitable * only for use with the backing map, and only for _comparison_ purposes. This * is a hack that lets us compare keys and values to referenced keys and * values without creating more references. */ private static class DummyReference { final Object wrapped; DummyReference(Object wrapped) { this.wrapped = wrapped; } Object unwrap() { return wrapped; } @Override public int hashCode() { return System.identityHashCode(wrapped); } @Override public boolean equals(Object object) { return object.equals(this); // Defer to the reference's equals() logic. } } /* * Tests weak and soft references for identity equality. Compares references * to other references and wrappers. If o is a reference, this returns true if * r == o or if r and o reference the same non-null object. If o is a wrapper, * this returns true if r's referent is identical to the wrapped object. */ private static boolean referenceEquals(Reference reference, Object object) { // Are they the same reference? Used in cleanup. if (object == reference) { return true; } if (object instanceof InternalReference) { /* * Do they reference identical values? Used in conditional puts. We can * assume it's of type InternalReference now, as no one outside * ReferenceMap can be invoking equals(). */ Object referent = ((InternalReference) object).get(); return referent != null && referent == reference.get(); } // Is the wrapped object identical to the referent? Used in lookups. return ((DummyReference) object).unwrap() == reference.get(); } private Object referenceKey(K key) { return keyStrategy.referenceKey(this, checkNotNull(key)); } private Object referenceValue(Object keyReference, V value) { return valueStrategy.referenceValue( this, keyReference, checkNotNull(value)); } /* * Converts a reference to a value. Do not call this method without being * certain that the object is a reference to a value type (V). */ @SuppressWarnings("unchecked") private V dereferenceValue(Object object) { if (object == null) { return null; } return (V) valueStrategy.dereferenceValue(object); } private static final long serialVersionUID = 0L; }