blob: 58446469f44cf6ab2df31968ea41f7b3400f80ef [file] [log] [blame]
/*
* Copyright (C) 2009 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.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.FinalizableReferenceQueue;
import com.google.common.base.FinalizableSoftReference;
import com.google.common.base.FinalizableWeakReference;
import com.google.common.base.Function;
import com.google.common.collect.CustomConcurrentHashMap.ComputingStrategy;
import com.google.common.collect.CustomConcurrentHashMap.Internals;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* A {@link ConcurrentMap} builder, providing any combination of these
* features: {@linkplain SoftReference soft} or {@linkplain WeakReference
* weak} keys, soft or weak values, timed expiration, and on-demand
* computation of values. Usage example: <pre> {@code
*
* ConcurrentMap<Key, Graph> graphs = new MapMaker()
* .concurrencyLevel(32)
* .softKeys()
* .weakValues()
* .expiration(30, TimeUnit.MINUTES)
* .makeComputingMap(
* new Function<Key, Graph>() {
* public Graph apply(Key key) {
* return createExpensiveGraph(key);
* }
* });}</pre>
*
* These features are all optional; {@code new MapMaker().makeMap()}
* returns a valid concurrent map that behaves exactly like a
* {@link ConcurrentHashMap}.
*
* The returned map is implemented as a hash table with similar performance
* characteristics to {@link ConcurrentHashMap}. It supports all optional
* operations of the {@code ConcurrentMap} interface. It does not permit
* null keys or values. It is serializable; however, serializing a map that
* uses soft or weak references can give unpredictable results.
*
* <p><b>Note:</b> by default, the returned map uses equality comparisons
* (the {@link Object#equals(Object) equals} method) to determine equality
* for keys or values. However, if {@link #weakKeys()} or {@link
* #softKeys()} was specified, the map uses identity ({@code ==})
* comparisons instead for keys. Likewise, if {@link #weakValues()} or
* {@link #softValues()} was specified, the map uses identity comparisons
* for values.
*
* <p>The returned map has <i>weakly consistent iteration</i>: an iterator
* over one of the map's view collections may reflect some, all or none of
* the changes made to the map after the iterator was created.
*
* <p>An entry whose key or value is reclaimed by the garbage collector
* immediately disappears from the map. (If the default settings of strong
* keys and strong values are used, this will never happen.) The client can
* never observe a partially-reclaimed entry. Any {@link java.util.Map.Entry}
* instance retrieved from the map's {@linkplain Map#entrySet() entry set}
* is snapshot of that entry's state at the time of retrieval.
*
* <p>{@code new MapMaker().weakKeys().makeMap()} can almost always be
* used as a drop-in replacement for {@link java.util.WeakHashMap}, adding
* concurrency, asynchronous cleanup, identity-based equality for keys, and
* great flexibility.
*
* @author Bob Lee
* @author Kevin Bourrillion
*/
@GwtCompatible(emulated = true)
public final class MapMaker {
private Strength keyStrength = Strength.STRONG;
private Strength valueStrength = Strength.STRONG;
private long expirationNanos = 0;
private boolean useCustomMap;
private final CustomConcurrentHashMap.Builder builder
= new CustomConcurrentHashMap.Builder();
/**
* Constructs a new {@code MapMaker} instance with default settings,
* including strong keys, strong values, and no automatic expiration.
*/
public MapMaker() {}
/**
* Sets a custom initial capacity (defaults to 16). Resizing this or
* any other kind of hash table is a relatively slow operation, so,
* when possible, it is a good idea to provide estimates of expected
* table sizes.
*
* @throws IllegalArgumentException if {@code initialCapacity} is
* negative
* @throws IllegalStateException if an initial capacity was already set
*/
public MapMaker initialCapacity(int initialCapacity) {
builder.initialCapacity(initialCapacity);
return this;
}
/**
* Guides the allowed concurrency among update operations. Used as a
* hint for internal sizing. The table is internally partitioned to try
* to permit the indicated number of concurrent updates without
* contention. Because placement in hash tables is essentially random,
* the actual concurrency will vary. Ideally, you should choose a value
* to accommodate as many threads as will ever concurrently modify the
* table. Using a significantly higher value than you need can waste
* space and time, and a significantly lower value can lead to thread
* contention. But overestimates and underestimates within an order of
* magnitude do not usually have much noticeable impact. A value of one
* is appropriate when it is known that only one thread will modify and
* all others will only read. Defaults to 16.
*
* @throws IllegalArgumentException if {@code concurrencyLevel} is
* nonpositive
* @throws IllegalStateException if a concurrency level was already set
*/
@GwtIncompatible("java.util.concurrent.ConcurrentHashMap concurrencyLevel")
public MapMaker concurrencyLevel(int concurrencyLevel) {
builder.concurrencyLevel(concurrencyLevel);
return this;
}
/**
* Specifies that each key (not value) stored in the map should be
* wrapped in a {@link WeakReference} (by default, strong references
* are used).
*
* <p><b>Note:</b> the map will use identity ({@code ==}) comparison
* to determine equality of weak keys, which may not behave as you expect.
* For example, storing a key in the map and then attempting a lookup
* using a different but {@link Object#equals(Object) equals}-equivalent
* key will always fail.
*
* @throws IllegalStateException if the key strength was already set
* @see WeakReference
*/
@GwtIncompatible("java.lang.ref.WeakReference")
public MapMaker weakKeys() {
return setKeyStrength(Strength.WEAK);
}
/**
* Specifies that each key (not value) stored in the map should be
* wrapped in a {@link SoftReference} (by default, strong references
* are used).
*
* <p><b>Note:</b> the map will use identity ({@code ==}) comparison
* to determine equality of soft keys, which may not behave as you expect.
* For example, storing a key in the map and then attempting a lookup
* using a different but {@link Object#equals(Object) equals}-equivalent
* key will always fail.
*
* @throws IllegalStateException if the key strength was already set
* @see SoftReference
*/
@GwtIncompatible("java.lang.ref.SoftReference")
public MapMaker softKeys() {
return setKeyStrength(Strength.SOFT);
}
private MapMaker setKeyStrength(Strength strength) {
if (keyStrength != Strength.STRONG) {
throw new IllegalStateException("Key strength was already set to "
+ keyStrength + ".");
}
keyStrength = strength;
useCustomMap = true;
return this;
}
/**
* Specifies that each value (not key) stored in the map should be
* wrapped in a {@link WeakReference} (by default, strong references
* are used).
*
* <p>Weak values will be garbage collected once they are weakly
* reachable. This makes them a poor candidate for caching; consider
* {@link #softValues()} instead.
*
* <p><b>Note:</b> the map will use identity ({@code ==}) comparison
* to determine equality of weak values. This will notably impact
* the behavior of {@link Map#containsValue(Object) containsValue},
* {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)},
* and {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}.
*
* @throws IllegalStateException if the key strength was already set
* @see WeakReference
*/
@GwtIncompatible("java.lang.ref.WeakReference")
public MapMaker weakValues() {
return setValueStrength(Strength.WEAK);
}
/**
* Specifies that each value (not key) stored in the map should be
* wrapped in a {@link SoftReference} (by default, strong references
* are used).
*
* <p>Soft values will be garbage collected in response to memory
* demand, and in a least-recently-used manner. This makes them a
* good candidate for caching.
*
* <p><b>Note:</b> the map will use identity ({@code ==}) comparison
* to determine equality of soft values. This will notably impact
* the behavior of {@link Map#containsValue(Object) containsValue},
* {@link ConcurrentMap#remove(Object, Object) remove(Object, Object)},
* and {@link ConcurrentMap#replace(Object, Object, Object) replace(K, V, V)}.
*
* @throws IllegalStateException if the value strength was already set
* @see SoftReference
*/
@GwtIncompatible("java.lang.ref.SoftReference")
public MapMaker softValues() {
return setValueStrength(Strength.SOFT);
}
private MapMaker setValueStrength(Strength strength) {
if (valueStrength != Strength.STRONG) {
throw new IllegalStateException("Value strength was already set to "
+ valueStrength + ".");
}
valueStrength = strength;
useCustomMap = true;
return this;
}
/**
* Specifies that each entry should be automatically removed from the
* map once a fixed duration has passed since the entry's creation.
*
* @param duration the length of time after an entry is created that it
* should be automatically removed
* @param unit the unit that {@code duration} is expressed in
* @throws IllegalArgumentException if {@code duration} is not positive
* @throws IllegalStateException if the expiration time was already set
*/
public MapMaker expiration(long duration, TimeUnit unit) {
if (expirationNanos != 0) {
throw new IllegalStateException("expiration time of "
+ expirationNanos + " ns was already set");
}
if (duration <= 0) {
throw new IllegalArgumentException("invalid duration: " + duration);
}
this.expirationNanos = unit.toNanos(duration);
useCustomMap = true;
return this;
}
/**
* Builds the final map, without on-demand computation of values. This method
* does not alter the state of this {@code MapMaker} instance, so it can be
* invoked again to create multiple independent maps.
*
* @param <K> the type of keys to be stored in the returned map
* @param <V> the type of values to be stored in the returned map
* @return a concurrent map having the requested features
*/
public <K, V> ConcurrentMap<K, V> makeMap() {
return useCustomMap
? new StrategyImpl<K, V>(this).map
: new ConcurrentHashMap<K, V>(builder.getInitialCapacity(),
0.75f, builder.getConcurrencyLevel());
}
/**
* Builds a map that supports atomic, on-demand computation of values. {@link
* Map#get} either returns an already-computed value for the given key,
* atomically computes it using the supplied function, or, if another thread
* is currently computing the value for this key, simply waits for that thread
* to finish and returns its computed value. Note that the function may be
* executed concurrently by multiple threads, but only for distinct keys.
*
* <p>If an entry's value has not finished computing yet, query methods
* besides {@code get} return immediately as if an entry doesn't exist. In
* other words, an entry isn't externally visible until the value's
* computation completes.
*
* <p>{@link Map#get} on the returned map will never return {@code null}. It
* may throw:
*
* <ul>
* <li>{@link NullPointerException} if the key is null or the computing
* function returns null
* <li>{@link ComputationException} if an exception was thrown by the
* computing function. If that exception is already of type {@link
* ComputationException}, it is propagated directly; otherwise it is
* wrapped.
* </ul>
*
* <p><b>Note:</b> Callers of {@code get} <i>must</i> ensure that the key
* argument is of type {@code K}. The {@code get} method accepts {@code
* Object}, so the key type is not checked at compile time. Passing an object
* of a type other than {@code K} can result in that object being unsafely
* passed to the computing function as type {@code K}, and unsafely stored in
* the map.
*
* <p>If {@link Map#put} is called before a computation completes, other
* threads waiting on the computation will wake up and return the stored
* value. When the computation completes, its new result will overwrite the
* value that was put in the map manually.
*
* <p>This method does not alter the state of this {@code MapMaker} instance,
* so it can be invoked again to create multiple independent maps.
*/
public <K, V> ConcurrentMap<K, V> makeComputingMap(
Function<? super K, ? extends V> computingFunction) {
return new StrategyImpl<K, V>(this, computingFunction).map;
}
// Remainder of this file is private implementation details
private enum Strength {
WEAK {
@Override boolean equal(Object a, Object b) {
return a == b;
}
@Override int hash(Object o) {
return System.identityHashCode(o);
}
@Override <K, V> ValueReference<K, V> referenceValue(
ReferenceEntry<K, V> entry, V value) {
return new WeakValueReference<K, V>(value, entry);
}
@Override <K, V> ReferenceEntry<K, V> newEntry(
Internals<K, V, ReferenceEntry<K, V>> internals, K key,
int hash, ReferenceEntry<K, V> next) {
return (next == null)
? new WeakEntry<K, V>(internals, key, hash)
: new LinkedWeakEntry<K, V>(internals, key, hash, next);
}
@Override <K, V> ReferenceEntry<K, V> copyEntry(
K key, ReferenceEntry<K, V> original,
ReferenceEntry<K, V> newNext) {
WeakEntry<K, V> from = (WeakEntry<K, V>) original;
return (newNext == null)
? new WeakEntry<K, V>(from.internals, key, from.hash)
: new LinkedWeakEntry<K, V>(
from.internals, key, from.hash, newNext);
}
},
SOFT {
@Override boolean equal(Object a, Object b) {
return a == b;
}
@Override int hash(Object o) {
return System.identityHashCode(o);
}
@Override <K, V> ValueReference<K, V> referenceValue(
ReferenceEntry<K, V> entry, V value) {
return new SoftValueReference<K, V>(value, entry);
}
@Override <K, V> ReferenceEntry<K, V> newEntry(
Internals<K, V, ReferenceEntry<K, V>> internals, K key,
int hash, ReferenceEntry<K, V> next) {
return (next == null)
? new SoftEntry<K, V>(internals, key, hash)
: new LinkedSoftEntry<K, V>(internals, key, hash, next);
}
@Override <K, V> ReferenceEntry<K, V> copyEntry(
K key, ReferenceEntry<K, V> original,
ReferenceEntry<K, V> newNext) {
SoftEntry<K, V> from = (SoftEntry<K, V>) original;
return (newNext == null)
? new SoftEntry<K, V>(from.internals, key, from.hash)
: new LinkedSoftEntry<K, V>(
from.internals, key, from.hash, newNext);
}
},
STRONG {
@Override boolean equal(Object a, Object b) {
return a.equals(b);
}
@Override int hash(Object o) {
return o.hashCode();
}
@Override <K, V> ValueReference<K, V> referenceValue(
ReferenceEntry<K, V> entry, V value) {
return new StrongValueReference<K, V>(value);
}
@Override <K, V> ReferenceEntry<K, V> newEntry(
Internals<K, V, ReferenceEntry<K, V>> internals, K key,
int hash, ReferenceEntry<K, V> next) {
return (next == null)
? new StrongEntry<K, V>(internals, key, hash)
: new LinkedStrongEntry<K, V>(
internals, key, hash, next);
}
@Override <K, V> ReferenceEntry<K, V> copyEntry(
K key, ReferenceEntry<K, V> original,
ReferenceEntry<K, V> newNext) {
StrongEntry<K, V> from = (StrongEntry<K, V>) original;
return (newNext == null)
? new StrongEntry<K, V>(from.internals, key, from.hash)
: new LinkedStrongEntry<K, V>(
from.internals, key, from.hash, newNext);
}
};
/**
* Determines if two keys or values are equal according to this
* strength strategy.
*/
abstract boolean equal(Object a, Object b);
/**
* Hashes a key according to this strategy.
*/
abstract int hash(Object o);
/**
* Creates a reference for the given value according to this value
* strength.
*/
abstract <K, V> ValueReference<K, V> referenceValue(
ReferenceEntry<K, V> entry, V value);
/**
* Creates a new entry based on the current key strength.
*/
abstract <K, V> ReferenceEntry<K, V> newEntry(
Internals<K, V, ReferenceEntry<K, V>> internals, K key,
int hash, ReferenceEntry<K, V> next);
/**
* Creates a new entry and copies the value and other state from an
* existing entry.
*/
abstract <K, V> ReferenceEntry<K, V> copyEntry(K key,
ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext);
}
private static class StrategyImpl<K, V> implements Serializable,
ComputingStrategy<K, V, ReferenceEntry<K, V>> {
final Strength keyStrength;
final Strength valueStrength;
final ConcurrentMap<K, V> map;
final long expirationNanos;
Internals<K, V, ReferenceEntry<K, V>> internals;
StrategyImpl(MapMaker maker) {
this.keyStrength = maker.keyStrength;
this.valueStrength = maker.valueStrength;
this.expirationNanos = maker.expirationNanos;
map = maker.builder.buildMap(this);
}
StrategyImpl(
MapMaker maker, Function<? super K, ? extends V> computer) {
this.keyStrength = maker.keyStrength;
this.valueStrength = maker.valueStrength;
this.expirationNanos = maker.expirationNanos;
map = maker.builder.buildComputingMap(this, computer);
}
public void setValue(ReferenceEntry<K, V> entry, V value) {
setValueReference(
entry, valueStrength.referenceValue(entry, value));
if (expirationNanos > 0) {
scheduleRemoval(entry.getKey(), value);
}
}
void scheduleRemoval(K key, V value) {
/*
* TODO: Keep weak reference to map, too. Build a priority
* queue out of the entries themselves instead of creating a
* task per entry. Then, we could have one recurring task per
* map (which would clean the entire map and then reschedule
* itself depending upon when the next expiration comes). We
* also want to avoid removing an entry prematurely if the
* entry was set to the same value again.
*/
final WeakReference<K> keyReference = new WeakReference<K>(key);
final WeakReference<V> valueReference = new WeakReference<V>(value);
ExpirationTimer.instance.schedule(
new TimerTask() {
@Override public void run() {
K key = keyReference.get();
if (key != null) {
// Remove if the value is still the same.
map.remove(key, valueReference.get());
}
}
}, TimeUnit.NANOSECONDS.toMillis(expirationNanos));
}
public boolean equalKeys(K a, Object b) {
return keyStrength.equal(a, b);
}
public boolean equalValues(V a, Object b) {
return valueStrength.equal(a, b);
}
public int hashKey(Object key) {
return keyStrength.hash(key);
}
public K getKey(ReferenceEntry<K, V> entry) {
return entry.getKey();
}
public int getHash(ReferenceEntry<K, V> entry) {
return entry.getHash();
}
public ReferenceEntry<K, V> newEntry(
K key, int hash, ReferenceEntry<K, V> next) {
return keyStrength.newEntry(internals, key, hash, next);
}
public ReferenceEntry<K, V> copyEntry(K key,
ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
ValueReference<K, V> valueReference = original.getValueReference();
if (valueReference == COMPUTING) {
ReferenceEntry<K, V> newEntry
= newEntry(key, original.getHash(), newNext);
newEntry.setValueReference(
new FutureValueReference(original, newEntry));
return newEntry;
} else {
ReferenceEntry<K, V> newEntry
= newEntry(key, original.getHash(), newNext);
newEntry.setValueReference(valueReference.copyFor(newEntry));
return newEntry;
}
}
/**
* Waits for a computation to complete. Returns the result of the
* computation or null if none was available.
*/
public V waitForValue(ReferenceEntry<K, V> entry)
throws InterruptedException {
ValueReference<K, V> valueReference = entry.getValueReference();
if (valueReference == COMPUTING) {
synchronized (entry) {
while ((valueReference = entry.getValueReference())
== COMPUTING) {
entry.wait();
}
}
}
return valueReference.waitForValue();
}
/**
* Used by CustomConcurrentHashMap to retrieve values. Returns null
* instead of blocking or throwing an exception.
*/
public V getValue(ReferenceEntry<K, V> entry) {
ValueReference<K, V> valueReference = entry.getValueReference();
return valueReference.get();
}
public V compute(K key, final ReferenceEntry<K, V> entry,
Function<? super K, ? extends V> computer) {
V value;
try {
value = computer.apply(key);
} catch (ComputationException e) {
// if computer has thrown a computation exception, propagate rather
// than wrap
setValueReference(entry,
new ComputationExceptionReference<K, V>(e.getCause()));
throw e;
} catch (Throwable t) {
setValueReference(
entry, new ComputationExceptionReference<K, V>(t));
throw new ComputationException(t);
}
if (value == null) {
String message
= computer + " returned null for key " + key + ".";
setValueReference(
entry, new NullOutputExceptionReference<K, V>(message));
throw new NullOutputException(message);
} else {
setValue(entry, value);
}
return value;
}
/**
* Sets the value reference on an entry and notifies waiting
* threads.
*/
void setValueReference(ReferenceEntry<K, V> entry,
ValueReference<K, V> valueReference) {
boolean notifyOthers = (entry.getValueReference() == COMPUTING);
entry.setValueReference(valueReference);
if (notifyOthers) {
synchronized (entry) {
entry.notifyAll();
}
}
}
/**
* Points to an old entry where a value is being computed. Used to
* support non-blocking copying of entries during table expansion,
* removals, etc.
*/
private class FutureValueReference implements ValueReference<K, V> {
final ReferenceEntry<K, V> original;
final ReferenceEntry<K, V> newEntry;
FutureValueReference(
ReferenceEntry<K, V> original, ReferenceEntry<K, V> newEntry) {
this.original = original;
this.newEntry = newEntry;
}
public V get() {
boolean success = false;
try {
V value = original.getValueReference().get();
success = true;
return value;
} finally {
if (!success) {
removeEntry();
}
}
}
public ValueReference<K, V> copyFor(ReferenceEntry<K, V> entry) {
return new FutureValueReference(original, entry);
}
public V waitForValue() throws InterruptedException {
boolean success = false;
try {
// assert that key != null
V value = StrategyImpl.this.waitForValue(original);
success = true;
return value;
} finally {
if (!success) {
removeEntry();
}
}
}
/**
* Removes the entry in the event of an exception. Ideally,
* we'd clean up as soon as the computation completes, but we
* can't do that without keeping a reference to this entry from
* the original.
*/
void removeEntry() {
internals.removeEntry(newEntry);
}
}
public ReferenceEntry<K, V> getNext(
ReferenceEntry<K, V> entry) {
return entry.getNext();
}
public void setInternals(
Internals<K, V, ReferenceEntry<K, V>> internals) {
this.internals = internals;
}
private static final long serialVersionUID = 0;
private void writeObject(ObjectOutputStream out)
throws IOException {
// Custom serialization code ensures that the key and value
// strengths are written before the map. We'll need them to
// deserialize the map entries.
out.writeObject(keyStrength);
out.writeObject(valueStrength);
out.writeLong(expirationNanos);
// TODO: It is possible for the strategy to try to use the map
// or internals during deserialization, for example, if an
// entry gets reclaimed. We could detect this case and queue up
// removals to be flushed after we deserialize the map.
out.writeObject(internals);
out.writeObject(map);
}
/**
* Fields used during deserialization. We use a nested class so we
* don't load them until we need them. We need to use reflection to
* set final fields outside of the constructor.
*/
private static class Fields {
static final Field keyStrength = findField("keyStrength");
static final Field valueStrength = findField("valueStrength");
static final Field expirationNanos = findField("expirationNanos");
static final Field internals = findField("internals");
static final Field map = findField("map");
static Field findField(String name) {
try {
Field f = StrategyImpl.class.getDeclaredField(name);
f.setAccessible(true);
return f;
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
}
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
try {
Fields.keyStrength.set(this, in.readObject());
Fields.valueStrength.set(this, in.readObject());
Fields.expirationNanos.set(this, in.readLong());
Fields.internals.set(this, in.readObject());
Fields.map.set(this, in.readObject());
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
}
/** A reference to a value. */
private interface ValueReference<K, V> {
/**
* Gets the value. Does not block or throw exceptions.
*/
V get();
/** Creates a copy of this reference for the given entry. */
ValueReference<K, V> copyFor(ReferenceEntry<K, V> entry);
/**
* Waits for a value that may still be computing. Unlike get(),
* this method can block (in the case of FutureValueReference) or
* throw an exception.
*/
V waitForValue() throws InterruptedException;
}
private static final ValueReference<Object, Object> COMPUTING
= new ValueReference<Object, Object>() {
public Object get() {
return null;
}
public ValueReference<Object, Object> copyFor(
ReferenceEntry<Object, Object> entry) {
throw new AssertionError();
}
public Object waitForValue() {
throw new AssertionError();
}
};
/**
* Singleton placeholder that indicates a value is being computed.
*/
@SuppressWarnings("unchecked")
// Safe because impl never uses a parameter or returns any non-null value
private static <K, V> ValueReference<K, V> computing() {
return (ValueReference<K, V>) COMPUTING;
}
/** Used to provide null output exceptions to other threads. */
private static class NullOutputExceptionReference<K, V>
implements ValueReference<K, V> {
final String message;
NullOutputExceptionReference(String message) {
this.message = message;
}
public V get() {
return null;
}
public ValueReference<K, V> copyFor(
ReferenceEntry<K, V> entry) {
return this;
}
public V waitForValue() {
throw new NullOutputException(message);
}
}
/** Used to provide computation exceptions to other threads. */
private static class ComputationExceptionReference<K, V>
implements ValueReference<K, V> {
final Throwable t;
ComputationExceptionReference(Throwable t) {
this.t = t;
}
public V get() {
return null;
}
public ValueReference<K, V> copyFor(
ReferenceEntry<K, V> entry) {
return this;
}
public V waitForValue() {
throw new AsynchronousComputationException(t);
}
}
/** Wrapper class ensures that queue isn't created until it's used. */
private static class QueueHolder {
static final FinalizableReferenceQueue queue
= new FinalizableReferenceQueue();
}
/**
* An entry in a reference map.
*/
private interface ReferenceEntry<K, V> {
/**
* Gets the value reference from this entry.
*/
ValueReference<K, V> getValueReference();
/**
* Sets the value reference for this entry.
*
* @param valueReference
*/
void setValueReference(ValueReference<K, V> valueReference);
/**
* Removes this entry from the map if its value reference hasn't
* changed. Used to clean up after values. The value reference can
* just call this method on the entry so it doesn't have to keep
* its own reference to the map.
*/
void valueReclaimed();
/** Gets the next entry in the chain. */
ReferenceEntry<K, V> getNext();
/** Gets the entry's hash. */
int getHash();
/** Gets the key for this entry. */
public K getKey();
}
/**
* Used for strongly-referenced keys.
*/
private static class StrongEntry<K, V> implements ReferenceEntry<K, V> {
final K key;
StrongEntry(Internals<K, V, ReferenceEntry<K, V>> internals, K key,
int hash) {
this.internals = internals;
this.key = key;
this.hash = hash;
}
public K getKey() {
return this.key;
}
// The code below is exactly the same for each entry type.
final Internals<K, V, ReferenceEntry<K, V>> internals;
final int hash;
volatile ValueReference<K, V> valueReference = computing();
public ValueReference<K, V> getValueReference() {
return valueReference;
}
public void setValueReference(
ValueReference<K, V> valueReference) {
this.valueReference = valueReference;
}
public void valueReclaimed() {
internals.removeEntry(this, null);
}
public ReferenceEntry<K, V> getNext() {
return null;
}
public int getHash() {
return hash;
}
}
private static class LinkedStrongEntry<K, V> extends StrongEntry<K, V> {
LinkedStrongEntry(Internals<K, V, ReferenceEntry<K, V>> internals,
K key, int hash, ReferenceEntry<K, V> next) {
super(internals, key, hash);
this.next = next;
}
final ReferenceEntry<K, V> next;
@Override public ReferenceEntry<K, V> getNext() {
return next;
}
}
/**
* Used for softly-referenced keys.
*/
private static class SoftEntry<K, V> extends FinalizableSoftReference<K>
implements ReferenceEntry<K, V> {
SoftEntry(Internals<K, V, ReferenceEntry<K, V>> internals, K key,
int hash) {
super(key, QueueHolder.queue);
this.internals = internals;
this.hash = hash;
}
public K getKey() {
return get();
}
public void finalizeReferent() {
internals.removeEntry(this);
}
// The code below is exactly the same for each entry type.
final Internals<K, V, ReferenceEntry<K, V>> internals;
final int hash;
volatile ValueReference<K, V> valueReference = computing();
public ValueReference<K, V> getValueReference() {
return valueReference;
}
public void setValueReference(
ValueReference<K, V> valueReference) {
this.valueReference = valueReference;
}
public void valueReclaimed() {
internals.removeEntry(this, null);
}
public ReferenceEntry<K, V> getNext() {
return null;
}
public int getHash() {
return hash;
}
}
private static class LinkedSoftEntry<K, V> extends SoftEntry<K, V> {
LinkedSoftEntry(Internals<K, V, ReferenceEntry<K, V>> internals,
K key, int hash, ReferenceEntry<K, V> next) {
super(internals, key, hash);
this.next = next;
}
final ReferenceEntry<K, V> next;
@Override public ReferenceEntry<K, V> getNext() {
return next;
}
}
/**
* Used for weakly-referenced keys.
*/
private static class WeakEntry<K, V> extends FinalizableWeakReference<K>
implements ReferenceEntry<K, V> {
WeakEntry(Internals<K, V, ReferenceEntry<K, V>> internals, K key,
int hash) {
super(key, QueueHolder.queue);
this.internals = internals;
this.hash = hash;
}
public K getKey() {
return get();
}
public void finalizeReferent() {
internals.removeEntry(this);
}
// The code below is exactly the same for each entry type.
final Internals<K, V, ReferenceEntry<K, V>> internals;
final int hash;
volatile ValueReference<K, V> valueReference = computing();
public ValueReference<K, V> getValueReference() {
return valueReference;
}
public void setValueReference(
ValueReference<K, V> valueReference) {
this.valueReference = valueReference;
}
public void valueReclaimed() {
internals.removeEntry(this, null);
}
public ReferenceEntry<K, V> getNext() {
return null;
}
public int getHash() {
return hash;
}
}
private static class LinkedWeakEntry<K, V> extends WeakEntry<K, V> {
LinkedWeakEntry(Internals<K, V, ReferenceEntry<K, V>> internals,
K key, int hash, ReferenceEntry<K, V> next) {
super(internals, key, hash);
this.next = next;
}
final ReferenceEntry<K, V> next;
@Override public ReferenceEntry<K, V> getNext() {
return next;
}
}
/** References a weak value. */
private static class WeakValueReference<K, V>
extends FinalizableWeakReference<V>
implements ValueReference<K, V> {
final ReferenceEntry<K, V> entry;
WeakValueReference(V referent, ReferenceEntry<K, V> entry) {
super(referent, QueueHolder.queue);
this.entry = entry;
}
public void finalizeReferent() {
entry.valueReclaimed();
}
public ValueReference<K, V> copyFor(
ReferenceEntry<K, V> entry) {
return new WeakValueReference<K, V>(get(), entry);
}
public V waitForValue() {
return get();
}
}
/** References a soft value. */
private static class SoftValueReference<K, V>
extends FinalizableSoftReference<V>
implements ValueReference<K, V> {
final ReferenceEntry<K, V> entry;
SoftValueReference(V referent, ReferenceEntry<K, V> entry) {
super(referent, QueueHolder.queue);
this.entry = entry;
}
public void finalizeReferent() {
entry.valueReclaimed();
}
public ValueReference<K, V> copyFor(
ReferenceEntry<K, V> entry) {
return new SoftValueReference<K, V>(get(), entry);
}
public V waitForValue() {
return get();
}
}
/** References a strong value. */
private static class StrongValueReference<K, V>
implements ValueReference<K, V> {
final V referent;
StrongValueReference(V referent) {
this.referent = referent;
}
public V get() {
return referent;
}
public ValueReference<K, V> copyFor(
ReferenceEntry<K, V> entry) {
return this;
}
public V waitForValue() {
return get();
}
}
}