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