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.
org.glassfish.jersey.internal.guava.MapMakerInternalMap Maven / Gradle / Ivy
Go to download
A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle
(jaxrs-ri.jar).
Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and
contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external
RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source
bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external
RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI
sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from
the command line.
/*
* 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 org.glassfish.jersey.internal.guava;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
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.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
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 static org.glassfish.jersey.internal.guava.Preconditions.checkNotNull;
/**
* The concurrent hash map implementation built by {@link MapMaker}.
*
*
This implementation is heavily derived from revision 1.96 of ConcurrentHashMap.java .
*
* @author Bob Lee
* @author Charles Fry
* @author Doug Lea ({@code ConcurrentHashMap})
*/
class MapMakerInternalMap
extends AbstractMap implements ConcurrentMap, Serializable {
/*
* 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.
*/
private static final int MAXIMUM_CAPACITY = Ints.MAX_POWER_OF_TWO;
/**
* The maximum number of segments to allow; used to bound constructor arguments.
*/
private static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
/**
* Number of (unsynchronized) retries in the containsValue method.
*/
private 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.
*/
private 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
private static final int DRAIN_MAX = 16;
// Fields
/**
* Placeholder. Indicates that the value hasn't been set yet.
*/
private static final ValueReference UNSET = new ValueReference() {
@Override
public Object get() {
return null;
}
@Override
public ReferenceEntry getEntry() {
return null;
}
@Override
public ValueReference copyFor(ReferenceQueue queue,
Object value, ReferenceEntry entry) {
return this;
}
@Override
public boolean isComputingReference() {
return false;
}
@Override
public void clear(ValueReference newValue) {
}
};
private static final Queue> DISCARDING_QUEUE = new AbstractQueue() {
@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 iterator() {
return Iterators.emptyIterator();
}
};
private static final Logger logger = Logger.getLogger(MapMakerInternalMap.class.getName());
private static final long serialVersionUID = 5;
/**
* Mask value for indexing into segments. The upper bits of a key's hash code are used to choose
* the segment.
*/
private final transient 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.
*/
private final transient int segmentShift;
/**
* The segments, each of which is a specialized hash table.
*/
private final transient Segment[] segments;
/**
* The concurrency level.
*/
private final int concurrencyLevel;
/**
* Strategy for comparing keys.
*/
private final Equivalence keyEquivalence;
/**
* Strategy for comparing values.
*/
private final Equivalence valueEquivalence;
/**
* Strategy for referencing keys.
*/
private final Strength keyStrength;
/**
* Strategy for referencing values.
*/
private final Strength valueStrength;
/**
* The maximum size of this map. MapMaker.UNSET_INT if there is no maximum.
*/
private final int maximumSize;
/**
* How long after the last access to an entry the map will retain that entry.
*/
private final long expireAfterAccessNanos;
/**
* How long after the last write to an entry the map will retain that entry.
*/
private final long expireAfterWriteNanos;
/**
* Entries waiting to be consumed by the removal listener.
*/
// TODO(fry): define a new type which creates event objects and automates the clear logic
private final Queue> removalNotificationQueue;
/**
* A listener that is invoked when an entry is removed due to expiration or garbage collection of
* soft/weak entries.
*/
private final MapMaker.RemovalListener removalListener;
/**
* Factory used to create new entries.
*/
private final transient EntryFactory entryFactory;
/**
* Measures time in a testable way.
*/
private final Ticker ticker;
private transient Set keySet;
private transient Collection values;
private transient Set> entrySet;
/**
* Creates a new, empty map with the specified strategy, initial capacity and concurrency level.
*/
private MapMakerInternalMap(MapMaker builder) {
concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
keyStrength = builder.getKeyStrength();
valueStrength = builder.getValueStrength();
keyEquivalence = builder.getKeyEquivalence();
valueEquivalence = valueStrength.defaultEquivalence();
maximumSize = builder.maximumSize;
expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
entryFactory = EntryFactory.getFactory(keyStrength, expires(), evictsBySize());
ticker = builder.getTicker();
removalListener = builder.getRemovalListener();
removalNotificationQueue = (removalListener == GenericMapMaker.NullListener.INSTANCE)
? MapMakerInternalMap.discardingQueue()
: new ConcurrentLinkedQueue>();
int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
if (evictsBySize()) {
initialCapacity = Math.min(initialCapacity, maximumSize);
}
// Find power-of-two sizes best matching arguments. Constraints:
// (segmentCount <= maximumSize)
// && (concurrencyLevel > maximumSize || segmentCount > concurrencyLevel)
int segmentShift = 0;
int segmentCount = 1;
while (segmentCount < concurrencyLevel
&& (!evictsBySize() || segmentCount * 2 <= maximumSize)) {
++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 sizes = overall max size
int maximumSegmentSize = maximumSize / segmentCount + 1;
int remainder = maximumSize % segmentCount;
for (int i = 0; i < this.segments.length; ++i) {
if (i == remainder) {
maximumSegmentSize--;
}
this.segments[i] =
createSegment(segmentSize, maximumSegmentSize);
}
} else {
for (int i = 0; i < this.segments.length; ++i) {
this.segments[i] =
createSegment(segmentSize, MapMaker.UNSET_INT);
}
}
}
/**
* Singleton placeholder that indicates a value is being computed.
*/
@SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value
private static ValueReference unset() {
return (ValueReference) UNSET;
}
@SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value
private static ReferenceEntry nullEntry() {
return (ReferenceEntry) NullEntry.INSTANCE;
}
/**
* Queue that discards all elements.
*/
@SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value
private static Queue discardingQueue() {
return (Queue) DISCARDING_QUEUE;
}
/**
* 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
*/
private 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);
}
// Guarded By Segment.this
private static void connectExpirables(ReferenceEntry previous, ReferenceEntry next) {
previous.setNextExpirable(next);
next.setPreviousExpirable(previous);
}
// Guarded By Segment.this
private static void nullifyExpirable(ReferenceEntry nulled) {
ReferenceEntry nullEntry = nullEntry();
nulled.setNextExpirable(nullEntry);
nulled.setPreviousExpirable(nullEntry);
}
/**
* Links the evitables together.
*/
// Guarded By Segment.this
private static void connectEvictables(ReferenceEntry previous, ReferenceEntry next) {
previous.setNextEvictable(next);
next.setPreviousEvictable(previous);
}
// Guarded By Segment.this
private static void nullifyEvictable(ReferenceEntry nulled) {
ReferenceEntry nullEntry = nullEntry();
nulled.setNextEvictable(nullEntry);
nulled.setPreviousEvictable(nullEntry);
}
boolean evictsBySize() {
return maximumSize != MapMaker.UNSET_INT;
}
boolean expires() {
return expiresAfterWrite() || expiresAfterAccess();
}
private boolean expiresAfterWrite() {
return expireAfterWriteNanos > 0;
}
/*
* 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.
*/
boolean expiresAfterAccess() {
return expireAfterAccessNanos > 0;
}
boolean usesKeyReferences() {
return keyStrength != Strength.STRONG;
}
boolean usesValueReferences() {
return valueStrength != Strength.STRONG;
}
private int hash(Object key) {
int h = keyEquivalence.hash(key);
return rehash(h);
}
void reclaimValue(ValueReference valueReference) {
ReferenceEntry entry = valueReference.getEntry();
int hash = entry.getHash();
segmentFor(hash).reclaimValue(entry.getKey(), hash, valueReference);
}
void reclaimKey(ReferenceEntry entry) {
int hash = entry.getHash();
segmentFor(hash).reclaimKey(entry, hash);
}
/**
* 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
*/
private Segment segmentFor(int hash) {
// TODO(fry): Lazily create segments?
return segments[(hash >>> segmentShift) & segmentMask];
}
private Segment createSegment(int initialCapacity, int maxSegmentSize) {
return new Segment(this, initialCapacity, maxSegmentSize);
}
/**
* Gets the value from an entry. Returns {@code null} if the entry is invalid,
* partially-collected, computing, or expired. Unlike {@link Segment#getLiveValue} this method
* does not attempt to clean up stale entries.
*/
private V getLiveValue(ReferenceEntry entry) {
if (entry.getKey() == null) {
return null;
}
V value = entry.getValueReference().get();
if (value == null) {
return null;
}
if (expires() && isExpired(entry)) {
return null;
}
return value;
}
/**
* Returns {@code true} if the entry has expired.
*/
boolean isExpired(ReferenceEntry entry) {
return isExpired(entry, ticker.read());
}
/**
* Returns {@code true} if the entry has expired.
*/
boolean isExpired(ReferenceEntry entry, long now) {
// if the expiration time had overflowed, this "undoes" the overflow
return now - entry.getExpirationTime() > 0;
}
/**
* 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() {
MapMaker.RemovalNotification notification;
while ((notification = removalNotificationQueue.poll()) != null) {
try {
removalListener.onRemoval(notification);
} catch (Exception e) {
logger.log(Level.WARNING, "Exception thrown by removal listener", e);
}
}
}
@SuppressWarnings("unchecked")
private Segment[] newSegmentArray(int ssize) {
return new Segment[ssize];
}
@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[] 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;
}
@Override
public int size() {
Segment[] segments = this.segments;
long sum = 0;
for (int i = 0; i < segments.length; ++i) {
sum += segments[i].count;
}
return Ints.saturatedCast(sum);
}
@Override
public V get(Object key) {
if (key == null) {
return null;
}
int hash = hash(key);
return segmentFor(hash).get(key, hash);
}
@Override
public boolean containsKey(Object key) {
if (key == null) {
return false;
}
int hash = hash(key);
return segmentFor(hash).containsKey(key, hash);
}
@Override
public boolean containsValue(Object value) {
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.
final Segment[] segments = this.segments;
long last = -1L;
for (int i = 0; i < CONTAINS_VALUE_RETRIES; i++) {
long sum = 0L;
for (Segment segment : segments) {
// ensure visibility of most recent completed write
@SuppressWarnings({"UnusedDeclaration", "unused"})
int c = segment.count; // read-volatile
AtomicReferenceArray> table = segment.table;
for (int j = 0; j < table.length(); j++) {
for (ReferenceEntry e = table.get(j); e != null; e = e.getNext()) {
V v = segment.getLiveValue(e);
if (v != null && valueEquivalence.equivalent(value, v)) {
return true;
}
}
}
sum += segment.modCount;
}
if (sum == last) {
break;
}
last = sum;
}
return false;
}
// expiration
@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(Object key) {
if (key == null) {
return null;
}
int hash = hash(key);
return segmentFor(hash).remove(key, hash);
}
// eviction
@Override
public boolean remove(Object key, 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, 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 segment : segments) {
segment.clear();
}
}
// Inner Classes
@Override
public Set keySet() {
Set ks = keySet;
return (ks != null) ? ks : (keySet = new KeySet());
}
// Queues
@Override
public Collection values() {
Collection vs = values;
return (vs != null) ? vs : (values = new Values());
}
@Override
public Set> entrySet() {
Set> es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
// ConcurrentMap methods
enum Strength {
/*
* TODO(kevinb): If we strongly reference the value and aren't computing, we needn't wrap the
* value. This could save ~8 bytes per entry.
*/
STRONG {
@Override
ValueReference referenceValue(
Segment segment, ReferenceEntry entry, V value) {
return new StrongValueReference(value);
}
@Override
Equivalence defaultEquivalence() {
return Equivalence.equals();
}
};
/**
* Creates a reference for the given value according to this value strength.
*/
abstract ValueReference referenceValue(
Segment segment, ReferenceEntry entry, V value);
/**
* 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, ReferenceEntry next) {
return new StrongEntry(key, hash, next);
}
},
STRONG_EXPIRABLE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, ReferenceEntry next) {
return new StrongExpirableEntry(key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
copyExpirableEntry(original, newEntry);
return newEntry;
}
},
STRONG_EVICTABLE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, ReferenceEntry next) {
return new StrongEvictableEntry(key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
copyEvictableEntry(original, newEntry);
return newEntry;
}
},
STRONG_EXPIRABLE_EVICTABLE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, ReferenceEntry next) {
return new StrongExpirableEvictableEntry(key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
copyExpirableEntry(original, newEntry);
copyEvictableEntry(original, newEntry);
return newEntry;
}
},
WEAK {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, ReferenceEntry next) {
return new WeakEntry(segment.keyReferenceQueue, key, hash, next);
}
},
WEAK_EXPIRABLE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, ReferenceEntry next) {
return new WeakExpirableEntry(segment.keyReferenceQueue, key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
copyExpirableEntry(original, newEntry);
return newEntry;
}
},
WEAK_EVICTABLE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, ReferenceEntry next) {
return new WeakEvictableEntry(segment.keyReferenceQueue, key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
copyEvictableEntry(original, newEntry);
return newEntry;
}
},
WEAK_EXPIRABLE_EVICTABLE {
@Override
ReferenceEntry newEntry(
Segment segment, K key, int hash, ReferenceEntry next) {
return new WeakExpirableEvictableEntry(segment.keyReferenceQueue, key, hash, next);
}
@Override
ReferenceEntry copyEntry(
Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
copyExpirableEntry(original, newEntry);
copyEvictableEntry(original, newEntry);
return newEntry;
}
};
/**
* Masks used to compute indices in the following table.
*/
static final int EXPIRABLE_MASK = 1;
static final int EVICTABLE_MASK = 2;
/**
* Look-up table for factories. First dimension is the reference type. The second dimension is
* the result of OR-ing the feature masks.
*/
static final EntryFactory[][] factories = {
{STRONG, STRONG_EXPIRABLE, STRONG_EVICTABLE, STRONG_EXPIRABLE_EVICTABLE},
{}, // no support for SOFT keys
{WEAK, WEAK_EXPIRABLE, WEAK_EVICTABLE, WEAK_EXPIRABLE_EVICTABLE}
};
static EntryFactory getFactory(Strength keyStrength, boolean expireAfterWrite,
boolean evictsBySize) {
int flags = (expireAfterWrite ? EXPIRABLE_MASK : 0) | (evictsBySize ? EVICTABLE_MASK : 0);
return factories[keyStrength.ordinal()][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, ReferenceEntry next);
/**
* Copies an entry, assigning it a new {@code next} entry.
*
* @param original the entry to copy
* @param newNext entry in the same bucket
*/
// Guarded By Segment.this
ReferenceEntry copyEntry(
Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
return newEntry(segment, original.getKey(), original.getHash(), newNext);
}
// Guarded By Segment.this
void copyExpirableEntry(ReferenceEntry original, ReferenceEntry newEntry) {
// TODO(fry): when we link values instead of entries this method can go
// away, as can connectExpirables, nullifyExpirable.
newEntry.setExpirationTime(original.getExpirationTime());
connectExpirables(original.getPreviousExpirable(), newEntry);
connectExpirables(newEntry, original.getNextExpirable());
nullifyExpirable(original);
}
// Guarded By Segment.this
void copyEvictableEntry(ReferenceEntry original, ReferenceEntry newEntry) {
// TODO(fry): when we link values instead of entries this method can go
// away, as can connectEvictables, nullifyEvictable.
connectEvictables(original.getPreviousEvictable(), newEntry);
connectEvictables(newEntry, original.getNextEvictable());
nullifyEvictable(original);
}
}
private enum NullEntry implements ReferenceEntry {
INSTANCE;
@Override
public ValueReference getValueReference() {
return null;
}
@Override
public void setValueReference(ValueReference valueReference) {
}
@Override
public ReferenceEntry getNext() {
return null;
}
@Override
public int getHash() {
return 0;
}
@Override
public Object getKey() {
return null;
}
@Override
public long getExpirationTime() {
return 0;
}
@Override
public void setExpirationTime(long time) {
}
@Override
public ReferenceEntry getNextExpirable() {
return this;
}
@Override
public void setNextExpirable(ReferenceEntry next) {
}
@Override
public ReferenceEntry getPreviousExpirable() {
return this;
}
@Override
public void setPreviousExpirable(ReferenceEntry previous) {
}
@Override
public ReferenceEntry getNextEvictable() {
return this;
}
@Override
public void setNextEvictable(ReferenceEntry next) {
}
@Override
public ReferenceEntry getPreviousEvictable() {
return this;
}
@Override
public void setPreviousEvictable(ReferenceEntry previous) {
}
}
/**
* A reference to a value.
*/
interface ValueReference {
/**
* Gets the value. Does not block or throw exceptions.
*/
V get();
/**
* Returns the entry associated with this value reference, or {@code null} if this value
* reference is independent of any entry.
*/
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, V value, ReferenceEntry entry);
/**
* Clears this reference object.
*
* @param newValue the new value reference which will replace this one; this is only used during
* computation to immediately notify blocked threads of the new value
*/
void clear(ValueReference newValue);
/**
* Returns {@code true} if the value type is a computing reference (regardless of whether or not
* computation has completed). This is necessary to distiguish between partially-collected
* entries and computing entries, which need to be cleaned up differently.
*/
boolean isComputingReference();
}
/**
* An entry in a reference map.
*
* Entries in the map can be in the following states:
*
* Valid:
* - Live: valid key/value are set
* - Computing: computation is pending
*
* Invalid:
* - Expired: time expired (key/value may still be set)
* - Collected: key/value was partially collected, but not yet cleaned up
*/
interface ReferenceEntry {
/**
* Gets the value reference from this entry.
*/
ValueReference getValueReference();
/**
* Sets the value reference for this entry.
*/
void setValueReference(ValueReference valueReference);
/**
* Gets the next entry in the chain.
*/
ReferenceEntry getNext();
/**
* Gets the entry's hash.
*/
int getHash();
/**
* Gets the key for this entry.
*/
K getKey();
/*
* Used by entries that are expirable. Expirable 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.
*/
/**
* Gets the entry expiration time in ns.
*/
long getExpirationTime();
/**
* Sets the entry expiration time in ns.
*/
void setExpirationTime(long time);
/**
* Gets the next entry in the recency list.
*/
ReferenceEntry getNextExpirable();
/**
* Sets the next entry in the recency list.
*/
void setNextExpirable(ReferenceEntry next);
/**
* Gets the previous entry in the recency list.
*/
ReferenceEntry getPreviousExpirable();
/**
* Sets the previous entry in the recency list.
*/
void setPreviousExpirable(ReferenceEntry previous);
/*
* Implemented by entries that are evictable. Evictable 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.
*/
/**
* Gets the next entry in the recency list.
*/
ReferenceEntry getNextEvictable();
/**
* Sets the next entry in the recency list.
*/
void setNextEvictable(ReferenceEntry next);
/**
* Gets the previous entry in the recency list.
*/
ReferenceEntry getPreviousEvictable();
/**
* Sets the previous entry in the recency list.
*/
void setPreviousEvictable(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 getExpirationTime() {
throw new UnsupportedOperationException();
}
@Override
public void setExpirationTime(long time) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getNextExpirable() {
throw new UnsupportedOperationException();
}
@Override
public void setNextExpirable(ReferenceEntry next) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getPreviousExpirable() {
throw new UnsupportedOperationException();
}
@Override
public void setPreviousExpirable(ReferenceEntry previous) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getNextEvictable() {
throw new UnsupportedOperationException();
}
@Override
public void setNextEvictable(ReferenceEntry next) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getPreviousEvictable() {
throw new UnsupportedOperationException();
}
@Override
public void setPreviousEvictable(ReferenceEntry previous) {
throw new UnsupportedOperationException();
}
}
/**
* Used for strongly-referenced keys.
*/
static class StrongEntry implements ReferenceEntry {
final K key;
final int hash;
final ReferenceEntry next;
// null expiration
volatile ValueReference valueReference = unset();
StrongEntry(K key, int hash, ReferenceEntry next) {
this.key = key;
this.hash = hash;
this.next = next;
}
@Override
public K getKey() {
return this.key;
}
@Override
public long getExpirationTime() {
throw new UnsupportedOperationException();
}
@Override
public void setExpirationTime(long time) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getNextExpirable() {
throw new UnsupportedOperationException();
}
// null eviction
@Override
public void setNextExpirable(ReferenceEntry next) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getPreviousExpirable() {
throw new UnsupportedOperationException();
}
@Override
public void setPreviousExpirable(ReferenceEntry previous) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getNextEvictable() {
throw new UnsupportedOperationException();
}
// The code below is exactly the same for each entry type.
@Override
public void setNextEvictable(ReferenceEntry next) {
throw new UnsupportedOperationException();
}
@Override
public ReferenceEntry getPreviousEvictable() {
throw new UnsupportedOperationException();
}
@Override
public void setPreviousEvictable(ReferenceEntry previous) {
throw new UnsupportedOperationException();
}
@Override
public ValueReference getValueReference() {
return valueReference;
}
@Override
public void setValueReference(ValueReference valueReference) {
ValueReference previous = this.valueReference;
this.valueReference = valueReference;
previous.clear(valueReference);
}
@Override
public int getHash() {
return hash;
}
@Override
public ReferenceEntry getNext() {
return next;
}
}
static final class StrongExpirableEntry extends StrongEntry
implements ReferenceEntry {
volatile long time = Long.MAX_VALUE;
// The code below is exactly the same for each expirable entry type.
// Guarded By Segment.this
ReferenceEntry nextExpirable = nullEntry();
// Guarded By Segment.this
ReferenceEntry previousExpirable = nullEntry();
StrongExpirableEntry(K key, int hash, ReferenceEntry next) {
super(key, hash, next);
}
@Override
public long getExpirationTime() {
return time;
}
@Override
public void setExpirationTime(long time) {
this.time = time;
}
@Override
public ReferenceEntry getNextExpirable() {
return nextExpirable;
}
@Override
public void setNextExpirable(ReferenceEntry next) {
this.nextExpirable = next;
}
@Override
public ReferenceEntry getPreviousExpirable() {
return previousExpirable;
}
@Override
public void setPreviousExpirable(ReferenceEntry previous) {
this.previousExpirable = previous;
}
}
static final class StrongEvictableEntry
extends StrongEntry implements ReferenceEntry {
// Guarded By Segment.this
ReferenceEntry nextEvictable = nullEntry();
// The code below is exactly the same for each evictable entry type.
// Guarded By Segment.this
ReferenceEntry previousEvictable = nullEntry();
StrongEvictableEntry(K key, int hash, ReferenceEntry next) {
super(key, hash, next);
}
@Override
public ReferenceEntry getNextEvictable() {
return nextEvictable;
}
@Override
public void setNextEvictable(ReferenceEntry next) {
this.nextEvictable = next;
}
@Override
public ReferenceEntry getPreviousEvictable() {
return previousEvictable;
}
@Override
public void setPreviousEvictable(ReferenceEntry previous) {
this.previousEvictable = previous;
}
}
static final class StrongExpirableEvictableEntry