/*
* 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 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;
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;
/**
* 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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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();
}
}
}