All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.topicus.jdbc.shaded.com.google.common.collect.MapMakerInternalMap Maven / Gradle / Ivy

There is a newer version: 1.1.6
Show newest version
/*
 * Copyright (C) 2009 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in nl.topicus.jdbc.shaded.com.liance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.nl.topicus.jdbc.shaded.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 nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.collect;

import static nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.base.Preconditions.checkNotNull;
import static nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.collect.CollectPreconditions.checkRemove;

import nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.annotations.GwtIncompatible;
import nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.annotations.VisibleForTesting;
import nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.base.Equivalence;
import nl.topicus.jdbc.shaded.com.google.nl.topicus.jdbc.shaded.com.on.primitives.Ints;
import nl.topicus.jdbc.shaded.com.google.errorprone.annotations.CanIgnoreReturnValue;
import nl.topicus.jdbc.shaded.com.google.j2objc.annotations.Weak;
import nl.topicus.jdbc.shaded.com.google.j2objc.annotations.WeakOuter;
import java.nl.topicus.jdbc.shaded.io.IOException;
import java.nl.topicus.jdbc.shaded.io.ObjectInputStream;
import java.nl.topicus.jdbc.shaded.io.ObjectOutputStream;
import java.nl.topicus.jdbc.shaded.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.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.locks.ReentrantLock;
import nl.topicus.jdbc.shaded.javax.annotation.Nullable;
import nl.topicus.jdbc.shaded.javax.annotation.concurrent.GuardedBy;

/**
 * The concurrent hash map implementation built by {@link MapMaker}.
 *
 * 

This implementation is heavily derived from revision 1.96 of ConcurrentHashMap.java. * * @param the type of the keys in the map * @param the type of the values in the map * @param the type of the {@link InternalEntry} entry implementation used internally * @param the type of the {@link Segment} entry implementation used internally * @author Bob Lee * @author Charles Fry * @author Doug Lea ({@code ConcurrentHashMap}) */ // TODO(kak/cpovirk): Consider removing @CanIgnoreReturnValue from this class. @GwtIncompatible class MapMakerInternalMap< K, V, E extends MapMakerInternalMap.InternalEntry, S extends MapMakerInternalMap.Segment> 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. * * 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 nl.topicus.jdbc.shaded.com.lexity. The initial LRU implementation * operates per-segment rather than globally for increased implementation simplicity. We expect * the cache hit rate to be similar to that of a global LRU algorithm. */ // Constants /** * The maximum capacity, used if a higher value is implicitly specified by either of the * constructors with arguments. MUST be a power of two <= 1<<30 to ensure that entries are * indexable using ints. */ static final int MAXIMUM_CAPACITY = Ints.MAX_POWER_OF_TWO; /** 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; static final long CLEANUP_EXECUTOR_DELAY_SECS = 60; // Fields /** * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose * the segment. */ 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. */ final transient int segmentShift; /** The segments, each of which is a specialized hash table. */ final transient Segment[] segments; /** The concurrency level. */ final int concurrencyLevel; /** Strategy for nl.topicus.jdbc.shaded.com.aring keys. */ final Equivalence keyEquivalence; /** Strategy for handling entries and segments in a type-safe and efficient manner. */ final transient InternalEntryHelper entryHelper; /** * Creates a new, empty map with the specified strategy, initial capacity and concurrency level. */ private MapMakerInternalMap(MapMaker builder, InternalEntryHelper entryHelper) { concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS); keyEquivalence = builder.getKeyEquivalence(); this.entryHelper = entryHelper; int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY); // Find power-of-two sizes best matching arguments. Constraints: // (segmentCount > concurrencyLevel) int segmentShift = 0; int segmentCount = 1; while (segmentCount < concurrencyLevel) { ++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; } for (int i = 0; i < this.segments.length; ++i) { this.segments[i] = createSegment(segmentSize, MapMaker.UNSET_INT); } } static MapMakerInternalMap, ?> create( MapMaker builder) { if (builder.getKeyStrength() == Strength.STRONG && builder.getValueStrength() == Strength.STRONG) { return new MapMakerInternalMap< K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment>( builder, StrongKeyStrongValueEntry.Helper.instance()); } if (builder.getKeyStrength() == Strength.STRONG && builder.getValueStrength() == Strength.WEAK) { return new MapMakerInternalMap< K, V, StrongKeyWeakValueEntry, StrongKeyWeakValueSegment>( builder, StrongKeyWeakValueEntry.Helper.instance()); } if (builder.getKeyStrength() == Strength.WEAK && builder.getValueStrength() == Strength.STRONG) { return new MapMakerInternalMap< K, V, WeakKeyStrongValueEntry, WeakKeyStrongValueSegment>( builder, WeakKeyStrongValueEntry.Helper.instance()); } if (builder.getKeyStrength() == Strength.WEAK && builder.getValueStrength() == Strength.WEAK) { return new MapMakerInternalMap< K, V, WeakKeyWeakValueEntry, WeakKeyWeakValueSegment>( builder, WeakKeyWeakValueEntry.Helper.instance()); } throw new AssertionError(); } enum Strength { STRONG { @Override Equivalence defaultEquivalence() { return Equivalence.equals(); } }, WEAK { @Override Equivalence defaultEquivalence() { return Equivalence.identity(); } }; /** * Returns the default equivalence strategy used to nl.topicus.jdbc.shaded.com.are 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(); } /** * A helper object for operating on {@link InternalEntry} instances in a type-safe and efficient * manner. * *

For each of the four nl.topicus.jdbc.shaded.com.inations of strong/weak key and strong/weak value, there are * corresponding {@link InternalEntry}, {@link Segment}, and {@link InternalEntryHelper} * implementations. * * @param the type of the key in each entry * @param the type of the value in each entry * @param the type of the {@link InternalEntry} entry implementation * @param the type of the {@link Segment} entry implementation */ interface InternalEntryHelper< K, V, E extends InternalEntry, S extends Segment> { /** The strength of the key type in each entry. */ Strength keyStrength(); /** The strength of the value type in each entry. */ Strength valueStrength(); /** Returns a freshly created segment, typed at the {@code S} type. */ S newSegment(MapMakerInternalMap map, int initialCapacity, int maxSegmentSize); /** * Returns a freshly created entry, typed at the {@code E} type, for the given {@code segment}. */ E newEntry(S segment, K key, int hash, @Nullable E next); /** * Returns a freshly created entry, typed at the {@code E} type, for the given {@code segment}, * that is a copy of the given {@code entry}. */ E copy(S segment, E entry, @Nullable E newNext); /** * Sets the value of the given {@code entry} in the given {@code segment} to be the given {@code * value} */ void setValue(S segment, E entry, V value); } /** * An entry in a hash table of a {@link Segment}. * *

Entries in the map can be in the following states: * *

Valid: - Live: valid key/value are set * *

Invalid: - Collected: key/value was partially collected, but not yet cleaned up */ interface InternalEntry> { /** Gets the next entry in the chain. */ E getNext(); /** * Gets the entry's hash. */ int getHash(); /** * Gets the key for this entry. */ K getKey(); /** Gets the value for the entry. */ V getValue(); } /* * Note: the following classes have a lot of duplicate code. It sucks, but it saves a lot of * memory. If only Java had mixins! */ /** Base class for {@link InternalEntry} implementations for strong keys. */ abstract static class AbstractStrongKeyEntry> implements InternalEntry { final K key; final int hash; final E next; AbstractStrongKeyEntry(K key, int hash, @Nullable E next) { this.key = key; this.hash = hash; this.next = next; } @Override public K getKey() { return this.key; } @Override public int getHash() { return hash; } @Override public E getNext() { return next; } } /** Marker interface for {@link InternalEntry} implementations for strong values. */ interface StrongValueEntry> extends InternalEntry {} /** Marker interface for {@link InternalEntry} implementations for weak values. */ interface WeakValueEntry> extends InternalEntry { /** Gets the weak value reference held by entry. */ WeakValueReference getValueReference(); /** * Clears the weak value reference held by the entry. Should be used when the entry's value is * overwritten. */ void clearValue(); } @SuppressWarnings("unchecked") // impl never uses a parameter or returns any non-null value static > WeakValueReference unsetWeakValueReference() { return (WeakValueReference) UNSET_WEAK_VALUE_REFERENCE; } /** Concrete implementation of {@link InternalEntry} for strong keys and strong values. */ static final class StrongKeyStrongValueEntry extends AbstractStrongKeyEntry> implements StrongValueEntry> { @Nullable private volatile V value = null; StrongKeyStrongValueEntry(K key, int hash, @Nullable StrongKeyStrongValueEntry next) { super(key, hash, next); } @Override @Nullable public V getValue() { return value; } void setValue(V value) { this.value = value; } StrongKeyStrongValueEntry copy(StrongKeyStrongValueEntry newNext) { StrongKeyStrongValueEntry newEntry = new StrongKeyStrongValueEntry(this.key, this.hash, newNext); newEntry.value = this.value; return newEntry; } /** Concrete implementation of {@link InternalEntryHelper} for strong keys and strong values. */ static final class Helper implements InternalEntryHelper< K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment> { private static final Helper INSTANCE = new Helper(); @SuppressWarnings("unchecked") static Helper instance() { return (Helper) INSTANCE; } @Override public Strength keyStrength() { return Strength.STRONG; } @Override public Strength valueStrength() { return Strength.STRONG; } @Override public StrongKeyStrongValueSegment newSegment( MapMakerInternalMap< K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment> map, int initialCapacity, int maxSegmentSize) { return new StrongKeyStrongValueSegment(map, initialCapacity, maxSegmentSize); } @Override public StrongKeyStrongValueEntry copy( StrongKeyStrongValueSegment segment, StrongKeyStrongValueEntry entry, @Nullable StrongKeyStrongValueEntry newNext) { return entry.copy(newNext); } @Override public void setValue( StrongKeyStrongValueSegment segment, StrongKeyStrongValueEntry entry, V value) { entry.setValue(value); } @Override public StrongKeyStrongValueEntry newEntry( StrongKeyStrongValueSegment segment, K key, int hash, @Nullable StrongKeyStrongValueEntry next) { return new StrongKeyStrongValueEntry(key, hash, next); } } } /** Concrete implementation of {@link InternalEntry} for strong keys and weak values. */ static final class StrongKeyWeakValueEntry extends AbstractStrongKeyEntry> implements WeakValueEntry> { private volatile WeakValueReference> valueReference = unsetWeakValueReference(); StrongKeyWeakValueEntry(K key, int hash, @Nullable StrongKeyWeakValueEntry next) { super(key, hash, next); } @Override public V getValue() { return valueReference.get(); } @Override public void clearValue() { valueReference.clear(); } void setValue(V value, ReferenceQueue queueForValues) { WeakValueReference> previous = this.valueReference; this.valueReference = new WeakValueReferenceImpl>( queueForValues, value, this); previous.clear(); } StrongKeyWeakValueEntry copy( ReferenceQueue queueForValues, StrongKeyWeakValueEntry newNext) { StrongKeyWeakValueEntry newEntry = new StrongKeyWeakValueEntry(key, hash, newNext); newEntry.valueReference = valueReference.copyFor(queueForValues, newEntry); return newEntry; } @Override public WeakValueReference> getValueReference() { return valueReference; } /** Concrete implementation of {@link InternalEntryHelper} for strong keys and weak values. */ static final class Helper implements InternalEntryHelper< K, V, StrongKeyWeakValueEntry, StrongKeyWeakValueSegment> { private static final Helper INSTANCE = new Helper(); @SuppressWarnings("unchecked") static Helper instance() { return (Helper) INSTANCE; } @Override public Strength keyStrength() { return Strength.STRONG; } @Override public Strength valueStrength() { return Strength.WEAK; } @Override public StrongKeyWeakValueSegment newSegment( MapMakerInternalMap, StrongKeyWeakValueSegment> map, int initialCapacity, int maxSegmentSize) { return new StrongKeyWeakValueSegment(map, initialCapacity, maxSegmentSize); } @Override public StrongKeyWeakValueEntry copy( StrongKeyWeakValueSegment segment, StrongKeyWeakValueEntry entry, @Nullable StrongKeyWeakValueEntry newNext) { if (Segment.isCollected(entry)) { return null; } return entry.copy(segment.queueForValues, newNext); } @Override public void setValue( StrongKeyWeakValueSegment segment, StrongKeyWeakValueEntry entry, V value) { entry.setValue(value, segment.queueForValues); } @Override public StrongKeyWeakValueEntry newEntry( StrongKeyWeakValueSegment segment, K key, int hash, @Nullable StrongKeyWeakValueEntry next) { return new StrongKeyWeakValueEntry(key, hash, next); } } } /** Base class for {@link InternalEntry} implementations for weak keys. */ abstract static class AbstractWeakKeyEntry> extends WeakReference implements InternalEntry { final int hash; final E next; AbstractWeakKeyEntry(ReferenceQueue queue, K key, int hash, @Nullable E next) { super(key, queue); this.hash = hash; this.next = next; } @Override public K getKey() { return get(); } @Override public int getHash() { return hash; } @Override public E getNext() { return next; } } /** Concrete implementation of {@link InternalEntry} for weak keys and strong values. */ static final class WeakKeyStrongValueEntry extends AbstractWeakKeyEntry> implements StrongValueEntry> { @Nullable private volatile V value = null; WeakKeyStrongValueEntry( ReferenceQueue queue, K key, int hash, @Nullable WeakKeyStrongValueEntry next) { super(queue, key, hash, next); } @Override @Nullable public V getValue() { return value; } void setValue(V value) { this.value = value; } WeakKeyStrongValueEntry copy( ReferenceQueue queueForKeys, WeakKeyStrongValueEntry newNext) { WeakKeyStrongValueEntry newEntry = new WeakKeyStrongValueEntry(queueForKeys, getKey(), this.hash, newNext); newEntry.setValue(value); return newEntry; } /** Concrete implementation of {@link InternalEntryHelper} for weak keys and strong values. */ static final class Helper implements InternalEntryHelper< K, V, WeakKeyStrongValueEntry, WeakKeyStrongValueSegment> { private static final Helper INSTANCE = new Helper(); @SuppressWarnings("unchecked") static Helper instance() { return (Helper) INSTANCE; } @Override public Strength keyStrength() { return Strength.WEAK; } @Override public Strength valueStrength() { return Strength.STRONG; } @Override public WeakKeyStrongValueSegment newSegment( MapMakerInternalMap, WeakKeyStrongValueSegment> map, int initialCapacity, int maxSegmentSize) { return new WeakKeyStrongValueSegment(map, initialCapacity, maxSegmentSize); } @Override public WeakKeyStrongValueEntry copy( WeakKeyStrongValueSegment segment, WeakKeyStrongValueEntry entry, @Nullable WeakKeyStrongValueEntry newNext) { if (entry.getKey() == null) { // key collected return null; } return entry.copy(segment.queueForKeys, newNext); } @Override public void setValue( WeakKeyStrongValueSegment segment, WeakKeyStrongValueEntry entry, V value) { entry.setValue(value); } @Override public WeakKeyStrongValueEntry newEntry( WeakKeyStrongValueSegment segment, K key, int hash, @Nullable WeakKeyStrongValueEntry next) { return new WeakKeyStrongValueEntry(segment.queueForKeys, key, hash, next); } } } /** Concrete implementation of {@link InternalEntry} for weak keys and weak values. */ static final class WeakKeyWeakValueEntry extends AbstractWeakKeyEntry> implements WeakValueEntry> { private volatile WeakValueReference> valueReference = unsetWeakValueReference(); WeakKeyWeakValueEntry( ReferenceQueue queue, K key, int hash, @Nullable WeakKeyWeakValueEntry next) { super(queue, key, hash, next); } @Override public V getValue() { return valueReference.get(); } WeakKeyWeakValueEntry copy( ReferenceQueue queueForKeys, ReferenceQueue queueForValues, WeakKeyWeakValueEntry newNext) { WeakKeyWeakValueEntry newEntry = new WeakKeyWeakValueEntry(queueForKeys, getKey(), this.hash, newNext); newEntry.valueReference = valueReference.copyFor(queueForValues, newEntry); return newEntry; } @Override public void clearValue() { valueReference.clear(); } void setValue(V value, ReferenceQueue queueForValues) { WeakValueReference> previous = this.valueReference; this.valueReference = new WeakValueReferenceImpl>( queueForValues, value, this); previous.clear(); } @Override public WeakValueReference> getValueReference() { return valueReference; } /** Concrete implementation of {@link InternalEntryHelper} for weak keys and weak values. */ static final class Helper implements InternalEntryHelper< K, V, WeakKeyWeakValueEntry, WeakKeyWeakValueSegment> { private static final Helper INSTANCE = new Helper(); @SuppressWarnings("unchecked") static Helper instance() { return (Helper) INSTANCE; } @Override public Strength keyStrength() { return Strength.WEAK; } @Override public Strength valueStrength() { return Strength.WEAK; } @Override public WeakKeyWeakValueSegment newSegment( MapMakerInternalMap, WeakKeyWeakValueSegment> map, int initialCapacity, int maxSegmentSize) { return new WeakKeyWeakValueSegment(map, initialCapacity, maxSegmentSize); } @Override public WeakKeyWeakValueEntry copy( WeakKeyWeakValueSegment segment, WeakKeyWeakValueEntry entry, @Nullable WeakKeyWeakValueEntry newNext) { if (entry.getKey() == null) { // key collected return null; } if (Segment.isCollected(entry)) { return null; } return entry.copy(segment.queueForKeys, segment.queueForValues, newNext); } @Override public void setValue( WeakKeyWeakValueSegment segment, WeakKeyWeakValueEntry entry, V value) { entry.setValue(value, segment.queueForValues); } @Override public WeakKeyWeakValueEntry newEntry( WeakKeyWeakValueSegment segment, K key, int hash, @Nullable WeakKeyWeakValueEntry next) { return new WeakKeyWeakValueEntry(segment.queueForKeys, key, hash, next); } } } /** A weakly referenced value that also has a reference to its containing entry. */ interface WeakValueReference> { /** * Returns the current value being referenced, or {@code null} if there is none (e.g. because * either it got collected, or {@link #clear} was called, or it wasn't set in the first place). */ @Nullable V get(); /** Returns the entry which contains this {@link WeakValueReference}. */ E getEntry(); /** Unsets the referenced value. Subsequent calls to {@link get} will return {@code null}. */ void clear(); /** * Returns a freshly created {@link WeakValueReference} for the given {@code entry} (and on the * given {@code queue} with the same value as this {@link WeakValueReference}. */ WeakValueReference copyFor(ReferenceQueue queue, E entry); } /** * A dummy implementation of {@link InternalEntry}, solely for use in the type signature of {@link * #UNSET_WEAK_VALUE_REFERENCE} below. */ static final class DummyInternalEntry implements InternalEntry { private DummyInternalEntry() { throw new AssertionError(); } @Override public DummyInternalEntry getNext() { throw new AssertionError(); } @Override public int getHash() { throw new AssertionError(); } @Override public Object getKey() { throw new AssertionError(); } @Override public Object getValue() { throw new AssertionError(); } } /** * A singleton {@link WeakValueReference} used to denote an unset value in a entry with weak * values. */ static final WeakValueReference UNSET_WEAK_VALUE_REFERENCE = new WeakValueReference() { @Override public DummyInternalEntry getEntry() { return null; } @Override public void clear() {} @Override public Object get() { return null; } @Override public WeakValueReference copyFor( ReferenceQueue queue, DummyInternalEntry entry) { return this; } }; /** Concrete implementation of {@link WeakValueReference}. */ static final class WeakValueReferenceImpl> extends WeakReference implements WeakValueReference { final E entry; WeakValueReferenceImpl(ReferenceQueue queue, V referent, E entry) { super(referent, queue); this.entry = entry; } @Override public E getEntry() { return entry; } @Override public WeakValueReference copyFor(ReferenceQueue queue, E entry) { return new WeakValueReferenceImpl(queue, get(), entry); } } /** * Applies a supplemental hash function to a given hash code, which defends against poor quality * hash functions. This is critical when the concurrent hash map uses power-of-two length hash * tables, that otherwise encounter collisions for hash codes that do not differ in lower or * upper bits. * * @param h hash code */ static int rehash(int h) { // Spread bits to regularize both segment and index locations, // using variant of single-word Wang/Jenkins hash. // TODO(kevinb): use Hashing/move this to Hashing? h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); } /** * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly. */ // Guarded By Segment.this @VisibleForTesting E copyEntry(E original, E newNext) { int hash = original.getHash(); return segmentFor(hash).copyEntry(original, newNext); } int hash(Object key) { int h = keyEquivalence.hash(key); return rehash(h); } void reclaimValue(WeakValueReference valueReference) { E entry = valueReference.getEntry(); int hash = entry.getHash(); segmentFor(hash).reclaimValue(entry.getKey(), hash, valueReference); } void reclaimKey(E entry) { int hash = entry.getHash(); segmentFor(hash).reclaimKey(entry, hash); } /** * This method is a convenience for testing. Code should call {@link Segment#getLiveValue} * instead. */ @VisibleForTesting boolean isLiveForTesting(InternalEntry entry) { return segmentFor(entry.getHash()).getLiveValueForTesting(entry) != null; } /** * Returns the segment that should be used for a key with the given hash. * * @param hash the hash code for the key * @return the segment */ Segment segmentFor(int hash) { // TODO(fry): Lazily create segments? return segments[(hash >>> segmentShift) & segmentMask]; } Segment createSegment(int initialCapacity, int maxSegmentSize) { return entryHelper.newSegment(this, initialCapacity, maxSegmentSize); } /** * Gets the value from an entry. Returns {@code null} if the entry is invalid, partially-collected * or nl.topicus.jdbc.shaded.com.uting. */ V getLiveValue(E entry) { if (entry.getKey() == null) { return null; } V value = entry.getValue(); if (value == null) { return null; } return value; } @SuppressWarnings("unchecked") final Segment[] newSegmentArray(int ssize) { return new Segment[ssize]; } // Inner Classes /** * Segments are specialized versions of hash tables. This subclass inherits from ReentrantLock * opportunistically, just to simplify some locking and avoid separate construction. */ @SuppressWarnings("serial") // This class is never serialized. abstract static class Segment< K, V, E extends InternalEntry, S extends Segment> extends ReentrantLock { /* * Segments maintain a table of entry lists that are ALWAYS kept in a consistent state, so can * be read without locking. Next fields of nodes are immutable (final). All list additions are * performed at the front of each bin. This makes it easy to check changes, and also fast to * traverse. When nodes would otherwise be changed, new nodes are created to replace them. This * works well for hash tables since the bin lists tend to be short. (The average length is less * than two.) * * Read operations can thus proceed without locking, but rely on selected uses of volatiles to * ensure that nl.topicus.jdbc.shaded.com.leted write operations performed by other threads are noticed. For most * purposes, the "count" field, tracking the number of elements, serves as that volatile * variable ensuring visibility. This is convenient because this field needs to be read in many * read operations anyway: * * - All (unsynchronized) read operations must first read the "count" field, and should not * look at table entries if it is 0. * * - All (synchronized) write operations should write to the "count" field after structurally * changing any bin. The operations must not take any action that could even momentarily * cause a concurrent read operation to see inconsistent data. This is made easier by the * nature of the read operations in Map. For example, no operation can reveal that the table * has grown but the threshold has not yet been updated, so there are no atomicity requirements * for this with respect to reads. * * As a guide, all critical volatile reads and writes to the count field are marked in code * nl.topicus.jdbc.shaded.com.ents. */ @Weak final MapMakerInternalMap map; /** * The number of live elements in this segment's region. This does not include unset elements * which are awaiting cleanup. */ volatile int count; /** * Number of updates that alter the size of the table. This is used during bulk-read methods to * make sure they see a consistent snapshot: If modCounts change during a traversal of segments * nl.topicus.jdbc.shaded.com.uting size or checking containsValue, then we might have an inconsistent view of state * so (usually) must retry. */ int modCount; /** * The table is expanded when its size exceeds this threshold. (The value of this field is * always {@code (int) (capacity * 0.75)}.) */ int threshold; /** The per-segment table. */ volatile AtomicReferenceArray table; /** * The maximum size of this map. MapMaker.UNSET_INT if there is no maximum. */ final int maxSegmentSize; /** * A counter of the number of reads since the last write, used to drain queues on a small * fraction of read operations. */ final AtomicInteger readCount = new AtomicInteger(); Segment(MapMakerInternalMap map, int initialCapacity, int maxSegmentSize) { this.map = map; this.maxSegmentSize = maxSegmentSize; initTable(newEntryArray(initialCapacity)); } /** * Returns {@code this} up-casted to the specific {@link Segment} implementation type {@code S}. * *

This method exists so that the {@link Segment} code can be generic in terms of {@code S}, * the type of the concrete implementation. */ abstract S self(); /** Drains the reference queues used by this segment, if any. */ @GuardedBy("this") void maybeDrainReferenceQueues() {} /** Clears the reference queues used by this segment, if any. */ void maybeClearReferenceQueues() {} /** Sets the value of the given {@code entry}. */ void setValue(E entry, V value) { this.map.entryHelper.setValue(self(), entry, value); } /** Returns a copy of the given {@code entry}. */ E copyEntry(E original, E newNext) { return this.map.entryHelper.copy(self(), original, newNext); } AtomicReferenceArray newEntryArray(int size) { return new AtomicReferenceArray(size); } void initTable(AtomicReferenceArray newTable) { this.threshold = newTable.length() * 3 / 4; // 0.75 if (this.threshold == maxSegmentSize) { // prevent spurious expansion before eviction this.threshold++; } this.table = newTable; } // Convenience methods for testing /** * Unsafe cast of the given entry to {@code E}, the type of the specific {@link InternalEntry} * implementation type. * *

This method is provided as a convenience for tests. Otherwise they'd need to be * knowledgable about all the implementation details of our type system trickery. */ abstract E castForTesting(InternalEntry entry); /** Unsafely extracts the key reference queue used by this segment. */ ReferenceQueue getKeyReferenceQueueForTesting() { throw new AssertionError(); } /** Unsafely extracts the value reference queue used by this segment. */ ReferenceQueue getValueReferenceQueueForTesting() { throw new AssertionError(); } /** Unsafely extracts the weak value reference inside of the given {@code entry}. */ WeakValueReference getWeakValueReferenceForTesting(InternalEntry entry) { throw new AssertionError(); } /** * Unsafely creates of a fresh {@link WeakValueReference}, referencing the given {@code value}, * for the given {@code entry} */ WeakValueReference newWeakValueReferenceForTesting( InternalEntry entry, V value) { throw new AssertionError(); } /** * Unsafely sets the weak value reference inside the given {@code entry} to be the given {@link * valueReference} */ void setWeakValueReferenceForTesting( InternalEntry entry, WeakValueReference> valueReference) { throw new AssertionError(); } /** * Unsafely sets the given index of this segment's internal hash table to be the given entry. */ void setTableEntryForTesting(int i, InternalEntry entry) { table.set(i, castForTesting(entry)); } /** Unsafely returns a copy of the given entry. */ E copyForTesting(InternalEntry entry, @Nullable InternalEntry newNext) { return this.map.entryHelper.copy(self(), castForTesting(entry), castForTesting(newNext)); } /** Unsafely sets the value of the given entry. */ void setValueForTesting(InternalEntry entry, V value) { this.map.entryHelper.setValue(self(), castForTesting(entry), value); } /** Unsafely returns a fresh entry. */ E newEntryForTesting(K key, int hash, @Nullable InternalEntry next) { return this.map.entryHelper.newEntry(self(), key, hash, castForTesting(next)); } /** Unsafely removes the given entry from this segment's hash table. */ @CanIgnoreReturnValue boolean removeTableEntryForTesting(InternalEntry entry) { return removeEntryForTesting(castForTesting(entry)); } /** Unsafely removes the given entry from the given chain in this segment's hash table. */ E removeFromChainForTesting(InternalEntry first, InternalEntry entry) { return removeFromChain(castForTesting(first), castForTesting(entry)); } /** * Unsafely returns the value of the given entry if it's still live, or {@code null} otherwise. */ @Nullable V getLiveValueForTesting(InternalEntry entry) { return getLiveValue(castForTesting(entry)); } // reference queues, for garbage collection cleanup /** * Cleanup collected entries when the lock is available. */ void tryDrainReferenceQueues() { if (tryLock()) { try { maybeDrainReferenceQueues(); } finally { unlock(); } } } @GuardedBy("this") void drainKeyReferenceQueue(ReferenceQueue keyReferenceQueue) { Reference ref; int i = 0; while ((ref = keyReferenceQueue.poll()) != null) { @SuppressWarnings("unchecked") E entry = (E) ref; map.reclaimKey(entry); if (++i == DRAIN_MAX) { break; } } } @GuardedBy("this") void drainValueReferenceQueue(ReferenceQueue valueReferenceQueue) { Reference ref; int i = 0; while ((ref = valueReferenceQueue.poll()) != null) { @SuppressWarnings("unchecked") WeakValueReference valueReference = (WeakValueReference) ref; map.reclaimValue(valueReference); if (++i == DRAIN_MAX) { break; } } } void clearReferenceQueue(ReferenceQueue referenceQueue) { while (referenceQueue.poll() != null) {} } /** Returns first entry of bin for given hash. */ E getFirst(int hash) { // read this volatile field only once AtomicReferenceArray table = this.table; return table.get(hash & (table.length() - 1)); } // Specialized implementations of map methods E getEntry(Object key, int hash) { if (count != 0) { // read-volatile for (E e = getFirst(hash); e != null; e = e.getNext()) { if (e.getHash() != hash) { continue; } K entryKey = e.getKey(); if (entryKey == null) { tryDrainReferenceQueues(); continue; } if (map.keyEquivalence.equivalent(key, entryKey)) { return e; } } } return null; } E getLiveEntry(Object key, int hash) { return getEntry(key, hash); } V get(Object key, int hash) { try { E e = getLiveEntry(key, hash); if (e == null) { return null; } V value = e.getValue(); if (value == null) { tryDrainReferenceQueues(); } return value; } finally { postReadCleanup(); } } boolean containsKey(Object key, int hash) { try { if (count != 0) { // read-volatile E e = getLiveEntry(key, hash); return e != null && e.getValue() != null; } return false; } finally { postReadCleanup(); } } /** * This method is a convenience for testing. Code should call {@link * MapMakerInternalMap#containsValue} directly. */ @VisibleForTesting boolean containsValue(Object value) { try { if (count != 0) { // read-volatile AtomicReferenceArray table = this.table; int length = table.length(); for (int i = 0; i < length; ++i) { for (E e = table.get(i); e != null; e = e.getNext()) { V entryValue = getLiveValue(e); if (entryValue == null) { continue; } if (map.valueEquivalence().equivalent(value, entryValue)) { return true; } } } } return false; } finally { postReadCleanup(); } } V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); try { preWriteCleanup(); int newCount = this.count + 1; if (newCount > this.threshold) { // ensure capacity expand(); newCount = this.count + 1; } AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); // Look for an existing entry. for (E e = first; e != null; e = e.getNext()) { K entryKey = e.getKey(); if (e.getHash() == hash && entryKey != null && map.keyEquivalence.equivalent(key, entryKey)) { // We found an existing entry. V entryValue = e.getValue(); if (entryValue == null) { ++modCount; setValue(e, value); newCount = this.count; // count remains unchanged this.count = newCount; // write-volatile return null; } else if (onlyIfAbsent) { // Mimic // "if (!map.containsKey(key)) ... // else return map.get(key); return entryValue; } else { // clobber existing entry, count remains unchanged ++modCount; setValue(e, value); return entryValue; } } } // Create a new entry. ++modCount; E newEntry = map.entryHelper.newEntry(self(), key, hash, first); setValue(newEntry, value); table.set(index, newEntry); this.count = newCount; // write-volatile return null; } finally { unlock(); } } /** * Expands the table if possible. */ @GuardedBy("this") void expand() { AtomicReferenceArray oldTable = table; int oldCapacity = oldTable.length(); if (oldCapacity >= MAXIMUM_CAPACITY) { return; } /* * Reclassify nodes in each list to new Map. Because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or move with a power of two offset. * We eliminate unnecessary node creation by catching cases where old nodes can be reused * because their next fields won't change. Statistically, at the default threshold, only * about one-sixth of them need cloning when a table doubles. The nodes they replace will be * garbage collectable as soon as they are no longer referenced by any reader thread that may * be in the midst of traversing table right now. */ int newCount = count; AtomicReferenceArray newTable = newEntryArray(oldCapacity << 1); threshold = newTable.length() * 3 / 4; int newMask = newTable.length() - 1; for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) { // We need to guarantee that any existing reads of old Map can // proceed. So we cannot yet null out each bin. E head = oldTable.get(oldIndex); if (head != null) { E next = head.getNext(); int headIndex = head.getHash() & newMask; // Single node on list if (next == null) { newTable.set(headIndex, head); } else { // Reuse the consecutive sequence of nodes with the same target // index from the end of the list. tail points to the first // entry in the reusable list. E tail = head; int tailIndex = headIndex; for (E e = next; e != null; e = e.getNext()) { int newIndex = e.getHash() & newMask; if (newIndex != tailIndex) { // The index changed. We'll need to copy the previous entry. tailIndex = newIndex; tail = e; } } newTable.set(tailIndex, tail); // Clone nodes leading up to the tail. for (E e = head; e != tail; e = e.getNext()) { int newIndex = e.getHash() & newMask; E newNext = newTable.get(newIndex); E newFirst = copyEntry(e, newNext); if (newFirst != null) { newTable.set(newIndex, newFirst); } else { newCount--; } } } } } table = newTable; this.count = newCount; } boolean replace(K key, int hash, V oldValue, V newValue) { lock(); try { preWriteCleanup(); AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); for (E e = first; e != null; e = e.getNext()) { K entryKey = e.getKey(); if (e.getHash() == hash && entryKey != null && map.keyEquivalence.equivalent(key, entryKey)) { // If the value disappeared, this entry is partially collected, // and we should pretend like it doesn't exist. V entryValue = e.getValue(); if (entryValue == null) { if (isCollected(e)) { int newCount = this.count - 1; ++modCount; E newFirst = removeFromChain(first, e); newCount = this.count - 1; table.set(index, newFirst); this.count = newCount; // write-volatile } return false; } if (map.valueEquivalence().equivalent(oldValue, entryValue)) { ++modCount; setValue(e, newValue); return true; } else { // Mimic // "if (map.containsKey(key) && map.get(key).equals(oldValue))..." return false; } } } return false; } finally { unlock(); } } V replace(K key, int hash, V newValue) { lock(); try { preWriteCleanup(); AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); for (E e = first; e != null; e = e.getNext()) { K entryKey = e.getKey(); if (e.getHash() == hash && entryKey != null && map.keyEquivalence.equivalent(key, entryKey)) { // If the value disappeared, this entry is partially collected, // and we should pretend like it doesn't exist. V entryValue = e.getValue(); if (entryValue == null) { if (isCollected(e)) { int newCount = this.count - 1; ++modCount; E newFirst = removeFromChain(first, e); newCount = this.count - 1; table.set(index, newFirst); this.count = newCount; // write-volatile } return null; } ++modCount; setValue(e, newValue); return entryValue; } } return null; } finally { unlock(); } } @CanIgnoreReturnValue V remove(Object key, int hash) { lock(); try { preWriteCleanup(); int newCount = this.count - 1; AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); for (E e = first; e != null; e = e.getNext()) { K entryKey = e.getKey(); if (e.getHash() == hash && entryKey != null && map.keyEquivalence.equivalent(key, entryKey)) { V entryValue = e.getValue(); if (entryValue != null) { // TODO(kak): Remove this branch } else if (isCollected(e)) { // TODO(kak): Remove this branch } else { return null; } ++modCount; E newFirst = removeFromChain(first, e); newCount = this.count - 1; table.set(index, newFirst); this.count = newCount; // write-volatile return entryValue; } } return null; } finally { unlock(); } } boolean remove(Object key, int hash, Object value) { lock(); try { preWriteCleanup(); int newCount = this.count - 1; AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); for (E e = first; e != null; e = e.getNext()) { K entryKey = e.getKey(); if (e.getHash() == hash && entryKey != null && map.keyEquivalence.equivalent(key, entryKey)) { V entryValue = e.getValue(); boolean explicitRemoval = false; if (map.valueEquivalence().equivalent(value, entryValue)) { explicitRemoval = true; } else if (isCollected(e)) { // TODO(kak): Remove this branch } else { return false; } ++modCount; E newFirst = removeFromChain(first, e); newCount = this.count - 1; table.set(index, newFirst); this.count = newCount; // write-volatile return explicitRemoval; } } return false; } finally { unlock(); } } void clear() { if (count != 0) { lock(); try { AtomicReferenceArray table = this.table; for (int i = 0; i < table.length(); ++i) { table.set(i, null); } maybeClearReferenceQueues(); readCount.set(0); ++modCount; count = 0; // write-volatile } finally { unlock(); } } } /** * Removes an entry from within a table. All entries following the removed node can stay, but * all preceding ones need to be cloned. * *

This method does not decrement count for the removed entry, but does decrement count for * all partially collected entries which are skipped. As such callers which are modifying count * must re-read it after calling removeFromChain. * * @param first the first entry of the table * @param entry the entry being removed from the table * @return the new first entry for the table */ @GuardedBy("this") E removeFromChain(E first, E entry) { int newCount = count; E newFirst = entry.getNext(); for (E e = first; e != entry; e = e.getNext()) { E next = copyEntry(e, newFirst); if (next != null) { newFirst = next; } else { newCount--; } } this.count = newCount; return newFirst; } /** Removes an entry whose key has been garbage collected. */ @CanIgnoreReturnValue boolean reclaimKey(E entry, int hash) { lock(); try { int newCount = count - 1; AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); for (E e = first; e != null; e = e.getNext()) { if (e == entry) { ++modCount; E newFirst = removeFromChain(first, e); newCount = this.count - 1; table.set(index, newFirst); this.count = newCount; // write-volatile return true; } } return false; } finally { unlock(); } } /** Removes an entry whose value has been garbage collected. */ @CanIgnoreReturnValue boolean reclaimValue(K key, int hash, WeakValueReference valueReference) { lock(); try { int newCount = this.count - 1; AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); for (E e = first; e != null; e = e.getNext()) { K entryKey = e.getKey(); if (e.getHash() == hash && entryKey != null && map.keyEquivalence.equivalent(key, entryKey)) { WeakValueReference v = ((WeakValueEntry) e).getValueReference(); if (v == valueReference) { ++modCount; E newFirst = removeFromChain(first, e); newCount = this.count - 1; table.set(index, newFirst); this.count = newCount; // write-volatile return true; } return false; } } return false; } finally { unlock(); } } /** Clears a value that has not yet been set, and thus does not require count to be modified. */ @CanIgnoreReturnValue boolean clearValueForTesting( K key, int hash, WeakValueReference> valueReference) { lock(); try { AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); for (E e = first; e != null; e = e.getNext()) { K entryKey = e.getKey(); if (e.getHash() == hash && entryKey != null && map.keyEquivalence.equivalent(key, entryKey)) { WeakValueReference v = ((WeakValueEntry) e).getValueReference(); if (v == valueReference) { E newFirst = removeFromChain(first, e); table.set(index, newFirst); return true; } return false; } } return false; } finally { unlock(); } } @GuardedBy("this") boolean removeEntryForTesting(E entry) { int hash = entry.getHash(); int newCount = this.count - 1; AtomicReferenceArray table = this.table; int index = hash & (table.length() - 1); E first = table.get(index); for (E e = first; e != null; e = e.getNext()) { if (e == entry) { ++modCount; E newFirst = removeFromChain(first, e); newCount = this.count - 1; table.set(index, newFirst); this.count = newCount; // write-volatile return true; } } return false; } /** * Returns {@code true} if the value has been partially collected, meaning that the value is * null. */ static > boolean isCollected(E entry) { return entry.getValue() == null; } /** * Gets the value from an entry. Returns {@code null} if the entry is invalid or * partially-collected. */ @Nullable V getLiveValue(E entry) { if (entry.getKey() == null) { tryDrainReferenceQueues(); return null; } V value = entry.getValue(); if (value == null) { tryDrainReferenceQueues(); return null; } return value; } /** * Performs routine cleanup following a read. Normally cleanup happens during writes, or from * the cleanupExecutor. If cleanup is not observed after a sufficient number of reads, try * cleaning up from the read thread. */ void postReadCleanup() { if ((readCount.incrementAndGet() & DRAIN_THRESHOLD) == 0) { runCleanup(); } } /** * Performs routine cleanup prior to executing a write. This should be called every time a * write thread acquires the segment lock, immediately after acquiring the lock. */ @GuardedBy("this") void preWriteCleanup() { runLockedCleanup(); } void runCleanup() { runLockedCleanup(); } void runLockedCleanup() { if (tryLock()) { try { maybeDrainReferenceQueues(); readCount.set(0); } finally { unlock(); } } } } /** Concrete implementation of {@link Segment} for strong keys and strong values. */ static final class StrongKeyStrongValueSegment extends Segment, StrongKeyStrongValueSegment> { /** * @param map * @param initialCapacity * @param maxSegmentSize */ StrongKeyStrongValueSegment( MapMakerInternalMap< K, V, StrongKeyStrongValueEntry, StrongKeyStrongValueSegment> map, int initialCapacity, int maxSegmentSize) { super(map, initialCapacity, maxSegmentSize); } @Override StrongKeyStrongValueSegment self() { return this; } @SuppressWarnings("unchecked") @Override public StrongKeyStrongValueEntry castForTesting(InternalEntry entry) { return (StrongKeyStrongValueEntry) entry; } } /** Concrete implementation of {@link Segment} for strong keys and weak values. */ static final class StrongKeyWeakValueSegment extends Segment, StrongKeyWeakValueSegment> { private final ReferenceQueue queueForValues = new ReferenceQueue(); /** * @param map * @param initialCapacity * @param maxSegmentSize */ StrongKeyWeakValueSegment( MapMakerInternalMap, StrongKeyWeakValueSegment> map, int initialCapacity, int maxSegmentSize) { super(map, initialCapacity, maxSegmentSize); } @Override StrongKeyWeakValueSegment self() { return this; } @Override ReferenceQueue getValueReferenceQueueForTesting() { return queueForValues; } @SuppressWarnings("unchecked") @Override public StrongKeyWeakValueEntry castForTesting(InternalEntry entry) { return (StrongKeyWeakValueEntry) entry; } @Override public WeakValueReference> getWeakValueReferenceForTesting( InternalEntry e) { return castForTesting(e).getValueReference(); } @Override public WeakValueReference> newWeakValueReferenceForTesting( InternalEntry e, V value) { return new WeakValueReferenceImpl>( queueForValues, value, castForTesting(e)); } @Override public void setWeakValueReferenceForTesting( InternalEntry e, WeakValueReference> valueReference) { StrongKeyWeakValueEntry entry = castForTesting(e); @SuppressWarnings("unchecked") WeakValueReference> newValueReference = (WeakValueReference>) valueReference; WeakValueReference> previous = entry.valueReference; entry.valueReference = newValueReference; previous.clear(); } @Override void maybeDrainReferenceQueues() { drainValueReferenceQueue(queueForValues); } @Override void maybeClearReferenceQueues() { clearReferenceQueue(queueForValues); } } /** Concrete implementation of {@link Segment} for weak keys and strong values. */ static final class WeakKeyStrongValueSegment extends Segment, WeakKeyStrongValueSegment> { private final ReferenceQueue queueForKeys = new ReferenceQueue(); /** * @param map * @param initialCapacity * @param maxSegmentSize */ WeakKeyStrongValueSegment( MapMakerInternalMap, WeakKeyStrongValueSegment> map, int initialCapacity, int maxSegmentSize) { super(map, initialCapacity, maxSegmentSize); } @Override WeakKeyStrongValueSegment self() { return this; } @Override ReferenceQueue getKeyReferenceQueueForTesting() { return queueForKeys; } @SuppressWarnings("unchecked") @Override public WeakKeyStrongValueEntry castForTesting(InternalEntry entry) { return (WeakKeyStrongValueEntry) entry; } @Override void maybeDrainReferenceQueues() { drainKeyReferenceQueue(queueForKeys); } @Override void maybeClearReferenceQueues() { clearReferenceQueue(queueForKeys); } } /** Concrete implementation of {@link Segment} for weak keys and weak values. */ static final class WeakKeyWeakValueSegment extends Segment, WeakKeyWeakValueSegment> { private final ReferenceQueue queueForKeys = new ReferenceQueue(); private final ReferenceQueue queueForValues = new ReferenceQueue(); /** * @param map * @param initialCapacity * @param maxSegmentSize */ WeakKeyWeakValueSegment( MapMakerInternalMap, WeakKeyWeakValueSegment> map, int initialCapacity, int maxSegmentSize) { super(map, initialCapacity, maxSegmentSize); } @Override WeakKeyWeakValueSegment self() { return this; } @Override ReferenceQueue getKeyReferenceQueueForTesting() { return queueForKeys; } @Override ReferenceQueue getValueReferenceQueueForTesting() { return queueForValues; } @SuppressWarnings("unchecked") @Override public WeakKeyWeakValueEntry castForTesting(InternalEntry entry) { return (WeakKeyWeakValueEntry) entry; } @Override public WeakValueReference> getWeakValueReferenceForTesting( InternalEntry e) { return (castForTesting(e)).getValueReference(); } @Override public WeakValueReference> newWeakValueReferenceForTesting( InternalEntry e, V value) { return new WeakValueReferenceImpl>( queueForValues, value, castForTesting(e)); } @Override public void setWeakValueReferenceForTesting( InternalEntry e, WeakValueReference> valueReference) { WeakKeyWeakValueEntry entry = castForTesting(e); @SuppressWarnings("unchecked") WeakValueReference> newValueReference = (WeakValueReference>) valueReference; WeakValueReference> previous = entry.valueReference; entry.valueReference = newValueReference; previous.clear(); } @Override void maybeDrainReferenceQueues() { drainKeyReferenceQueue(queueForKeys); drainValueReferenceQueue(queueForValues); } @Override void maybeClearReferenceQueues() { clearReferenceQueue(queueForKeys); } } static final class CleanupMapTask implements Runnable { final WeakReference> mapReference; public CleanupMapTask(MapMakerInternalMap map) { this.mapReference = new WeakReference>(map); } @Override public void run() { MapMakerInternalMap map = mapReference.get(); if (map == null) { throw new CancellationException(); } for (Segment segment : map.segments) { segment.runCleanup(); } } } @VisibleForTesting Strength keyStrength() { return entryHelper.keyStrength(); } @VisibleForTesting Strength valueStrength() { return entryHelper.valueStrength(); } @VisibleForTesting Equivalence valueEquivalence() { return entryHelper.valueStrength().defaultEquivalence(); } // ConcurrentMap methods @Override public boolean isEmpty() { /* * Sum per-segment modCounts to avoid mis-reporting when elements are concurrently added and * removed in one segment while checking another, in which case the table was never actually * empty at any point. (The sum ensures accuracy up through at least 1<<31 per-segment * modifications before recheck.) Method containsValue() uses similar constructions for * stability checks. */ long sum = 0L; Segment[] 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(@Nullable Object key) { if (key == null) { return null; } int hash = hash(key); return segmentFor(hash).get(key, hash); } /** * Returns the internal entry for the specified key. The entry may be nl.topicus.jdbc.shaded.com.uting or partially * collected. Does not impact recency ordering. */ E getEntry(@Nullable Object key) { if (key == null) { return null; } int hash = hash(key); return segmentFor(hash).getEntry(key, hash); } @Override public boolean containsKey(@Nullable Object key) { if (key == null) { return false; } int hash = hash(key); return segmentFor(hash).containsKey(key, hash); } @Override public boolean containsValue(@Nullable 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 nl.topicus.jdbc.shaded.com.leted write int unused = segment.count; // read-volatile AtomicReferenceArray table = segment.table; for (int j = 0; j < table.length(); j++) { for (E 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; } @CanIgnoreReturnValue @Override public V put(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); return segmentFor(hash).put(key, hash, value, false); } @CanIgnoreReturnValue @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 m) { for (Entry e : m.entrySet()) { put(e.getKey(), e.getValue()); } } @CanIgnoreReturnValue @Override public V remove(@Nullable Object key) { if (key == null) { return null; } int hash = hash(key); return segmentFor(hash).remove(key, hash); } @CanIgnoreReturnValue @Override public boolean remove(@Nullable Object key, @Nullable Object value) { if (key == null || value == null) { return false; } int hash = hash(key); return segmentFor(hash).remove(key, hash, value); } @CanIgnoreReturnValue @Override public boolean replace(K key, @Nullable V oldValue, V newValue) { checkNotNull(key); checkNotNull(newValue); if (oldValue == null) { return false; } int hash = hash(key); return segmentFor(hash).replace(key, hash, oldValue, newValue); } @CanIgnoreReturnValue @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(); } } transient Set keySet; @Override public Set keySet() { Set ks = keySet; return (ks != null) ? ks : (keySet = new KeySet()); } transient Collection values; @Override public Collection values() { Collection vs = values; return (vs != null) ? vs : (values = new Values()); } transient Set> entrySet; @Override public Set> entrySet() { Set> es = entrySet; return (es != null) ? es : (entrySet = new EntrySet()); } // Iterator Support abstract class HashIterator implements Iterator { int nextSegmentIndex; int nextTableIndex; Segment currentSegment; AtomicReferenceArray currentTable; E nextEntry; WriteThroughEntry nextExternal; WriteThroughEntry lastReturned; HashIterator() { nextSegmentIndex = segments.length - 1; nextTableIndex = -1; advance(); } @Override public abstract T next(); final void advance() { nextExternal = null; if (nextInChain()) { return; } if (nextInTable()) { return; } while (nextSegmentIndex >= 0) { currentSegment = segments[nextSegmentIndex--]; if (currentSegment.count != 0) { currentTable = currentSegment.table; nextTableIndex = currentTable.length() - 1; if (nextInTable()) { return; } } } } /** * Finds the next entry in the current chain. Returns {@code true} if an entry was found. */ boolean nextInChain() { if (nextEntry != null) { for (nextEntry = nextEntry.getNext(); nextEntry != null; nextEntry = nextEntry.getNext()) { if (advanceTo(nextEntry)) { return true; } } } return false; } /** * Finds the next entry in the current table. Returns {@code true} if an entry was found. */ boolean nextInTable() { while (nextTableIndex >= 0) { if ((nextEntry = currentTable.get(nextTableIndex--)) != null) { if (advanceTo(nextEntry) || nextInChain()) { return true; } } } return false; } /** * Advances to the given entry. Returns {@code true} if the entry was valid, {@code false} if it * should be skipped. */ boolean advanceTo(E entry) { try { K key = entry.getKey(); V value = getLiveValue(entry); if (value != null) { nextExternal = new WriteThroughEntry(key, value); return true; } else { // Skip stale entry. return false; } } finally { currentSegment.postReadCleanup(); } } @Override public boolean hasNext() { return nextExternal != null; } WriteThroughEntry nextEntry() { if (nextExternal == null) { throw new NoSuchElementException(); } lastReturned = nextExternal; advance(); return lastReturned; } @Override public void remove() { checkRemove(lastReturned != null); MapMakerInternalMap.this.remove(lastReturned.getKey()); lastReturned = null; } } final class KeyIterator extends HashIterator { @Override public K next() { return nextEntry().getKey(); } } final class ValueIterator extends HashIterator { @Override public V next() { return nextEntry().getValue(); } } /** * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the * underlying map. */ final class WriteThroughEntry extends AbstractMapEntry { final K key; // non-null V value; // non-null WriteThroughEntry(K key, V value) { this.key = key; this.value = value; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } @Override public boolean equals(@Nullable Object object) { // Cannot use key and value equivalence if (object instanceof Entry) { Entry that = (Entry) object; return key.equals(that.getKey()) && value.equals(that.getValue()); } return false; } @Override public int hashCode() { // Cannot use key and value equivalence return key.hashCode() ^ value.hashCode(); } @Override public V setValue(V newValue) { V oldValue = put(key, newValue); value = newValue; // only if put succeeds return oldValue; } } final class EntryIterator extends HashIterator> { @Override public Entry next() { return nextEntry(); } } @WeakOuter final class KeySet extends SafeToArraySet { @Override public Iterator iterator() { return new KeyIterator(); } @Override public int size() { return MapMakerInternalMap.this.size(); } @Override public boolean isEmpty() { return MapMakerInternalMap.this.isEmpty(); } @Override public boolean contains(Object o) { return MapMakerInternalMap.this.containsKey(o); } @Override public boolean remove(Object o) { return MapMakerInternalMap.this.remove(o) != null; } @Override public void clear() { MapMakerInternalMap.this.clear(); } } @WeakOuter final class Values extends AbstractCollection { @Override public Iterator iterator() { return new ValueIterator(); } @Override public int size() { return MapMakerInternalMap.this.size(); } @Override public boolean isEmpty() { return MapMakerInternalMap.this.isEmpty(); } @Override public boolean contains(Object o) { return MapMakerInternalMap.this.containsValue(o); } @Override public void clear() { MapMakerInternalMap.this.clear(); } // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. // https://code.google.nl.topicus.jdbc.shaded.com.p/android/issues/detail?id=36519 / http://r.android.nl.topicus.jdbc.shaded.com.47508 @Override public Object[] toArray() { return toArrayList(this).toArray(); } @Override public E[] toArray(E[] a) { return toArrayList(this).toArray(a); } } @WeakOuter final class EntrySet extends SafeToArraySet> { @Override public Iterator> iterator() { return new EntryIterator(); } @Override public boolean contains(Object o) { if (!(o instanceof Entry)) { return false; } Entry e = (Entry) o; Object key = e.getKey(); if (key == null) { return false; } V v = MapMakerInternalMap.this.get(key); return v != null && valueEquivalence().equivalent(e.getValue(), v); } @Override public boolean remove(Object o) { if (!(o instanceof Entry)) { return false; } Entry e = (Entry) o; Object key = e.getKey(); return key != null && MapMakerInternalMap.this.remove(key, e.getValue()); } @Override public int size() { return MapMakerInternalMap.this.size(); } @Override public boolean isEmpty() { return MapMakerInternalMap.this.isEmpty(); } @Override public void clear() { MapMakerInternalMap.this.clear(); } } private abstract static class SafeToArraySet extends AbstractSet { // super.toArray() may misbehave if size() is inaccurate, at least on old versions of Android. // https://code.google.nl.topicus.jdbc.shaded.com.p/android/issues/detail?id=36519 / http://r.android.nl.topicus.jdbc.shaded.com.47508 @Override public Object[] toArray() { return toArrayList(this).toArray(); } @Override public E[] toArray(E[] a) { return toArrayList(this).toArray(a); } } private static ArrayList toArrayList(Collection c) { // Avoid calling ArrayList(Collection), which may call back into toArray. ArrayList result = new ArrayList(c.size()); Iterators.addAll(result, c.iterator()); return result; } // Serialization Support private static final long serialVersionUID = 5; Object writeReplace() { return new SerializationProxy( entryHelper.keyStrength(), entryHelper.valueStrength(), keyEquivalence, entryHelper.valueStrength().defaultEquivalence(), concurrencyLevel, this); } /** * The actual object that gets serialized. Unfortunately, readResolve() doesn't get called when a * circular dependency is present, so the proxy must be able to behave as the map itself. */ abstract static class AbstractSerializationProxy extends ForwardingConcurrentMap implements Serializable { private static final long serialVersionUID = 3; final Strength keyStrength; final Strength valueStrength; final Equivalence keyEquivalence; final Equivalence valueEquivalence; final int concurrencyLevel; transient ConcurrentMap delegate; AbstractSerializationProxy( Strength keyStrength, Strength valueStrength, Equivalence keyEquivalence, Equivalence valueEquivalence, int concurrencyLevel, ConcurrentMap delegate) { this.keyStrength = keyStrength; this.valueStrength = valueStrength; this.keyEquivalence = keyEquivalence; this.valueEquivalence = valueEquivalence; this.concurrencyLevel = concurrencyLevel; this.delegate = delegate; } @Override protected ConcurrentMap delegate() { return delegate; } void writeMapTo(ObjectOutputStream out) throws IOException { out.writeInt(delegate.size()); for (Entry entry : delegate.entrySet()) { out.writeObject(entry.getKey()); out.writeObject(entry.getValue()); } out.writeObject(null); // terminate entries } @SuppressWarnings("deprecation") // serialization of deprecated feature MapMaker readMapMaker(ObjectInputStream in) throws IOException { int size = in.readInt(); return new MapMaker() .initialCapacity(size) .setKeyStrength(keyStrength) .setValueStrength(valueStrength) .keyEquivalence(keyEquivalence) .concurrencyLevel(concurrencyLevel); } @SuppressWarnings("unchecked") void readEntries(ObjectInputStream in) throws IOException, ClassNotFoundException { while (true) { K key = (K) in.readObject(); if (key == null) { break; // terminator } V value = (V) in.readObject(); delegate.put(key, value); } } } /** * The actual object that gets serialized. Unfortunately, readResolve() doesn't get called when a * circular dependency is present, so the proxy must be able to behave as the map itself. */ private static final class SerializationProxy extends AbstractSerializationProxy { private static final long serialVersionUID = 3; SerializationProxy( Strength keyStrength, Strength valueStrength, Equivalence keyEquivalence, Equivalence valueEquivalence, int concurrencyLevel, ConcurrentMap delegate) { super( keyStrength, valueStrength, keyEquivalence, valueEquivalence, concurrencyLevel, delegate); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); writeMapTo(out); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); MapMaker mapMaker = readMapMaker(in); delegate = mapMaker.makeMap(); readEntries(in); } private Object readResolve() { return delegate; } } }