diff --git a/internal-api/src/main/java/datadog/trace/util/Hashtable.java b/internal-api/src/main/java/datadog/trace/util/Hashtable.java
index cc7bc4eddf5..32351cbf208 100644
--- a/internal-api/src/main/java/datadog/trace/util/Hashtable.java
+++ b/internal-api/src/main/java/datadog/trace/util/Hashtable.java
@@ -8,6 +8,8 @@
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
/**
* Light weight simple Hashtable system that can be useful when HashMap would be unnecessarily
@@ -23,10 +25,14 @@
* Convenience classes are provided for lower key dimensions.
*
*
For higher key dimensions, client code must implement its own class, but can still use the
- * support class to ease the implementation complexity.
+ * static building blocks on this class to ease the implementation complexity.
*
*
This outer class is a pure namespace -- it can't be instantiated. The actual table types are
- * {@link D1}, {@link D2}, and (for higher-arity callers) {@link Support}-driven custom tables.
+ * {@link D1}, {@link D2}, and (for higher-arity callers) custom tables driven by the static
+ * building blocks on this class (see {@link #createFixedBuckets(Class, int)}, {@link
+ * #bucket(Hashtable.Entry[], long)}, {@link #insertHeadEntry(Hashtable.Entry[], int,
+ * Hashtable.Entry)}, and friends). The deprecated {@link Support} class is a thin facade over those
+ * same statics, retained for source compatibility.
*/
public final class Hashtable {
private Hashtable() {}
@@ -37,7 +43,8 @@ private Hashtable() {}
*
*
Subclasses add the actual key field(s) and a {@code matches(...)} method tailored to their
* key arity. See {@link D1.Entry} and {@link D2.Entry}; for higher arities, client code can
- * subclass this directly and use {@link Support} to drive the table mechanics.
+ * subclass this directly and drive the table with the static building blocks on {@link
+ * Hashtable}.
*/
public abstract static class Entry {
public final long keyHash;
@@ -47,11 +54,12 @@ protected Entry(long keyHash) {
this.keyHash = keyHash;
}
- public final void setNext(TEntry next) {
+ public final void setNext(@Nullable TEntry next) {
this.next = next;
}
@SuppressWarnings("unchecked")
+ @Nullable
public final TEntry next() {
return (TEntry) this.next;
}
@@ -94,17 +102,18 @@ public static final class D1> {
public abstract static class Entry extends Hashtable.Entry {
final K key;
- protected Entry(K key) {
+ protected Entry(@Nullable K key) {
super(hash(key));
this.key = key;
}
/** The key this entry was created with. */
+ @Nullable
public K key() {
return this.key;
}
- public boolean matches(Object key) {
+ public boolean matches(@Nullable Object key) {
return Objects.equals(this.key, key);
}
@@ -116,40 +125,57 @@ public boolean matches(Object key) {
* [Integer.MIN_VALUE, Integer.MAX_VALUE]}; real-key collisions in chains are resolved by
* {@link #matches(Object)}.
*/
- public static long hash(Object key) {
+ public static long hash(@Nullable Object key) {
return (key == null) ? Long.MIN_VALUE : key.hashCode();
}
}
- // Package-private so iterator tests in the same package can drive Support.bucketIterator and
- // friends directly against the table's bucket array.
+ // Package-private so iterator tests in the same package can drive the Hashtable static
+ // building blocks directly against the table's bucket array.
final Hashtable.Entry[] buckets;
private int size;
public D1(int capacity) {
- this.buckets = Support.create(capacity);
+ this.buckets = new Hashtable.Entry[sizeFor(capacity)];
this.size = 0;
}
+ /**
+ * Creates a single-key table with a fixed bucket count sized for {@code capacity} entries. The
+ * {@code entryClass} pins the concrete entry type so the compiler infers both {@code K} and
+ * {@code TEntry} at the call site -- e.g. {@code D1.createFixedBuckets(MyEntry.class, 64)} --
+ * keeping the factory symmetric with the rest of the flat-collections family (see {@link
+ * Hashtable#createFixedBuckets(Class, int)} for why the class isn't otherwise consumed).
+ * Capacity is fixed; the table does not resize.
+ */
+ @Nonnull
+ public static > D1 createFixedBuckets(
+ @Nonnull Class entryClass, int capacity) {
+ return new D1<>(capacity);
+ }
+
public int size() {
return this.size;
}
- public TEntry get(K key) {
+ @Nullable
+ public TEntry get(@Nullable K key) {
long keyHash = D1.Entry.hash(key);
- for (TEntry te = Support.bucket(this.buckets, keyHash); te != null; te = te.next()) {
- if (te.keyHash == keyHash && te.matches(key)) {
- return te;
+ for (TEntry curEntry = bucket(this.buckets, keyHash);
+ curEntry != null;
+ curEntry = curEntry.next()) {
+ if (curEntry.keyHash == keyHash && curEntry.matches(key)) {
+ return curEntry;
}
}
return null;
}
- public TEntry remove(K key) {
+ @Nullable
+ public TEntry remove(@Nullable K key) {
long keyHash = D1.Entry.hash(key);
- for (MutatingBucketIterator iter =
- Support.mutatingBucketIterator(this.buckets, keyHash);
+ for (MutatingBucketIterator iter = mutatingBucketIterator(this.buckets, keyHash);
iter.hasNext(); ) {
TEntry curEntry = iter.next();
@@ -163,14 +189,15 @@ public TEntry remove(K key) {
return null;
}
- public void insert(TEntry newEntry) {
- Support.insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
+ public void insert(@Nonnull TEntry newEntry) {
+ insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
this.size += 1;
}
- public TEntry insertOrReplace(TEntry newEntry) {
+ @Nullable
+ public TEntry insertOrReplace(@Nonnull TEntry newEntry) {
for (MutatingBucketIterator iter =
- Support.mutatingBucketIterator(this.buckets, newEntry.keyHash);
+ mutatingBucketIterator(this.buckets, newEntry.keyHash);
iter.hasNext(); ) {
TEntry curEntry = iter.next();
@@ -180,7 +207,7 @@ public TEntry insertOrReplace(TEntry newEntry) {
}
}
- Support.insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
+ insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
this.size += 1;
return null;
}
@@ -195,26 +222,30 @@ public TEntry insertOrReplace(TEntry newEntry) {
* that calls {@code super(key)}. A mismatched hash will leave the new entry inserted at a
* bucket that future {@link #get} calls won't probe.
*/
- public TEntry getOrCreate(K key, Function super K, ? extends TEntry> creator) {
+ @Nonnull
+ public TEntry getOrCreate(
+ @Nullable K key, @Nonnull Function super K, ? extends TEntry> creator) {
long keyHash = D1.Entry.hash(key);
- for (TEntry te = Support.bucket(this.buckets, keyHash); te != null; te = te.next()) {
- if (te.keyHash == keyHash && te.matches(key)) {
- return te;
+ for (TEntry curEntry = bucket(this.buckets, keyHash);
+ curEntry != null;
+ curEntry = curEntry.next()) {
+ if (curEntry.keyHash == keyHash && curEntry.matches(key)) {
+ return curEntry;
}
}
TEntry newEntry = creator.apply(key);
- Support.insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
+ insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
this.size += 1;
return newEntry;
}
public void clear() {
- Support.clear(this.buckets);
+ Hashtable.clear(this.buckets);
this.size = 0;
}
- public void forEach(Consumer super TEntry> consumer) {
- Support.forEach(this.buckets, consumer);
+ public void forEach(@Nonnull Consumer super TEntry> consumer) {
+ Hashtable.forEach(this.buckets, consumer);
}
/**
@@ -222,8 +253,8 @@ public void forEach(Consumer super TEntry> consumer) {
* -- pass a non-capturing {@link BiConsumer} (typically a {@code static final}) plus whatever
* side-band state it needs as {@code context}.
*/
- public void forEach(T context, BiConsumer super T, ? super TEntry> consumer) {
- Support.forEach(this.buckets, context, consumer);
+ public void forEach(C context, @Nonnull BiConsumer super C, ? super TEntry> consumer) {
+ Hashtable.forEach(this.buckets, context, consumer);
}
}
@@ -264,13 +295,25 @@ public abstract static class Entry extends Hashtable.Entry {
final K1 key1;
final K2 key2;
- protected Entry(K1 key1, K2 key2) {
+ protected Entry(@Nullable K1 key1, @Nullable K2 key2) {
super(hash(key1, key2));
this.key1 = key1;
this.key2 = key2;
}
- public boolean matches(K1 key1, K2 key2) {
+ /** The first key part this entry was created with. */
+ @Nullable
+ public K1 key1() {
+ return this.key1;
+ }
+
+ /** The second key part this entry was created with. */
+ @Nullable
+ public K2 key2() {
+ return this.key2;
+ }
+
+ public boolean matches(@Nullable K1 key1, @Nullable K2 key2) {
return Objects.equals(this.key1, key1) && Objects.equals(this.key2, key2);
}
@@ -281,7 +324,7 @@ public boolean matches(K1 key1, K2 key2) {
* combinations whose chained hash equals {@code hash(0, 0) = 0} or similar values. {@link
* #matches(Object, Object)} resolves any such collision.
*/
- public static long hash(Object key1, Object key2) {
+ public static long hash(@Nullable Object key1, @Nullable Object key2) {
return LongHashingUtils.hash(key1, key2);
}
}
@@ -291,29 +334,46 @@ public static long hash(Object key1, Object key2) {
private int size;
public D2(int capacity) {
- this.buckets = Support.create(capacity);
+ this.buckets = new Hashtable.Entry[sizeFor(capacity)];
this.size = 0;
}
+ /**
+ * Creates a composite-key table with a fixed bucket count sized for {@code capacity} entries.
+ * The {@code entryClass} pins the concrete entry type so the compiler infers {@code K1}, {@code
+ * K2}, and {@code TEntry} at the call site -- e.g. {@code D2.createFixedBuckets(MyEntry.class,
+ * 64)} -- keeping the factory symmetric with the rest of the flat-collections family (see
+ * {@link Hashtable#createFixedBuckets(Class, int)} for why the class isn't otherwise consumed).
+ * Capacity is fixed; the table does not resize.
+ */
+ @Nonnull
+ public static > D2 createFixedBuckets(
+ @Nonnull Class entryClass, int capacity) {
+ return new D2<>(capacity);
+ }
+
public int size() {
return this.size;
}
- public TEntry get(K1 key1, K2 key2) {
+ @Nullable
+ public TEntry get(@Nullable K1 key1, @Nullable K2 key2) {
long keyHash = D2.Entry.hash(key1, key2);
- for (TEntry te = Support.bucket(this.buckets, keyHash); te != null; te = te.next()) {
- if (te.keyHash == keyHash && te.matches(key1, key2)) {
- return te;
+ for (TEntry curEntry = bucket(this.buckets, keyHash);
+ curEntry != null;
+ curEntry = curEntry.next()) {
+ if (curEntry.keyHash == keyHash && curEntry.matches(key1, key2)) {
+ return curEntry;
}
}
return null;
}
- public TEntry remove(K1 key1, K2 key2) {
+ @Nullable
+ public TEntry remove(@Nullable K1 key1, @Nullable K2 key2) {
long keyHash = D2.Entry.hash(key1, key2);
- for (MutatingBucketIterator iter =
- Support.mutatingBucketIterator(this.buckets, keyHash);
+ for (MutatingBucketIterator iter = mutatingBucketIterator(this.buckets, keyHash);
iter.hasNext(); ) {
TEntry curEntry = iter.next();
@@ -327,14 +387,15 @@ public TEntry remove(K1 key1, K2 key2) {
return null;
}
- public void insert(TEntry newEntry) {
- Support.insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
+ public void insert(@Nonnull TEntry newEntry) {
+ insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
this.size += 1;
}
- public TEntry insertOrReplace(TEntry newEntry) {
+ @Nullable
+ public TEntry insertOrReplace(@Nonnull TEntry newEntry) {
for (MutatingBucketIterator iter =
- Support.mutatingBucketIterator(this.buckets, newEntry.keyHash);
+ mutatingBucketIterator(this.buckets, newEntry.keyHash);
iter.hasNext(); ) {
TEntry curEntry = iter.next();
@@ -344,7 +405,7 @@ public TEntry insertOrReplace(TEntry newEntry) {
}
}
- Support.insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
+ insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
this.size += 1;
return null;
}
@@ -354,27 +415,32 @@ public TEntry insertOrReplace(TEntry newEntry) {
* both lookup and (on miss) insert. The {@code creator} is expected to build an entry whose
* {@code keyHash} equals {@link Entry#hash(Object, Object) D2.Entry.hash(key1, key2)}.
*/
+ @Nonnull
public TEntry getOrCreate(
- K1 key1, K2 key2, BiFunction super K1, ? super K2, ? extends TEntry> creator) {
+ @Nullable K1 key1,
+ @Nullable K2 key2,
+ @Nonnull BiFunction super K1, ? super K2, ? extends TEntry> creator) {
long keyHash = D2.Entry.hash(key1, key2);
- for (TEntry te = Support.bucket(this.buckets, keyHash); te != null; te = te.next()) {
- if (te.keyHash == keyHash && te.matches(key1, key2)) {
- return te;
+ for (TEntry curEntry = bucket(this.buckets, keyHash);
+ curEntry != null;
+ curEntry = curEntry.next()) {
+ if (curEntry.keyHash == keyHash && curEntry.matches(key1, key2)) {
+ return curEntry;
}
}
TEntry newEntry = creator.apply(key1, key2);
- Support.insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
+ insertHeadEntry(this.buckets, newEntry.keyHash, newEntry);
this.size += 1;
return newEntry;
}
public void clear() {
- Support.clear(this.buckets);
+ Hashtable.clear(this.buckets);
this.size = 0;
}
- public void forEach(Consumer super TEntry> consumer) {
- Support.forEach(this.buckets, consumer);
+ public void forEach(@Nonnull Consumer super TEntry> consumer) {
+ Hashtable.forEach(this.buckets, consumer);
}
/**
@@ -382,195 +448,352 @@ public void forEach(Consumer super TEntry> consumer) {
* -- pass a non-capturing {@link BiConsumer} (typically a {@code static final}) plus whatever
* side-band state it needs as {@code context}.
*/
- public void forEach(T context, BiConsumer super T, ? super TEntry> consumer) {
- Support.forEach(this.buckets, context, consumer);
+ public void forEach(C context, @Nonnull BiConsumer super C, ? super TEntry> consumer) {
+ Hashtable.forEach(this.buckets, context, consumer);
}
}
+ // ============================================================================================
+ // Static building blocks over a caller-owned bucket array.
+ //
+ // Use these to assemble a custom table (higher arity, primitive keys, extra value fields) when
+ // D1/D2 don't fit; D1/D2 delegate to them internally. This is the same "static functions over a
+ // caller-owned array" shape as the concurrent variant (ConcurrentHashtable); see how
+ // AggregateTable drives a Hashtable.Entry[] with these. The calling class owns the array and
+ // exposes whatever operations it needs.
+ //
+ // Not thread-safe: there is no locking here. Concurrent access, including mixing reads with
+ // writes, requires external synchronization.
+ //
+ // These were previously nested under the Support class; that class is now a deprecated facade
+ // delegating here (retained for source compatibility with existing callers such as client-side
+ // statistics).
+ // ============================================================================================
+
+ /** Upper bound on the bucket-array length returned by {@link #sizeFor(int)}. */
+ static final int MAX_BUCKETS = 1 << 30;
+
/**
- * Building blocks for hash-table operations.
+ * Allocates a fixed-size bucket array sized to hold {@code capacity} entries: {@code capacity}
+ * rounded up to the next power of two.
*
- * Used by {@link D1} and {@link D2}, and available to callers that want to assemble their own
- * higher-arity table (3+ key parts) without re-implementing the bucket-array mechanics. The
- * typical recipe:
+ *
Returns a concrete {@code Hashtable.Entry[]} (chain heads are stored at the base type), so
+ * the array assigns directly to a caller's {@code Hashtable.Entry[]} field. As with the
+ * concurrent variant's {@code createFixedBuckets}, {@code entryClass} is not consumed to
+ * allocate -- the array is a heterogeneous {@code Entry[]}, not a reflectively-allocated {@code
+ * TEntry[]}. It is accepted only to keep the factory call-shape symmetric across the
+ * flat-collections family ({@code createFixedBuckets(MyEntry.class, n)}). Capacity is fixed; the
+ * table does not resize.
*
- *
- * - Subclass {@link Hashtable.Entry} directly, adding the key fields and a {@code
- * matches(...)} method of your chosen arity.
- *
- Allocate a backing array with {@link #create(int)} or {@link #create(int, float)} (the
- * latter scales for a target load factor; see {@link #MAX_RATIO}).
- *
- Use {@link #bucketIndex(Object[], long)} for the bucket lookup, {@link
- * #bucketIterator(Hashtable.Entry[], long)} for read-only chain walks, and {@link
- * #mutatingBucketIterator(Hashtable.Entry[], long)} when you also need {@code remove} /
- * {@code replace}.
- *
- Use {@link #insertHeadEntry(Hashtable.Entry[], int, Hashtable.Entry)} to splice a new
- * entry as the head of a bucket chain.
- *
- Iterate every entry with {@link #forEach(Hashtable.Entry[], Consumer)} or its
- * context-passing sibling. For full-table sweeps with {@code remove}, use {@link
- * #mutatingTableIterator(Hashtable.Entry[])}.
- *
- Clear with {@link #clear(Hashtable.Entry[])}.
- *
+ * For load-factor headroom over a target working-set size, size {@code capacity} yourself
+ * (e.g. {@code createFixedBuckets(MyEntry.class, (int) (n * 4 / 3f))}); the deprecated {@link
+ * Support#create(int, float)} bundled that scaling but has no blessed equivalent.
+ */
+ @Nonnull
+ public static Hashtable.Entry[] createFixedBuckets(
+ @Nonnull Class entryClass, int capacity) {
+ return new Entry[sizeFor(capacity)];
+ }
+
+ /**
+ * Rounds {@code requestedSize} up to the next power of two, capped at {@link #MAX_BUCKETS}, and
+ * returns the bucket-array length to allocate. Throws {@link IllegalArgumentException} for
+ * negative inputs or inputs above the cap. The concurrent variant shares this so the two families
+ * round identically.
+ */
+ public static int sizeFor(int requestedSize) {
+ if (requestedSize < 0) {
+ throw new IllegalArgumentException("requestedSize must be non-negative: " + requestedSize);
+ }
+ if (requestedSize > MAX_BUCKETS) {
+ throw new IllegalArgumentException(
+ "requestedSize exceeds maximum bucket count (" + MAX_BUCKETS + "): " + requestedSize);
+ }
+ if (requestedSize <= 1) {
+ return 1;
+ }
+ return Integer.highestOneBit(requestedSize - 1) << 1;
+ }
+
+ public static int bucketIndex(@Nonnull Object[] buckets, long keyHash) {
+ return (int) (keyHash & buckets.length - 1);
+ }
+
+ /**
+ * Returns the head entry of the bucket that {@code keyHash} maps to, cast to the caller's
+ * concrete entry type. The unchecked cast lives here so the chain-walk loop at the call site
+ * doesn't need to thread a raw {@link Entry} variable through.
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public static TEntry bucket(
+ @Nonnull Hashtable.Entry[] buckets, long keyHash) {
+ return (TEntry) buckets[bucketIndex(buckets, keyHash)];
+ }
+
+ /**
+ * Splices {@code entry} in as the new head of the chain at {@code bucketIndex}. Caller is
+ * responsible for size accounting -- this method only touches the chain pointers.
+ */
+ public static void insertHeadEntry(
+ @Nonnull Hashtable.Entry[] buckets, int bucketIndex, @Nonnull Hashtable.Entry entry) {
+ entry.setNext(buckets[bucketIndex]);
+ buckets[bucketIndex] = entry;
+ }
+
+ /**
+ * Convenience overload of {@link #insertHeadEntry(Hashtable.Entry[], int, Hashtable.Entry)} that
+ * derives the bucket index from {@code keyHash}. Use this when the caller has the hash but not
+ * the index; if the index has already been computed for another reason, prefer the int-taking
+ * overload to avoid the redundant mask.
+ */
+ public static void insertHeadEntry(
+ @Nonnull Hashtable.Entry[] buckets, long keyHash, @Nonnull Hashtable.Entry entry) {
+ insertHeadEntry(buckets, bucketIndex(buckets, keyHash), entry);
+ }
+
+ public static void clear(@Nonnull Hashtable.Entry[] buckets) {
+ Arrays.fill(buckets, null);
+ }
+
+ /**
+ * Walks every entry in {@code buckets} and invokes {@code consumer} on it. The unchecked cast to
+ * {@code TEntry} lives here (mirroring {@link Entry#next()}) so callers don't have to sprinkle it
+ * across their own forEach loops.
+ */
+ @SuppressWarnings("unchecked")
+ public static void forEach(
+ @Nonnull Hashtable.Entry[] buckets, @Nonnull Consumer super TEntry> consumer) {
+ for (int i = 0; i < buckets.length; i++) {
+ for (Hashtable.Entry e = buckets[i]; e != null; e = e.next()) {
+ consumer.accept((TEntry) e);
+ }
+ }
+ }
+
+ /**
+ * Context-passing variant of {@link #forEach(Hashtable.Entry[], Consumer)}. Pair a non-capturing
+ * {@link BiConsumer} (typically a {@code static final}) with side-band state passed as {@code
+ * context} to avoid a fresh-Consumer allocation each call.
+ */
+ @SuppressWarnings("unchecked")
+ public static void forEach(
+ @Nonnull Hashtable.Entry[] buckets,
+ C context,
+ @Nonnull BiConsumer super C, ? super TEntry> consumer) {
+ for (int i = 0; i < buckets.length; i++) {
+ for (Hashtable.Entry e = buckets[i]; e != null; e = e.next()) {
+ consumer.accept(context, (TEntry) e);
+ }
+ }
+ }
+
+ @Nonnull
+ public static BucketIterator bucketIterator(
+ @Nonnull Hashtable.Entry[] buckets, long keyHash) {
+ return new BucketIterator(buckets, keyHash);
+ }
+
+ @Nonnull
+ public static
+ MutatingBucketIterator mutatingBucketIterator(
+ @Nonnull Hashtable.Entry[] buckets, long keyHash) {
+ return new MutatingBucketIterator(buckets, keyHash);
+ }
+
+ /**
+ * Returns a {@link MutatingTableIterator} over every entry in {@code buckets}. Useful for sweeps
+ * -- eviction, expunge -- that aren't keyed to a specific hash.
+ */
+ @Nonnull
+ public static
+ MutatingTableIterator mutatingTableIterator(@Nonnull Hashtable.Entry[] buckets) {
+ return new MutatingTableIterator(buckets, 0, buckets.length);
+ }
+
+ /**
+ * Variant of {@link #mutatingTableIterator(Hashtable.Entry[])} that walks only the half-open
+ * bucket range {@code [startBucket, endBucket)}. Useful for resumable sweeps -- e.g. cursor-based
+ * eviction in {@code AggregateTable} -- where one call drives {@code [cursor, length)} and a
+ * wrap-around call drives {@code [0, cursor)}. The iterator does not wrap around within a
+ * single instance; callers compose two iterators when wrap-around is desired. An empty range
+ * ({@code startBucket == endBucket}) produces an immediately exhausted iterator.
+ *
+ * @param startBucket inclusive lower bound; must be in {@code [0, buckets.length]}.
+ * @param endBucket exclusive upper bound; must be in {@code [startBucket, buckets.length]}.
+ */
+ @Nonnull
+ public static
+ MutatingTableIterator mutatingTableIterator(
+ @Nonnull Hashtable.Entry[] buckets, int startBucket, int endBucket) {
+ return new MutatingTableIterator(buckets, startBucket, endBucket);
+ }
+
+ /**
+ * Deprecated facade over the static building blocks that are now methods on {@link Hashtable}
+ * itself (mirroring the concurrent variant). Each method here delegates to its {@code
+ * Hashtable.*} counterpart; the two sizing helpers with no blessed equivalent -- {@link
+ * #create(int, float)} and {@link #MAX_RATIO} -- keep their real bodies here.
*
- * All bucket arrays produced by {@code create} have a power-of-two length, so {@link
- * #bucketIndex(Object[], long)} can use a bit mask.
+ *
Retained only for source compatibility with existing callers (e.g. client-side statistics).
+ * New code should call the {@code Hashtable.*} statics directly.
+ *
+ * @deprecated use the static building blocks on {@link Hashtable} directly.
*/
+ @Deprecated
public static final class Support {
+ private Support() {}
+
/**
- * Allocates a bucket array sized to hold {@code requestedSize} entries. Returned length is
- * {@code requestedSize} rounded up to the next power of two (capped at {@link #MAX_BUCKETS}).
+ * @deprecated use {@link Hashtable#createFixedBuckets(Class, int)}.
*/
- public static final Hashtable.Entry[] create(int requestedSize) {
+ @Deprecated
+ @Nonnull
+ public static Hashtable.Entry[] create(int requestedSize) {
return new Entry[sizeFor(requestedSize)];
}
/**
- * Variant of {@link #create(int)} that scales the requested working-set size before sizing the
- * bucket array. Pair with {@link #MAX_RATIO} to leave headroom over the working set for a
- * desired load factor; the canonical call is {@code create(n, MAX_RATIO)}.
+ * Scales the requested working-set size before sizing the bucket array. Pair with {@link
+ * #MAX_RATIO} to leave headroom over the working set for a desired load factor; the canonical
+ * call is {@code create(n, MAX_RATIO)}.
+ *
+ *
The scaled size is truncated to {@code int} before going through {@link
+ * Hashtable#sizeFor(int)}. Truncation rather than {@code ceil} is intentional: {@code sizeFor}
+ * rounds up to the next power of two anyway, so the fractional part would only matter when
+ * float fuzz pushes the result across a power-of-two boundary -- {@code ceil} would then double
+ * the array size for no reason (e.g. {@code 12 * 4/3 = 16.0...0005f -> ceil 17 -> sizeFor 32}).
*
- *
The scaled size is truncated to {@code int} before going through {@link #sizeFor(int)}.
- * Truncation rather than {@code ceil} is intentional: {@code sizeFor} rounds up to the next
- * power of two anyway, so the fractional part would only matter when float fuzz pushes the
- * result across a power-of-two boundary -- {@code ceil} would then double the array size for no
- * reason (e.g. {@code 12 * 4/3 = 16.0...0005f -> ceil 17 -> sizeFor 32}).
+ *
No blessed equivalent: callers wanting load-factor headroom size the capacity themselves
+ * and call {@link Hashtable#createFixedBuckets(Class, int)}.
+ *
+ * @deprecated size the capacity yourself and use {@link Hashtable#createFixedBuckets(Class,
+ * int)}.
*/
- public static final Hashtable.Entry[] create(int requestedSize, float scale) {
+ @Deprecated
+ @Nonnull
+ public static Hashtable.Entry[] create(int requestedSize, float scale) {
return new Entry[sizeFor((int) (requestedSize * scale))];
}
- /** Upper bound on the bucket array length returned by {@link #sizeFor(int)}. */
- static final int MAX_BUCKETS = 1 << 30;
-
/**
* Inverse of a 75% load factor. Callers that size their bucket array from a target working-set
* size {@code n} should pass {@code create(n, MAX_RATIO)} to leave ~25% headroom in the array.
*/
- public static final float MAX_RATIO = 4.0f / 3.0f;
+ @Deprecated public static final float MAX_RATIO = 4.0f / 3.0f;
/**
- * Rounds {@code requestedSize} up to the next power of two, capped at {@link #MAX_BUCKETS}.
- * Throws {@link IllegalArgumentException} for negative inputs or inputs above the cap. Returns
- * the bucket-array length to allocate.
+ * @deprecated use {@link Hashtable#sizeFor(int)}.
*/
- static final int sizeFor(int requestedSize) {
- if (requestedSize < 0) {
- throw new IllegalArgumentException("requestedSize must be non-negative: " + requestedSize);
- }
- if (requestedSize > MAX_BUCKETS) {
- throw new IllegalArgumentException(
- "requestedSize exceeds maximum bucket count (" + MAX_BUCKETS + "): " + requestedSize);
- }
- if (requestedSize <= 1) {
- return 1;
- }
- return Integer.highestOneBit(requestedSize - 1) << 1;
+ @Deprecated
+ static int sizeFor(int requestedSize) {
+ return Hashtable.sizeFor(requestedSize);
}
- public static final void clear(Hashtable.Entry[] buckets) {
- Arrays.fill(buckets, null);
+ /**
+ * @deprecated use {@link Hashtable#clear(Hashtable.Entry[])}.
+ */
+ @Deprecated
+ public static void clear(@Nonnull Hashtable.Entry[] buckets) {
+ Hashtable.clear(buckets);
}
- public static final BucketIterator bucketIterator(
- Hashtable.Entry[] buckets, long keyHash) {
- return new BucketIterator(buckets, keyHash);
+ /**
+ * @deprecated use {@link Hashtable#bucketIterator(Hashtable.Entry[], long)}.
+ */
+ @Deprecated
+ @Nonnull
+ public static BucketIterator bucketIterator(
+ @Nonnull Hashtable.Entry[] buckets, long keyHash) {
+ return Hashtable.bucketIterator(buckets, keyHash);
}
- public static final
+ /**
+ * @deprecated use {@link Hashtable#mutatingBucketIterator(Hashtable.Entry[], long)}.
+ */
+ @Deprecated
+ @Nonnull
+ public static
MutatingBucketIterator mutatingBucketIterator(
- Hashtable.Entry[] buckets, long keyHash) {
- return new MutatingBucketIterator(buckets, keyHash);
+ @Nonnull Hashtable.Entry[] buckets, long keyHash) {
+ return Hashtable.mutatingBucketIterator(buckets, keyHash);
}
/**
- * Returns a {@link MutatingTableIterator} over every entry in {@code buckets}. Useful for
- * sweeps -- eviction, expunge -- that aren't keyed to a specific hash.
+ * @deprecated use {@link Hashtable#mutatingTableIterator(Hashtable.Entry[])}.
*/
- public static final
- MutatingTableIterator mutatingTableIterator(Hashtable.Entry[] buckets) {
- return new MutatingTableIterator(buckets, 0, buckets.length);
+ @Deprecated
+ @Nonnull
+ public static
+ MutatingTableIterator mutatingTableIterator(@Nonnull Hashtable.Entry[] buckets) {
+ return Hashtable.mutatingTableIterator(buckets);
}
/**
- * Variant of {@link #mutatingTableIterator(Hashtable.Entry[])} that walks only the half-open
- * bucket range {@code [startBucket, endBucket)}. Useful for resumable sweeps -- e.g. cursor-
- * based eviction in {@code AggregateTable} -- where one call drives {@code [cursor, length)}
- * and a wrap-around call drives {@code [0, cursor)}. The iterator does not wrap around
- * within a single instance; callers compose two iterators when wrap-around is desired. An empty
- * range ({@code startBucket == endBucket}) produces an immediately exhausted iterator.
- *
- * @param startBucket inclusive lower bound; must be in {@code [0, buckets.length]}.
- * @param endBucket exclusive upper bound; must be in {@code [startBucket, buckets.length]}.
+ * @deprecated use {@link Hashtable#mutatingTableIterator(Hashtable.Entry[], int, int)}.
*/
- public static final
+ @Deprecated
+ @Nonnull
+ public static
MutatingTableIterator mutatingTableIterator(
- Hashtable.Entry[] buckets, int startBucket, int endBucket) {
- return new MutatingTableIterator(buckets, startBucket, endBucket);
+ @Nonnull Hashtable.Entry[] buckets, int startBucket, int endBucket) {
+ return Hashtable.mutatingTableIterator(buckets, startBucket, endBucket);
}
- public static final int bucketIndex(Object[] buckets, long keyHash) {
- return (int) (keyHash & buckets.length - 1);
+ /**
+ * @deprecated use {@link Hashtable#bucketIndex(Object[], long)}.
+ */
+ @Deprecated
+ public static int bucketIndex(@Nonnull Object[] buckets, long keyHash) {
+ return Hashtable.bucketIndex(buckets, keyHash);
}
/**
- * Splices {@code entry} in as the new head of the chain at {@code bucketIndex}. Caller is
- * responsible for size accounting -- this method only touches the chain pointers.
+ * @deprecated use {@link Hashtable#insertHeadEntry(Hashtable.Entry[], int, Hashtable.Entry)}.
*/
- public static final void insertHeadEntry(
- Hashtable.Entry[] buckets, int bucketIndex, Hashtable.Entry entry) {
- entry.setNext(buckets[bucketIndex]);
- buckets[bucketIndex] = entry;
+ @Deprecated
+ public static void insertHeadEntry(
+ @Nonnull Hashtable.Entry[] buckets, int bucketIndex, @Nonnull Hashtable.Entry entry) {
+ Hashtable.insertHeadEntry(buckets, bucketIndex, entry);
}
/**
- * Convenience overload of {@link #insertHeadEntry(Hashtable.Entry[], int, Hashtable.Entry)}
- * that derives the bucket index from {@code keyHash}. Use this when the caller has the hash but
- * not the index; if the index has already been computed for another reason, prefer the
- * int-taking overload to avoid the redundant mask.
+ * @deprecated use {@link Hashtable#insertHeadEntry(Hashtable.Entry[], long, Hashtable.Entry)}.
*/
- public static final void insertHeadEntry(
- Hashtable.Entry[] buckets, long keyHash, Hashtable.Entry entry) {
- insertHeadEntry(buckets, bucketIndex(buckets, keyHash), entry);
+ @Deprecated
+ public static void insertHeadEntry(
+ @Nonnull Hashtable.Entry[] buckets, long keyHash, @Nonnull Hashtable.Entry entry) {
+ Hashtable.insertHeadEntry(buckets, keyHash, entry);
}
/**
- * Returns the head entry of the bucket that {@code keyHash} maps to, cast to the caller's
- * concrete entry type. The unchecked cast lives here so the chain-walk loop at the call site
- * doesn't need to thread a raw {@link Entry} variable through.
+ * @deprecated use {@link Hashtable#bucket(Hashtable.Entry[], long)}.
*/
- @SuppressWarnings("unchecked")
- public static final TEntry bucket(
- Hashtable.Entry[] buckets, long keyHash) {
- return (TEntry) buckets[bucketIndex(buckets, keyHash)];
+ @Deprecated
+ @Nullable
+ public static TEntry bucket(
+ @Nonnull Hashtable.Entry[] buckets, long keyHash) {
+ return Hashtable.bucket(buckets, keyHash);
}
/**
- * Walks every entry in {@code buckets} and invokes {@code consumer} on it. The unchecked cast
- * to {@code TEntry} lives here (mirroring {@link Entry#next()}) so callers don't have to
- * sprinkle it across their own forEach loops.
+ * @deprecated use {@link Hashtable#forEach(Hashtable.Entry[], Consumer)}.
*/
- @SuppressWarnings("unchecked")
- public static final void forEach(
- Hashtable.Entry[] buckets, Consumer super TEntry> consumer) {
- for (int i = 0; i < buckets.length; i++) {
- for (Hashtable.Entry e = buckets[i]; e != null; e = e.next()) {
- consumer.accept((TEntry) e);
- }
- }
+ @Deprecated
+ public static void forEach(
+ @Nonnull Hashtable.Entry[] buckets, @Nonnull Consumer super TEntry> consumer) {
+ Hashtable.forEach(buckets, consumer);
}
/**
- * Context-passing variant of {@link #forEach(Hashtable.Entry[], Consumer)}. Pair a
- * non-capturing {@link BiConsumer} (typically a {@code static final}) with side-band state
- * passed as {@code context} to avoid a fresh-Consumer allocation each call.
+ * @deprecated use {@link Hashtable#forEach(Hashtable.Entry[], Object, BiConsumer)}.
*/
- @SuppressWarnings("unchecked")
- public static final void forEach(
- Hashtable.Entry[] buckets, T context, BiConsumer super T, ? super TEntry> consumer) {
- for (int i = 0; i < buckets.length; i++) {
- for (Hashtable.Entry e = buckets[i]; e != null; e = e.next()) {
- consumer.accept(context, (TEntry) e);
- }
- }
+ @Deprecated
+ public static void forEach(
+ @Nonnull Hashtable.Entry[] buckets,
+ C context,
+ @Nonnull BiConsumer super C, ? super TEntry> consumer) {
+ Hashtable.forEach(buckets, context, consumer);
}
}
@@ -589,7 +812,7 @@ public static final class BucketIterator implements Iterat
private final long keyHash;
private Hashtable.Entry nextEntry;
- BucketIterator(Hashtable.Entry[] buckets, long keyHash) {
+ BucketIterator(@Nonnull Hashtable.Entry[] buckets, long keyHash) {
this.keyHash = keyHash;
Hashtable.Entry cur = buckets[Support.bucketIndex(buckets, keyHash)];
while (cur != null && cur.keyHash != keyHash) {
@@ -605,6 +828,7 @@ public boolean hasNext() {
@Override
@SuppressWarnings("unchecked")
+ @Nonnull
public TEntry next() {
Hashtable.Entry cur = this.nextEntry;
if (cur == null) {
@@ -651,7 +875,7 @@ public static final class MutatingBucketIterator
/** The next entry to be returned by next */
private Hashtable.Entry nextEntry;
- MutatingBucketIterator(Hashtable.Entry[] buckets, long keyHash) {
+ MutatingBucketIterator(@Nonnull Hashtable.Entry[] buckets, long keyHash) {
this.buckets = buckets;
this.keyHash = keyHash;
@@ -685,6 +909,7 @@ public boolean hasNext() {
@Override
@SuppressWarnings("unchecked")
+ @Nonnull
public TEntry next() {
Hashtable.Entry curEntry = this.nextEntry;
if (curEntry == null) {
@@ -729,7 +954,7 @@ public void remove() {
this.curEntry = null;
}
- public void replace(TEntry replacementEntry) {
+ public void replace(@Nonnull TEntry replacementEntry) {
Hashtable.Entry oldCurEntry = this.curEntry;
if (oldCurEntry == null) {
throw new IllegalStateException();
@@ -749,7 +974,7 @@ public void replace(TEntry replacementEntry) {
this.curEntry = replacementEntry;
}
- void setPrevNext(Hashtable.Entry nextEntry) {
+ void setPrevNext(@Nullable Hashtable.Entry nextEntry) {
if (this.curPrevEntry == null) {
Hashtable.Entry[] buckets = this.buckets;
buckets[Support.bucketIndex(buckets, this.keyHash)] = nextEntry;
@@ -806,7 +1031,7 @@ public static final class MutatingTableIterator
*/
private Hashtable.Entry curEntry;
- MutatingTableIterator(Hashtable.Entry[] buckets, int startBucket, int endBucket) {
+ MutatingTableIterator(@Nonnull Hashtable.Entry[] buckets, int startBucket, int endBucket) {
this.buckets = buckets;
if (startBucket < 0 || startBucket > buckets.length) {
throw new IndexOutOfBoundsException(
@@ -843,6 +1068,7 @@ public boolean hasNext() {
@Override
@SuppressWarnings("unchecked")
+ @Nonnull
public TEntry next() {
Hashtable.Entry e = this.nextEntry;
if (e == null) {
diff --git a/internal-api/src/test/java/datadog/trace/util/HashtableTest.java b/internal-api/src/test/java/datadog/trace/util/HashtableTest.java
index 953453ca3aa..f566d04ee0d 100644
--- a/internal-api/src/test/java/datadog/trace/util/HashtableTest.java
+++ b/internal-api/src/test/java/datadog/trace/util/HashtableTest.java
@@ -23,16 +23,16 @@
class HashtableTest {
- // ============ Support ============
+ // ============ Static building blocks ============
@Nested
- class SupportTests {
+ class StaticBuildingBlockTests {
@Test
void createRoundsCapacityUpToPowerOfTwo() {
// The Hashtable.D1 / D2 size() reflects entries, but the bucket array length is
// a power of two >= requestedCapacity. We can verify indirectly via bucketIndex masking.
- Hashtable.Entry[] buckets = Support.create(5);
+ Hashtable.Entry[] buckets = Hashtable.createFixedBuckets(StringIntEntry.class, 5);
// Length must be a power of two >= 5
int len = buckets.length;
assertTrue(len >= 5);
@@ -41,51 +41,78 @@ void createRoundsCapacityUpToPowerOfTwo() {
@Test
void sizeForReturnsAtLeastOne() {
- assertEquals(1, Support.sizeFor(0));
- assertEquals(1, Support.sizeFor(1));
+ assertEquals(1, Hashtable.sizeFor(0));
+ assertEquals(1, Hashtable.sizeFor(1));
}
@Test
void sizeForRoundsUpToPowerOfTwo() {
- assertEquals(2, Support.sizeFor(2));
- assertEquals(4, Support.sizeFor(3));
- assertEquals(4, Support.sizeFor(4));
- assertEquals(8, Support.sizeFor(5));
- assertEquals(1 << 30, Support.sizeFor(1 << 30));
+ assertEquals(2, Hashtable.sizeFor(2));
+ assertEquals(4, Hashtable.sizeFor(3));
+ assertEquals(4, Hashtable.sizeFor(4));
+ assertEquals(8, Hashtable.sizeFor(5));
+ assertEquals(1 << 30, Hashtable.sizeFor(1 << 30));
}
@Test
void sizeForRejectsCapacityAboveMax() {
- assertThrows(IllegalArgumentException.class, () -> Support.sizeFor((1 << 30) + 1));
- assertThrows(IllegalArgumentException.class, () -> Support.sizeFor(Integer.MAX_VALUE));
+ assertThrows(IllegalArgumentException.class, () -> Hashtable.sizeFor((1 << 30) + 1));
+ assertThrows(IllegalArgumentException.class, () -> Hashtable.sizeFor(Integer.MAX_VALUE));
}
@Test
void sizeForRejectsNegativeCapacity() {
- assertThrows(IllegalArgumentException.class, () -> Support.sizeFor(-1));
- assertThrows(IllegalArgumentException.class, () -> Support.sizeFor(Integer.MIN_VALUE));
+ assertThrows(IllegalArgumentException.class, () -> Hashtable.sizeFor(-1));
+ assertThrows(IllegalArgumentException.class, () -> Hashtable.sizeFor(Integer.MIN_VALUE));
}
@Test
void bucketIndexIsBoundedByArrayLength() {
- Hashtable.Entry[] buckets = Support.create(16);
+ Hashtable.Entry[] buckets = Hashtable.createFixedBuckets(StringIntEntry.class, 16);
for (long h : new long[] {0L, 1L, -1L, Long.MIN_VALUE, Long.MAX_VALUE, 12345L}) {
- int idx = Support.bucketIndex(buckets, h);
+ int idx = Hashtable.bucketIndex(buckets, h);
assertTrue(idx >= 0 && idx < buckets.length, "bucketIndex out of range for hash " + h);
}
}
@Test
void clearNullsAllBuckets() {
- Hashtable.Entry[] buckets = Support.create(4);
+ Hashtable.Entry[] buckets = Hashtable.createFixedBuckets(StringIntEntry.class, 4);
buckets[0] = new StringIntEntry("x", 1);
buckets[1] = new StringIntEntry("y", 2);
- Support.clear(buckets);
+ Hashtable.clear(buckets);
for (Hashtable.Entry b : buckets) {
assertNull(b);
}
}
+ @Test
+ void insertHeadEntrySplicesAsNewHead() {
+ Hashtable.Entry[] buckets = Hashtable.createFixedBuckets(StringIntEntry.class, 4);
+ StringIntEntry a = new StringIntEntry("a", 1);
+ StringIntEntry b = new StringIntEntry("b", 2);
+ Hashtable.insertHeadEntry(buckets, 0, a);
+ assertSame(a, buckets[0]);
+ assertNull(a.next());
+
+ Hashtable.insertHeadEntry(buckets, 0, b);
+ assertSame(b, buckets[0]);
+ assertSame(a, b.next());
+ assertNull(a.next());
+ }
+ }
+
+ // ============ Deprecated Support facade ============
+
+ /**
+ * The scaled {@code create(int, float)} factory and {@code MAX_RATIO} are deprecated-only: they
+ * have no blessed equivalent on {@link Hashtable} but remain in use by client-side statistics, so
+ * they keep dedicated coverage here.
+ */
+ @Nested
+ @SuppressWarnings("deprecation")
+ class DeprecatedSupportTests {
+
@Test
void maxRatioScalesTargetForLoadFactor() {
// 75% load factor => bucket array sized at requestedSize * 4/3, rounded up to power of 2.
@@ -101,21 +128,6 @@ void createWithScaleRoundsUpToPowerOfTwo() {
Hashtable.Entry[] buckets = Support.create(7, 1.5f);
assertEquals(16, buckets.length);
}
-
- @Test
- void insertHeadEntrySplicesAsNewHead() {
- Hashtable.Entry[] buckets = Support.create(4);
- StringIntEntry a = new StringIntEntry("a", 1);
- StringIntEntry b = new StringIntEntry("b", 2);
- Support.insertHeadEntry(buckets, 0, a);
- assertSame(a, buckets[0]);
- assertNull(a.next());
-
- Support.insertHeadEntry(buckets, 0, b);
- assertSame(b, buckets[0]);
- assertSame(a, b.next());
- assertNull(a.next());
- }
}
// ============ BucketIterator ============
@@ -126,7 +138,7 @@ class BucketIteratorTests {
@Test
void walksOnlyMatchingHash() {
// Build a bucket array with two entries that share a bucket but have different hashes.
- // Use Hashtable.D1 to seed; then call Support.bucketIterator directly with the matching
+ // Use Hashtable.D1 to seed; then call Hashtable.bucketIterator directly with the matching
// hash and verify it only returns the matching entry.
Hashtable.D1 table = new Hashtable.D1<>(4);
CollidingKey k1 = new CollidingKey("first", 17);
@@ -136,7 +148,7 @@ void walksOnlyMatchingHash() {
table.insert(new CollidingKeyEntry(k2, 2));
table.insert(new CollidingKeyEntry(k3, 3));
// All three share the same hash (17), so a bucket iterator over hash=17 yields all three.
- BucketIterator it = Support.bucketIterator(table.buckets, 17L);
+ BucketIterator it = Hashtable.bucketIterator(table.buckets, 17L);
int count = 0;
while (it.hasNext()) {
assertNotNull(it.next());
@@ -150,7 +162,7 @@ void exhaustedIteratorThrowsNoSuchElement() {
Hashtable.D1 table = new Hashtable.D1<>(4);
table.insert(new StringIntEntry("only", 1));
long h = Hashtable.D1.Entry.hash("only");
- BucketIterator it = Support.bucketIterator(table.buckets, h);
+ BucketIterator it = Hashtable.bucketIterator(table.buckets, h);
it.next();
assertFalse(it.hasNext());
assertThrows(NoSuchElementException.class, it::next);
@@ -174,7 +186,7 @@ void removeFromHeadOfChainUnlinks() {
table.insert(new CollidingKeyEntry(k3, 3));
MutatingBucketIterator it =
- Support.mutatingBucketIterator(table.buckets, 17L);
+ Hashtable.mutatingBucketIterator(table.buckets, 17L);
it.next(); // first match (head of chain in insertion-reverse order)
it.remove();
// Two should remain
@@ -207,7 +219,7 @@ void replaceSwapsEntryAndPreservesChain() {
table.insert(e2);
MutatingBucketIterator it =
- Support.mutatingBucketIterator(table.buckets, 17L);
+ Hashtable.mutatingBucketIterator(table.buckets, 17L);
CollidingKeyEntry first = it.next();
CollidingKeyEntry replacement = new CollidingKeyEntry(first.key, 999);
it.replace(replacement);
@@ -223,7 +235,7 @@ void removeWithoutNextThrows() {
Hashtable.D1 table = new Hashtable.D1<>(4);
table.insert(new StringIntEntry("a", 1));
MutatingBucketIterator it =
- Support.mutatingBucketIterator(table.buckets, Hashtable.D1.Entry.hash("a"));
+ Hashtable.mutatingBucketIterator(table.buckets, Hashtable.D1.Entry.hash("a"));
assertThrows(IllegalStateException.class, it::remove);
}
}
@@ -241,7 +253,8 @@ void walksEveryEntryAcrossBuckets() {
table.insert(new StringIntEntry("c", 3));
Set seen = new HashSet<>();
- for (MutatingTableIterator it = Support.mutatingTableIterator(table.buckets);
+ for (MutatingTableIterator it =
+ Hashtable.mutatingTableIterator(table.buckets);
it.hasNext(); ) {
seen.add(it.next().key);
}
@@ -254,7 +267,7 @@ void walksEveryEntryAcrossBuckets() {
@Test
void emptyTableIteratorIsExhausted() {
Hashtable.D1 table = new Hashtable.D1<>(8);
- MutatingTableIterator it = Support.mutatingTableIterator(table.buckets);
+ MutatingTableIterator it = Hashtable.mutatingTableIterator(table.buckets);
assertFalse(it.hasNext());
assertThrows(NoSuchElementException.class, it::next);
}
@@ -268,7 +281,7 @@ void removeUnlinksBucketHead() {
table.insert(new CollidingKeyEntry(k2, 2));
// The head of the chain is whichever was inserted last (insert prepends).
- MutatingTableIterator it = Support.mutatingTableIterator(table.buckets);
+ MutatingTableIterator it = Hashtable.mutatingTableIterator(table.buckets);
CollidingKeyEntry head = it.next();
it.remove();
@@ -289,7 +302,7 @@ void removeUnlinksMidChainEntry() {
table.insert(new CollidingKeyEntry(k3, 3));
// Walk to the second entry, remove it.
- MutatingTableIterator it = Support.mutatingTableIterator(table.buckets);
+ MutatingTableIterator it = Hashtable.mutatingTableIterator(table.buckets);
it.next();
CollidingKeyEntry victim = it.next();
it.remove();
@@ -320,7 +333,7 @@ void removeSkipsOverEmptyBuckets() {
table.insert(new StringIntEntry("beta", 2));
table.insert(new StringIntEntry("gamma", 3));
- MutatingTableIterator it = Support.mutatingTableIterator(table.buckets);
+ MutatingTableIterator it = Hashtable.mutatingTableIterator(table.buckets);
it.next();
it.remove();
int remaining = 0;
@@ -335,7 +348,7 @@ void removeSkipsOverEmptyBuckets() {
void removeWithoutNextThrows() {
Hashtable.D1 table = new Hashtable.D1<>(4);
table.insert(new StringIntEntry("a", 1));
- MutatingTableIterator it = Support.mutatingTableIterator(table.buckets);
+ MutatingTableIterator it = Hashtable.mutatingTableIterator(table.buckets);
assertThrows(IllegalStateException.class, it::remove);
}
@@ -344,7 +357,7 @@ void removeTwiceWithoutInterveningNextThrows() {
Hashtable.D1 table = new Hashtable.D1<>(4);
table.insert(new StringIntEntry("a", 1));
table.insert(new StringIntEntry("b", 2));
- MutatingTableIterator it = Support.mutatingTableIterator(table.buckets);
+ MutatingTableIterator it = Hashtable.mutatingTableIterator(table.buckets);
it.next();
it.remove();
assertThrows(IllegalStateException.class, it::remove);
@@ -362,7 +375,7 @@ void halfOpenRangeOmitsBucketsOutsideTheRange() {
Set seen = new HashSet<>();
for (MutatingTableIterator it =
- Support.mutatingTableIterator(table.buckets, 5, 10);
+ Hashtable.mutatingTableIterator(table.buckets, 5, 10);
it.hasNext(); ) {
seen.add(it.next().key.label);
}
@@ -376,7 +389,8 @@ void emptyHalfOpenRangeIsExhausted() {
// pass [0, cursor) when cursor == 0 in resumable sweeps.
Hashtable.D1 table = new Hashtable.D1<>(8);
table.insert(new StringIntEntry("a", 1));
- MutatingTableIterator it = Support.mutatingTableIterator(table.buckets, 0, 0);
+ MutatingTableIterator it =
+ Hashtable.mutatingTableIterator(table.buckets, 0, 0);
assertFalse(it.hasNext());
}
@@ -385,14 +399,14 @@ void rangeBoundsOutOfOrderThrows() {
Hashtable.D1 table = new Hashtable.D1<>(8);
assertThrows(
IndexOutOfBoundsException.class,
- () -> Support.mutatingTableIterator(table.buckets, -1, 4));
+ () -> Hashtable.mutatingTableIterator(table.buckets, -1, 4));
assertThrows(
IndexOutOfBoundsException.class,
- () -> Support.mutatingTableIterator(table.buckets, 4, 2)); // end < start
+ () -> Hashtable.mutatingTableIterator(table.buckets, 4, 2)); // end < start
assertThrows(
IndexOutOfBoundsException.class,
() ->
- Support.mutatingTableIterator(
+ Hashtable.mutatingTableIterator(
table.buckets, 0, table.buckets.length + 1)); // end > len
}
@@ -403,7 +417,7 @@ void currentBucketReportsLandingIndex() {
Hashtable.D1 table = new Hashtable.D1<>(16);
table.insert(new CollidingKeyEntry(new CollidingKey("b3", 3), 1));
- MutatingTableIterator it = Support.mutatingTableIterator(table.buckets);
+ MutatingTableIterator it = Hashtable.mutatingTableIterator(table.buckets);
assertEquals(-1, it.currentBucket(), "before any next() currentBucket should be -1");
it.next();
assertEquals(3, it.currentBucket(), "currentBucket should report the entry's bucket");