summaryrefslogtreecommitdiffstats
path: root/guava/src/com/google/common/cache/LocalCache.java
diff options
context:
space:
mode:
authorYohann Roussel <yroussel@google.com>2014-03-19 16:25:37 +0100
committerYohann Roussel <yroussel@google.com>2014-03-20 15:13:33 +0100
commit4eceb95409e844fdc33c9c706e1dc307bfd40303 (patch)
treeee9f4f3fc79f757c79081c336bce4f1782c6ccd8 /guava/src/com/google/common/cache/LocalCache.java
parent3d2402901b1a6462e2cf47a6fd09711f327961c3 (diff)
downloadtoolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.zip
toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.gz
toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.bz2
Initial Jack import.
Change-Id: I953cf0a520195a7187d791b2885848ad0d5a9b43
Diffstat (limited to 'guava/src/com/google/common/cache/LocalCache.java')
-rw-r--r--guava/src/com/google/common/cache/LocalCache.java4913
1 files changed, 4913 insertions, 0 deletions
diff --git a/guava/src/com/google/common/cache/LocalCache.java b/guava/src/com/google/common/cache/LocalCache.java
new file mode 100644
index 0000000..6079b9b
--- /dev/null
+++ b/guava/src/com/google/common/cache/LocalCache.java
@@ -0,0 +1,4913 @@
+/*
+ * Copyright (C) 2009 The Guava Authors
+ *
+ * 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.cache;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.cache.CacheBuilder.NULL_TICKER;
+import static com.google.common.cache.CacheBuilder.UNSET_INT;
+import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Ticker;
+import com.google.common.cache.AbstractCache.SimpleStatsCounter;
+import com.google.common.cache.AbstractCache.StatsCounter;
+import com.google.common.cache.CacheBuilder.NullListener;
+import com.google.common.cache.CacheBuilder.OneWeigher;
+import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
+import com.google.common.cache.CacheLoader.UnsupportedLoadingOperationException;
+import com.google.common.collect.AbstractSequentialIterator;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.primitives.Ints;
+import com.google.common.util.concurrent.ExecutionError;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.AbstractCollection;
+import java.util.AbstractMap;
+import java.util.AbstractQueue;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * The concurrent hash map implementation built by {@link CacheBuilder}.
+ *
+ * <p>This implementation is heavily derived from revision 1.96 of <a
+ * href="http://tinyurl.com/ConcurrentHashMap">ConcurrentHashMap.java</a>.
+ *
+ * @author Charles Fry
+ * @author Bob Lee ({@code com.google.common.collect.MapMaker})
+ * @author Doug Lea ({@code ConcurrentHashMap})
+ */
+class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
+
+ /*
+ * The basic strategy is to subdivide the table among Segments, each of which itself is a
+ * concurrently readable hash table. The map supports non-blocking reads and concurrent writes
+ * across different segments.
+ *
+ * If a maximum size is specified, a best-effort bounding is performed per segment, using a
+ * page-replacement algorithm to determine which entries to evict when the capacity has been
+ * exceeded.
+ *
+ * The page replacement algorithm's data structures are kept casually consistent with the map. The
+ * ordering of writes to a segment is sequentially consistent. An update to the map and recording
+ * of reads may not be immediately reflected on the algorithm's data structures. These structures
+ * are guarded by a lock and operations are applied in batches to avoid lock contention. The
+ * penalty of applying the batches is spread across threads so that the amortized cost is slightly
+ * higher than performing just the operation without enforcing the capacity constraint.
+ *
+ * This implementation uses a per-segment queue to record a memento of the additions, removals,
+ * and accesses that were performed on the map. The queue is drained on writes and when it exceeds
+ * its capacity threshold.
+ *
+ * The Least Recently Used page replacement algorithm was chosen due to its simplicity, high hit
+ * rate, and ability to be implemented with O(1) time complexity. The initial LRU implementation
+ * operates per-segment rather than globally for increased implementation simplicity. We expect
+ * the cache hit rate to be similar to that of a global LRU algorithm.
+ */
+
+ // Constants
+
+ /**
+ * The maximum capacity, used if a higher value is implicitly specified by either of the
+ * constructors with arguments. MUST be a power of two <= 1<<30 to ensure that entries are
+ * indexable using ints.
+ */
+ static final int MAXIMUM_CAPACITY = 1 << 30;
+
+ /** The maximum number of segments to allow; used to bound constructor arguments. */
+ static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
+
+ /** Number of (unsynchronized) retries in the containsValue method. */
+ static final int CONTAINS_VALUE_RETRIES = 3;
+
+ /**
+ * Number of cache access operations that can be buffered per segment before the cache's recency
+ * ordering information is updated. This is used to avoid lock contention by recording a memento
+ * of reads and delaying a lock acquisition until the threshold is crossed or a mutation occurs.
+ *
+ * <p>This must be a (2^n)-1 as it is used as a mask.
+ */
+ static final int DRAIN_THRESHOLD = 0x3F;
+
+ /**
+ * Maximum number of entries to be drained in a single cleanup run. This applies independently to
+ * the cleanup queue and both reference queues.
+ */
+ // TODO(fry): empirically optimize this
+ static final int DRAIN_MAX = 16;
+
+ // Fields
+
+ static final Logger logger = Logger.getLogger(LocalCache.class.getName());
+
+ static final ListeningExecutorService sameThreadExecutor = MoreExecutors.sameThreadExecutor();
+
+ /**
+ * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose
+ * the segment.
+ */
+ final int segmentMask;
+
+ /**
+ * Shift value for indexing within segments. Helps prevent entries that end up in the same segment
+ * from also ending up in the same bucket.
+ */
+ final int segmentShift;
+
+ /** The segments, each of which is a specialized hash table. */
+ final Segment<K, V>[] segments;
+
+ /** The concurrency level. */
+ final int concurrencyLevel;
+
+ /** Strategy for comparing keys. */
+ final Equivalence<Object> keyEquivalence;
+
+ /** Strategy for comparing values. */
+ final Equivalence<Object> valueEquivalence;
+
+ /** Strategy for referencing keys. */
+ final Strength keyStrength;
+
+ /** Strategy for referencing values. */
+ final Strength valueStrength;
+
+ /** The maximum weight of this map. UNSET_INT if there is no maximum. */
+ final long maxWeight;
+
+ /** Weigher to weigh cache entries. */
+ final Weigher<K, V> weigher;
+
+ /** How long after the last access to an entry the map will retain that entry. */
+ final long expireAfterAccessNanos;
+
+ /** How long after the last write to an entry the map will retain that entry. */
+ final long expireAfterWriteNanos;
+
+ /** How long after the last write an entry becomes a candidate for refresh. */
+ final long refreshNanos;
+
+ /** Entries waiting to be consumed by the removal listener. */
+ // TODO(fry): define a new type which creates event objects and automates the clear logic
+ final Queue<RemovalNotification<K, V>> removalNotificationQueue;
+
+ /**
+ * A listener that is invoked when an entry is removed due to expiration or garbage collection of
+ * soft/weak entries.
+ */
+ final RemovalListener<K, V> removalListener;
+
+ /** Measures time in a testable way. */
+ final Ticker ticker;
+
+ /** Factory used to create new entries. */
+ final EntryFactory entryFactory;
+
+ /**
+ * Accumulates global cache statistics. Note that there are also per-segments stats counters
+ * which must be aggregated to obtain a global stats view.
+ */
+ final StatsCounter globalStatsCounter;
+
+ /**
+ * The default cache loader to use on loading operations.
+ */
+ @Nullable
+ final CacheLoader<? super K, V> defaultLoader;
+
+ /**
+ * Creates a new, empty map with the specified strategy, initial capacity and concurrency level.
+ */
+ LocalCache(
+ CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {
+ concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
+
+ keyStrength = builder.getKeyStrength();
+ valueStrength = builder.getValueStrength();
+
+ keyEquivalence = builder.getKeyEquivalence();
+ valueEquivalence = builder.getValueEquivalence();
+
+ maxWeight = builder.getMaximumWeight();
+ weigher = builder.getWeigher();
+ expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
+ expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
+ refreshNanos = builder.getRefreshNanos();
+
+ removalListener = builder.getRemovalListener();
+ removalNotificationQueue = (removalListener == NullListener.INSTANCE)
+ ? LocalCache.<RemovalNotification<K, V>>discardingQueue()
+ : new ConcurrentLinkedQueue<RemovalNotification<K, V>>();
+
+ ticker = builder.getTicker(recordsTime());
+ entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
+ globalStatsCounter = builder.getStatsCounterSupplier().get();
+ defaultLoader = loader;
+
+ int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
+ if (evictsBySize() && !customWeigher()) {
+ initialCapacity = Math.min(initialCapacity, (int) maxWeight);
+ }
+
+ // Find the lowest power-of-two segmentCount that exceeds concurrencyLevel, unless
+ // maximumSize/Weight is specified in which case ensure that each segment gets at least 10
+ // entries. The special casing for size-based eviction is only necessary because that eviction
+ // happens per segment instead of globally, so too many segments compared to the maximum size
+ // will result in random eviction behavior.
+ int segmentShift = 0;
+ int segmentCount = 1;
+ while (segmentCount < concurrencyLevel
+ && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
+ ++segmentShift;
+ segmentCount <<= 1;
+ }
+ this.segmentShift = 32 - segmentShift;
+ segmentMask = segmentCount - 1;
+
+ this.segments = newSegmentArray(segmentCount);
+
+ int segmentCapacity = initialCapacity / segmentCount;
+ if (segmentCapacity * segmentCount < initialCapacity) {
+ ++segmentCapacity;
+ }
+
+ int segmentSize = 1;
+ while (segmentSize < segmentCapacity) {
+ segmentSize <<= 1;
+ }
+
+ if (evictsBySize()) {
+ // Ensure sum of segment max weights = overall max weights
+ long maxSegmentWeight = maxWeight / segmentCount + 1;
+ long remainder = maxWeight % segmentCount;
+ for (int i = 0; i < this.segments.length; ++i) {
+ if (i == remainder) {
+ maxSegmentWeight--;
+ }
+ this.segments[i] =
+ createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get());
+ }
+ } else {
+ for (int i = 0; i < this.segments.length; ++i) {
+ this.segments[i] =
+ createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get());
+ }
+ }
+ }
+
+ boolean evictsBySize() {
+ return maxWeight >= 0;
+ }
+
+ boolean customWeigher() {
+ return weigher != OneWeigher.INSTANCE;
+ }
+
+ boolean expires() {
+ return expiresAfterWrite() || expiresAfterAccess();
+ }
+
+ boolean expiresAfterWrite() {
+ return expireAfterWriteNanos > 0;
+ }
+
+ boolean expiresAfterAccess() {
+ return expireAfterAccessNanos > 0;
+ }
+
+ boolean refreshes() {
+ return refreshNanos > 0;
+ }
+
+ boolean usesAccessQueue() {
+ return expiresAfterAccess() || evictsBySize();
+ }
+
+ boolean usesWriteQueue() {
+ return expiresAfterWrite();
+ }
+
+ boolean recordsWrite() {
+ return expiresAfterWrite() || refreshes();
+ }
+
+ boolean recordsAccess() {
+ return expiresAfterAccess();
+ }
+
+ boolean recordsTime() {
+ return recordsWrite() || recordsAccess();
+ }
+
+ boolean usesWriteEntries() {
+ return usesWriteQueue() || recordsWrite();
+ }
+
+ boolean usesAccessEntries() {
+ return usesAccessQueue() || recordsAccess();
+ }
+
+ boolean usesKeyReferences() {
+ return keyStrength != Strength.STRONG;
+ }
+
+ boolean usesValueReferences() {
+ return valueStrength != Strength.STRONG;
+ }
+
+ enum Strength {
+ /*
+ * TODO(kevinb): If we strongly reference the value and aren't loading, we needn't wrap the
+ * value. This could save ~8 bytes per entry.
+ */
+
+ STRONG {
+ @Override
+ <K, V> ValueReference<K, V> referenceValue(
+ Segment<K, V> segment, ReferenceEntry<K, V> entry, V value, int weight) {
+ return (weight == 1)
+ ? new StrongValueReference<K, V>(value)
+ : new WeightedStrongValueReference<K, V>(value, weight);
+ }
+
+ @Override
+ Equivalence<Object> defaultEquivalence() {
+ return Equivalence.equals();
+ }
+ },
+
+ SOFT {
+ @Override
+ <K, V> ValueReference<K, V> referenceValue(
+ Segment<K, V> segment, ReferenceEntry<K, V> entry, V value, int weight) {
+ return (weight == 1)
+ ? new SoftValueReference<K, V>(segment.valueReferenceQueue, value, entry)
+ : new WeightedSoftValueReference<K, V>(
+ segment.valueReferenceQueue, value, entry, weight);
+ }
+
+ @Override
+ Equivalence<Object> defaultEquivalence() {
+ return Equivalence.identity();
+ }
+ },
+
+ WEAK {
+ @Override
+ <K, V> ValueReference<K, V> referenceValue(
+ Segment<K, V> segment, ReferenceEntry<K, V> entry, V value, int weight) {
+ return (weight == 1)
+ ? new WeakValueReference<K, V>(segment.valueReferenceQueue, value, entry)
+ : new WeightedWeakValueReference<K, V>(
+ segment.valueReferenceQueue, value, entry, weight);
+ }
+
+ @Override
+ Equivalence<Object> defaultEquivalence() {
+ return Equivalence.identity();
+ }
+ };
+
+ /**
+ * Creates a reference for the given value according to this value strength.
+ */
+ abstract <K, V> ValueReference<K, V> referenceValue(
+ Segment<K, V> segment, ReferenceEntry<K, V> entry, V value, int weight);
+
+ /**
+ * Returns the default equivalence strategy used to compare and hash keys or values referenced
+ * at this strength. This strategy will be used unless the user explicitly specifies an
+ * alternate strategy.
+ */
+ abstract Equivalence<Object> defaultEquivalence();
+ }
+
+ /**
+ * Creates new entries.
+ */
+ enum EntryFactory {
+ STRONG {
+ @Override
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return new StrongEntry<K, V>(key, hash, next);
+ }
+ },
+ STRONG_ACCESS {
+ @Override
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return new StrongAccessEntry<K, V>(key, hash, next);
+ }
+
+ @Override
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ Segment<K, V> segment, ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ ReferenceEntry<K, V> newEntry = super.copyEntry(segment, original, newNext);
+ copyAccessEntry(original, newEntry);
+ return newEntry;
+ }
+ },
+ STRONG_WRITE {
+ @Override
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return new StrongWriteEntry<K, V>(key, hash, next);
+ }
+
+ @Override
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ Segment<K, V> segment, ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ ReferenceEntry<K, V> newEntry = super.copyEntry(segment, original, newNext);
+ copyWriteEntry(original, newEntry);
+ return newEntry;
+ }
+ },
+ STRONG_ACCESS_WRITE {
+ @Override
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return new StrongAccessWriteEntry<K, V>(key, hash, next);
+ }
+
+ @Override
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ Segment<K, V> segment, ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ ReferenceEntry<K, V> newEntry = super.copyEntry(segment, original, newNext);
+ copyAccessEntry(original, newEntry);
+ copyWriteEntry(original, newEntry);
+ return newEntry;
+ }
+ },
+
+ WEAK {
+ @Override
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return new WeakEntry<K, V>(segment.keyReferenceQueue, key, hash, next);
+ }
+ },
+ WEAK_ACCESS {
+ @Override
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return new WeakAccessEntry<K, V>(segment.keyReferenceQueue, key, hash, next);
+ }
+
+ @Override
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ Segment<K, V> segment, ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ ReferenceEntry<K, V> newEntry = super.copyEntry(segment, original, newNext);
+ copyAccessEntry(original, newEntry);
+ return newEntry;
+ }
+ },
+ WEAK_WRITE {
+ @Override
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return new WeakWriteEntry<K, V>(segment.keyReferenceQueue, key, hash, next);
+ }
+
+ @Override
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ Segment<K, V> segment, ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ ReferenceEntry<K, V> newEntry = super.copyEntry(segment, original, newNext);
+ copyWriteEntry(original, newEntry);
+ return newEntry;
+ }
+ },
+ WEAK_ACCESS_WRITE {
+ @Override
+ <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return new WeakAccessWriteEntry<K, V>(segment.keyReferenceQueue, key, hash, next);
+ }
+
+ @Override
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ Segment<K, V> segment, ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ ReferenceEntry<K, V> newEntry = super.copyEntry(segment, original, newNext);
+ copyAccessEntry(original, newEntry);
+ copyWriteEntry(original, newEntry);
+ return newEntry;
+ }
+ };
+
+ /**
+ * Masks used to compute indices in the following table.
+ */
+ static final int ACCESS_MASK = 1;
+ static final int WRITE_MASK = 2;
+ static final int WEAK_MASK = 4;
+
+ /**
+ * Look-up table for factories.
+ */
+ static final EntryFactory[] factories = {
+ STRONG, STRONG_ACCESS, STRONG_WRITE, STRONG_ACCESS_WRITE,
+ WEAK, WEAK_ACCESS, WEAK_WRITE, WEAK_ACCESS_WRITE,
+ };
+
+ static EntryFactory getFactory(Strength keyStrength, boolean usesAccessQueue,
+ boolean usesWriteQueue) {
+ int flags = ((keyStrength == Strength.WEAK) ? WEAK_MASK : 0)
+ | (usesAccessQueue ? ACCESS_MASK : 0)
+ | (usesWriteQueue ? WRITE_MASK : 0);
+ return factories[flags];
+ }
+
+ /**
+ * Creates a new entry.
+ *
+ * @param segment to create the entry for
+ * @param key of the entry
+ * @param hash of the key
+ * @param next entry in the same bucket
+ */
+ abstract <K, V> ReferenceEntry<K, V> newEntry(
+ Segment<K, V> segment, K key, int hash, @Nullable ReferenceEntry<K, V> next);
+
+ /**
+ * Copies an entry, assigning it a new {@code next} entry.
+ *
+ * @param original the entry to copy
+ * @param newNext entry in the same bucket
+ */
+ @GuardedBy("Segment.this")
+ <K, V> ReferenceEntry<K, V> copyEntry(
+ Segment<K, V> segment, ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ return newEntry(segment, original.getKey(), original.getHash(), newNext);
+ }
+
+ @GuardedBy("Segment.this")
+ <K, V> void copyAccessEntry(ReferenceEntry<K, V> original, ReferenceEntry<K, V> newEntry) {
+ // TODO(fry): when we link values instead of entries this method can go
+ // away, as can connectAccessOrder, nullifyAccessOrder.
+ newEntry.setAccessTime(original.getAccessTime());
+
+ connectAccessOrder(original.getPreviousInAccessQueue(), newEntry);
+ connectAccessOrder(newEntry, original.getNextInAccessQueue());
+
+ nullifyAccessOrder(original);
+ }
+
+ @GuardedBy("Segment.this")
+ <K, V> void copyWriteEntry(ReferenceEntry<K, V> original, ReferenceEntry<K, V> newEntry) {
+ // TODO(fry): when we link values instead of entries this method can go
+ // away, as can connectWriteOrder, nullifyWriteOrder.
+ newEntry.setWriteTime(original.getWriteTime());
+
+ connectWriteOrder(original.getPreviousInWriteQueue(), newEntry);
+ connectWriteOrder(newEntry, original.getNextInWriteQueue());
+
+ nullifyWriteOrder(original);
+ }
+ }
+
+ /**
+ * A reference to a value.
+ */
+ interface ValueReference<K, V> {
+ /**
+ * Returns the value. Does not block or throw exceptions.
+ */
+ @Nullable
+ V get();
+
+ /**
+ * Waits for a value that may still be loading. Unlike get(), this method can block (in the
+ * case of FutureValueReference).
+ *
+ * @throws ExecutionException if the loading thread throws an exception
+ * @throws ExecutionError if the loading thread throws an error
+ */
+ V waitForValue() throws ExecutionException;
+
+ /**
+ * Returns the weight of this entry. This is assumed to be static between calls to setValue.
+ */
+ int getWeight();
+
+ /**
+ * Returns the entry associated with this value reference, or {@code null} if this value
+ * reference is independent of any entry.
+ */
+ @Nullable
+ ReferenceEntry<K, V> getEntry();
+
+ /**
+ * Creates a copy of this reference for the given entry.
+ *
+ * <p>{@code value} may be null only for a loading reference.
+ */
+ ValueReference<K, V> copyFor(
+ ReferenceQueue<V> queue, @Nullable V value, ReferenceEntry<K, V> entry);
+
+ /**
+ * Notifify pending loads that a new value was set. This is only relevant to loading
+ * value references.
+ */
+ void notifyNewValue(@Nullable V newValue);
+
+ /**
+ * Returns true if a new value is currently loading, regardless of whether or not there is an
+ * existing value. It is assumed that the return value of this method is constant for any given
+ * ValueReference instance.
+ */
+ boolean isLoading();
+
+ /**
+ * Returns true if this reference contains an active value, meaning one that is still considered
+ * present in the cache. Active values consist of live values, which are returned by cache
+ * lookups, and dead values, which have been evicted but awaiting removal. Non-active values
+ * consist strictly of loading values, though during refresh a value may be both active and
+ * loading.
+ */
+ boolean isActive();
+ }
+
+ /**
+ * Placeholder. Indicates that the value hasn't been set yet.
+ */
+ static final ValueReference<Object, Object> UNSET = new ValueReference<Object, Object>() {
+ @Override
+ public Object get() {
+ return null;
+ }
+
+ @Override
+ public int getWeight() {
+ return 0;
+ }
+
+ @Override
+ public ReferenceEntry<Object, Object> getEntry() {
+ return null;
+ }
+
+ @Override
+ public ValueReference<Object, Object> copyFor(ReferenceQueue<Object> queue,
+ @Nullable Object value, ReferenceEntry<Object, Object> entry) {
+ return this;
+ }
+
+ @Override
+ public boolean isLoading() {
+ return false;
+ }
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
+ public Object waitForValue() {
+ return null;
+ }
+
+ @Override
+ public void notifyNewValue(Object newValue) {}
+ };
+
+ /**
+ * Singleton placeholder that indicates a value is being loaded.
+ */
+ @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value
+ static <K, V> ValueReference<K, V> unset() {
+ return (ValueReference<K, V>) UNSET;
+ }
+
+ /**
+ * An entry in a reference map.
+ *
+ * Entries in the map can be in the following states:
+ *
+ * Valid:
+ * - Live: valid key/value are set
+ * - Loading: loading is pending
+ *
+ * Invalid:
+ * - Expired: time expired (key/value may still be set)
+ * - Collected: key/value was partially collected, but not yet cleaned up
+ * - Unset: marked as unset, awaiting cleanup or reuse
+ */
+ interface ReferenceEntry<K, V> {
+ /**
+ * Returns the value reference from this entry.
+ */
+ ValueReference<K, V> getValueReference();
+
+ /**
+ * Sets the value reference for this entry.
+ */
+ void setValueReference(ValueReference<K, V> valueReference);
+
+ /**
+ * Returns the next entry in the chain.
+ */
+ @Nullable
+ ReferenceEntry<K, V> getNext();
+
+ /**
+ * Returns the entry's hash.
+ */
+ int getHash();
+
+ /**
+ * Returns the key for this entry.
+ */
+ @Nullable
+ K getKey();
+
+ /*
+ * Used by entries that use access order. Access entries are maintained in a doubly-linked list.
+ * New entries are added at the tail of the list at write time; stale entries are expired from
+ * the head of the list.
+ */
+
+ /**
+ * Returns the time that this entry was last accessed, in ns.
+ */
+ long getAccessTime();
+
+ /**
+ * Sets the entry access time in ns.
+ */
+ void setAccessTime(long time);
+
+ /**
+ * Returns the next entry in the access queue.
+ */
+ ReferenceEntry<K, V> getNextInAccessQueue();
+
+ /**
+ * Sets the next entry in the access queue.
+ */
+ void setNextInAccessQueue(ReferenceEntry<K, V> next);
+
+ /**
+ * Returns the previous entry in the access queue.
+ */
+ ReferenceEntry<K, V> getPreviousInAccessQueue();
+
+ /**
+ * Sets the previous entry in the access queue.
+ */
+ void setPreviousInAccessQueue(ReferenceEntry<K, V> previous);
+
+ /*
+ * Implemented by entries that use write order. Write entries are maintained in a
+ * doubly-linked list. New entries are added at the tail of the list at write time and stale
+ * entries are expired from the head of the list.
+ */
+
+ /**
+ * Returns the time that this entry was last written, in ns.
+ */
+ long getWriteTime();
+
+ /**
+ * Sets the entry write time in ns.
+ */
+ void setWriteTime(long time);
+
+ /**
+ * Returns the next entry in the write queue.
+ */
+ ReferenceEntry<K, V> getNextInWriteQueue();
+
+ /**
+ * Sets the next entry in the write queue.
+ */
+ void setNextInWriteQueue(ReferenceEntry<K, V> next);
+
+ /**
+ * Returns the previous entry in the write queue.
+ */
+ ReferenceEntry<K, V> getPreviousInWriteQueue();
+
+ /**
+ * Sets the previous entry in the write queue.
+ */
+ void setPreviousInWriteQueue(ReferenceEntry<K, V> previous);
+ }
+
+ private enum NullEntry implements ReferenceEntry<Object, Object> {
+ INSTANCE;
+
+ @Override
+ public ValueReference<Object, Object> getValueReference() {
+ return null;
+ }
+
+ @Override
+ public void setValueReference(ValueReference<Object, Object> valueReference) {}
+
+ @Override
+ public ReferenceEntry<Object, Object> getNext() {
+ return null;
+ }
+
+ @Override
+ public int getHash() {
+ return 0;
+ }
+
+ @Override
+ public Object getKey() {
+ return null;
+ }
+
+ @Override
+ public long getAccessTime() {
+ return 0;
+ }
+
+ @Override
+ public void setAccessTime(long time) {}
+
+ @Override
+ public ReferenceEntry<Object, Object> getNextInAccessQueue() {
+ return this;
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<Object, Object> next) {}
+
+ @Override
+ public ReferenceEntry<Object, Object> getPreviousInAccessQueue() {
+ return this;
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<Object, Object> previous) {}
+
+ @Override
+ public long getWriteTime() {
+ return 0;
+ }
+
+ @Override
+ public void setWriteTime(long time) {}
+
+ @Override
+ public ReferenceEntry<Object, Object> getNextInWriteQueue() {
+ return this;
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<Object, Object> next) {}
+
+ @Override
+ public ReferenceEntry<Object, Object> getPreviousInWriteQueue() {
+ return this;
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<Object, Object> previous) {}
+ }
+
+ static abstract class AbstractReferenceEntry<K, V> implements ReferenceEntry<K, V> {
+ @Override
+ public ValueReference<K, V> getValueReference() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setValueReference(ValueReference<K, V> valueReference) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNext() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getHash() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public K getKey() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getAccessTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setAccessTime(long time) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNextInAccessQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInAccessQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public long getWriteTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setWriteTime(long time) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNextInWriteQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<K, V> next) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInWriteQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<K, V> previous) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value
+ static <K, V> ReferenceEntry<K, V> nullEntry() {
+ return (ReferenceEntry<K, V>) NullEntry.INSTANCE;
+ }
+
+ static final Queue<? extends Object> DISCARDING_QUEUE = new AbstractQueue<Object>() {
+ @Override
+ public boolean offer(Object o) {
+ return true;
+ }
+
+ @Override
+ public Object peek() {
+ return null;
+ }
+
+ @Override
+ public Object poll() {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+
+ @Override
+ public Iterator<Object> iterator() {
+ return Iterators.emptyIterator();
+ }
+ };
+
+ /**
+ * Queue that discards all elements.
+ */
+ @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value
+ static <E> Queue<E> discardingQueue() {
+ return (Queue) DISCARDING_QUEUE;
+ }
+
+ /*
+ * Note: All of this duplicate code sucks, but it saves a lot of memory. If only Java had mixins!
+ * To maintain this code, make a change for the strong reference type. Then, cut and paste, and
+ * replace "Strong" with "Soft" or "Weak" within the pasted text. The primary difference is that
+ * strong entries store the key reference directly while soft and weak entries delegate to their
+ * respective superclasses.
+ */
+
+ /**
+ * Used for strongly-referenced keys.
+ */
+ static class StrongEntry<K, V> implements ReferenceEntry<K, V> {
+ final K key;
+
+ StrongEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ this.key = key;
+ this.hash = hash;
+ this.next = next;
+ }
+
+ @Override
+ public K getKey() {
+ return this.key;
+ }
+
+ // null access
+
+ @Override
+ public long getAccessTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setAccessTime(long time) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNextInAccessQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInAccessQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
+ throw new UnsupportedOperationException();
+ }
+
+ // null write
+
+ @Override
+ public long getWriteTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setWriteTime(long time) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNextInWriteQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<K, V> next) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInWriteQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<K, V> previous) {
+ throw new UnsupportedOperationException();
+ }
+
+ // The code below is exactly the same for each entry type.
+
+ final int hash;
+ final ReferenceEntry<K, V> next;
+ volatile ValueReference<K, V> valueReference = unset();
+
+ @Override
+ public ValueReference<K, V> getValueReference() {
+ return valueReference;
+ }
+
+ @Override
+ public void setValueReference(ValueReference<K, V> valueReference) {
+ this.valueReference = valueReference;
+ }
+
+ @Override
+ public int getHash() {
+ return hash;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNext() {
+ return next;
+ }
+ }
+
+ static final class StrongAccessEntry<K, V> extends StrongEntry<K, V>
+ implements ReferenceEntry<K, V> {
+ StrongAccessEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ super(key, hash, next);
+ }
+
+ // The code below is exactly the same for each access entry type.
+
+ volatile long accessTime = Long.MAX_VALUE;
+
+ @Override
+ public long getAccessTime() {
+ return accessTime;
+ }
+
+ @Override
+ public void setAccessTime(long time) {
+ this.accessTime = time;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> nextAccess = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getNextInAccessQueue() {
+ return nextAccess;
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
+ this.nextAccess = next;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> previousAccess = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInAccessQueue() {
+ return previousAccess;
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
+ this.previousAccess = previous;
+ }
+ }
+
+ static final class StrongWriteEntry<K, V>
+ extends StrongEntry<K, V> implements ReferenceEntry<K, V> {
+ StrongWriteEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ super(key, hash, next);
+ }
+
+ // The code below is exactly the same for each write entry type.
+
+ volatile long writeTime = Long.MAX_VALUE;
+
+ @Override
+ public long getWriteTime() {
+ return writeTime;
+ }
+
+ @Override
+ public void setWriteTime(long time) {
+ this.writeTime = time;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> nextWrite = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getNextInWriteQueue() {
+ return nextWrite;
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<K, V> next) {
+ this.nextWrite = next;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> previousWrite = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInWriteQueue() {
+ return previousWrite;
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<K, V> previous) {
+ this.previousWrite = previous;
+ }
+ }
+
+ static final class StrongAccessWriteEntry<K, V>
+ extends StrongEntry<K, V> implements ReferenceEntry<K, V> {
+ StrongAccessWriteEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ super(key, hash, next);
+ }
+
+ // The code below is exactly the same for each access entry type.
+
+ volatile long accessTime = Long.MAX_VALUE;
+
+ @Override
+ public long getAccessTime() {
+ return accessTime;
+ }
+
+ @Override
+ public void setAccessTime(long time) {
+ this.accessTime = time;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> nextAccess = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getNextInAccessQueue() {
+ return nextAccess;
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
+ this.nextAccess = next;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> previousAccess = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInAccessQueue() {
+ return previousAccess;
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
+ this.previousAccess = previous;
+ }
+
+ // The code below is exactly the same for each write entry type.
+
+ volatile long writeTime = Long.MAX_VALUE;
+
+ @Override
+ public long getWriteTime() {
+ return writeTime;
+ }
+
+ @Override
+ public void setWriteTime(long time) {
+ this.writeTime = time;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> nextWrite = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getNextInWriteQueue() {
+ return nextWrite;
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<K, V> next) {
+ this.nextWrite = next;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> previousWrite = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInWriteQueue() {
+ return previousWrite;
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<K, V> previous) {
+ this.previousWrite = previous;
+ }
+ }
+
+ /**
+ * Used for weakly-referenced keys.
+ */
+ static class WeakEntry<K, V> extends WeakReference<K> implements ReferenceEntry<K, V> {
+ WeakEntry(ReferenceQueue<K> queue, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ super(key, queue);
+ this.hash = hash;
+ this.next = next;
+ }
+
+ @Override
+ public K getKey() {
+ return get();
+ }
+
+ // null access
+
+ @Override
+ public long getAccessTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setAccessTime(long time) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNextInAccessQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInAccessQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
+ throw new UnsupportedOperationException();
+ }
+
+ // null write
+
+ @Override
+ public long getWriteTime() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setWriteTime(long time) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNextInWriteQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<K, V> next) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInWriteQueue() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<K, V> previous) {
+ throw new UnsupportedOperationException();
+ }
+
+ // The code below is exactly the same for each entry type.
+
+ final int hash;
+ final ReferenceEntry<K, V> next;
+ volatile ValueReference<K, V> valueReference = unset();
+
+ @Override
+ public ValueReference<K, V> getValueReference() {
+ return valueReference;
+ }
+
+ @Override
+ public void setValueReference(ValueReference<K, V> valueReference) {
+ this.valueReference = valueReference;
+ }
+
+ @Override
+ public int getHash() {
+ return hash;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getNext() {
+ return next;
+ }
+ }
+
+ static final class WeakAccessEntry<K, V>
+ extends WeakEntry<K, V> implements ReferenceEntry<K, V> {
+ WeakAccessEntry(
+ ReferenceQueue<K> queue, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ super(queue, key, hash, next);
+ }
+
+ // The code below is exactly the same for each access entry type.
+
+ volatile long accessTime = Long.MAX_VALUE;
+
+ @Override
+ public long getAccessTime() {
+ return accessTime;
+ }
+
+ @Override
+ public void setAccessTime(long time) {
+ this.accessTime = time;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> nextAccess = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getNextInAccessQueue() {
+ return nextAccess;
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
+ this.nextAccess = next;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> previousAccess = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInAccessQueue() {
+ return previousAccess;
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
+ this.previousAccess = previous;
+ }
+ }
+
+ static final class WeakWriteEntry<K, V>
+ extends WeakEntry<K, V> implements ReferenceEntry<K, V> {
+ WeakWriteEntry(
+ ReferenceQueue<K> queue, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ super(queue, key, hash, next);
+ }
+
+ // The code below is exactly the same for each write entry type.
+
+ volatile long writeTime = Long.MAX_VALUE;
+
+ @Override
+ public long getWriteTime() {
+ return writeTime;
+ }
+
+ @Override
+ public void setWriteTime(long time) {
+ this.writeTime = time;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> nextWrite = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getNextInWriteQueue() {
+ return nextWrite;
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<K, V> next) {
+ this.nextWrite = next;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> previousWrite = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInWriteQueue() {
+ return previousWrite;
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<K, V> previous) {
+ this.previousWrite = previous;
+ }
+ }
+
+ static final class WeakAccessWriteEntry<K, V>
+ extends WeakEntry<K, V> implements ReferenceEntry<K, V> {
+ WeakAccessWriteEntry(
+ ReferenceQueue<K> queue, K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ super(queue, key, hash, next);
+ }
+
+ // The code below is exactly the same for each access entry type.
+
+ volatile long accessTime = Long.MAX_VALUE;
+
+ @Override
+ public long getAccessTime() {
+ return accessTime;
+ }
+
+ @Override
+ public void setAccessTime(long time) {
+ this.accessTime = time;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> nextAccess = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getNextInAccessQueue() {
+ return nextAccess;
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
+ this.nextAccess = next;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> previousAccess = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInAccessQueue() {
+ return previousAccess;
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
+ this.previousAccess = previous;
+ }
+
+ // The code below is exactly the same for each write entry type.
+
+ volatile long writeTime = Long.MAX_VALUE;
+
+ @Override
+ public long getWriteTime() {
+ return writeTime;
+ }
+
+ @Override
+ public void setWriteTime(long time) {
+ this.writeTime = time;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> nextWrite = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getNextInWriteQueue() {
+ return nextWrite;
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<K, V> next) {
+ this.nextWrite = next;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> previousWrite = nullEntry();
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInWriteQueue() {
+ return previousWrite;
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<K, V> previous) {
+ this.previousWrite = previous;
+ }
+ }
+
+ /**
+ * References a weak value.
+ */
+ static class WeakValueReference<K, V>
+ extends WeakReference<V> implements ValueReference<K, V> {
+ final ReferenceEntry<K, V> entry;
+
+ WeakValueReference(ReferenceQueue<V> queue, V referent, ReferenceEntry<K, V> entry) {
+ super(referent, queue);
+ this.entry = entry;
+ }
+
+ @Override
+ public int getWeight() {
+ return 1;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getEntry() {
+ return entry;
+ }
+
+ @Override
+ public void notifyNewValue(V newValue) {}
+
+ @Override
+ public ValueReference<K, V> copyFor(
+ ReferenceQueue<V> queue, V value, ReferenceEntry<K, V> entry) {
+ return new WeakValueReference<K, V>(queue, value, entry);
+ }
+
+ @Override
+ public boolean isLoading() {
+ return false;
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ public V waitForValue() {
+ return get();
+ }
+ }
+
+ /**
+ * References a soft value.
+ */
+ static class SoftValueReference<K, V>
+ extends SoftReference<V> implements ValueReference<K, V> {
+ final ReferenceEntry<K, V> entry;
+
+ SoftValueReference(ReferenceQueue<V> queue, V referent, ReferenceEntry<K, V> entry) {
+ super(referent, queue);
+ this.entry = entry;
+ }
+
+ @Override
+ public int getWeight() {
+ return 1;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getEntry() {
+ return entry;
+ }
+
+ @Override
+ public void notifyNewValue(V newValue) {}
+
+ @Override
+ public ValueReference<K, V> copyFor(
+ ReferenceQueue<V> queue, V value, ReferenceEntry<K, V> entry) {
+ return new SoftValueReference<K, V>(queue, value, entry);
+ }
+
+ @Override
+ public boolean isLoading() {
+ return false;
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ public V waitForValue() {
+ return get();
+ }
+ }
+
+ /**
+ * References a strong value.
+ */
+ static class StrongValueReference<K, V> implements ValueReference<K, V> {
+ final V referent;
+
+ StrongValueReference(V referent) {
+ this.referent = referent;
+ }
+
+ @Override
+ public V get() {
+ return referent;
+ }
+
+ @Override
+ public int getWeight() {
+ return 1;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getEntry() {
+ return null;
+ }
+
+ @Override
+ public ValueReference<K, V> copyFor(
+ ReferenceQueue<V> queue, V value, ReferenceEntry<K, V> entry) {
+ return this;
+ }
+
+ @Override
+ public boolean isLoading() {
+ return false;
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ public V waitForValue() {
+ return get();
+ }
+
+ @Override
+ public void notifyNewValue(V newValue) {}
+ }
+
+ /**
+ * References a weak value.
+ */
+ static final class WeightedWeakValueReference<K, V> extends WeakValueReference<K, V> {
+ final int weight;
+
+ WeightedWeakValueReference(ReferenceQueue<V> queue, V referent, ReferenceEntry<K, V> entry,
+ int weight) {
+ super(queue, referent, entry);
+ this.weight = weight;
+ }
+
+ @Override
+ public int getWeight() {
+ return weight;
+ }
+
+ @Override
+ public ValueReference<K, V> copyFor(
+ ReferenceQueue<V> queue, V value, ReferenceEntry<K, V> entry) {
+ return new WeightedWeakValueReference<K, V>(queue, value, entry, weight);
+ }
+ }
+
+ /**
+ * References a soft value.
+ */
+ static final class WeightedSoftValueReference<K, V> extends SoftValueReference<K, V> {
+ final int weight;
+
+ WeightedSoftValueReference(ReferenceQueue<V> queue, V referent, ReferenceEntry<K, V> entry,
+ int weight) {
+ super(queue, referent, entry);
+ this.weight = weight;
+ }
+
+ @Override
+ public int getWeight() {
+ return weight;
+ }
+ @Override
+ public ValueReference<K, V> copyFor(
+ ReferenceQueue<V> queue, V value, ReferenceEntry<K, V> entry) {
+ return new WeightedSoftValueReference<K, V>(queue, value, entry, weight);
+ }
+
+ }
+
+ /**
+ * References a strong value.
+ */
+ static final class WeightedStrongValueReference<K, V> extends StrongValueReference<K, V> {
+ final int weight;
+
+ WeightedStrongValueReference(V referent, int weight) {
+ super(referent);
+ this.weight = weight;
+ }
+
+ @Override
+ public int getWeight() {
+ return weight;
+ }
+ }
+
+ /**
+ * Applies a supplemental hash function to a given hash code, which defends against poor quality
+ * hash functions. This is critical when the concurrent hash map uses power-of-two length hash
+ * tables, that otherwise encounter collisions for hash codes that do not differ in lower or
+ * upper bits.
+ *
+ * @param h hash code
+ */
+ static int rehash(int h) {
+ // Spread bits to regularize both segment and index locations,
+ // using variant of single-word Wang/Jenkins hash.
+ // TODO(kevinb): use Hashing/move this to Hashing?
+ h += (h << 15) ^ 0xffffcd7d;
+ h ^= (h >>> 10);
+ h += (h << 3);
+ h ^= (h >>> 6);
+ h += (h << 2) + (h << 14);
+ return h ^ (h >>> 16);
+ }
+
+ /**
+ * This method is a convenience for testing. Code should call {@link Segment#newEntry} directly.
+ */
+ @GuardedBy("Segment.this")
+ @VisibleForTesting
+ ReferenceEntry<K, V> newEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return segmentFor(hash).newEntry(key, hash, next);
+ }
+
+ /**
+ * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly.
+ */
+ @GuardedBy("Segment.this")
+ @VisibleForTesting
+ ReferenceEntry<K, V> copyEntry(ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ int hash = original.getHash();
+ return segmentFor(hash).copyEntry(original, newNext);
+ }
+
+ /**
+ * This method is a convenience for testing. Code should call {@link Segment#setValue} instead.
+ */
+ @GuardedBy("Segment.this")
+ @VisibleForTesting
+ ValueReference<K, V> newValueReference(ReferenceEntry<K, V> entry, V value, int weight) {
+ int hash = entry.getHash();
+ return valueStrength.referenceValue(segmentFor(hash), entry, value, weight);
+ }
+
+ int hash(Object key) {
+ int h = keyEquivalence.hash(key);
+ return rehash(h);
+ }
+
+ void reclaimValue(ValueReference<K, V> valueReference) {
+ ReferenceEntry<K, V> entry = valueReference.getEntry();
+ int hash = entry.getHash();
+ segmentFor(hash).reclaimValue(entry.getKey(), hash, valueReference);
+ }
+
+ void reclaimKey(ReferenceEntry<K, V> entry) {
+ int hash = entry.getHash();
+ segmentFor(hash).reclaimKey(entry, hash);
+ }
+
+ /**
+ * This method is a convenience for testing. Code should call {@link Segment#getLiveValue}
+ * instead.
+ */
+ @VisibleForTesting
+ boolean isLive(ReferenceEntry<K, V> entry, long now) {
+ return segmentFor(entry.getHash()).getLiveValue(entry, now) != null;
+ }
+
+ /**
+ * Returns the segment that should be used for a key with the given hash.
+ *
+ * @param hash the hash code for the key
+ * @return the segment
+ */
+ Segment<K, V> segmentFor(int hash) {
+ // TODO(fry): Lazily create segments?
+ return segments[(hash >>> segmentShift) & segmentMask];
+ }
+
+ Segment<K, V> createSegment(
+ int initialCapacity, long maxSegmentWeight, StatsCounter statsCounter) {
+ return new Segment<K, V>(this, initialCapacity, maxSegmentWeight, statsCounter);
+ }
+
+ /**
+ * Gets the value from an entry. Returns null if the entry is invalid, partially-collected,
+ * loading, or expired. Unlike {@link Segment#getLiveValue} this method does not attempt to
+ * cleanup stale entries. As such it should only be called outside of a segment context, such as
+ * during iteration.
+ */
+ @Nullable
+ V getLiveValue(ReferenceEntry<K, V> entry, long now) {
+ if (entry.getKey() == null) {
+ return null;
+ }
+ V value = entry.getValueReference().get();
+ if (value == null) {
+ return null;
+ }
+
+ if (isExpired(entry, now)) {
+ return null;
+ }
+ return value;
+ }
+
+ // expiration
+
+ /**
+ * Returns true if the entry has expired.
+ */
+ boolean isExpired(ReferenceEntry<K, V> entry, long now) {
+ if (expiresAfterAccess()
+ && (now - entry.getAccessTime() > expireAfterAccessNanos)) {
+ return true;
+ }
+ if (expiresAfterWrite()
+ && (now - entry.getWriteTime() > expireAfterWriteNanos)) {
+ return true;
+ }
+ return false;
+ }
+
+ // queues
+
+ @GuardedBy("Segment.this")
+ static <K, V> void connectAccessOrder(ReferenceEntry<K, V> previous, ReferenceEntry<K, V> next) {
+ previous.setNextInAccessQueue(next);
+ next.setPreviousInAccessQueue(previous);
+ }
+
+ @GuardedBy("Segment.this")
+ static <K, V> void nullifyAccessOrder(ReferenceEntry<K, V> nulled) {
+ ReferenceEntry<K, V> nullEntry = nullEntry();
+ nulled.setNextInAccessQueue(nullEntry);
+ nulled.setPreviousInAccessQueue(nullEntry);
+ }
+
+ @GuardedBy("Segment.this")
+ static <K, V> void connectWriteOrder(ReferenceEntry<K, V> previous, ReferenceEntry<K, V> next) {
+ previous.setNextInWriteQueue(next);
+ next.setPreviousInWriteQueue(previous);
+ }
+
+ @GuardedBy("Segment.this")
+ static <K, V> void nullifyWriteOrder(ReferenceEntry<K, V> nulled) {
+ ReferenceEntry<K, V> nullEntry = nullEntry();
+ nulled.setNextInWriteQueue(nullEntry);
+ nulled.setPreviousInWriteQueue(nullEntry);
+ }
+
+ /**
+ * Notifies listeners that an entry has been automatically removed due to expiration, eviction,
+ * or eligibility for garbage collection. This should be called every time expireEntries or
+ * evictEntry is called (once the lock is released).
+ */
+ void processPendingNotifications() {
+ RemovalNotification<K, V> notification;
+ while ((notification = removalNotificationQueue.poll()) != null) {
+ try {
+ removalListener.onRemoval(notification);
+ } catch (Throwable e) {
+ logger.log(Level.WARNING, "Exception thrown by removal listener", e);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ final Segment<K, V>[] newSegmentArray(int ssize) {
+ return new Segment[ssize];
+ }
+
+ // Inner Classes
+
+ /**
+ * Segments are specialized versions of hash tables. This subclass inherits from ReentrantLock
+ * opportunistically, just to simplify some locking and avoid separate construction.
+ */
+ @SuppressWarnings("serial") // This class is never serialized.
+ static class Segment<K, V> extends ReentrantLock {
+
+ /*
+ * TODO(fry): Consider copying variables (like evictsBySize) from outer class into this class.
+ * It will require more memory but will reduce indirection.
+ */
+
+ /*
+ * Segments maintain a table of entry lists that are ALWAYS kept in a consistent state, so can
+ * be read without locking. Next fields of nodes are immutable (final). All list additions are
+ * performed at the front of each bin. This makes it easy to check changes, and also fast to
+ * traverse. When nodes would otherwise be changed, new nodes are created to replace them. This
+ * works well for hash tables since the bin lists tend to be short. (The average length is less
+ * than two.)
+ *
+ * Read operations can thus proceed without locking, but rely on selected uses of volatiles to
+ * ensure that completed write operations performed by other threads are noticed. For most
+ * purposes, the "count" field, tracking the number of elements, serves as that volatile
+ * variable ensuring visibility. This is convenient because this field needs to be read in many
+ * read operations anyway:
+ *
+ * - All (unsynchronized) read operations must first read the "count" field, and should not
+ * look at table entries if it is 0.
+ *
+ * - All (synchronized) write operations should write to the "count" field after structurally
+ * changing any bin. The operations must not take any action that could even momentarily
+ * cause a concurrent read operation to see inconsistent data. This is made easier by the
+ * nature of the read operations in Map. For example, no operation can reveal that the table
+ * has grown but the threshold has not yet been updated, so there are no atomicity requirements
+ * for this with respect to reads.
+ *
+ * As a guide, all critical volatile reads and writes to the count field are marked in code
+ * comments.
+ */
+
+ final LocalCache<K, V> map;
+
+ /**
+ * The number of live elements in this segment's region.
+ */
+ volatile int count;
+
+ /**
+ * The weight of the live elements in this segment's region.
+ */
+ @GuardedBy("Segment.this")
+ int totalWeight;
+
+ /**
+ * Number of updates that alter the size of the table. This is used during bulk-read methods to
+ * make sure they see a consistent snapshot: If modCounts change during a traversal of segments
+ * loading size or checking containsValue, then we might have an inconsistent view of state
+ * so (usually) must retry.
+ */
+ int modCount;
+
+ /**
+ * The table is expanded when its size exceeds this threshold. (The value of this field is
+ * always {@code (int)(capacity * 0.75)}.)
+ */
+ int threshold;
+
+ /**
+ * The per-segment table.
+ */
+ volatile AtomicReferenceArray<ReferenceEntry<K, V>> table;
+
+ /**
+ * The maximum weight of this segment. UNSET_INT if there is no maximum.
+ */
+ final long maxSegmentWeight;
+
+ /**
+ * The key reference queue contains entries whose keys have been garbage collected, and which
+ * need to be cleaned up internally.
+ */
+ final ReferenceQueue<K> keyReferenceQueue;
+
+ /**
+ * The value reference queue contains value references whose values have been garbage collected,
+ * and which need to be cleaned up internally.
+ */
+ final ReferenceQueue<V> valueReferenceQueue;
+
+ /**
+ * The recency queue is used to record which entries were accessed for updating the access
+ * list's ordering. It is drained as a batch operation when either the DRAIN_THRESHOLD is
+ * crossed or a write occurs on the segment.
+ */
+ final Queue<ReferenceEntry<K, V>> recencyQueue;
+
+ /**
+ * A counter of the number of reads since the last write, used to drain queues on a small
+ * fraction of read operations.
+ */
+ final AtomicInteger readCount = new AtomicInteger();
+
+ /**
+ * A queue of elements currently in the map, ordered by write time. Elements are added to the
+ * tail of the queue on write.
+ */
+ @GuardedBy("Segment.this")
+ final Queue<ReferenceEntry<K, V>> writeQueue;
+
+ /**
+ * A queue of elements currently in the map, ordered by access time. Elements are added to the
+ * tail of the queue on access (note that writes count as accesses).
+ */
+ @GuardedBy("Segment.this")
+ final Queue<ReferenceEntry<K, V>> accessQueue;
+
+ /** Accumulates cache statistics. */
+ final StatsCounter statsCounter;
+
+ Segment(LocalCache<K, V> map, int initialCapacity, long maxSegmentWeight,
+ StatsCounter statsCounter) {
+ this.map = map;
+ this.maxSegmentWeight = maxSegmentWeight;
+ this.statsCounter = statsCounter;
+ initTable(newEntryArray(initialCapacity));
+
+ keyReferenceQueue = map.usesKeyReferences()
+ ? new ReferenceQueue<K>() : null;
+
+ valueReferenceQueue = map.usesValueReferences()
+ ? new ReferenceQueue<V>() : null;
+
+ recencyQueue = map.usesAccessQueue()
+ ? new ConcurrentLinkedQueue<ReferenceEntry<K, V>>()
+ : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
+
+ writeQueue = map.usesWriteQueue()
+ ? new WriteQueue<K, V>()
+ : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
+
+ accessQueue = map.usesAccessQueue()
+ ? new AccessQueue<K, V>()
+ : LocalCache.<ReferenceEntry<K, V>>discardingQueue();
+ }
+
+ AtomicReferenceArray<ReferenceEntry<K, V>> newEntryArray(int size) {
+ return new AtomicReferenceArray<ReferenceEntry<K, V>>(size);
+ }
+
+ void initTable(AtomicReferenceArray<ReferenceEntry<K, V>> newTable) {
+ this.threshold = newTable.length() * 3 / 4; // 0.75
+ if (!map.customWeigher() && this.threshold == maxSegmentWeight) {
+ // prevent spurious expansion before eviction
+ this.threshold++;
+ }
+ this.table = newTable;
+ }
+
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> newEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
+ return map.entryFactory.newEntry(this, key, hash, next);
+ }
+
+ /**
+ * Copies {@code original} into a new entry chained to {@code newNext}. Returns the new entry,
+ * or {@code null} if {@code original} was already garbage collected.
+ */
+ @GuardedBy("Segment.this")
+ ReferenceEntry<K, V> copyEntry(ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) {
+ if (original.getKey() == null) {
+ // key collected
+ return null;
+ }
+
+ ValueReference<K, V> valueReference = original.getValueReference();
+ V value = valueReference.get();
+ if ((value == null) && valueReference.isActive()) {
+ // value collected
+ return null;
+ }
+
+ ReferenceEntry<K, V> newEntry = map.entryFactory.copyEntry(this, original, newNext);
+ newEntry.setValueReference(valueReference.copyFor(this.valueReferenceQueue, value, newEntry));
+ return newEntry;
+ }
+
+ /**
+ * Sets a new value of an entry. Adds newly created entries at the end of the access queue.
+ */
+ @GuardedBy("Segment.this")
+ void setValue(ReferenceEntry<K, V> entry, K key, V value, long now) {
+ ValueReference<K, V> previous = entry.getValueReference();
+ int weight = map.weigher.weigh(key, value);
+ checkState(weight >= 0, "Weights must be non-negative");
+
+ ValueReference<K, V> valueReference =
+ map.valueStrength.referenceValue(this, entry, value, weight);
+ entry.setValueReference(valueReference);
+ recordWrite(entry, weight, now);
+ previous.notifyNewValue(value);
+ }
+
+ // loading
+
+ V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
+ try {
+ if (count != 0) { // read-volatile
+ // don't call getLiveEntry, which would ignore loading values
+ ReferenceEntry<K, V> e = getEntry(key, hash);
+ if (e != null) {
+ long now = map.ticker.read();
+ V value = getLiveValue(e, now);
+ if (value != null) {
+ recordRead(e, now);
+ statsCounter.recordHits(1);
+ return scheduleRefresh(e, key, hash, value, now, loader);
+ }
+ ValueReference<K, V> valueReference = e.getValueReference();
+ if (valueReference.isLoading()) {
+ return waitForLoadingValue(e, key, valueReference);
+ }
+ }
+ }
+
+ // at this point e is either null or expired;
+ return lockedGetOrLoad(key, hash, loader);
+ } catch (ExecutionException ee) {
+ Throwable cause = ee.getCause();
+ if (cause instanceof Error) {
+ throw new ExecutionError((Error) cause);
+ } else if (cause instanceof RuntimeException) {
+ throw new UncheckedExecutionException(cause);
+ }
+ throw ee;
+ } finally {
+ postReadCleanup();
+ }
+ }
+
+ V lockedGetOrLoad(K key, int hash, CacheLoader<? super K, V> loader)
+ throws ExecutionException {
+ ReferenceEntry<K, V> e;
+ ValueReference<K, V> valueReference = null;
+ LoadingValueReference<K, V> loadingValueReference = null;
+ boolean createNewEntry = true;
+
+ lock();
+ try {
+ // re-read ticker once inside the lock
+ long now = map.ticker.read();
+ preWriteCleanup(now);
+
+ int newCount = this.count - 1;
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ valueReference = e.getValueReference();
+ if (valueReference.isLoading()) {
+ createNewEntry = false;
+ } else {
+ V value = valueReference.get();
+ if (value == null) {
+ enqueueNotification(entryKey, hash, valueReference, RemovalCause.COLLECTED);
+ } else if (map.isExpired(e, now)) {
+ // This is a duplicate check, as preWriteCleanup already purged expired
+ // entries, but let's accomodate an incorrect expiration queue.
+ enqueueNotification(entryKey, hash, valueReference, RemovalCause.EXPIRED);
+ } else {
+ recordLockedRead(e, now);
+ statsCounter.recordHits(1);
+ // we were concurrent with loading; don't consider refresh
+ return value;
+ }
+
+ // immediately reuse invalid entries
+ writeQueue.remove(e);
+ accessQueue.remove(e);
+ this.count = newCount; // write-volatile
+ }
+ break;
+ }
+ }
+
+ if (createNewEntry) {
+ loadingValueReference = new LoadingValueReference<K, V>();
+
+ if (e == null) {
+ e = newEntry(key, hash, first);
+ e.setValueReference(loadingValueReference);
+ table.set(index, e);
+ } else {
+ e.setValueReference(loadingValueReference);
+ }
+ }
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+
+ if (createNewEntry) {
+ try {
+ // Synchronizes on the entry to allow failing fast when a recursive load is
+ // detected. This may be circumvented when an entry is copied, but will fail fast most
+ // of the time.
+ synchronized (e) {
+ return loadSync(key, hash, loadingValueReference, loader);
+ }
+ } finally {
+ statsCounter.recordMisses(1);
+ }
+ } else {
+ // The entry already exists. Wait for loading.
+ return waitForLoadingValue(e, key, valueReference);
+ }
+ }
+
+ V waitForLoadingValue(ReferenceEntry<K, V> e, K key, ValueReference<K, V> valueReference)
+ throws ExecutionException {
+ if (!valueReference.isLoading()) {
+ throw new AssertionError();
+ }
+
+ checkState(!Thread.holdsLock(e), "Recursive load");
+ // don't consider expiration as we're concurrent with loading
+ try {
+ V value = valueReference.waitForValue();
+ if (value == null) {
+ throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + ".");
+ }
+ // re-read ticker now that loading has completed
+ long now = map.ticker.read();
+ recordRead(e, now);
+ return value;
+ } finally {
+ statsCounter.recordMisses(1);
+ }
+ }
+
+ // at most one of loadSync/loadAsync may be called for any given LoadingValueReference
+
+ V loadSync(K key, int hash, LoadingValueReference<K, V> loadingValueReference,
+ CacheLoader<? super K, V> loader) throws ExecutionException {
+ ListenableFuture<V> loadingFuture = loadingValueReference.loadFuture(key, loader);
+ return getAndRecordStats(key, hash, loadingValueReference, loadingFuture);
+ }
+
+ ListenableFuture<V> loadAsync(final K key, final int hash,
+ final LoadingValueReference<K, V> loadingValueReference, CacheLoader<? super K, V> loader) {
+ final ListenableFuture<V> loadingFuture = loadingValueReference.loadFuture(key, loader);
+ loadingFuture.addListener(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ V newValue = getAndRecordStats(key, hash, loadingValueReference, loadingFuture);
+ // update loadingFuture for the sake of other pending requests
+ loadingValueReference.set(newValue);
+ } catch (Throwable t) {
+ logger.log(Level.WARNING, "Exception thrown during refresh", t);
+ loadingValueReference.setException(t);
+ }
+ }
+ }, sameThreadExecutor);
+ return loadingFuture;
+ }
+
+ /**
+ * Waits uninterruptibly for {@code newValue} to be loaded, and then records loading stats.
+ */
+ V getAndRecordStats(K key, int hash, LoadingValueReference<K, V> loadingValueReference,
+ ListenableFuture<V> newValue) throws ExecutionException {
+ V value = null;
+ try {
+ value = getUninterruptibly(newValue);
+ if (value == null) {
+ throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + ".");
+ }
+ statsCounter.recordLoadSuccess(loadingValueReference.elapsedNanos());
+ storeLoadedValue(key, hash, loadingValueReference, value);
+ return value;
+ } finally {
+ if (value == null) {
+ statsCounter.recordLoadException(loadingValueReference.elapsedNanos());
+ removeLoadingValue(key, hash, loadingValueReference);
+ }
+ }
+ }
+
+ V scheduleRefresh(ReferenceEntry<K, V> entry, K key, int hash, V oldValue, long now,
+ CacheLoader<? super K, V> loader) {
+ if (map.refreshes() && (now - entry.getWriteTime() > map.refreshNanos)) {
+ V newValue = refresh(key, hash, loader);
+ if (newValue != null) {
+ return newValue;
+ }
+ }
+ return oldValue;
+ }
+
+ /**
+ * Refreshes the value associated with {@code key}, unless another thread is already doing so.
+ * Returns the newly refreshed value associated with {@code key} if it was refreshed inline, or
+ * {@code null} if another thread is performing the refresh or if an error occurs during
+ * refresh.
+ */
+ @Nullable
+ V refresh(K key, int hash, CacheLoader<? super K, V> loader) {
+ final LoadingValueReference<K, V> loadingValueReference =
+ insertLoadingValueReference(key, hash);
+ if (loadingValueReference == null) {
+ return null;
+ }
+
+ ListenableFuture<V> result = loadAsync(key, hash, loadingValueReference, loader);
+ if (result.isDone()) {
+ try {
+ return Uninterruptibles.getUninterruptibly(result);
+ } catch (Throwable t) {
+ // don't let refresh exceptions propagate; error was already logged
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a newly inserted {@code LoadingValueReference}, or null if the live value reference
+ * is already loading.
+ */
+ @Nullable
+ LoadingValueReference<K, V> insertLoadingValueReference(final K key, final int hash) {
+ ReferenceEntry<K, V> e = null;
+ lock();
+ try {
+ long now = map.ticker.read();
+ preWriteCleanup(now);
+
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ // Look for an existing entry.
+ for (e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ // We found an existing entry.
+
+ ValueReference<K, V> valueReference = e.getValueReference();
+ if (valueReference.isLoading()) {
+ // refresh is a no-op if loading is pending
+ return null;
+ }
+
+ // continue returning old value while loading
+ ++modCount;
+ LoadingValueReference<K, V> loadingValueReference =
+ new LoadingValueReference<K, V>(valueReference);
+ e.setValueReference(loadingValueReference);
+ return loadingValueReference;
+ }
+ }
+
+ ++modCount;
+ LoadingValueReference<K, V> loadingValueReference = new LoadingValueReference<K, V>();
+ e = newEntry(key, hash, first);
+ e.setValueReference(loadingValueReference);
+ table.set(index, e);
+ return loadingValueReference;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ // reference queues, for garbage collection cleanup
+
+ /**
+ * Cleanup collected entries when the lock is available.
+ */
+ void tryDrainReferenceQueues() {
+ if (tryLock()) {
+ try {
+ drainReferenceQueues();
+ } finally {
+ unlock();
+ }
+ }
+ }
+
+ /**
+ * Drain the key and value reference queues, cleaning up internal entries containing garbage
+ * collected keys or values.
+ */
+ @GuardedBy("Segment.this")
+ void drainReferenceQueues() {
+ if (map.usesKeyReferences()) {
+ drainKeyReferenceQueue();
+ }
+ if (map.usesValueReferences()) {
+ drainValueReferenceQueue();
+ }
+ }
+
+ @GuardedBy("Segment.this")
+ void drainKeyReferenceQueue() {
+ Reference<? extends K> ref;
+ int i = 0;
+ while ((ref = keyReferenceQueue.poll()) != null) {
+ @SuppressWarnings("unchecked")
+ ReferenceEntry<K, V> entry = (ReferenceEntry<K, V>) ref;
+ map.reclaimKey(entry);
+ if (++i == DRAIN_MAX) {
+ break;
+ }
+ }
+ }
+
+ @GuardedBy("Segment.this")
+ void drainValueReferenceQueue() {
+ Reference<? extends V> ref;
+ int i = 0;
+ while ((ref = valueReferenceQueue.poll()) != null) {
+ @SuppressWarnings("unchecked")
+ ValueReference<K, V> valueReference = (ValueReference<K, V>) ref;
+ map.reclaimValue(valueReference);
+ if (++i == DRAIN_MAX) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Clears all entries from the key and value reference queues.
+ */
+ void clearReferenceQueues() {
+ if (map.usesKeyReferences()) {
+ clearKeyReferenceQueue();
+ }
+ if (map.usesValueReferences()) {
+ clearValueReferenceQueue();
+ }
+ }
+
+ void clearKeyReferenceQueue() {
+ while (keyReferenceQueue.poll() != null) {}
+ }
+
+ void clearValueReferenceQueue() {
+ while (valueReferenceQueue.poll() != null) {}
+ }
+
+ // recency queue, shared by expiration and eviction
+
+ /**
+ * Records the relative order in which this read was performed by adding {@code entry} to the
+ * recency queue. At write-time, or when the queue is full past the threshold, the queue will
+ * be drained and the entries therein processed.
+ *
+ * <p>Note: locked reads should use {@link #recordLockedRead}.
+ */
+ void recordRead(ReferenceEntry<K, V> entry, long now) {
+ if (map.recordsAccess()) {
+ entry.setAccessTime(now);
+ }
+ recencyQueue.add(entry);
+ }
+
+ /**
+ * Updates the eviction metadata that {@code entry} was just read. This currently amounts to
+ * adding {@code entry} to relevant eviction lists.
+ *
+ * <p>Note: this method should only be called under lock, as it directly manipulates the
+ * eviction queues. Unlocked reads should use {@link #recordRead}.
+ */
+ @GuardedBy("Segment.this")
+ void recordLockedRead(ReferenceEntry<K, V> entry, long now) {
+ if (map.recordsAccess()) {
+ entry.setAccessTime(now);
+ }
+ accessQueue.add(entry);
+ }
+
+ /**
+ * Updates eviction metadata that {@code entry} was just written. This currently amounts to
+ * adding {@code entry} to relevant eviction lists.
+ */
+ @GuardedBy("Segment.this")
+ void recordWrite(ReferenceEntry<K, V> entry, int weight, long now) {
+ // we are already under lock, so drain the recency queue immediately
+ drainRecencyQueue();
+ totalWeight += weight;
+
+ if (map.recordsAccess()) {
+ entry.setAccessTime(now);
+ }
+ if (map.recordsWrite()) {
+ entry.setWriteTime(now);
+ }
+ accessQueue.add(entry);
+ writeQueue.add(entry);
+ }
+
+ /**
+ * Drains the recency queue, updating eviction metadata that the entries therein were read in
+ * the specified relative order. This currently amounts to adding them to relevant eviction
+ * lists (accounting for the fact that they could have been removed from the map since being
+ * added to the recency queue).
+ */
+ @GuardedBy("Segment.this")
+ void drainRecencyQueue() {
+ ReferenceEntry<K, V> e;
+ while ((e = recencyQueue.poll()) != null) {
+ // An entry may be in the recency queue despite it being removed from
+ // the map . This can occur when the entry was concurrently read while a
+ // writer is removing it from the segment or after a clear has removed
+ // all of the segment's entries.
+ if (accessQueue.contains(e)) {
+ accessQueue.add(e);
+ }
+ }
+ }
+
+ // expiration
+
+ /**
+ * Cleanup expired entries when the lock is available.
+ */
+ void tryExpireEntries(long now) {
+ if (tryLock()) {
+ try {
+ expireEntries(now);
+ } finally {
+ unlock();
+ // don't call postWriteCleanup as we're in a read
+ }
+ }
+ }
+
+ @GuardedBy("Segment.this")
+ void expireEntries(long now) {
+ drainRecencyQueue();
+
+ ReferenceEntry<K, V> e;
+ while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {
+ if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
+ throw new AssertionError();
+ }
+ }
+ while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {
+ if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ // eviction
+
+ @GuardedBy("Segment.this")
+ void enqueueNotification(ReferenceEntry<K, V> entry, RemovalCause cause) {
+ enqueueNotification(entry.getKey(), entry.getHash(), entry.getValueReference(), cause);
+ }
+
+ @GuardedBy("Segment.this")
+ void enqueueNotification(@Nullable K key, int hash, ValueReference<K, V> valueReference,
+ RemovalCause cause) {
+ totalWeight -= valueReference.getWeight();
+ if (cause.wasEvicted()) {
+ statsCounter.recordEviction();
+ }
+ if (map.removalNotificationQueue != DISCARDING_QUEUE) {
+ V value = valueReference.get();
+ RemovalNotification<K, V> notification = new RemovalNotification<K, V>(key, value, cause);
+ map.removalNotificationQueue.offer(notification);
+ }
+ }
+
+ /**
+ * Performs eviction if the segment is full. This should only be called prior to adding a new
+ * entry and increasing {@code count}.
+ */
+ @GuardedBy("Segment.this")
+ void evictEntries() {
+ if (!map.evictsBySize()) {
+ return;
+ }
+
+ drainRecencyQueue();
+ while (totalWeight > maxSegmentWeight) {
+ ReferenceEntry<K, V> e = getNextEvictable();
+ if (!removeEntry(e, e.getHash(), RemovalCause.SIZE)) {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ // TODO(fry): instead implement this with an eviction head
+ ReferenceEntry<K, V> getNextEvictable() {
+ for (ReferenceEntry<K, V> e : accessQueue) {
+ int weight = e.getValueReference().getWeight();
+ if (weight > 0) {
+ return e;
+ }
+ }
+ throw new AssertionError();
+ }
+
+ /**
+ * Returns first entry of bin for given hash.
+ */
+ ReferenceEntry<K, V> getFirst(int hash) {
+ // read this volatile field only once
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ return table.get(hash & (table.length() - 1));
+ }
+
+ // Specialized implementations of map methods
+
+ @Nullable
+ ReferenceEntry<K, V> getEntry(Object key, int hash) {
+ for (ReferenceEntry<K, V> e = getFirst(hash); e != null; e = e.getNext()) {
+ if (e.getHash() != hash) {
+ continue;
+ }
+
+ K entryKey = e.getKey();
+ if (entryKey == null) {
+ tryDrainReferenceQueues();
+ continue;
+ }
+
+ if (map.keyEquivalence.equivalent(key, entryKey)) {
+ return e;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ ReferenceEntry<K, V> getLiveEntry(Object key, int hash, long now) {
+ ReferenceEntry<K, V> e = getEntry(key, hash);
+ if (e == null) {
+ return null;
+ } else if (map.isExpired(e, now)) {
+ tryExpireEntries(now);
+ return null;
+ }
+ return e;
+ }
+
+ /**
+ * Gets the value from an entry. Returns null if the entry is invalid, partially-collected,
+ * loading, or expired.
+ */
+ V getLiveValue(ReferenceEntry<K, V> entry, long now) {
+ if (entry.getKey() == null) {
+ tryDrainReferenceQueues();
+ return null;
+ }
+ V value = entry.getValueReference().get();
+ if (value == null) {
+ tryDrainReferenceQueues();
+ return null;
+ }
+
+ if (map.isExpired(entry, now)) {
+ tryExpireEntries(now);
+ return null;
+ }
+ return value;
+ }
+
+ @Nullable
+ V get(Object key, int hash) {
+ try {
+ if (count != 0) { // read-volatile
+ long now = map.ticker.read();
+ ReferenceEntry<K, V> e = getLiveEntry(key, hash, now);
+ if (e == null) {
+ return null;
+ }
+
+ V value = e.getValueReference().get();
+ if (value != null) {
+ recordRead(e, now);
+ return scheduleRefresh(e, e.getKey(), hash, value, now, map.defaultLoader);
+ }
+ tryDrainReferenceQueues();
+ }
+ return null;
+ } finally {
+ postReadCleanup();
+ }
+ }
+
+ boolean containsKey(Object key, int hash) {
+ try {
+ if (count != 0) { // read-volatile
+ long now = map.ticker.read();
+ ReferenceEntry<K, V> e = getLiveEntry(key, hash, now);
+ if (e == null) {
+ return false;
+ }
+ return e.getValueReference().get() != null;
+ }
+
+ return false;
+ } finally {
+ postReadCleanup();
+ }
+ }
+
+ /**
+ * This method is a convenience for testing. Code should call {@link
+ * LocalCache#containsValue} directly.
+ */
+ @VisibleForTesting
+ boolean containsValue(Object value) {
+ try {
+ if (count != 0) { // read-volatile
+ long now = map.ticker.read();
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int length = table.length();
+ for (int i = 0; i < length; ++i) {
+ for (ReferenceEntry<K, V> e = table.get(i); e != null; e = e.getNext()) {
+ V entryValue = getLiveValue(e, now);
+ if (entryValue == null) {
+ continue;
+ }
+ if (map.valueEquivalence.equivalent(value, entryValue)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ } finally {
+ postReadCleanup();
+ }
+ }
+
+ @Nullable
+ V put(K key, int hash, V value, boolean onlyIfAbsent) {
+ lock();
+ try {
+ long now = map.ticker.read();
+ preWriteCleanup(now);
+
+ int newCount = this.count + 1;
+ if (newCount > this.threshold) { // ensure capacity
+ expand();
+ newCount = this.count + 1;
+ }
+
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ // Look for an existing entry.
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ // We found an existing entry.
+
+ ValueReference<K, V> valueReference = e.getValueReference();
+ V entryValue = valueReference.get();
+
+ if (entryValue == null) {
+ ++modCount;
+ if (valueReference.isActive()) {
+ enqueueNotification(key, hash, valueReference, RemovalCause.COLLECTED);
+ setValue(e, key, value, now);
+ newCount = this.count; // count remains unchanged
+ } else {
+ setValue(e, key, value, now);
+ newCount = this.count + 1;
+ }
+ this.count = newCount; // write-volatile
+ evictEntries();
+ return null;
+ } else if (onlyIfAbsent) {
+ // Mimic
+ // "if (!map.containsKey(key)) ...
+ // else return map.get(key);
+ recordLockedRead(e, now);
+ return entryValue;
+ } else {
+ // clobber existing entry, count remains unchanged
+ ++modCount;
+ enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);
+ setValue(e, key, value, now);
+ evictEntries();
+ return entryValue;
+ }
+ }
+ }
+
+ // Create a new entry.
+ ++modCount;
+ ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);
+ setValue(newEntry, key, value, now);
+ table.set(index, newEntry);
+ newCount = this.count + 1;
+ this.count = newCount; // write-volatile
+ evictEntries();
+ return null;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ /**
+ * Expands the table if possible.
+ */
+ @GuardedBy("Segment.this")
+ void expand() {
+ AtomicReferenceArray<ReferenceEntry<K, V>> oldTable = table;
+ int oldCapacity = oldTable.length();
+ if (oldCapacity >= MAXIMUM_CAPACITY) {
+ return;
+ }
+
+ /*
+ * Reclassify nodes in each list to new Map. Because we are using power-of-two expansion, the
+ * elements from each bin must either stay at same index, or move with a power of two offset.
+ * We eliminate unnecessary node creation by catching cases where old nodes can be reused
+ * because their next fields won't change. Statistically, at the default threshold, only
+ * about one-sixth of them need cloning when a table doubles. The nodes they replace will be
+ * garbage collectable as soon as they are no longer referenced by any reader thread that may
+ * be in the midst of traversing table right now.
+ */
+
+ int newCount = count;
+ AtomicReferenceArray<ReferenceEntry<K, V>> newTable = newEntryArray(oldCapacity << 1);
+ threshold = newTable.length() * 3 / 4;
+ int newMask = newTable.length() - 1;
+ for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) {
+ // We need to guarantee that any existing reads of old Map can
+ // proceed. So we cannot yet null out each bin.
+ ReferenceEntry<K, V> head = oldTable.get(oldIndex);
+
+ if (head != null) {
+ ReferenceEntry<K, V> next = head.getNext();
+ int headIndex = head.getHash() & newMask;
+
+ // Single node on list
+ if (next == null) {
+ newTable.set(headIndex, head);
+ } else {
+ // Reuse the consecutive sequence of nodes with the same target
+ // index from the end of the list. tail points to the first
+ // entry in the reusable list.
+ ReferenceEntry<K, V> tail = head;
+ int tailIndex = headIndex;
+ for (ReferenceEntry<K, V> e = next; e != null; e = e.getNext()) {
+ int newIndex = e.getHash() & newMask;
+ if (newIndex != tailIndex) {
+ // The index changed. We'll need to copy the previous entry.
+ tailIndex = newIndex;
+ tail = e;
+ }
+ }
+ newTable.set(tailIndex, tail);
+
+ // Clone nodes leading up to the tail.
+ for (ReferenceEntry<K, V> e = head; e != tail; e = e.getNext()) {
+ int newIndex = e.getHash() & newMask;
+ ReferenceEntry<K, V> newNext = newTable.get(newIndex);
+ ReferenceEntry<K, V> newFirst = copyEntry(e, newNext);
+ if (newFirst != null) {
+ newTable.set(newIndex, newFirst);
+ } else {
+ removeCollectedEntry(e);
+ newCount--;
+ }
+ }
+ }
+ }
+ }
+ table = newTable;
+ this.count = newCount;
+ }
+
+ boolean replace(K key, int hash, V oldValue, V newValue) {
+ lock();
+ try {
+ long now = map.ticker.read();
+ preWriteCleanup(now);
+
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ ValueReference<K, V> valueReference = e.getValueReference();
+ V entryValue = valueReference.get();
+ if (entryValue == null) {
+ if (valueReference.isActive()) {
+ // If the value disappeared, this entry is partially collected.
+ int newCount = this.count - 1;
+ ++modCount;
+ ReferenceEntry<K, V> newFirst = removeValueFromChain(
+ first, e, entryKey, hash, valueReference, RemovalCause.COLLECTED);
+ newCount = this.count - 1;
+ table.set(index, newFirst);
+ this.count = newCount; // write-volatile
+ }
+ return false;
+ }
+
+ if (map.valueEquivalence.equivalent(oldValue, entryValue)) {
+ ++modCount;
+ enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);
+ setValue(e, key, newValue, now);
+ evictEntries();
+ return true;
+ } else {
+ // Mimic
+ // "if (map.containsKey(key) && map.get(key).equals(oldValue))..."
+ recordLockedRead(e, now);
+ return false;
+ }
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ @Nullable
+ V replace(K key, int hash, V newValue) {
+ lock();
+ try {
+ long now = map.ticker.read();
+ preWriteCleanup(now);
+
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ ValueReference<K, V> valueReference = e.getValueReference();
+ V entryValue = valueReference.get();
+ if (entryValue == null) {
+ if (valueReference.isActive()) {
+ // If the value disappeared, this entry is partially collected.
+ int newCount = this.count - 1;
+ ++modCount;
+ ReferenceEntry<K, V> newFirst = removeValueFromChain(
+ first, e, entryKey, hash, valueReference, RemovalCause.COLLECTED);
+ newCount = this.count - 1;
+ table.set(index, newFirst);
+ this.count = newCount; // write-volatile
+ }
+ return null;
+ }
+
+ ++modCount;
+ enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);
+ setValue(e, key, newValue, now);
+ evictEntries();
+ return entryValue;
+ }
+ }
+
+ return null;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ @Nullable
+ V remove(Object key, int hash) {
+ lock();
+ try {
+ long now = map.ticker.read();
+ preWriteCleanup(now);
+
+ int newCount = this.count - 1;
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ ValueReference<K, V> valueReference = e.getValueReference();
+ V entryValue = valueReference.get();
+
+ RemovalCause cause;
+ if (entryValue != null) {
+ cause = RemovalCause.EXPLICIT;
+ } else if (valueReference.isActive()) {
+ cause = RemovalCause.COLLECTED;
+ } else {
+ // currently loading
+ return null;
+ }
+
+ ++modCount;
+ ReferenceEntry<K, V> newFirst = removeValueFromChain(
+ first, e, entryKey, hash, valueReference, cause);
+ newCount = this.count - 1;
+ table.set(index, newFirst);
+ this.count = newCount; // write-volatile
+ return entryValue;
+ }
+ }
+
+ return null;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ boolean storeLoadedValue(K key, int hash, LoadingValueReference<K, V> oldValueReference,
+ V newValue) {
+ lock();
+ try {
+ long now = map.ticker.read();
+ preWriteCleanup(now);
+
+ int newCount = this.count + 1;
+ if (newCount > this.threshold) { // ensure capacity
+ expand();
+ newCount = this.count + 1;
+ }
+
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ ValueReference<K, V> valueReference = e.getValueReference();
+ V entryValue = valueReference.get();
+ // replace the old LoadingValueReference if it's live, otherwise
+ // perform a putIfAbsent
+ if (oldValueReference == valueReference
+ || (entryValue == null && valueReference != UNSET)) {
+ ++modCount;
+ if (oldValueReference.isActive()) {
+ RemovalCause cause =
+ (entryValue == null) ? RemovalCause.COLLECTED : RemovalCause.REPLACED;
+ enqueueNotification(key, hash, oldValueReference, cause);
+ newCount--;
+ }
+ setValue(e, key, newValue, now);
+ this.count = newCount; // write-volatile
+ evictEntries();
+ return true;
+ }
+
+ // the loaded value was already clobbered
+ valueReference = new WeightedStrongValueReference<K, V>(newValue, 0);
+ enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);
+ return false;
+ }
+ }
+
+ ++modCount;
+ ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);
+ setValue(newEntry, key, newValue, now);
+ table.set(index, newEntry);
+ this.count = newCount; // write-volatile
+ evictEntries();
+ return true;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ boolean remove(Object key, int hash, Object value) {
+ lock();
+ try {
+ long now = map.ticker.read();
+ preWriteCleanup(now);
+
+ int newCount = this.count - 1;
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ ValueReference<K, V> valueReference = e.getValueReference();
+ V entryValue = valueReference.get();
+
+ RemovalCause cause;
+ if (map.valueEquivalence.equivalent(value, entryValue)) {
+ cause = RemovalCause.EXPLICIT;
+ } else if (entryValue == null && valueReference.isActive()) {
+ cause = RemovalCause.COLLECTED;
+ } else {
+ // currently loading
+ return false;
+ }
+
+ ++modCount;
+ ReferenceEntry<K, V> newFirst = removeValueFromChain(
+ first, e, entryKey, hash, valueReference, cause);
+ newCount = this.count - 1;
+ table.set(index, newFirst);
+ this.count = newCount; // write-volatile
+ return (cause == RemovalCause.EXPLICIT);
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ void clear() {
+ if (count != 0) { // read-volatile
+ lock();
+ try {
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ for (int i = 0; i < table.length(); ++i) {
+ for (ReferenceEntry<K, V> e = table.get(i); e != null; e = e.getNext()) {
+ // Loading references aren't actually in the map yet.
+ if (e.getValueReference().isActive()) {
+ enqueueNotification(e, RemovalCause.EXPLICIT);
+ }
+ }
+ }
+ for (int i = 0; i < table.length(); ++i) {
+ table.set(i, null);
+ }
+ clearReferenceQueues();
+ writeQueue.clear();
+ accessQueue.clear();
+ readCount.set(0);
+
+ ++modCount;
+ count = 0; // write-volatile
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+ }
+
+ @GuardedBy("Segment.this")
+ @Nullable
+ ReferenceEntry<K, V> removeValueFromChain(ReferenceEntry<K, V> first,
+ ReferenceEntry<K, V> entry, @Nullable K key, int hash, ValueReference<K, V> valueReference,
+ RemovalCause cause) {
+ enqueueNotification(key, hash, valueReference, cause);
+ writeQueue.remove(entry);
+ accessQueue.remove(entry);
+
+ if (valueReference.isLoading()) {
+ valueReference.notifyNewValue(null);
+ return first;
+ } else {
+ return removeEntryFromChain(first, entry);
+ }
+ }
+
+ @GuardedBy("Segment.this")
+ @Nullable
+ ReferenceEntry<K, V> removeEntryFromChain(ReferenceEntry<K, V> first,
+ ReferenceEntry<K, V> entry) {
+ int newCount = count;
+ ReferenceEntry<K, V> newFirst = entry.getNext();
+ for (ReferenceEntry<K, V> e = first; e != entry; e = e.getNext()) {
+ ReferenceEntry<K, V> next = copyEntry(e, newFirst);
+ if (next != null) {
+ newFirst = next;
+ } else {
+ removeCollectedEntry(e);
+ newCount--;
+ }
+ }
+ this.count = newCount;
+ return newFirst;
+ }
+
+ @GuardedBy("Segment.this")
+ void removeCollectedEntry(ReferenceEntry<K, V> entry) {
+ enqueueNotification(entry, RemovalCause.COLLECTED);
+ writeQueue.remove(entry);
+ accessQueue.remove(entry);
+ }
+
+ /**
+ * Removes an entry whose key has been garbage collected.
+ */
+ boolean reclaimKey(ReferenceEntry<K, V> entry, int hash) {
+ lock();
+ try {
+ int newCount = count - 1;
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ if (e == entry) {
+ ++modCount;
+ ReferenceEntry<K, V> newFirst = removeValueFromChain(
+ first, e, e.getKey(), hash, e.getValueReference(), RemovalCause.COLLECTED);
+ newCount = this.count - 1;
+ table.set(index, newFirst);
+ this.count = newCount; // write-volatile
+ return true;
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ /**
+ * Removes an entry whose value has been garbage collected.
+ */
+ boolean reclaimValue(K key, int hash, ValueReference<K, V> valueReference) {
+ lock();
+ try {
+ int newCount = this.count - 1;
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ ValueReference<K, V> v = e.getValueReference();
+ if (v == valueReference) {
+ ++modCount;
+ ReferenceEntry<K, V> newFirst = removeValueFromChain(
+ first, e, entryKey, hash, valueReference, RemovalCause.COLLECTED);
+ newCount = this.count - 1;
+ table.set(index, newFirst);
+ this.count = newCount; // write-volatile
+ return true;
+ }
+ return false;
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ if (!isHeldByCurrentThread()) { // don't cleanup inside of put
+ postWriteCleanup();
+ }
+ }
+ }
+
+ boolean removeLoadingValue(K key, int hash, LoadingValueReference<K, V> valueReference) {
+ lock();
+ try {
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ K entryKey = e.getKey();
+ if (e.getHash() == hash && entryKey != null
+ && map.keyEquivalence.equivalent(key, entryKey)) {
+ ValueReference<K, V> v = e.getValueReference();
+ if (v == valueReference) {
+ if (valueReference.isActive()) {
+ e.setValueReference(valueReference.getOldValue());
+ } else {
+ ReferenceEntry<K, V> newFirst = removeEntryFromChain(first, e);
+ table.set(index, newFirst);
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+
+ return false;
+ } finally {
+ unlock();
+ postWriteCleanup();
+ }
+ }
+
+ @GuardedBy("Segment.this")
+ boolean removeEntry(ReferenceEntry<K, V> entry, int hash, RemovalCause cause) {
+ int newCount = this.count - 1;
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
+ int index = hash & (table.length() - 1);
+ ReferenceEntry<K, V> first = table.get(index);
+
+ for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
+ if (e == entry) {
+ ++modCount;
+ ReferenceEntry<K, V> newFirst = removeValueFromChain(
+ first, e, e.getKey(), hash, e.getValueReference(), cause);
+ newCount = this.count - 1;
+ table.set(index, newFirst);
+ this.count = newCount; // write-volatile
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Performs routine cleanup following a read. Normally cleanup happens during writes. If cleanup
+ * is not observed after a sufficient number of reads, try cleaning up from the read thread.
+ */
+ void postReadCleanup() {
+ if ((readCount.incrementAndGet() & DRAIN_THRESHOLD) == 0) {
+ cleanUp();
+ }
+ }
+
+ /**
+ * Performs routine cleanup prior to executing a write. This should be called every time a
+ * write thread acquires the segment lock, immediately after acquiring the lock.
+ *
+ * <p>Post-condition: expireEntries has been run.
+ */
+ @GuardedBy("Segment.this")
+ void preWriteCleanup(long now) {
+ runLockedCleanup(now);
+ }
+
+ /**
+ * Performs routine cleanup following a write.
+ */
+ void postWriteCleanup() {
+ runUnlockedCleanup();
+ }
+
+ void cleanUp() {
+ long now = map.ticker.read();
+ runLockedCleanup(now);
+ runUnlockedCleanup();
+ }
+
+ void runLockedCleanup(long now) {
+ if (tryLock()) {
+ try {
+ drainReferenceQueues();
+ expireEntries(now); // calls drainRecencyQueue
+ readCount.set(0);
+ } finally {
+ unlock();
+ }
+ }
+ }
+
+ void runUnlockedCleanup() {
+ // locked cleanup may generate notifications we can send unlocked
+ if (!isHeldByCurrentThread()) {
+ map.processPendingNotifications();
+ }
+ }
+
+ }
+
+ static class LoadingValueReference<K, V> implements ValueReference<K, V> {
+ volatile ValueReference<K, V> oldValue;
+
+ // TODO(fry): rename get, then extend AbstractFuture instead of containing SettableFuture
+ final SettableFuture<V> futureValue = SettableFuture.create();
+ final Stopwatch stopwatch = new Stopwatch();
+
+ public LoadingValueReference() {
+ this(LocalCache.<K, V>unset());
+ }
+
+ public LoadingValueReference(ValueReference<K, V> oldValue) {
+ this.oldValue = oldValue;
+ }
+
+ @Override
+ public boolean isLoading() {
+ return true;
+ }
+
+ @Override
+ public boolean isActive() {
+ return oldValue.isActive();
+ }
+
+ @Override
+ public int getWeight() {
+ return oldValue.getWeight();
+ }
+
+ public boolean set(@Nullable V newValue) {
+ return futureValue.set(newValue);
+ }
+
+ public boolean setException(Throwable t) {
+ return setException(futureValue, t);
+ }
+
+ private static boolean setException(SettableFuture<?> future, Throwable t) {
+ try {
+ return future.setException(t);
+ } catch (Error e) {
+ // the error will already be propagated by the loading thread
+ return false;
+ }
+ }
+
+ private ListenableFuture<V> fullyFailedFuture(Throwable t) {
+ SettableFuture<V> future = SettableFuture.create();
+ setException(future, t);
+ return future;
+ }
+
+ @Override
+ public void notifyNewValue(@Nullable V newValue) {
+ if (newValue != null) {
+ // The pending load was clobbered by a manual write.
+ // Unblock all pending gets, and have them return the new value.
+ set(newValue);
+ } else {
+ // The pending load was removed. Delay notifications until loading completes.
+ oldValue = unset();
+ }
+
+ // TODO(fry): could also cancel loading if we had a handle on its future
+ }
+
+ public ListenableFuture<V> loadFuture(K key, CacheLoader<? super K, V> loader) {
+ stopwatch.start();
+ V previousValue = oldValue.get();
+ try {
+ if (previousValue == null) {
+ V newValue = loader.load(key);
+ return set(newValue) ? futureValue : Futures.immediateFuture(newValue);
+ } else {
+ ListenableFuture<V> newValue = loader.reload(key, previousValue);
+ // rely on loadAsync to call set in order to avoid adding a second listener here
+ return newValue != null ? newValue : Futures.<V>immediateFuture(null);
+ }
+ } catch (Throwable t) {
+ if (t instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ return setException(t) ? futureValue : fullyFailedFuture(t);
+ }
+ }
+
+ public long elapsedNanos() {
+ return stopwatch.elapsedTime(NANOSECONDS);
+ }
+
+ @Override
+ public V waitForValue() throws ExecutionException {
+ return getUninterruptibly(futureValue);
+ }
+
+ @Override
+ public V get() {
+ return oldValue.get();
+ }
+
+ public ValueReference<K, V> getOldValue() {
+ return oldValue;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> getEntry() {
+ return null;
+ }
+
+ @Override
+ public ValueReference<K, V> copyFor(
+ ReferenceQueue<V> queue, @Nullable V value, ReferenceEntry<K, V> entry) {
+ return this;
+ }
+ }
+
+ // Queues
+
+ /**
+ * A custom queue for managing eviction order. Note that this is tightly integrated with {@code
+ * ReferenceEntry}, upon which it relies to perform its linking.
+ *
+ * <p>Note that this entire implementation makes the assumption that all elements which are in
+ * the map are also in this queue, and that all elements not in the queue are not in the map.
+ *
+ * <p>The benefits of creating our own queue are that (1) we can replace elements in the middle
+ * of the queue as part of copyWriteEntry, and (2) the contains method is highly optimized
+ * for the current model.
+ */
+ static final class WriteQueue<K, V> extends AbstractQueue<ReferenceEntry<K, V>> {
+ final ReferenceEntry<K, V> head = new AbstractReferenceEntry<K, V>() {
+
+ @Override
+ public long getWriteTime() {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public void setWriteTime(long time) {}
+
+ ReferenceEntry<K, V> nextWrite = this;
+
+ @Override
+ public ReferenceEntry<K, V> getNextInWriteQueue() {
+ return nextWrite;
+ }
+
+ @Override
+ public void setNextInWriteQueue(ReferenceEntry<K, V> next) {
+ this.nextWrite = next;
+ }
+
+ ReferenceEntry<K, V> previousWrite = this;
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInWriteQueue() {
+ return previousWrite;
+ }
+
+ @Override
+ public void setPreviousInWriteQueue(ReferenceEntry<K, V> previous) {
+ this.previousWrite = previous;
+ }
+ };
+
+ // implements Queue
+
+ @Override
+ public boolean offer(ReferenceEntry<K, V> entry) {
+ // unlink
+ connectWriteOrder(entry.getPreviousInWriteQueue(), entry.getNextInWriteQueue());
+
+ // add to tail
+ connectWriteOrder(head.getPreviousInWriteQueue(), entry);
+ connectWriteOrder(entry, head);
+
+ return true;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> peek() {
+ ReferenceEntry<K, V> next = head.getNextInWriteQueue();
+ return (next == head) ? null : next;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> poll() {
+ ReferenceEntry<K, V> next = head.getNextInWriteQueue();
+ if (next == head) {
+ return null;
+ }
+
+ remove(next);
+ return next;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean remove(Object o) {
+ ReferenceEntry<K, V> e = (ReferenceEntry) o;
+ ReferenceEntry<K, V> previous = e.getPreviousInWriteQueue();
+ ReferenceEntry<K, V> next = e.getNextInWriteQueue();
+ connectWriteOrder(previous, next);
+ nullifyWriteOrder(e);
+
+ return next != NullEntry.INSTANCE;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean contains(Object o) {
+ ReferenceEntry<K, V> e = (ReferenceEntry) o;
+ return e.getNextInWriteQueue() != NullEntry.INSTANCE;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return head.getNextInWriteQueue() == head;
+ }
+
+ @Override
+ public int size() {
+ int size = 0;
+ for (ReferenceEntry<K, V> e = head.getNextInWriteQueue(); e != head;
+ e = e.getNextInWriteQueue()) {
+ size++;
+ }
+ return size;
+ }
+
+ @Override
+ public void clear() {
+ ReferenceEntry<K, V> e = head.getNextInWriteQueue();
+ while (e != head) {
+ ReferenceEntry<K, V> next = e.getNextInWriteQueue();
+ nullifyWriteOrder(e);
+ e = next;
+ }
+
+ head.setNextInWriteQueue(head);
+ head.setPreviousInWriteQueue(head);
+ }
+
+ @Override
+ public Iterator<ReferenceEntry<K, V>> iterator() {
+ return new AbstractSequentialIterator<ReferenceEntry<K, V>>(peek()) {
+ @Override
+ protected ReferenceEntry<K, V> computeNext(ReferenceEntry<K, V> previous) {
+ ReferenceEntry<K, V> next = previous.getNextInWriteQueue();
+ return (next == head) ? null : next;
+ }
+ };
+ }
+ }
+
+ /**
+ * A custom queue for managing access order. Note that this is tightly integrated with
+ * {@code ReferenceEntry}, upon which it reliese to perform its linking.
+ *
+ * <p>Note that this entire implementation makes the assumption that all elements which are in
+ * the map are also in this queue, and that all elements not in the queue are not in the map.
+ *
+ * <p>The benefits of creating our own queue are that (1) we can replace elements in the middle
+ * of the queue as part of copyWriteEntry, and (2) the contains method is highly optimized
+ * for the current model.
+ */
+ static final class AccessQueue<K, V> extends AbstractQueue<ReferenceEntry<K, V>> {
+ final ReferenceEntry<K, V> head = new AbstractReferenceEntry<K, V>() {
+
+ @Override
+ public long getAccessTime() {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public void setAccessTime(long time) {}
+
+ ReferenceEntry<K, V> nextAccess = this;
+
+ @Override
+ public ReferenceEntry<K, V> getNextInAccessQueue() {
+ return nextAccess;
+ }
+
+ @Override
+ public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
+ this.nextAccess = next;
+ }
+
+ ReferenceEntry<K, V> previousAccess = this;
+
+ @Override
+ public ReferenceEntry<K, V> getPreviousInAccessQueue() {
+ return previousAccess;
+ }
+
+ @Override
+ public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
+ this.previousAccess = previous;
+ }
+ };
+
+ // implements Queue
+
+ @Override
+ public boolean offer(ReferenceEntry<K, V> entry) {
+ // unlink
+ connectAccessOrder(entry.getPreviousInAccessQueue(), entry.getNextInAccessQueue());
+
+ // add to tail
+ connectAccessOrder(head.getPreviousInAccessQueue(), entry);
+ connectAccessOrder(entry, head);
+
+ return true;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> peek() {
+ ReferenceEntry<K, V> next = head.getNextInAccessQueue();
+ return (next == head) ? null : next;
+ }
+
+ @Override
+ public ReferenceEntry<K, V> poll() {
+ ReferenceEntry<K, V> next = head.getNextInAccessQueue();
+ if (next == head) {
+ return null;
+ }
+
+ remove(next);
+ return next;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean remove(Object o) {
+ ReferenceEntry<K, V> e = (ReferenceEntry) o;
+ ReferenceEntry<K, V> previous = e.getPreviousInAccessQueue();
+ ReferenceEntry<K, V> next = e.getNextInAccessQueue();
+ connectAccessOrder(previous, next);
+ nullifyAccessOrder(e);
+
+ return next != NullEntry.INSTANCE;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean contains(Object o) {
+ ReferenceEntry<K, V> e = (ReferenceEntry) o;
+ return e.getNextInAccessQueue() != NullEntry.INSTANCE;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return head.getNextInAccessQueue() == head;
+ }
+
+ @Override
+ public int size() {
+ int size = 0;
+ for (ReferenceEntry<K, V> e = head.getNextInAccessQueue(); e != head;
+ e = e.getNextInAccessQueue()) {
+ size++;
+ }
+ return size;
+ }
+
+ @Override
+ public void clear() {
+ ReferenceEntry<K, V> e = head.getNextInAccessQueue();
+ while (e != head) {
+ ReferenceEntry<K, V> next = e.getNextInAccessQueue();
+ nullifyAccessOrder(e);
+ e = next;
+ }
+
+ head.setNextInAccessQueue(head);
+ head.setPreviousInAccessQueue(head);
+ }
+
+ @Override
+ public Iterator<ReferenceEntry<K, V>> iterator() {
+ return new AbstractSequentialIterator<ReferenceEntry<K, V>>(peek()) {
+ @Override
+ protected ReferenceEntry<K, V> computeNext(ReferenceEntry<K, V> previous) {
+ ReferenceEntry<K, V> next = previous.getNextInAccessQueue();
+ return (next == head) ? null : next;
+ }
+ };
+ }
+ }
+
+ // Cache support
+
+ public void cleanUp() {
+ for (Segment<?, ?> segment : segments) {
+ segment.cleanUp();
+ }
+ }
+
+ // ConcurrentMap methods
+
+ @Override
+ public boolean isEmpty() {
+ /*
+ * Sum per-segment modCounts to avoid mis-reporting when elements are concurrently added and
+ * removed in one segment while checking another, in which case the table was never actually
+ * empty at any point. (The sum ensures accuracy up through at least 1<<31 per-segment
+ * modifications before recheck.) Method containsValue() uses similar constructions for
+ * stability checks.
+ */
+ long sum = 0L;
+ Segment<K, V>[] segments = this.segments;
+ for (int i = 0; i < segments.length; ++i) {
+ if (segments[i].count != 0) {
+ return false;
+ }
+ sum += segments[i].modCount;
+ }
+
+ if (sum != 0L) { // recheck unless no modifications
+ for (int i = 0; i < segments.length; ++i) {
+ if (segments[i].count != 0) {
+ return false;
+ }
+ sum -= segments[i].modCount;
+ }
+ if (sum != 0L) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ long longSize() {
+ Segment<K, V>[] segments = this.segments;
+ long sum = 0;
+ for (int i = 0; i < segments.length; ++i) {
+ sum += segments[i].count;
+ }
+ return sum;
+ }
+
+ @Override
+ public int size() {
+ return Ints.saturatedCast(longSize());
+ }
+
+ @Override
+ @Nullable
+ public V get(@Nullable Object key) {
+ if (key == null) {
+ return null;
+ }
+ int hash = hash(key);
+ return segmentFor(hash).get(key, hash);
+ }
+
+ @Nullable
+ public V getIfPresent(Object key) {
+ int hash = hash(checkNotNull(key));
+ V value = segmentFor(hash).get(key, hash);
+ if (value == null) {
+ globalStatsCounter.recordMisses(1);
+ } else {
+ globalStatsCounter.recordHits(1);
+ }
+ return value;
+ }
+
+ V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
+ int hash = hash(checkNotNull(key));
+ return segmentFor(hash).get(key, hash, loader);
+ }
+
+ V getOrLoad(K key) throws ExecutionException {
+ return get(key, defaultLoader);
+ }
+
+ ImmutableMap<K, V> getAllPresent(Iterable<?> keys) {
+ int hits = 0;
+ int misses = 0;
+
+ Map<K, V> result = Maps.newLinkedHashMap();
+ for (Object key : keys) {
+ V value = get(key);
+ if (value == null) {
+ misses++;
+ } else {
+ // TODO(fry): store entry key instead of query key
+ @SuppressWarnings("unchecked")
+ K castKey = (K) key;
+ result.put(castKey, value);
+ hits++;
+ }
+ }
+ globalStatsCounter.recordHits(hits);
+ globalStatsCounter.recordMisses(misses);
+ return ImmutableMap.copyOf(result);
+ }
+
+ ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
+ int hits = 0;
+ int misses = 0;
+
+ Map<K, V> result = Maps.newLinkedHashMap();
+ Set<K> keysToLoad = Sets.newLinkedHashSet();
+ for (K key : keys) {
+ V value = get(key);
+ if (!result.containsKey(key)) {
+ result.put(key, value);
+ if (value == null) {
+ misses++;
+ keysToLoad.add(key);
+ } else {
+ hits++;
+ }
+ }
+ }
+
+ try {
+ if (!keysToLoad.isEmpty()) {
+ try {
+ Map<K, V> newEntries = loadAll(keysToLoad, defaultLoader);
+ for (K key : keysToLoad) {
+ V value = newEntries.get(key);
+ if (value == null) {
+ throw new InvalidCacheLoadException("loadAll failed to return a value for " + key);
+ }
+ result.put(key, value);
+ }
+ } catch (UnsupportedLoadingOperationException e) {
+ // loadAll not implemented, fallback to load
+ for (K key : keysToLoad) {
+ misses--; // get will count this miss
+ result.put(key, get(key, defaultLoader));
+ }
+ }
+ }
+ return ImmutableMap.copyOf(result);
+ } finally {
+ globalStatsCounter.recordHits(hits);
+ globalStatsCounter.recordMisses(misses);
+ }
+ }
+
+ /**
+ * Returns the result of calling {@link CacheLoader#loadAll}, or null if {@code loader} doesn't
+ * implement {@code loadAll}.
+ */
+ @Nullable
+ Map<K, V> loadAll(Set<? extends K> keys, CacheLoader<? super K, V> loader)
+ throws ExecutionException {
+ Stopwatch stopwatch = new Stopwatch().start();
+ Map<K, V> result;
+ boolean success = false;
+ try {
+ @SuppressWarnings("unchecked") // safe since all keys extend K
+ Map<K, V> map = (Map<K, V>) loader.loadAll(keys);
+ result = map;
+ success = true;
+ } catch (UnsupportedLoadingOperationException e) {
+ success = true;
+ throw e;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new ExecutionException(e);
+ } catch (RuntimeException e) {
+ throw new UncheckedExecutionException(e);
+ } catch (Exception e) {
+ throw new ExecutionException(e);
+ } catch (Error e) {
+ throw new ExecutionError(e);
+ } finally {
+ if (!success) {
+ globalStatsCounter.recordLoadException(stopwatch.elapsedTime(NANOSECONDS));
+ }
+ }
+
+ if (result == null) {
+ globalStatsCounter.recordLoadException(stopwatch.elapsedTime(NANOSECONDS));
+ throw new InvalidCacheLoadException(loader + " returned null map from loadAll");
+ }
+
+ stopwatch.stop();
+ // TODO(fry): batch by segment
+ boolean nullsPresent = false;
+ for (Map.Entry<K, V> entry : result.entrySet()) {
+ K key = entry.getKey();
+ V value = entry.getValue();
+ if (key == null || value == null) {
+ // delay failure until non-null entries are stored
+ nullsPresent = true;
+ } else {
+ put(key, value);
+ }
+ }
+
+ if (nullsPresent) {
+ globalStatsCounter.recordLoadException(stopwatch.elapsedTime(NANOSECONDS));
+ throw new InvalidCacheLoadException(loader + " returned null keys or values from loadAll");
+ }
+
+ // TODO(fry): record count of loaded entries
+ globalStatsCounter.recordLoadSuccess(stopwatch.elapsedTime(NANOSECONDS));
+ return result;
+ }
+
+ /**
+ * Returns the internal entry for the specified key. The entry may be loading, expired, or
+ * partially collected.
+ */
+ ReferenceEntry<K, V> getEntry(@Nullable Object key) {
+ // does not impact recency ordering
+ if (key == null) {
+ return null;
+ }
+ int hash = hash(key);
+ return segmentFor(hash).getEntry(key, hash);
+ }
+
+ /**
+ * Returns the live internal entry for the specified key.
+ */
+ ReferenceEntry<K, V> getLiveEntry(@Nullable Object key) {
+ // does not impact recency ordering
+ if (key == null) {
+ return null;
+ }
+ int hash = hash(key);
+ return segmentFor(hash).getLiveEntry(key, hash, ticker.read());
+ }
+
+ void refresh(K key) {
+ int hash = hash(checkNotNull(key));
+ segmentFor(hash).refresh(key, hash, defaultLoader);
+ }
+
+ @Override
+ public boolean containsKey(@Nullable Object key) {
+ // does not impact recency ordering
+ if (key == null) {
+ return false;
+ }
+ int hash = hash(key);
+ return segmentFor(hash).containsKey(key, hash);
+ }
+
+ @Override
+ public boolean containsValue(@Nullable Object value) {
+ // does not impact recency ordering
+ if (value == null) {
+ return false;
+ }
+
+ // This implementation is patterned after ConcurrentHashMap, but without the locking. The only
+ // way for it to return a false negative would be for the target value to jump around in the map
+ // such that none of the subsequent iterations observed it, despite the fact that at every point
+ // in time it was present somewhere int the map. This becomes increasingly unlikely as
+ // CONTAINS_VALUE_RETRIES increases, though without locking it is theoretically possible.
+ long now = ticker.read();
+ final Segment<K,V>[] segments = this.segments;
+ long last = -1L;
+ for (int i = 0; i < CONTAINS_VALUE_RETRIES; i++) {
+ long sum = 0L;
+ for (Segment<K, V> segment : segments) {
+ // ensure visibility of most recent completed write
+ @SuppressWarnings({"UnusedDeclaration", "unused"})
+ int c = segment.count; // read-volatile
+
+ AtomicReferenceArray<ReferenceEntry<K, V>> table = segment.table;
+ for (int j = 0 ; j < table.length(); j++) {
+ for (ReferenceEntry<K, V> e = table.get(j); e != null; e = e.getNext()) {
+ V v = segment.getLiveValue(e, now);
+ if (v != null && valueEquivalence.equivalent(value, v)) {
+ return true;
+ }
+ }
+ }
+ sum += segment.modCount;
+ }
+ if (sum == last) {
+ break;
+ }
+ last = sum;
+ }
+ return false;
+ }
+
+ @Override
+ public V put(K key, V value) {
+ checkNotNull(key);
+ checkNotNull(value);
+ int hash = hash(key);
+ return segmentFor(hash).put(key, hash, value, false);
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ checkNotNull(key);
+ checkNotNull(value);
+ int hash = hash(key);
+ return segmentFor(hash).put(key, hash, value, true);
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ for (Entry<? extends K, ? extends V> e : m.entrySet()) {
+ put(e.getKey(), e.getValue());
+ }
+ }
+
+ @Override
+ public V remove(@Nullable Object key) {
+ if (key == null) {
+ return null;
+ }
+ int hash = hash(key);
+ return segmentFor(hash).remove(key, hash);
+ }
+
+ @Override
+ public boolean remove(@Nullable Object key, @Nullable Object value) {
+ if (key == null || value == null) {
+ return false;
+ }
+ int hash = hash(key);
+ return segmentFor(hash).remove(key, hash, value);
+ }
+
+ @Override
+ public boolean replace(K key, @Nullable V oldValue, V newValue) {
+ checkNotNull(key);
+ checkNotNull(newValue);
+ if (oldValue == null) {
+ return false;
+ }
+ int hash = hash(key);
+ return segmentFor(hash).replace(key, hash, oldValue, newValue);
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ checkNotNull(key);
+ checkNotNull(value);
+ int hash = hash(key);
+ return segmentFor(hash).replace(key, hash, value);
+ }
+
+ @Override
+ public void clear() {
+ for (Segment<K, V> segment : segments) {
+ segment.clear();
+ }
+ }
+
+ void invalidateAll(Iterable<?> keys) {
+ // TODO(fry): batch by segment
+ for (Object key : keys) {
+ remove(key);
+ }
+ }
+
+ Set<K> keySet;
+
+ @Override
+ public Set<K> keySet() {
+ // does not impact recency ordering
+ Set<K> ks = keySet;
+ return (ks != null) ? ks : (keySet = new KeySet());
+ }
+
+ Collection<V> values;
+
+ @Override
+ public Collection<V> values() {
+ // does not impact recency ordering
+ Collection<V> vs = values;
+ return (vs != null) ? vs : (values = new Values());
+ }
+
+ Set<Entry<K, V>> entrySet;
+
+ @Override
+ public Set<Entry<K, V>> entrySet() {
+ // does not impact recency ordering
+ Set<Entry<K, V>> es = entrySet;
+ return (es != null) ? es : (entrySet = new EntrySet());
+ }
+
+ // Iterator Support
+
+ abstract class HashIterator {
+
+ int nextSegmentIndex;
+ int nextTableIndex;
+ Segment<K, V> currentSegment;
+ AtomicReferenceArray<ReferenceEntry<K, V>> currentTable;
+ ReferenceEntry<K, V> nextEntry;
+ WriteThroughEntry nextExternal;
+ WriteThroughEntry lastReturned;
+
+ HashIterator() {
+ nextSegmentIndex = segments.length - 1;
+ nextTableIndex = -1;
+ advance();
+ }
+
+ final void advance() {
+ nextExternal = null;
+
+ if (nextInChain()) {
+ return;
+ }
+
+ if (nextInTable()) {
+ return;
+ }
+
+ while (nextSegmentIndex >= 0) {
+ currentSegment = segments[nextSegmentIndex--];
+ if (currentSegment.count != 0) {
+ currentTable = currentSegment.table;
+ nextTableIndex = currentTable.length() - 1;
+ if (nextInTable()) {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds the next entry in the current chain. Returns true if an entry was found.
+ */
+ boolean nextInChain() {
+ if (nextEntry != null) {
+ for (nextEntry = nextEntry.getNext(); nextEntry != null; nextEntry = nextEntry.getNext()) {
+ if (advanceTo(nextEntry)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Finds the next entry in the current table. Returns true if an entry was found.
+ */
+ boolean nextInTable() {
+ while (nextTableIndex >= 0) {
+ if ((nextEntry = currentTable.get(nextTableIndex--)) != null) {
+ if (advanceTo(nextEntry) || nextInChain()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Advances to the given entry. Returns true if the entry was valid, false if it should be
+ * skipped.
+ */
+ boolean advanceTo(ReferenceEntry<K, V> entry) {
+ try {
+ long now = ticker.read();
+ K key = entry.getKey();
+ V value = getLiveValue(entry, now);
+ if (value != null) {
+ nextExternal = new WriteThroughEntry(key, value);
+ return true;
+ } else {
+ // Skip stale entry.
+ return false;
+ }
+ } finally {
+ currentSegment.postReadCleanup();
+ }
+ }
+
+ public boolean hasNext() {
+ return nextExternal != null;
+ }
+
+ WriteThroughEntry nextEntry() {
+ if (nextExternal == null) {
+ throw new NoSuchElementException();
+ }
+ lastReturned = nextExternal;
+ advance();
+ return lastReturned;
+ }
+
+ public void remove() {
+ checkState(lastReturned != null);
+ LocalCache.this.remove(lastReturned.getKey());
+ lastReturned = null;
+ }
+ }
+
+ final class KeyIterator extends HashIterator implements Iterator<K> {
+
+ @Override
+ public K next() {
+ return nextEntry().getKey();
+ }
+ }
+
+ final class ValueIterator extends HashIterator implements Iterator<V> {
+
+ @Override
+ public V next() {
+ return nextEntry().getValue();
+ }
+ }
+
+ /**
+ * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the
+ * underlying map.
+ */
+ final class WriteThroughEntry implements Entry<K, V> {
+ final K key; // non-null
+ V value; // non-null
+
+ WriteThroughEntry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ @Override
+ public V getValue() {
+ return value;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ // Cannot use key and value equivalence
+ if (object instanceof Entry) {
+ Entry<?, ?> that = (Entry<?, ?>) object;
+ return key.equals(that.getKey()) && value.equals(that.getValue());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ // Cannot use key and value equivalence
+ return key.hashCode() ^ value.hashCode();
+ }
+
+ @Override
+ public V setValue(V newValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns a string representation of the form <code>{key}={value}</code>.
+ */
+ @Override public String toString() {
+ return getKey() + "=" + getValue();
+ }
+ }
+
+ final class EntryIterator extends HashIterator implements Iterator<Entry<K, V>> {
+
+ @Override
+ public Entry<K, V> next() {
+ return nextEntry();
+ }
+ }
+
+ final class KeySet extends AbstractSet<K> {
+
+ @Override
+ public Iterator<K> iterator() {
+ return new KeyIterator();
+ }
+
+ @Override
+ public int size() {
+ return LocalCache.this.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return LocalCache.this.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return LocalCache.this.containsKey(o);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return LocalCache.this.remove(o) != null;
+ }
+
+ @Override
+ public void clear() {
+ LocalCache.this.clear();
+ }
+ }
+
+ final class Values extends AbstractCollection<V> {
+
+ @Override
+ public Iterator<V> iterator() {
+ return new ValueIterator();
+ }
+
+ @Override
+ public int size() {
+ return LocalCache.this.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return LocalCache.this.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return LocalCache.this.containsValue(o);
+ }
+
+ @Override
+ public void clear() {
+ LocalCache.this.clear();
+ }
+ }
+
+ final class EntrySet extends AbstractSet<Entry<K, V>> {
+
+ @Override
+ public Iterator<Entry<K, V>> iterator() {
+ return new EntryIterator();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
+ }
+ Entry<?, ?> e = (Entry<?, ?>) o;
+ Object key = e.getKey();
+ if (key == null) {
+ return false;
+ }
+ V v = LocalCache.this.get(key);
+
+ return v != null && valueEquivalence.equivalent(e.getValue(), v);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
+ }
+ Entry<?, ?> e = (Entry<?, ?>) o;
+ Object key = e.getKey();
+ return key != null && LocalCache.this.remove(key, e.getValue());
+ }
+
+ @Override
+ public int size() {
+ return LocalCache.this.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return LocalCache.this.isEmpty();
+ }
+
+ @Override
+ public void clear() {
+ LocalCache.this.clear();
+ }
+ }
+
+ // Serialization Support
+
+ /**
+ * Serializes the configuration of a LocalCache, reconsitituting it as a Cache using
+ * CacheBuilder upon deserialization. An instance of this class is fit for use by the writeReplace
+ * of LocalManualCache.
+ *
+ * Unfortunately, readResolve() doesn't get called when a circular dependency is present, so the
+ * proxy must be able to behave as the cache itself.
+ */
+ static class ManualSerializationProxy<K, V>
+ extends ForwardingCache<K, V> implements Serializable {
+ private static final long serialVersionUID = 1;
+
+ final Strength keyStrength;
+ final Strength valueStrength;
+ final Equivalence<Object> keyEquivalence;
+ final Equivalence<Object> valueEquivalence;
+ final long expireAfterWriteNanos;
+ final long expireAfterAccessNanos;
+ final long maxWeight;
+ final Weigher<K, V> weigher;
+ final int concurrencyLevel;
+ final RemovalListener<? super K, ? super V> removalListener;
+ final Ticker ticker;
+ final CacheLoader<? super K, V> loader;
+
+ transient Cache<K, V> delegate;
+
+ ManualSerializationProxy(LocalCache<K, V> cache) {
+ this(
+ cache.keyStrength,
+ cache.valueStrength,
+ cache.keyEquivalence,
+ cache.valueEquivalence,
+ cache.expireAfterWriteNanos,
+ cache.expireAfterAccessNanos,
+ cache.maxWeight,
+ cache.weigher,
+ cache.concurrencyLevel,
+ cache.removalListener,
+ cache.ticker,
+ cache.defaultLoader);
+ }
+
+ private ManualSerializationProxy(
+ Strength keyStrength, Strength valueStrength,
+ Equivalence<Object> keyEquivalence, Equivalence<Object> valueEquivalence,
+ long expireAfterWriteNanos, long expireAfterAccessNanos, long maxWeight,
+ Weigher<K, V> weigher, int concurrencyLevel,
+ RemovalListener<? super K, ? super V> removalListener,
+ Ticker ticker, CacheLoader<? super K, V> loader) {
+ this.keyStrength = keyStrength;
+ this.valueStrength = valueStrength;
+ this.keyEquivalence = keyEquivalence;
+ this.valueEquivalence = valueEquivalence;
+ this.expireAfterWriteNanos = expireAfterWriteNanos;
+ this.expireAfterAccessNanos = expireAfterAccessNanos;
+ this.maxWeight = maxWeight;
+ this.weigher = weigher;
+ this.concurrencyLevel = concurrencyLevel;
+ this.removalListener = removalListener;
+ this.ticker = (ticker == Ticker.systemTicker() || ticker == NULL_TICKER)
+ ? null : ticker;
+ this.loader = loader;
+ }
+
+ CacheBuilder<Object, Object> recreateCacheBuilder() {
+ CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder()
+ .setKeyStrength(keyStrength)
+ .setValueStrength(valueStrength)
+ .keyEquivalence(keyEquivalence)
+ .valueEquivalence(valueEquivalence)
+ .concurrencyLevel(concurrencyLevel);
+ builder.strictParsing = false;
+ builder.removalListener(removalListener);
+ if (expireAfterWriteNanos > 0) {
+ builder.expireAfterWrite(expireAfterWriteNanos, TimeUnit.NANOSECONDS);
+ }
+ if (expireAfterAccessNanos > 0) {
+ builder.expireAfterAccess(expireAfterAccessNanos, TimeUnit.NANOSECONDS);
+ }
+ if (weigher != OneWeigher.INSTANCE) {
+ builder.weigher(weigher);
+ if (maxWeight != UNSET_INT) {
+ builder.maximumWeight(maxWeight);
+ }
+ } else {
+ if (maxWeight != UNSET_INT) {
+ builder.maximumSize(maxWeight);
+ }
+ }
+ if (ticker != null) {
+ builder.ticker(ticker);
+ }
+ return builder;
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ CacheBuilder<Object, Object> builder = recreateCacheBuilder();
+ this.delegate = builder.build();
+ }
+
+ private Object readResolve() {
+ return delegate;
+ }
+
+ @Override
+ protected Cache<K, V> delegate() {
+ return delegate;
+ }
+ }
+
+ /**
+ * Serializes the configuration of a LocalCache, reconsitituting it as an LoadingCache using
+ * CacheBuilder upon deserialization. An instance of this class is fit for use by the writeReplace
+ * of LocalLoadingCache.
+ *
+ * Unfortunately, readResolve() doesn't get called when a circular dependency is present, so the
+ * proxy must be able to behave as the cache itself.
+ */
+ static final class LoadingSerializationProxy<K, V>
+ extends ManualSerializationProxy<K, V> implements LoadingCache<K, V>, Serializable {
+ private static final long serialVersionUID = 1;
+
+ transient LoadingCache<K, V> autoDelegate;
+
+ LoadingSerializationProxy(LocalCache<K, V> cache) {
+ super(cache);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ CacheBuilder<Object, Object> builder = recreateCacheBuilder();
+ this.autoDelegate = builder.build(loader);
+ }
+
+ @Override
+ public V get(K key) throws ExecutionException {
+ return autoDelegate.get(key);
+ }
+
+ @Override
+ public V getUnchecked(K key) {
+ return autoDelegate.getUnchecked(key);
+ }
+
+ @Override
+ public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
+ return autoDelegate.getAll(keys);
+ }
+
+ @Override
+ public final V apply(K key) {
+ return autoDelegate.apply(key);
+ }
+
+ @Override
+ public void refresh(K key) {
+ autoDelegate.refresh(key);
+ }
+
+ private Object readResolve() {
+ return autoDelegate;
+ }
+ }
+
+ static class LocalManualCache<K, V> implements Cache<K, V>, Serializable {
+ final LocalCache<K, V> localCache;
+
+ LocalManualCache(CacheBuilder<? super K, ? super V> builder) {
+ this(new LocalCache<K, V>(builder, null));
+ }
+
+ private LocalManualCache(LocalCache<K, V> localCache) {
+ this.localCache = localCache;
+ }
+
+ // Cache methods
+
+ @Override
+ @Nullable
+ public V getIfPresent(Object key) {
+ return localCache.getIfPresent(key);
+ }
+
+ @Override
+ public V get(K key, final Callable<? extends V> valueLoader) throws ExecutionException {
+ checkNotNull(valueLoader);
+ return localCache.get(key, new CacheLoader<Object, V>() {
+ @Override
+ public V load(Object key) throws Exception {
+ return valueLoader.call();
+ }
+ });
+ }
+
+ @Override
+ public ImmutableMap<K, V> getAllPresent(Iterable<?> keys) {
+ return localCache.getAllPresent(keys);
+ }
+
+ @Override
+ public void put(K key, V value) {
+ localCache.put(key, value);
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ localCache.putAll(m);
+ }
+
+ @Override
+ public void invalidate(Object key) {
+ checkNotNull(key);
+ localCache.remove(key);
+ }
+
+ @Override
+ public void invalidateAll(Iterable<?> keys) {
+ localCache.invalidateAll(keys);
+ }
+
+ @Override
+ public void invalidateAll() {
+ localCache.clear();
+ }
+
+ @Override
+ public long size() {
+ return localCache.longSize();
+ }
+
+ @Override
+ public ConcurrentMap<K, V> asMap() {
+ return localCache;
+ }
+
+ @Override
+ public CacheStats stats() {
+ SimpleStatsCounter aggregator = new SimpleStatsCounter();
+ aggregator.incrementBy(localCache.globalStatsCounter);
+ for (Segment<K, V> segment : localCache.segments) {
+ aggregator.incrementBy(segment.statsCounter);
+ }
+ return aggregator.snapshot();
+ }
+
+ @Override
+ public void cleanUp() {
+ localCache.cleanUp();
+ }
+
+ // Serialization Support
+
+ private static final long serialVersionUID = 1;
+
+ Object writeReplace() {
+ return new ManualSerializationProxy<K, V>(localCache);
+ }
+ }
+
+ static class LocalLoadingCache<K, V>
+ extends LocalManualCache<K, V> implements LoadingCache<K, V> {
+
+ LocalLoadingCache(CacheBuilder<? super K, ? super V> builder,
+ CacheLoader<? super K, V> loader) {
+ super(new LocalCache<K, V>(builder, checkNotNull(loader)));
+ }
+
+ // LoadingCache methods
+
+ @Override
+ public V get(K key) throws ExecutionException {
+ return localCache.getOrLoad(key);
+ }
+
+ @Override
+ public V getUnchecked(K key) {
+ try {
+ return get(key);
+ } catch (ExecutionException e) {
+ throw new UncheckedExecutionException(e.getCause());
+ }
+ }
+
+ @Override
+ public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
+ return localCache.getAll(keys);
+ }
+
+ @Override
+ public void refresh(K key) {
+ localCache.refresh(key);
+ }
+
+ @Override
+ public final V apply(K key) {
+ return getUnchecked(key);
+ }
+
+ // Serialization Support
+
+ private static final long serialVersionUID = 1;
+
+ Object writeReplace() {
+ return new LoadingSerializationProxy<K, V>(localCache);
+ }
+ }
+}