Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.common.cache.LocalCache Maven / Gradle / Ivy
Go to download
Guava is a suite of core and expanded libraries that include
utility classes, Google's collections, I/O classes, and
much more.
This project includes GWT-friendly sources.
/*
* 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.Futures.transform;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly;
import static java.util.Collections.unmodifiableSet;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
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.ImmutableSet;
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.SettableFuture;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import com.google.errorprone.annotations.concurrent.LazyInit;
import com.google.j2objc.annotations.RetainedWith;
import com.google.j2objc.annotations.Weak;
import java.io.IOException;
import java.io.InvalidObjectException;
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.ArrayList;
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.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* The concurrent hash map implementation built by {@link CacheBuilder}.
*
* This implementation is heavily derived from revision 1.96 of ConcurrentHashMap.java .
*
* @author Charles Fry
* @author Bob Lee ({@code com.google.common.collect.MapMaker})
* @author Doug Lea ({@code ConcurrentHashMap})
*/
@SuppressWarnings({
"GoodTime", // lots of violations (nanosecond math)
"nullness", // too much trouble for the payoff
})
@GwtCompatible(emulated = true)
// TODO(cpovirk): Annotate for nullness.
class LocalCache extends AbstractMap implements ConcurrentMap {
/*
* 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 {@code <= 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.
*
* 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());
/**
* 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[] segments;
/** The concurrency level. */
final int concurrencyLevel;
/** Strategy for comparing keys. */
final Equivalence keyEquivalence;
/** Strategy for comparing values. */
final Equivalence 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 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> removalNotificationQueue;
/**
* A listener that is invoked when an entry is removed due to expiration or garbage collection of
* soft/weak entries.
*/
final RemovalListener 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. */
@CheckForNull 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, @CheckForNull 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.discardingQueue()
: new ConcurrentLinkedQueue<>();
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 = (int) Math.min(initialCapacity, 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
ValueReference referenceValue(
Segment segment, ReferenceEntry entry, V value, int weight) {
return (weight == 1)
? new StrongValueReference(value)
: new WeightedStrongValueReference(value, weight);
}
@Override
Equivalence defaultEquivalence() {
return Equivalence.equals();
}
},
SOFT {
@Override
ValueReference referenceValue(
Segment segment, ReferenceEntry entry, V value, int weight) {
return (weight == 1)
? new SoftValueReference(segment.valueReferenceQueue, value, entry)
: new WeightedSoftValueReference(
segment.valueReferenceQueue, value, entry, weight);
}
@Override
Equivalence defaultEquivalence() {
return Equivalence.identity();
}
},
WEAK {
@Override
ValueReference referenceValue(
Segment segment, ReferenceEntry entry, V value, int weight) {
return (weight == 1)
? new WeakValueReference(segment.valueReferenceQueue, value, entry)
: new WeightedWeakValueReference(
segment.valueReferenceQueue, value, entry, weight);
}
@Override
Equivalence defaultEquivalence() {
return Equivalence.identity();
}
};
/** Creates a reference for the given value according to this value strength. */
abstract ValueReference referenceValue(
Segment segment, ReferenceEntry 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 defaultEquivalence();
}
/** Creates new entries. */
enum EntryFactory {
STRONG {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next) {
return new StrongEntry<>(key, hash, next);
}
},
STRONG_ACCESS {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next) {
return new StrongAccessEntry<>(key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment,
ReferenceEntry original,
ReferenceEntry newNext,
K key) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key);
copyAccessEntry(original, newEntry);
return newEntry;
}
},
STRONG_WRITE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next) {
return new StrongWriteEntry<>(key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment,
ReferenceEntry original,
ReferenceEntry newNext,
K key) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key);
copyWriteEntry(original, newEntry);
return newEntry;
}
},
STRONG_ACCESS_WRITE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next) {
return new StrongAccessWriteEntry<>(key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment,
ReferenceEntry original,
ReferenceEntry newNext,
K key) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key);
copyAccessEntry(original, newEntry);
copyWriteEntry(original, newEntry);
return newEntry;
}
},
WEAK {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next) {
return new WeakEntry<>(segment.keyReferenceQueue, key, hash, next);
}
},
WEAK_ACCESS {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next) {
return new WeakAccessEntry<>(segment.keyReferenceQueue, key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment,
ReferenceEntry original,
ReferenceEntry newNext,
K key) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key);
copyAccessEntry(original, newEntry);
return newEntry;
}
},
WEAK_WRITE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next) {
return new WeakWriteEntry<>(segment.keyReferenceQueue, key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment,
ReferenceEntry original,
ReferenceEntry newNext,
K key) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key);
copyWriteEntry(original, newEntry);
return newEntry;
}
},
WEAK_ACCESS_WRITE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next) {
return new WeakAccessWriteEntry<>(segment.keyReferenceQueue, key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment,
ReferenceEntry original,
ReferenceEntry newNext,
K key) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext, key);
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 ReferenceEntry newEntry(
Segment segment, K key, int hash, @CheckForNull ReferenceEntry next);
/**
* Copies an entry, assigning it a new {@code next} entry.
*
* @param original the entry to copy. But avoid calling {@code getKey} on it: Instead, use the
* {@code key} parameter. That way, we prevent the key from being garbage collected in the
* case of weak keys. If we create a new entry with a key that is null at construction time,
* we're not sure if entry will necessarily ever be garbage collected.
* @param newNext entry in the same bucket
* @param key the key to copy from the original entry to the new one. Use this in preference to
* {@code original.getKey()}.
*/
// Guarded By Segment.this
ReferenceEntry copyEntry(
Segment segment, ReferenceEntry original, ReferenceEntry newNext, K key) {
return newEntry(segment, key, original.getHash(), newNext);
}
// Guarded By Segment.this
void copyAccessEntry(ReferenceEntry original, ReferenceEntry 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);
}
// Guarded By Segment.this
void copyWriteEntry(ReferenceEntry original, ReferenceEntry 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 {
/** Returns the value. Does not block or throw exceptions. */
@CheckForNull
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.
*/
@CheckForNull
ReferenceEntry getEntry();
/**
* Creates a copy of this reference for the given entry.
*
* {@code value} may be null only for a loading reference.
*/
ValueReference copyFor(
ReferenceQueue queue, @CheckForNull V value, ReferenceEntry entry);
/**
* Notify pending loads that a new value was set. This is only relevant to loading value
* references.
*/
void notifyNewValue(@CheckForNull V newValue);
/**
* Returns true if a new value is currently loading, regardless of whether 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 UNSET =
new ValueReference() {
@CheckForNull
@Override
public Object get() {
return null;
}
@Override
public int getWeight() {
return 0;
}
@CheckForNull
@Override
public ReferenceEntry getEntry() {
return null;
}
@Override
public ValueReference copyFor(
ReferenceQueue queue,
@CheckForNull Object value,
ReferenceEntry entry) {
return this;
}
@Override
public boolean isLoading() {
return false;
}
@Override
public boolean isActive() {
return false;
}
@CheckForNull
@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 ValueReference unset() {
return (ValueReference) UNSET;
}
private enum NullEntry implements ReferenceEntry {
INSTANCE;
@CheckForNull
@Override
public ValueReference getValueReference() {
return null;
}
@Override
public void setValueReference(ValueReference valueReference) {}
@CheckForNull
@Override
public ReferenceEntry getNext() {
return null;
}
@Override
public int getHash() {
return 0;
}
@CheckForNull
@Override
public Object getKey() {
return null;
}
@Override
public long getAccessTime() {
return 0;
}
@Override
public void setAccessTime(long time) {}
@Override
public ReferenceEntry getNextInAccessQueue() {
return this;
}
@Override
public void setNextInAccessQueue(ReferenceEntry next) {}
@Override
public ReferenceEntry getPreviousInAccessQueue() {
return this;
}
@Override
public void setPreviousInAccessQueue(ReferenceEntry previous) {}
@Override
public long getWriteTime() {
return 0;
}
@Override
public void setWriteTime(long time) {}
@Override
public ReferenceEntry getNextInWriteQueue() {
return this;
}
@Override
public void setNextInWriteQueue(ReferenceEntry next) {}
@Override
public ReferenceEntry getPreviousInWriteQueue() {
return this;
}
@Override
public void setPreviousInWriteQueue(ReferenceEntry previous) {}
}
abstract static class AbstractReferenceEntry implements ReferenceEntry {
@Override
public ValueReference getValueReference() {
throw new UnsupportedOperationException();
}
@Override
public void setValueReference(ValueReference valueReference) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry 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 getNextInAccessQueue() {
throw new UnsupportedOperationException();
}
@Override
public void setNextInAccessQueue(ReferenceEntry next) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getPreviousInAccessQueue() {
throw new UnsupportedOperationException();
}
@Override
public void setPreviousInAccessQueue(ReferenceEntry previous) {
throw new UnsupportedOperationException();
}
@Override
public long getWriteTime() {
throw new UnsupportedOperationException();
}
@Override
public void setWriteTime(long time) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getNextInWriteQueue() {
throw new UnsupportedOperationException();
}
@Override
public void setNextInWriteQueue(ReferenceEntry next) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getPreviousInWriteQueue() {
throw new UnsupportedOperationException();
}
@Override
public void setPreviousInWriteQueue(ReferenceEntry previous) {
throw new UnsupportedOperationException();
}
}
@SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value
static ReferenceEntry nullEntry() {
return (ReferenceEntry) NullEntry.INSTANCE;
}
static final Queue> DISCARDING_QUEUE =
new AbstractQueue() {
@Override
public boolean offer(Object o) {
return true;
}
@CheckForNull
@Override
public Object peek() {
return null;
}
@CheckForNull
@Override
public Object poll() {
return null;
}
@Override
public int size() {
return 0;
}
@Override
public Iterator iterator() {
return ImmutableSet.of().iterator();
}
};
/** Queue that discards all elements. */
@SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value
static Queue 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 extends AbstractReferenceEntry {
final K key;
StrongEntry(K key, int hash, @CheckForNull ReferenceEntry next) {
this.key = key;
this.hash = hash;
this.next = next;
}
@Override
public K getKey() {
return this.key;
}
// The code below is exactly the same for each entry type.
final int hash;
@CheckForNull final ReferenceEntry next;
volatile ValueReference valueReference = unset();
@Override
public ValueReference getValueReference() {
return valueReference;
}
@Override
public void setValueReference(ValueReference valueReference) {
this.valueReference = valueReference;
}
@Override
public int getHash() {
return hash;
}
@Override
public ReferenceEntry getNext() {
return next;
}
}
static final class StrongAccessEntry extends StrongEntry {
StrongAccessEntry(K key, int hash, @CheckForNull ReferenceEntry 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;
}
// Guarded By Segment.this
@Weak ReferenceEntry nextAccess = nullEntry();
@Override
public ReferenceEntry getNextInAccessQueue() {
return nextAccess;
}
@Override
public void setNextInAccessQueue(ReferenceEntry next) {
this.nextAccess = next;
}
// Guarded By Segment.this
@Weak ReferenceEntry previousAccess = nullEntry();
@Override
public ReferenceEntry getPreviousInAccessQueue() {
return previousAccess;
}
@Override
public void setPreviousInAccessQueue(ReferenceEntry previous) {
this.previousAccess = previous;
}
}
static final class StrongWriteEntry extends StrongEntry {
StrongWriteEntry(K key, int hash, @CheckForNull ReferenceEntry 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;
}
// Guarded By Segment.this
@Weak ReferenceEntry nextWrite = nullEntry();
@Override
public ReferenceEntry getNextInWriteQueue() {
return nextWrite;
}
@Override
public void setNextInWriteQueue(ReferenceEntry next) {
this.nextWrite = next;
}
// Guarded By Segment.this
@Weak ReferenceEntry previousWrite = nullEntry();
@Override
public ReferenceEntry getPreviousInWriteQueue() {
return previousWrite;
}
@Override
public void setPreviousInWriteQueue(ReferenceEntry previous) {
this.previousWrite = previous;
}
}
static final class StrongAccessWriteEntry extends StrongEntry {
StrongAccessWriteEntry(K key, int hash, @CheckForNull ReferenceEntry 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;
}
// Guarded By Segment.this
@Weak ReferenceEntry nextAccess = nullEntry();
@Override
public ReferenceEntry getNextInAccessQueue() {
return nextAccess;
}
@Override
public void setNextInAccessQueue(ReferenceEntry next) {
this.nextAccess = next;
}
// Guarded By Segment.this
@Weak ReferenceEntry previousAccess = nullEntry();
@Override
public ReferenceEntry getPreviousInAccessQueue() {
return previousAccess;
}
@Override
public void setPreviousInAccessQueue(ReferenceEntry 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;
}
// Guarded By Segment.this
@Weak ReferenceEntry nextWrite = nullEntry();
@Override
public ReferenceEntry getNextInWriteQueue() {
return nextWrite;
}
@Override
public void setNextInWriteQueue(ReferenceEntry next) {
this.nextWrite = next;
}
// Guarded By Segment.this
@Weak ReferenceEntry previousWrite = nullEntry();
@Override
public ReferenceEntry getPreviousInWriteQueue() {
return previousWrite;
}
@Override
public void setPreviousInWriteQueue(ReferenceEntry previous) {
this.previousWrite = previous;
}
}
/** Used for weakly-referenced keys. */
static class WeakEntry extends WeakReference implements ReferenceEntry {
WeakEntry(ReferenceQueue queue, K key, int hash, @CheckForNull ReferenceEntry next) {
super(key, queue);
this.hash = hash;
this.next = next;
}
@Override
public K getKey() {
return get();
}
/*
* It'd be nice to get these for free from AbstractReferenceEntry, but we're already extending
* WeakReference.
*/
// null access
@Override
public long getAccessTime() {
throw new UnsupportedOperationException();
}
@Override
public void setAccessTime(long time) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getNextInAccessQueue() {
throw new UnsupportedOperationException();
}
@Override
public void setNextInAccessQueue(ReferenceEntry next) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getPreviousInAccessQueue() {
throw new UnsupportedOperationException();
}
@Override
public void setPreviousInAccessQueue(ReferenceEntry 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 getNextInWriteQueue() {
throw new UnsupportedOperationException();
}
@Override
public void setNextInWriteQueue(ReferenceEntry next) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getPreviousInWriteQueue() {
throw new UnsupportedOperationException();
}
@Override
public void setPreviousInWriteQueue(ReferenceEntry previous) {
throw new UnsupportedOperationException();
}
// The code below is exactly the same for each entry type.
final int hash;
@CheckForNull final ReferenceEntry next;
volatile ValueReference valueReference = unset();
@Override
public ValueReference getValueReference() {
return valueReference;
}
@Override
public void setValueReference(ValueReference valueReference) {
this.valueReference = valueReference;
}
@Override
public int getHash() {
return hash;
}
@Override
public ReferenceEntry getNext() {
return next;
}
}
static final class WeakAccessEntry extends WeakEntry {
WeakAccessEntry(
ReferenceQueue queue, K key, int hash, @CheckForNull ReferenceEntry 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;
}
// Guarded By Segment.this
@Weak ReferenceEntry nextAccess = nullEntry();
@Override
public ReferenceEntry getNextInAccessQueue() {
return nextAccess;
}
@Override
public void setNextInAccessQueue(ReferenceEntry next) {
this.nextAccess = next;
}
// Guarded By Segment.this
@Weak ReferenceEntry previousAccess = nullEntry();
@Override
public ReferenceEntry getPreviousInAccessQueue() {
return previousAccess;
}
@Override
public void setPreviousInAccessQueue(ReferenceEntry previous) {
this.previousAccess = previous;
}
}
static final class WeakWriteEntry extends WeakEntry {
WeakWriteEntry(
ReferenceQueue queue, K key, int hash, @CheckForNull ReferenceEntry 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;
}
// Guarded By Segment.this
@Weak ReferenceEntry nextWrite = nullEntry();
@Override
public ReferenceEntry getNextInWriteQueue() {
return nextWrite;
}
@Override
public void setNextInWriteQueue(ReferenceEntry next) {
this.nextWrite = next;
}
// Guarded By Segment.this
@Weak ReferenceEntry previousWrite = nullEntry();
@Override
public ReferenceEntry getPreviousInWriteQueue() {
return previousWrite;
}
@Override
public void setPreviousInWriteQueue(ReferenceEntry previous) {
this.previousWrite = previous;
}
}
static final class WeakAccessWriteEntry extends WeakEntry {
WeakAccessWriteEntry(
ReferenceQueue queue, K key, int hash, @CheckForNull ReferenceEntry