diff --git a/internal-api/src/main/java/datadog/trace/api/HashSupport.java b/internal-api/src/main/java/datadog/trace/api/HashSupport.java
new file mode 100644
index 00000000000..1c7874433ab
--- /dev/null
+++ b/internal-api/src/main/java/datadog/trace/api/HashSupport.java
@@ -0,0 +1,461 @@
+package datadog.trace.api;
+
+import datadog.trace.api.TagMap.BucketGroup;
+import datadog.trace.api.TagMap.Entry;
+import datadog.trace.api.TagMap.EntryReader;
+import datadog.trace.api.function.TriConsumer;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * The raw bucket mechanics of {@link TagMap}, over one fixed-size {@code Object[]} of buckets of
+ * shareable, immutable {@link Entry} objects. A bucket holds a lone Entry directly; colliding
+ * entries share a fixed-size {@link BucketGroup}, chained when one fills.
+ *
+ *
Stateless: each operation reports what it did through its return value -- put returns the
+ * entry it replaced, remove the entry it removed, merge how much the map grew -- and the calling
+ * map keeps its own count. Read-through to a parent map and its tombstones stay with the map.
+ */
+final class HashSupport {
+ private HashSupport() {}
+
+ /** A fresh bucket array; its length must be a power of 2 for bucket masking. */
+ static Object[] newBuckets() {
+ // needs to be a power of 2 for bucket masking calculation to work as intended
+ return new Object[1 << 4];
+ }
+
+ /** The entry for {@code (hash, tag)} in {@code buckets}, or null. */
+ static Entry find(Object[] buckets, int hash, String tag) {
+ return findInBucket(buckets[hash & (buckets.length - 1)], hash, tag);
+ }
+
+ /**
+ * Finds an entry by hash/tag within a single bucket object (Entry | BucketGroup chain | null).
+ */
+ static Entry findInBucket(Object bucket, int hash, String tag) {
+ if (bucket instanceof Entry) {
+ Entry tagEntry = (Entry) bucket;
+ return tagEntry.matches(tag) ? tagEntry : null;
+ } else if (bucket instanceof BucketGroup) {
+ return ((BucketGroup) bucket).findInChain(hash, tag);
+ }
+ return null;
+ }
+
+ /** Inserts or replaces {@code newEntry}, returning the entry it replaced, or null if it added. */
+ static Entry put(Object[] buckets, Entry newEntry) {
+ Object[] thisBuckets = buckets;
+
+ int newHash = newEntry.hash();
+ int bucketIndex = newHash & (thisBuckets.length - 1);
+
+ Object bucket = thisBuckets[bucketIndex];
+ if (bucket == null) {
+ thisBuckets[bucketIndex] = newEntry;
+
+ return null;
+ } else if (bucket instanceof Entry) {
+ Entry existingEntry = (Entry) bucket;
+ if (existingEntry.matches(newEntry.tag)) {
+ thisBuckets[bucketIndex] = newEntry;
+
+ // replaced existing entry - no size change
+ return existingEntry;
+ } else {
+ thisBuckets[bucketIndex] =
+ new BucketGroup(existingEntry.hash(), existingEntry, newHash, newEntry);
+
+ return null;
+ }
+ } else if (bucket instanceof BucketGroup) {
+ BucketGroup lastGroup = (BucketGroup) bucket;
+
+ BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag);
+ if (containingGroup != null) {
+ // replaced existing entry - no size change
+ return containingGroup._replace(newHash, newEntry);
+ }
+
+ if (!lastGroup.insertInChain(newHash, newEntry)) {
+ thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup);
+ }
+ return null;
+ }
+
+ // unreachable
+ return null;
+ }
+
+ /** Removes the entry for {@code (hash, tag)}, returning it, or null if there was none. */
+ static Entry remove(Object[] buckets, int hash, String tag) {
+ Object[] thisBuckets = buckets;
+
+ int bucketIndex = hash & (thisBuckets.length - 1);
+
+ Object bucket = thisBuckets[bucketIndex];
+ // null bucket case - do nothing
+ if (bucket instanceof Entry) {
+ Entry existingEntry = (Entry) bucket;
+ if (existingEntry.matches(tag)) {
+ thisBuckets[bucketIndex] = null;
+
+ return existingEntry;
+ } else {
+ return null;
+ }
+ } else if (bucket instanceof BucketGroup) {
+ BucketGroup lastGroup = (BucketGroup) bucket;
+
+ BucketGroup containingGroup = lastGroup.findContainingGroupInChain(hash, tag);
+ if (containingGroup == null) {
+ return null;
+ }
+
+ Entry existingEntry = containingGroup._remove(hash, tag);
+ if (containingGroup._isEmpty()) {
+ thisBuckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup);
+ }
+
+ return existingEntry;
+ }
+ return null;
+ }
+
+ /**
+ * Copies {@code thatBuckets} into the empty {@code thisBuckets}, sharing Entry objects and
+ * cloning only BucketGroup chains.
+ */
+ static void copyInto(Object[] thisBuckets, Object[] thatBuckets) {
+ // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check
+ // elimination
+ for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) {
+ Object thatBucket = thatBuckets[i];
+
+ // faster to explicitly null check first, then do instanceof
+ if (thatBucket == null) {
+ // do nothing
+ } else if (thatBucket instanceof BucketGroup) {
+ // if it is a BucketGroup, then need to clone
+ BucketGroup thatGroup = (BucketGroup) thatBucket;
+
+ thisBuckets[i] = thatGroup.cloneChain();
+ } else { // if ( thatBucket instanceof Entry )
+ thisBuckets[i] = thatBucket;
+ }
+ }
+ }
+
+ /**
+ * Merges {@code thatBuckets} into {@code thisBuckets}, sharing Entry objects and cloning only
+ * BucketGroup chains; incoming entries replace existing ones. Returns how much the map grew.
+ */
+ static int merge(Object[] thisBuckets, Object[] thatBuckets) {
+ int added = 0;
+
+ // Since TagMap-s don't support expansion, buckets are perfectly aligned
+ // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check
+ // elimination
+ for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) {
+ Object thatBucket = thatBuckets[i];
+
+ // if nothing incoming, nothing to do
+ if (thatBucket == null) continue;
+
+ Object thisBucket = thisBuckets[i];
+ if (thisBucket == null) {
+ // This bucket is null, easy case
+ // Either copy over the sole entry or clone the BucketGroup chain
+
+ if (thatBucket instanceof Entry) {
+ thisBuckets[i] = thatBucket;
+ added += 1;
+ } else if (thatBucket instanceof BucketGroup) {
+ BucketGroup thatGroup = (BucketGroup) thatBucket;
+
+ BucketGroup thisNewGroup = thatGroup.cloneChain();
+ thisBuckets[i] = thisNewGroup;
+ added += thisNewGroup.sizeInChain();
+ }
+ } else if (thisBucket instanceof Entry) {
+ // This bucket is a single entry, medium complexity case
+ // If other side is an Entry - just merge the entries into a bucket
+ // If other side is a BucketGroup - then clone the group and insert the entry normally into
+ // the cloned group
+
+ Entry thisEntry = (Entry) thisBucket;
+ int thisHash = thisEntry.hash();
+
+ if (thatBucket instanceof Entry) {
+ Entry thatEntry = (Entry) thatBucket;
+ int thatHash = thatEntry.hash();
+
+ if (thisHash == thatHash && thisEntry.matches(thatEntry.tag())) {
+ thisBuckets[i] = thatEntry;
+ // replacing entry, no size change
+ } else {
+ thisBuckets[i] =
+ new BucketGroup(
+ thisHash, thisEntry,
+ thatHash, thatEntry);
+ added += 1;
+ }
+ } else if (thatBucket instanceof BucketGroup) {
+ BucketGroup thatGroup = (BucketGroup) thatBucket;
+
+ // Clone the other group, then place this entry into that group
+ BucketGroup thisNewGroup = thatGroup.cloneChain();
+ int thisNewGroupSize = thisNewGroup.sizeInChain();
+
+ Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag());
+ if (incomingEntry != null) {
+ // there's already an entry w/ the same tag from the incoming TagMap
+ // incoming entry clobbers the existing try, so we're done
+ thisBuckets[i] = thisNewGroup;
+
+ // overlapping group - subtract one for clobbered existing entry
+ added += thisNewGroupSize - 1;
+ } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) {
+ // able to add thisEntry into the existing groups
+ thisBuckets[i] = thisNewGroup;
+
+ // non overlapping group - existing entry already accounted for in this.size
+ added += thisNewGroupSize;
+ } else {
+ // unable to add into the existing groups
+ thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup);
+
+ // non overlapping group - existing entry already accounted for in this.size
+ added += thisNewGroupSize;
+ }
+ }
+ } else if (thisBucket instanceof BucketGroup) {
+ // This bucket is a BucketGroup, medium to hard case
+ // If the other side is an entry, just normal insertion procedure - no cloning required
+ BucketGroup thisGroup = (BucketGroup) thisBucket;
+
+ if (thatBucket instanceof Entry) {
+ Entry thatEntry = (Entry) thatBucket;
+ int thatHash = thatEntry.hash();
+
+ if (thisGroup.replaceInChain(thatHash, thatEntry) != null) {
+ // replaced existing entry no size change
+ } else if (thisGroup.insertInChain(thatHash, thatEntry)) {
+ added += 1;
+ } else {
+ thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup);
+ added += 1;
+ }
+ } else if (thatBucket instanceof BucketGroup) {
+ // Most complicated case - need to walk that bucket group chain and update this chain
+ BucketGroup thatGroup = (BucketGroup) thatBucket;
+
+ // Taking the easy / expensive way out for updating size
+ int thisPrevGroupSize = thisGroup.sizeInChain();
+
+ BucketGroup thisNewGroup = thisGroup.replaceOrInsertAllInChain(thatGroup);
+ int thisNewGroupSize = thisNewGroup.sizeInChain();
+
+ thisBuckets[i] = thisNewGroup;
+ added += (thisNewGroupSize - thisPrevGroupSize);
+ }
+ }
+ }
+ return added;
+ }
+
+ /** Visits every entry in {@code buckets}. */
+ static void forEach(Object[] buckets, Consumer super EntryReader> consumer) {
+ Object[] thisBuckets = buckets;
+
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ Object thisBucket = thisBuckets[i];
+
+ if (thisBucket instanceof Entry) {
+ Entry thisEntry = (Entry) thisBucket;
+
+ consumer.accept(thisEntry);
+ } else if (thisBucket instanceof BucketGroup) {
+ BucketGroup thisGroup = (BucketGroup) thisBucket;
+
+ thisGroup.forEachInChain(consumer);
+ }
+ }
+ }
+
+ /** {@link #forEach(Object[], Consumer)} with a context object for a non-capturing consumer. */
+ static void forEach(
+ Object[] buckets, T thisObj, BiConsumer consumer) {
+ Object[] thisBuckets = buckets;
+
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ Object thisBucket = thisBuckets[i];
+
+ if (thisBucket instanceof Entry) {
+ Entry thisEntry = (Entry) thisBucket;
+
+ consumer.accept(thisObj, thisEntry);
+ } else if (thisBucket instanceof BucketGroup) {
+ BucketGroup thisGroup = (BucketGroup) thisBucket;
+
+ thisGroup.forEachInChain(thisObj, consumer);
+ }
+ }
+ }
+
+ /** {@link #forEach(Object[], Consumer)} with two context objects. */
+ static void forEach(
+ Object[] buckets, T thisObj, U otherObj, TriConsumer consumer) {
+ Object[] thisBuckets = buckets;
+
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ Object thisBucket = thisBuckets[i];
+
+ if (thisBucket instanceof Entry) {
+ Entry thisEntry = (Entry) thisBucket;
+
+ consumer.accept(thisObj, otherObj, thisEntry);
+ } else if (thisBucket instanceof BucketGroup) {
+ BucketGroup thisGroup = (BucketGroup) thisBucket;
+
+ thisGroup.forEachInChain(thisObj, otherObj, consumer);
+ }
+ }
+ }
+
+ /** Puts every entry in {@code buckets} into {@code map}, by tag, as its object value. */
+ static void fillMap(Object[] buckets, Map super String, Object> map) {
+ Object[] thisBuckets = buckets;
+
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ Object thisBucket = thisBuckets[i];
+
+ if (thisBucket instanceof Entry) {
+ Entry thisEntry = (Entry) thisBucket;
+
+ map.put(thisEntry.tag, thisEntry.objectValue());
+ } else if (thisBucket instanceof BucketGroup) {
+ BucketGroup thisGroup = (BucketGroup) thisBucket;
+
+ thisGroup.fillMapFromChain(map);
+ }
+ }
+ }
+
+ /** Puts every entry in {@code buckets} into {@code stringMap}, by tag, as its string value. */
+ static void fillStringMap(Object[] buckets, Map super String, ? super String> stringMap) {
+ Object[] thisBuckets = buckets;
+
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ Object thisBucket = thisBuckets[i];
+
+ if (thisBucket instanceof Entry) {
+ Entry thisEntry = (Entry) thisBucket;
+
+ stringMap.put(thisEntry.tag, thisEntry.stringValue());
+ } else if (thisBucket instanceof BucketGroup) {
+ BucketGroup thisGroup = (BucketGroup) thisBucket;
+
+ thisGroup.fillStringMapFromChain(stringMap);
+ }
+ }
+ }
+
+ /** The number of entries in {@code buckets}, counted. */
+ static int count(Object[] buckets) {
+ Object[] thisBuckets = buckets;
+
+ int size = 0;
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ Object curBucket = thisBuckets[i];
+
+ if (curBucket instanceof Entry) {
+ size += 1;
+ } else if (curBucket instanceof BucketGroup) {
+ BucketGroup curGroup = (BucketGroup) curBucket;
+ size += curGroup.sizeInChain();
+ }
+ }
+ return size;
+ }
+
+ /** Whether {@code buckets} holds no entry, checked bucket by bucket. */
+ static boolean isEmpty(Object[] buckets) {
+ Object[] thisBuckets = buckets;
+
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ Object curBucket = thisBuckets[i];
+
+ if (curBucket instanceof Entry) {
+ return false;
+ } else if (curBucket instanceof BucketGroup) {
+ BucketGroup curGroup = (BucketGroup) curBucket;
+ if (!curGroup.isEmptyChain()) return false;
+ }
+ }
+
+ return true;
+ }
+
+ /** Checks that every entry in {@code buckets} sits in the bucket its hash selects. */
+ static void checkBuckets(Object[] buckets) {
+ Object[] thisBuckets = buckets;
+
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ Object thisBucket = thisBuckets[i];
+
+ if (thisBucket instanceof Entry) {
+ Entry thisEntry = (Entry) thisBucket;
+ int thisHash = thisEntry.hash();
+
+ int expectedBucket = thisHash & (thisBuckets.length - 1);
+ if (expectedBucket != i) {
+ throw new IllegalStateException("incorrect bucket");
+ }
+ } else if (thisBucket instanceof BucketGroup) {
+ BucketGroup thisGroup = (BucketGroup) thisBucket;
+
+ for (BucketGroup curGroup = thisGroup; curGroup != null; curGroup = curGroup.prev) {
+ for (int j = 0; j < BucketGroup.LEN; ++j) {
+ Entry thisEntry = curGroup._entryAt(i);
+ if (thisEntry == null) continue;
+
+ int thisHash = thisEntry.hash();
+ assert curGroup._hashAt(i) == thisHash;
+
+ int expectedBucket = thisHash & (thisBuckets.length - 1);
+ if (expectedBucket != i) {
+ throw new IllegalStateException("incorrect bucket");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** The buckets' internal structure, for deep debugging. */
+ static String toInternalString(Object[] buckets) {
+ Object[] thisBuckets = buckets;
+
+ StringBuilder ledger = new StringBuilder(128);
+ for (int i = 0; i < thisBuckets.length; ++i) {
+ ledger.append('[').append(i).append("] = ");
+
+ Object thisBucket = thisBuckets[i];
+ if (thisBucket == null) {
+ ledger.append("null");
+ } else if (thisBucket instanceof Entry) {
+ ledger.append('{').append(thisBucket).append('}');
+ } else if (thisBucket instanceof BucketGroup) {
+ for (BucketGroup curGroup = (BucketGroup) thisBucket;
+ curGroup != null;
+ curGroup = curGroup.prev) {
+ ledger.append(curGroup).append(" -> ");
+ }
+ }
+ ledger.append('\n');
+ }
+ return ledger.toString();
+ }
+}
diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java
index b4cc9997222..8cbc4358c9c 100644
--- a/internal-api/src/main/java/datadog/trace/api/TagMap.java
+++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java
@@ -1238,8 +1238,7 @@ public TagMap() {
* optimizations can treat it as fixed.
*/
private TagMap(TagMap parent) {
- // needs to be a power of 2 for bucket masking calculation to work as intended
- this.buckets = new Object[1 << 4];
+ this.buckets = HashSupport.newBuckets();
this.size = 0;
this.frozen = false;
this.parent = parent;
@@ -1488,22 +1487,7 @@ private Entry getEntry(String canonicalTag, long tagHash) {
/** Looks up an entry in this map's own buckets only — no read-through to the parent. */
private Entry getLocalEntry(String tag, long tagHash) {
- Object[] thisBuckets = this.buckets;
- int hash = Entry.bucketHash(tagHash);
- return findInBucket(thisBuckets[hash & (thisBuckets.length - 1)], hash, tag);
- }
-
- /**
- * Finds an entry by hash/tag within a single bucket object (Entry | BucketGroup chain | null).
- */
- private static Entry findInBucket(Object bucket, int hash, String tag) {
- if (bucket instanceof Entry) {
- Entry tagEntry = (Entry) bucket;
- return tagEntry.matches(tag) ? tagEntry : null;
- } else if (bucket instanceof BucketGroup) {
- return ((BucketGroup) bucket).findInChain(hash, tag);
- }
- return null;
+ return HashSupport.find(this.buckets, Entry.bucketHash(tagHash), tag);
}
/**
@@ -1528,7 +1512,7 @@ boolean parentEntryVisible(Entry parentEntry, TagMap fromAncestor) {
// shadowed by a nearer level's local entry
Object[] nearerBuckets = nearer.buckets;
Object nearerBucket = nearerBuckets[hash & (nearerBuckets.length - 1)];
- if (findInBucket(nearerBucket, hash, tag) != null) {
+ if (HashSupport.findInBucket(nearerBucket, hash, tag) != null) {
return false;
}
}
@@ -1809,49 +1793,11 @@ Entry putEntry(@Nonnull Entry newEntry) {
this.removedFromParent.remove(newEntry.tag);
}
- Object[] thisBuckets = this.buckets;
-
- int newHash = newEntry.hash();
- int bucketIndex = newHash & (thisBuckets.length - 1);
-
- Object bucket = thisBuckets[bucketIndex];
- if (bucket == null) {
- thisBuckets[bucketIndex] = newEntry;
-
- this.size += 1;
- return null;
- } else if (bucket instanceof Entry) {
- Entry existingEntry = (Entry) bucket;
- if (existingEntry.matches(newEntry.tag)) {
- thisBuckets[bucketIndex] = newEntry;
-
- // replaced existing entry - no size change
- return existingEntry;
- } else {
- thisBuckets[bucketIndex] =
- new BucketGroup(existingEntry.hash(), existingEntry, newHash, newEntry);
-
- this.size += 1;
- return null;
- }
- } else if (bucket instanceof BucketGroup) {
- BucketGroup lastGroup = (BucketGroup) bucket;
-
- BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag);
- if (containingGroup != null) {
- // replaced existing entry - no size change
- return containingGroup._replace(newHash, newEntry);
- }
-
- if (!lastGroup.insertInChain(newHash, newEntry)) {
- thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup);
- }
+ Entry replaced = HashSupport.put(this.buckets, newEntry);
+ if (replaced == null) {
this.size += 1;
- return null;
}
-
- // unreachable
- return null;
+ return replaced;
}
public Entry getAndSet(@Nonnull String tag, Object value) {
@@ -1928,143 +1874,14 @@ private void putAllOptimizedMap(TagMap that) {
}
private void putAllMerge(TagMap that) {
- Object[] thisBuckets = this.buckets;
- Object[] thatBuckets = that.buckets;
-
- // Since TagMap-s don't support expansion, buckets are perfectly aligned
- // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check
- // elimination
- for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) {
- Object thatBucket = thatBuckets[i];
-
- // if nothing incoming, nothing to do
- if (thatBucket == null) continue;
-
- Object thisBucket = thisBuckets[i];
- if (thisBucket == null) {
- // This bucket is null, easy case
- // Either copy over the sole entry or clone the BucketGroup chain
-
- if (thatBucket instanceof Entry) {
- thisBuckets[i] = thatBucket;
- this.size += 1;
- } else if (thatBucket instanceof BucketGroup) {
- BucketGroup thatGroup = (BucketGroup) thatBucket;
-
- BucketGroup thisNewGroup = thatGroup.cloneChain();
- thisBuckets[i] = thisNewGroup;
- this.size += thisNewGroup.sizeInChain();
- }
- } else if (thisBucket instanceof Entry) {
- // This bucket is a single entry, medium complexity case
- // If other side is an Entry - just merge the entries into a bucket
- // If other side is a BucketGroup - then clone the group and insert the entry normally into
- // the cloned group
-
- Entry thisEntry = (Entry) thisBucket;
- int thisHash = thisEntry.hash();
-
- if (thatBucket instanceof Entry) {
- Entry thatEntry = (Entry) thatBucket;
- int thatHash = thatEntry.hash();
-
- if (thisHash == thatHash && thisEntry.matches(thatEntry.tag())) {
- thisBuckets[i] = thatEntry;
- // replacing entry, no size change
- } else {
- thisBuckets[i] =
- new BucketGroup(
- thisHash, thisEntry,
- thatHash, thatEntry);
- this.size += 1;
- }
- } else if (thatBucket instanceof BucketGroup) {
- BucketGroup thatGroup = (BucketGroup) thatBucket;
-
- // Clone the other group, then place this entry into that group
- BucketGroup thisNewGroup = thatGroup.cloneChain();
- int thisNewGroupSize = thisNewGroup.sizeInChain();
-
- Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag());
- if (incomingEntry != null) {
- // there's already an entry w/ the same tag from the incoming TagMap
- // incoming entry clobbers the existing try, so we're done
- thisBuckets[i] = thisNewGroup;
-
- // overlapping group - subtract one for clobbered existing entry
- this.size += thisNewGroupSize - 1;
- } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) {
- // able to add thisEntry into the existing groups
- thisBuckets[i] = thisNewGroup;
-
- // non overlapping group - existing entry already accounted for in this.size
- this.size += thisNewGroupSize;
- } else {
- // unable to add into the existing groups
- thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup);
-
- // non overlapping group - existing entry already accounted for in this.size
- this.size += thisNewGroupSize;
- }
- }
- } else if (thisBucket instanceof BucketGroup) {
- // This bucket is a BucketGroup, medium to hard case
- // If the other side is an entry, just normal insertion procedure - no cloning required
- BucketGroup thisGroup = (BucketGroup) thisBucket;
-
- if (thatBucket instanceof Entry) {
- Entry thatEntry = (Entry) thatBucket;
- int thatHash = thatEntry.hash();
-
- if (thisGroup.replaceInChain(thatHash, thatEntry) != null) {
- // replaced existing entry no size change
- } else if (thisGroup.insertInChain(thatHash, thatEntry)) {
- this.size += 1;
- } else {
- thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup);
- this.size += 1;
- }
- } else if (thatBucket instanceof BucketGroup) {
- // Most complicated case - need to walk that bucket group chain and update this chain
- BucketGroup thatGroup = (BucketGroup) thatBucket;
-
- // Taking the easy / expensive way out for updating size
- int thisPrevGroupSize = thisGroup.sizeInChain();
-
- BucketGroup thisNewGroup = thisGroup.replaceOrInsertAllInChain(thatGroup);
- int thisNewGroupSize = thisNewGroup.sizeInChain();
-
- thisBuckets[i] = thisNewGroup;
- this.size += (thisNewGroupSize - thisPrevGroupSize);
- }
- }
- }
+ this.size += HashSupport.merge(this.buckets, that.buckets);
}
/*
* Specially optimized version of putAll for the common case of destination map being empty
*/
private void putAllIntoEmptyMap(TagMap that) {
- Object[] thisBuckets = this.buckets;
- Object[] thatBuckets = that.buckets;
-
- // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check
- // elimination
- for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) {
- Object thatBucket = thatBuckets[i];
-
- // faster to explicitly null check first, then do instanceof
- if (thatBucket == null) {
- // do nothing
- } else if (thatBucket instanceof BucketGroup) {
- // if it is a BucketGroup, then need to clone
- BucketGroup thatGroup = (BucketGroup) thatBucket;
-
- thisBuckets[i] = thatGroup.cloneChain();
- } else { // if ( thatBucket instanceof Entry )
- thisBuckets[i] = thatBucket;
- }
- }
+ HashSupport.copyInto(this.buckets, that.buckets);
this.size = that.size;
}
@@ -2077,21 +1894,7 @@ public void fillMap(Map super String, Object> map) {
return;
}
- Object[] thisBuckets = this.buckets;
-
- for (int i = 0; i < thisBuckets.length; ++i) {
- Object thisBucket = thisBuckets[i];
-
- if (thisBucket instanceof Entry) {
- Entry thisEntry = (Entry) thisBucket;
-
- map.put(thisEntry.tag, thisEntry.objectValue());
- } else if (thisBucket instanceof BucketGroup) {
- BucketGroup thisGroup = (BucketGroup) thisBucket;
-
- thisGroup.fillMapFromChain(map);
- }
- }
+ HashSupport.fillMap(this.buckets, map);
}
public void fillStringMap(Map super String, ? super String> stringMap) {
@@ -2102,21 +1905,7 @@ public void fillStringMap(Map super String, ? super String> stringMap) {
return;
}
- Object[] thisBuckets = this.buckets;
-
- for (int i = 0; i < thisBuckets.length; ++i) {
- Object thisBucket = thisBuckets[i];
-
- if (thisBucket instanceof Entry) {
- Entry thisEntry = (Entry) thisBucket;
-
- stringMap.put(thisEntry.tag, thisEntry.stringValue());
- } else if (thisBucket instanceof BucketGroup) {
- BucketGroup thisGroup = (BucketGroup) thisBucket;
-
- thisGroup.fillStringMapFromChain(stringMap);
- }
- }
+ HashSupport.fillStringMap(this.buckets, stringMap);
}
@Override
@@ -2177,40 +1966,11 @@ private Entry getAndRemove(String canonicalTag, long tagHash) {
/** Removes an entry from this map's own buckets only — no parent/tombstone handling. */
private Entry removeLocal(String tag, long tagHash) {
- Object[] thisBuckets = this.buckets;
-
- int hash = Entry.bucketHash(tagHash);
- int bucketIndex = hash & (thisBuckets.length - 1);
-
- Object bucket = thisBuckets[bucketIndex];
- // null bucket case - do nothing
- if (bucket instanceof Entry) {
- Entry existingEntry = (Entry) bucket;
- if (existingEntry.matches(tag)) {
- thisBuckets[bucketIndex] = null;
-
- this.size -= 1;
- return existingEntry;
- } else {
- return null;
- }
- } else if (bucket instanceof BucketGroup) {
- BucketGroup lastGroup = (BucketGroup) bucket;
-
- BucketGroup containingGroup = lastGroup.findContainingGroupInChain(hash, tag);
- if (containingGroup == null) {
- return null;
- }
-
- Entry existingEntry = containingGroup._remove(hash, tag);
- if (containingGroup._isEmpty()) {
- this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup);
- }
-
+ Entry removed = HashSupport.remove(this.buckets, Entry.bucketHash(tagHash), tag);
+ if (removed != null) {
this.size -= 1;
- return existingEntry;
}
- return null;
+ return removed;
}
public TagMap copy() {
@@ -2244,21 +2004,7 @@ public Stream stream() {
@Override
public void forEach(Consumer super TagMap.EntryReader> consumer) {
- Object[] thisBuckets = this.buckets;
-
- for (int i = 0; i < thisBuckets.length; ++i) {
- Object thisBucket = thisBuckets[i];
-
- if (thisBucket instanceof Entry) {
- Entry thisEntry = (Entry) thisBucket;
-
- consumer.accept(thisEntry);
- } else if (thisBucket instanceof BucketGroup) {
- BucketGroup thisGroup = (BucketGroup) thisBucket;
-
- thisGroup.forEachInChain(consumer);
- }
- }
+ HashSupport.forEach(this.buckets, consumer);
// read-through: parent entries not shadowed locally or tombstoned. Kept out of line so the
// common parent == null path stays byte-identical to before (small / inlinable).
@@ -2293,21 +2039,7 @@ private void forEachParent(Consumer super TagMap.EntryReader> consumer) {
}
public void forEach(T thisObj, BiConsumer consumer) {
- Object[] thisBuckets = this.buckets;
-
- for (int i = 0; i < thisBuckets.length; ++i) {
- Object thisBucket = thisBuckets[i];
-
- if (thisBucket instanceof Entry) {
- Entry thisEntry = (Entry) thisBucket;
-
- consumer.accept(thisObj, thisEntry);
- } else if (thisBucket instanceof BucketGroup) {
- BucketGroup thisGroup = (BucketGroup) thisBucket;
-
- thisGroup.forEachInChain(thisObj, consumer);
- }
- }
+ HashSupport.forEach(this.buckets, thisObj, consumer);
// read-through: parent entries not shadowed locally or tombstoned (kept out of line).
if (this.parent != null) {
@@ -2341,21 +2073,7 @@ private void forEachParent(T thisObj, BiConsumer void forEach(
T thisObj, U otherObj, TriConsumer consumer) {
- Object[] thisBuckets = this.buckets;
-
- for (int i = 0; i < thisBuckets.length; ++i) {
- Object thisBucket = thisBuckets[i];
-
- if (thisBucket instanceof Entry) {
- Entry thisEntry = (Entry) thisBucket;
-
- consumer.accept(thisObj, otherObj, thisEntry);
- } else if (thisBucket instanceof BucketGroup) {
- BucketGroup thisGroup = (BucketGroup) thisBucket;
-
- thisGroup.forEachInChain(thisObj, otherObj, consumer);
- }
- }
+ HashSupport.forEach(this.buckets, thisObj, otherObj, consumer);
// read-through: parent entries not shadowed locally or tombstoned (kept out of line).
if (this.parent != null) {
@@ -2420,38 +2138,7 @@ void checkIntegrity() {
// That was done to avoid the extra static initialization needed for an assertion
// While that's probably an unnecessary optimization, this method is only called in tests
- Object[] thisBuckets = this.buckets;
-
- for (int i = 0; i < thisBuckets.length; ++i) {
- Object thisBucket = thisBuckets[i];
-
- if (thisBucket instanceof Entry) {
- Entry thisEntry = (Entry) thisBucket;
- int thisHash = thisEntry.hash();
-
- int expectedBucket = thisHash & (thisBuckets.length - 1);
- if (expectedBucket != i) {
- throw new IllegalStateException("incorrect bucket");
- }
- } else if (thisBucket instanceof BucketGroup) {
- BucketGroup thisGroup = (BucketGroup) thisBucket;
-
- for (BucketGroup curGroup = thisGroup; curGroup != null; curGroup = curGroup.prev) {
- for (int j = 0; j < BucketGroup.LEN; ++j) {
- Entry thisEntry = curGroup._entryAt(i);
- if (thisEntry == null) continue;
-
- int thisHash = thisEntry.hash();
- assert curGroup._hashAt(i) == thisHash;
-
- int expectedBucket = thisHash & (thisBuckets.length - 1);
- if (expectedBucket != i) {
- throw new IllegalStateException("incorrect bucket");
- }
- }
- }
- }
- }
+ HashSupport.checkBuckets(this.buckets);
if (this.size != this.computeSize()) {
throw new IllegalStateException("incorrect size");
@@ -2464,37 +2151,11 @@ void checkIntegrity() {
}
int computeSize() {
- Object[] thisBuckets = this.buckets;
-
- int size = 0;
- for (int i = 0; i < thisBuckets.length; ++i) {
- Object curBucket = thisBuckets[i];
-
- if (curBucket instanceof Entry) {
- size += 1;
- } else if (curBucket instanceof BucketGroup) {
- BucketGroup curGroup = (BucketGroup) curBucket;
- size += curGroup.sizeInChain();
- }
- }
- return size;
+ return HashSupport.count(this.buckets);
}
boolean checkIfEmpty() {
- Object[] thisBuckets = this.buckets;
-
- for (int i = 0; i < thisBuckets.length; ++i) {
- Object curBucket = thisBuckets[i];
-
- if (curBucket instanceof Entry) {
- return false;
- } else if (curBucket instanceof BucketGroup) {
- BucketGroup curGroup = (BucketGroup) curBucket;
- if (!curGroup.isEmptyChain()) return false;
- }
- }
-
- return true;
+ return HashSupport.isEmpty(this.buckets);
}
@Override
@@ -2552,27 +2213,7 @@ String toPrettyString() {
* debugging
*/
String toInternalString() {
- Object[] thisBuckets = this.buckets;
-
- StringBuilder ledger = new StringBuilder(128);
- for (int i = 0; i < thisBuckets.length; ++i) {
- ledger.append('[').append(i).append("] = ");
-
- Object thisBucket = thisBuckets[i];
- if (thisBucket == null) {
- ledger.append("null");
- } else if (thisBucket instanceof Entry) {
- ledger.append('{').append(thisBucket).append('}');
- } else if (thisBucket instanceof BucketGroup) {
- for (BucketGroup curGroup = (BucketGroup) thisBucket;
- curGroup != null;
- curGroup = curGroup.prev) {
- ledger.append(curGroup).append(" -> ");
- }
- }
- ledger.append('\n');
- }
- return ledger.toString();
+ return HashSupport.toInternalString(this.buckets);
}
abstract static class IteratorBase {