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
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* 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 * 20L <= 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 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