From 94822322d20a79455e97f6233b29c82fa81d3558 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 12 Jan 2026 14:33:29 -0500 Subject: [PATCH 01/22] Updating tests to account for API changes --- .../core/taginterceptor/TagInterceptor.java | 2 +- .../main/java/datadog/trace/api/TagMap.java | 668 ++++++++++++++++-- .../datadog/trace/api/TagMapFuzzTest.java | 6 +- .../java/datadog/trace/api/TagMapTest.java | 2 +- 4 files changed, 614 insertions(+), 64 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java index 3da653c5398..f2c6640d721 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java @@ -85,7 +85,7 @@ public TagInterceptor( } public boolean needsIntercept(TagMap map) { - for (TagMap.Entry entry : map) { + for (TagMap.EntryReader entry : map) { if (needsIntercept(entry.tag())) return true; } return false; 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 79fc79e4c4d..5423de22e66 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -43,7 +43,7 @@ *
  • adaptive collision * */ -public interface TagMap extends Map, Iterable { +public interface TagMap extends Map, Iterable { /** Immutable empty TagMap - similar to {@link Collections#emptyMap()} */ TagMap EMPTY = TagMapFactory.INSTANCE.empty(); @@ -166,7 +166,7 @@ static Ledger ledger(int size) { void set(String tag, double value); - void set(Entry newEntry); + void set(EntryReader newEntry); /** sets the value while returning the prior Entry */ Entry getAndSet(String tag, Object value); @@ -237,14 +237,16 @@ static Ledger ledger(int size) { * , but with less allocation */ @Override - Iterator iterator(); + Iterator iterator(); - Stream stream(); + Stream stream(); + + EntryIterator entryIterator(); /** * Visits each Entry in this TagMap This method is more efficient than {@link TagMap#iterator()} */ - void forEach(Consumer consumer); + void forEach(Consumer consumer); /** * Version of forEach that takes an extra context object that is passed as the first argument to @@ -252,7 +254,7 @@ static Ledger ledger(int size) { * *

    The intention is to use this method to avoid using a capturing lambda */ - void forEach(T thisObj, BiConsumer consumer); + void forEach(T thisObj, BiConsumer consumer); /** * Version of forEach that takes two extra context objects that are passed as the first two @@ -260,7 +262,8 @@ static Ledger ledger(int size) { * *

    The intention is to use this method to avoid using a capturing lambda */ - void forEach(T thisObj, U otherObj, TriConsumer consumer); + void forEach( + T thisObj, U otherObj, TriConsumer consumer); /** Clears the TagMap */ void clear(); @@ -307,12 +310,7 @@ public boolean isRemoval() { } } - final class Entry extends EntryChange implements Map.Entry { - /* - * Special value used for Objects that haven't been type checked yet. - * These objects might be primitive box objects. - */ - public static final byte ANY = 0; + interface EntryReader { public static final byte OBJECT = 1; /* @@ -331,6 +329,54 @@ final class Entry extends EntryChange implements Map.Entry { public static final byte FLOAT = 8; public static final byte DOUBLE = 9; + String tag(); + + byte type(); + + boolean is(byte type); + + boolean isNumericPrimitive(); + + boolean isNumber(); + + boolean isObject(); + + Object objectValue(); + + String stringValue(); + + boolean booleanValue(); + + int intValue(); + + long longValue(); + + float floatValue(); + + double doubleValue(); + + Map.Entry mapEntry(); + + Entry entry(); + } + + interface EntryIterator extends EntryReader { + boolean hasNext(); + + boolean next(); + } + + interface EntryChangeIterator extends EntryIterator { + boolean isRemoval(); + } + + final class Entry extends EntryChange implements Map.Entry, EntryReader { + /* + * Special value used for Objects that haven't been type checked yet. + * These objects might be primitive box objects. + */ + static final byte ANY = 0; + static Entry newAnyEntry(Map.Entry entry) { return newAnyEntry(entry.getKey(), entry.getValue()); } @@ -400,7 +446,7 @@ static Entry newDoubleEntry(String tag, Double box) { // no type checks are done during construction. // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into // obj - // If an ANY entry is later type checked or request as a primitive, then the ANY will be + // If an ANY entry is later type checked or requested as a primitive, then the ANY will be // resolved // to the correct type. @@ -436,10 +482,22 @@ int hash() { return hash; } + @Override + public Entry entry() { + return this; + } + + @Override + public java.util.Map.Entry mapEntry() { + return this; + } + + @Override public byte type() { return this.resolveAny(); } + @Override public boolean is(byte type) { byte curType = this.rawType; if (curType == type) { @@ -451,6 +509,7 @@ public boolean is(byte type) { } } + @Override public boolean isNumericPrimitive() { byte curType = this.rawType; if (_isNumericPrimitive(curType)) { @@ -462,6 +521,7 @@ public boolean isNumericPrimitive() { } } + @Override public boolean isNumber() { byte curType = this.rawType; return _isNumericPrimitive(curType) || (this.rawObj instanceof Number); @@ -517,6 +577,7 @@ private void _setPrim(byte type, long prim) { this.rawType = type; } + @Override public boolean isObject() { return this.is(OBJECT); } @@ -525,6 +586,7 @@ public boolean isRemoval() { return false; } + @Override public Object objectValue() { if (this.rawObj != null) { return this.rawObj; @@ -562,6 +624,7 @@ public Object objectValue() { return this.rawObj; } + @Override public boolean booleanValue() { byte type = this.rawType; @@ -597,6 +660,7 @@ public boolean booleanValue() { return false; } + @Override public int intValue() { byte type = this.rawType; @@ -632,6 +696,7 @@ public int intValue() { return 0; } + @Override public long longValue() { byte type = this.rawType; @@ -667,6 +732,7 @@ public long longValue() { return 0; } + @Override public float floatValue() { byte type = this.rawType; @@ -702,6 +768,7 @@ public float floatValue() { return 0F; } + @Override public double doubleValue() { byte type = this.rawType; @@ -737,6 +804,7 @@ public double doubleValue() { return 0D; } + @Override public String stringValue() { String strCache = this.strCache; if (strCache != null) { @@ -1266,8 +1334,8 @@ public boolean containsKey(Object key) { @Override public boolean containsValue(Object value) { // This could be optimized - but probably isn't called enough to be worth it - for (Entry entry : this) { - if (entry.objectValue().equals(value)) return true; + for (EntryReader entryReader : this) { + if (entryReader.objectValue().equals(value)) return true; } return false; } @@ -1317,8 +1385,8 @@ public Object put(String tag, Object value) { } @Override - public void set(TagMap.Entry newEntry) { - this.getAndSet(newEntry); + public void set(TagMap.EntryReader newEntryReader) { + this.getAndSet(newEntryReader.entry()); } @Override @@ -1727,17 +1795,22 @@ public TagMap immutableCopy() { } @Override - public Iterator iterator() { - return new EntryIterator(this); + public Iterator iterator() { + return new EntryReaderIterator(this); } @Override - public Stream stream() { + public Stream stream() { return StreamSupport.stream(spliterator(), false); } @Override - public void forEach(Consumer consumer) { + public EntryIterator entryIterator() { + return new EntryIteratorImpl(this); + } + + @Override + public void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; for (int i = 0; i < thisBuckets.length; ++i) { @@ -1756,7 +1829,7 @@ public void forEach(Consumer consumer) { } @Override - public void forEach(T thisObj, BiConsumer consumer) { + public void forEach(T thisObj, BiConsumer consumer) { Object[] thisBuckets = this.buckets; for (int i = 0; i < thisBuckets.length; ++i) { @@ -1776,7 +1849,7 @@ public void forEach(T thisObj, BiConsumer consumer) @Override public void forEach( - T thisObj, U otherObj, TriConsumer consumer) { + T thisObj, U otherObj, TriConsumer consumer) { Object[] thisBuckets = this.buckets; for (int i = 0; i < thisBuckets.length; ++i) { @@ -1932,14 +2005,14 @@ String toPrettyString() { StringBuilder ledger = new StringBuilder(128); ledger.append('{'); - for (Entry entry : this) { + for (EntryReader entry : this) { if (first) { first = false; } else { ledger.append(", "); } - ledger.append(entry.tag).append('=').append(entry.stringValue()); + ledger.append(entry.tag()).append('=').append(entry.stringValue()); } ledger.append('}'); return ledger.toString(); @@ -1973,7 +2046,7 @@ String toInternalString() { return ledger.toString(); } - abstract static class MapIterator implements Iterator { + abstract static class IteratorBase { private final Object[] buckets; private Entry nextEntry; @@ -1983,12 +2056,11 @@ abstract static class MapIterator implements Iterator { private BucketGroup group = null; private int groupIndex = 0; - MapIterator(OptimizedTagMap map) { + IteratorBase(OptimizedTagMap map) { this.buckets = map.buckets; } - @Override - public boolean hasNext() { + public final boolean hasNext() { if (this.nextEntry != null) return true; while (this.bucketIndex < this.buckets.length) { @@ -1999,7 +2071,7 @@ public boolean hasNext() { return false; } - Entry nextEntry() { + final Entry nextEntryOrThrowNoSuchElement() { if (this.nextEntry != null) { Entry nextEntry = this.nextEntry; this.nextEntry = null; @@ -2013,7 +2085,17 @@ Entry nextEntry() { } } - private Entry advance() { + final Entry nextEntryOrNull() { + if (this.nextEntry != null) { + Entry nextEntry = this.nextEntry; + this.nextEntry = null; + return nextEntry; + } + + return this.hasNext() ? this.nextEntry : null; + } + + private final Entry advance() { while (this.bucketIndex < this.buckets.length) { if (this.group != null) { for (++this.groupIndex; this.groupIndex < BucketGroup.LEN; ++this.groupIndex) { @@ -2047,14 +2129,103 @@ private Entry advance() { } } - static final class EntryIterator extends MapIterator { - EntryIterator(OptimizedTagMap map) { + static final class EntryReaderIterator extends IteratorBase implements Iterator { + EntryReaderIterator(OptimizedTagMap map) { super(map); } @Override - public Entry next() { - return this.nextEntry(); + public EntryReader next() { + return this.nextEntryOrThrowNoSuchElement(); + } + } + + static final class EntryIteratorImpl extends IteratorBase implements EntryIterator { + private TagMap.Entry curEntry; + + EntryIteratorImpl(OptimizedTagMap map) { + super(map); + } + + @Override + public boolean next() { + this.curEntry = this.nextEntryOrNull(); + return (this.curEntry != null); + } + + @Override + public String tag() { + return this.curEntry.tag(); + } + + @Override + public byte type() { + return this.curEntry.type(); + } + + @Override + public boolean is(byte type) { + return this.curEntry.is(type); + } + + @Override + public boolean isNumber() { + return this.curEntry.isNumber(); + } + + @Override + public boolean isNumericPrimitive() { + return this.curEntry.isNumericPrimitive(); + } + + @Override + public boolean isObject() { + return this.curEntry.isObject(); + } + + @Override + public boolean booleanValue() { + return this.curEntry.booleanValue(); + } + + @Override + public int intValue() { + return this.curEntry.intValue(); + } + + @Override + public long longValue() { + return this.curEntry.longValue(); + } + + @Override + public float floatValue() { + return this.curEntry.floatValue(); + } + + @Override + public double doubleValue() { + return this.curEntry.doubleValue(); + } + + @Override + public Object objectValue() { + return this.curEntry.objectValue(); + } + + @Override + public String stringValue() { + return this.curEntry.stringValue(); + } + + @Override + public Entry entry() { + return this.curEntry; + } + + @Override + public Map.Entry mapEntry() { + return this.curEntry; } } @@ -2406,39 +2577,40 @@ Entry _remove(int hash, String tag) { return existingEntry; } - void forEachInChain(Consumer consumer) { + void forEachInChain(Consumer consumer) { for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(consumer); } } - void _forEach(Consumer consumer) { + void _forEach(Consumer consumer) { if (this.entry0 != null) consumer.accept(this.entry0); if (this.entry1 != null) consumer.accept(this.entry1); if (this.entry2 != null) consumer.accept(this.entry2); if (this.entry3 != null) consumer.accept(this.entry3); } - void forEachInChain(T thisObj, BiConsumer consumer) { + void forEachInChain(T thisObj, BiConsumer consumer) { for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(thisObj, consumer); } } - void _forEach(T thisObj, BiConsumer consumer) { + void _forEach(T thisObj, BiConsumer consumer) { if (this.entry0 != null) consumer.accept(thisObj, this.entry0); if (this.entry1 != null) consumer.accept(thisObj, this.entry1); if (this.entry2 != null) consumer.accept(thisObj, this.entry2); if (this.entry3 != null) consumer.accept(thisObj, this.entry3); } - void forEachInChain(T thisObj, U otherObj, TriConsumer consumer) { + void forEachInChain( + T thisObj, U otherObj, TriConsumer consumer) { for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(thisObj, otherObj, consumer); } } - void _forEach(T thisObj, U otherObj, TriConsumer consumer) { + void _forEach(T thisObj, U otherObj, TriConsumer consumer) { if (this.entry0 != null) consumer.accept(thisObj, otherObj, this.entry0); if (this.entry1 != null) consumer.accept(thisObj, otherObj, this.entry1); if (this.entry2 != null) consumer.accept(thisObj, otherObj, this.entry2); @@ -2574,14 +2746,14 @@ public Iterator iterator() { } } - static final class KeysIterator extends MapIterator { + static final class KeysIterator extends IteratorBase implements Iterator { KeysIterator(OptimizedTagMap map) { super(map); } @Override public String next() { - return this.nextEntry().tag(); + return this.nextEntryOrThrowNoSuchElement().tag(); } } @@ -2613,18 +2785,112 @@ public Iterator iterator() { } } - static final class ValuesIterator extends MapIterator { + static final class ValuesIterator extends IteratorBase implements Iterator { ValuesIterator(OptimizedTagMap map) { super(map); } @Override public Object next() { - return this.nextEntry().objectValue(); + return this.nextEntryOrThrowNoSuchElement().objectValue(); } } } +final class EntryReadingHelper implements TagMap.EntryReader { + private Map.Entry mapEntry; + private String tag; + private Object value; + + void set(String tag, Object value) { + this.mapEntry = null; + this.tag = tag; + this.value = value; + } + + void set(Map.Entry mapEntry) { + this.mapEntry = mapEntry; + this.tag = mapEntry.getKey(); + this.value = mapEntry.getValue(); + } + + @Override + public String tag() { + return this.tag; + } + + @Override + public byte type() { + return TagValueConversions.typeOf(this.value); + } + + @Override + public boolean is(byte type) { + return TagValueConversions.isA(this.value, type); + } + + @Override + public boolean isNumber() { + return TagValueConversions.isNumber(this.value); + } + + @Override + public boolean isNumericPrimitive() { + return TagValueConversions.isNumericPrimitive(this.value); + } + + @Override + public boolean isObject() { + return TagValueConversions.isObject(this.value); + } + + @Override + public boolean booleanValue() { + return TagValueConversions.toBoolean(this.value); + } + + @Override + public int intValue() { + return TagValueConversions.toInt(this.value); + } + + @Override + public long longValue() { + return TagValueConversions.toLong(this.value); + } + + @Override + public float floatValue() { + return TagValueConversions.toFloat(this.value); + } + + @Override + public double doubleValue() { + return TagValueConversions.toDouble(this.value); + } + + @Override + public String stringValue() { + return TagValueConversions.toString(this.value); + } + + @Override + public Object objectValue() { + return this.value; + } + + @Override + public TagMap.Entry entry() { + return TagMap.Entry.newAnyEntry(this.tag, this.value); + } + + @Override + public Map.Entry mapEntry() { + Map.Entry mapEntry = this.mapEntry; + return (mapEntry != null) ? mapEntry : this.entry(); + } +} + final class LegacyTagMap extends HashMap implements TagMap { private static final long serialVersionUID = 77473435283123683L; @@ -2688,25 +2954,35 @@ public void fillStringMap(Map stringMap) { } @Override - public void forEach(Consumer consumer) { + public void forEach(Consumer consumer) { + EntryReadingHelper entryReadingHelper = new EntryReadingHelper(); + + // TODO: optimize to take advantage of EntryReader for (Map.Entry entry : this.entrySet()) { consumer.accept(TagMap.Entry.newAnyEntry(entry)); } } @Override - public void forEach( - T thisObj, BiConsumer consumer) { + public void forEach(T thisObj, BiConsumer consumer) { + EntryReadingHelper entryReadingHelper = new EntryReadingHelper(); + for (Map.Entry entry : this.entrySet()) { - consumer.accept(thisObj, TagMap.Entry.newAnyEntry(entry)); + entryReadingHelper.set(entry); + + consumer.accept(thisObj, entryReadingHelper); } } @Override public void forEach( - T thisObj, U otherObj, TriConsumer consumer) { + T thisObj, U otherObj, TriConsumer consumer) { + EntryReadingHelper entryReadingHelper = new EntryReadingHelper(); + for (Map.Entry entry : this.entrySet()) { - consumer.accept(thisObj, otherObj, TagMap.Entry.newAnyEntry(entry)); + entryReadingHelper.set(entry); + + consumer.accept(thisObj, otherObj, entryReadingHelper); } } @@ -2911,8 +3187,8 @@ public void set(String tag, Object value) { } @Override - public void set(TagMap.Entry newEntry) { - this.put(newEntry.tag(), newEntry.objectValue()); + public void set(TagMap.EntryReader newEntryReader) { + this.put(newEntryReader.tag(), newEntryReader.objectValue()); } @Override @@ -2989,20 +3265,27 @@ public TagMap immutableCopy() { } @Override - public Iterator iterator() { + public Iterator iterator() { return new IteratorImpl(this); } @Override - public Stream stream() { + public Stream stream() { return StreamSupport.stream(this.spliterator(), false); } - private static final class IteratorImpl implements Iterator { + @Override + public TagMap.EntryIterator entryIterator() { + return new EntryIteratorImpl(this); + } + + private static final class IteratorImpl implements Iterator { private final Iterator> wrappedIter; + private final EntryReadingHelper entryReadingHelper; IteratorImpl(LegacyTagMap legacyMap) { this.wrappedIter = legacyMap.entrySet().iterator(); + this.entryReadingHelper = new EntryReadingHelper(); } @Override @@ -3011,8 +3294,275 @@ public boolean hasNext() { } @Override - public TagMap.Entry next() { - return TagMap.Entry.newAnyEntry(this.wrappedIter.next()); + public TagMap.EntryReader next() { + Map.Entry entry = this.wrappedIter.next(); + this.entryReadingHelper.set(entry.getKey(), entry.getValue()); + + return this.entryReadingHelper; + } + } + + private static final class EntryIteratorImpl implements EntryIterator { + private final Iterator> wrappedIter; + private Map.Entry curEntry; + + EntryIteratorImpl(LegacyTagMap legacyMap) { + this.wrappedIter = legacyMap.entrySet().iterator(); + } + + @Override + public boolean hasNext() { + return this.wrappedIter.hasNext(); + } + + @Override + public boolean next() { + boolean hasNext = this.wrappedIter.hasNext(); + if (!hasNext) return false; + + this.curEntry = this.wrappedIter.next(); + return true; + } + + @Override + public String tag() { + return this.curEntry.getKey(); + } + + @Override + public byte type() { + return TagValueConversions.typeOf(this.curEntry.getValue()); + } + + @Override + public boolean is(byte type) { + return TagValueConversions.isA(this.curEntry.getValue(), type); + } + + @Override + public boolean isNumber() { + return TagValueConversions.isNumber(this.curEntry.getValue()); + } + + @Override + public boolean isNumericPrimitive() { + return TagValueConversions.isNumericPrimitive(this.curEntry.getValue()); + } + + @Override + public boolean isObject() { + return TagValueConversions.isObject(this.curEntry.getValue()); + } + + @Override + public TagMap.Entry entry() { + return TagMap.Entry.newAnyEntry(this.curEntry); + } + + @Override + public Map.Entry mapEntry() { + return this.curEntry; + } + + @Override + public boolean booleanValue() { + return TagValueConversions.toBoolean(this.curEntry.getValue()); + } + + @Override + public int intValue() { + return TagValueConversions.toInt(this.curEntry.getValue()); + } + + @Override + public long longValue() { + return TagValueConversions.toLong(this.curEntry.getValue()); + } + + @Override + public float floatValue() { + return TagValueConversions.toFloat(this.curEntry.getValue()); + } + + @Override + public double doubleValue() { + return TagValueConversions.toDouble(this.curEntry.getValue()); } + + @Override + public Object objectValue() { + return this.curEntry.getValue(); + } + + @Override + public String stringValue() { + return TagValueConversions.toString(this.curEntry.getValue()); + } + } +} + +final class TagValueConversions { + TagValueConversions() {} + + static byte typeOf(Object value) { + if (value instanceof Integer) { + return TagMap.EntryReader.INT; + } else if (value instanceof Long) { + return TagMap.EntryReader.LONG; + } else if (value instanceof Double) { + return TagMap.EntryReader.DOUBLE; + } else if (value instanceof Float) { + return TagMap.EntryReader.FLOAT; + } else if (value instanceof Boolean) { + return TagMap.EntryReader.BOOLEAN; + } else if (value instanceof Short) { + return TagMap.EntryReader.SHORT; + } else if (value instanceof Byte) { + return TagMap.EntryReader.BYTE; + } else if (value instanceof Character) { + return TagMap.EntryReader.CHAR; + } else { + return TagMap.EntryReader.OBJECT; + } + } + + static boolean isA(Object value, byte type) { + switch (type) { + case TagMap.EntryReader.BYTE: + return (value instanceof Byte); + + case TagMap.EntryReader.BOOLEAN: + return (value instanceof Boolean); + + case TagMap.EntryReader.CHAR: + return (value instanceof Character); + + case TagMap.EntryReader.SHORT: + return (value instanceof Short); + + case TagMap.EntryReader.INT: + return (value instanceof Integer); + + case TagMap.EntryReader.LONG: + return (value instanceof Long); + + case TagMap.EntryReader.FLOAT: + return (value instanceof Float); + + case TagMap.EntryReader.DOUBLE: + return (value instanceof Double); + + case TagMap.EntryReader.OBJECT: + return true; + + default: + return false; + } + } + + static boolean isNumber(Object value) { + return (value instanceof Number); + } + + static boolean isNumericPrimitive(Object value) { + return (value instanceof Integer) + || (value instanceof Long) + || (value instanceof Double) + || (value instanceof Float) + || (value instanceof Short) + || (value instanceof Byte); + } + + static boolean isObject(Object value) { + return (value instanceof Integer) + || (value instanceof Long) + || (value instanceof Double) + || (value instanceof Float) + || (value instanceof Boolean) + || (value instanceof Character) + || (value instanceof Short) + || (value instanceof Byte); + } + + static boolean toBooleanOrDefault(Object value, boolean defaultValue) { + return (value == null) ? defaultValue : toBoolean(value); + } + + static boolean toBoolean(Object value) { + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof Number) { + return ((Number) value).intValue() != 0; + } else { + return false; + } + } + + static int toIntOrDefault(Object value, int defaultValue) { + return (value == null) ? defaultValue : toInt(value); + } + + static int toInt(Object value) { + if (value instanceof Integer) { + return (Integer) value; + } else if (value instanceof Number) { + return ((Number) value).intValue(); + } else if (value instanceof Boolean) { + return (Boolean) value ? 1 : 0; + } else { + return 0; + } + } + + static long toLong(Object value) { + if (value instanceof Long) { + return (Long) value; + } else if (value instanceof Number) { + return ((Number) value).longValue(); + } else if (value instanceof Boolean) { + return (Boolean) value ? 1L : 0L; + } else { + return 0L; + } + } + + static long toLongOrDefault(Object value, long defaultValue) { + return (value == null) ? defaultValue : toLong(value); + } + + static float toFloat(Object value) { + if (value instanceof Float) { + return (Float) value; + } else if (value instanceof Number) { + return ((Number) value).floatValue(); + } else if (value instanceof Boolean) { + return (Boolean) value ? 1F : 0F; + } else { + return 0F; + } + } + + static float toFloatOrDefault(Object value, float defaultValue) { + return (value == null) ? defaultValue : toFloat(value); + } + + static double toDouble(Object value) { + if (value instanceof Double) { + return (Double) value; + } else if (value instanceof Number) { + return ((Number) value).doubleValue(); + } else if (value instanceof Boolean) { + return (Boolean) value ? 1D : 0D; + } else { + return 0D; + } + } + + static double toDoubleOrDefault(Object value, double defaultValue) { + return (value == null) ? defaultValue : toDouble(value); + } + + static String toString(Object value) { + return value.toString(); } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java index 692aaaf37f3..48254ae9bd1 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -1023,7 +1023,7 @@ static final void assertMapEquals(Map expected, OptimizedTagMap assertEquals(expectedEntry.getValue(), actualEntry.getValue()); } - for (TagMap.Entry actualEntry : actual) { + for (TagMap.EntryReader actualEntry : actual) { Object expectedValue = expected.get(actualEntry.tag()); assertEquals(expectedValue, actualEntry.objectValue()); } @@ -1378,7 +1378,7 @@ protected void _applyToExpectedMap(Map expectedMap) { @Override public void verifyTestMap(TagMap expectedMap) { - for (TagMap.Entry entry : this.tagMap) { + for (TagMap.EntryReader entry : this.tagMap) { assertEquals(entry.objectValue(), expectedMap.get(entry.tag()), "key=" + entry.tag()); } } @@ -1417,7 +1417,7 @@ public void verifyTestMap(TagMap expectedMap) { // ledger may contain multiple updates of the same key // easier to produce a TagMap and check against it - for (TagMap.Entry entry : this.ledger.buildImmutable()) { + for (TagMap.EntryReader entry : this.ledger.buildImmutable()) { assertEquals(entry.objectValue(), expectedMap.get(entry.tag()), "key=" + entry.tag()); } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 0c89086d84e..f48c2adc0dd 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -787,7 +787,7 @@ public void iterator(TagMapType mapType) { TagMap map = createTagMap(mapType, size); Set keys = new HashSet<>(); - for (TagMap.Entry entry : map) { + for (TagMap.EntryReader entry : map) { // makes sure that each key is visited once and only once assertTrue(keys.add(entry.tag())); } From 8720c240a78d5681167bd8ad001c3860ac6a269f Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 12 Jan 2026 15:45:42 -0500 Subject: [PATCH 02/22] Adding tagIterator and valueIterator --- .../main/java/datadog/trace/api/TagMap.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) 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 5423de22e66..b9113138e56 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -86,10 +86,14 @@ static Ledger ledger(int size) { /** Inefficiently implemented for optimized TagMap */ @Deprecated Set keySet(); + + Iterator tagIterator(); /** Inefficiently implemented for optimized TagMap - requires boxing primitives */ @Deprecated Collection values(); + + Iterator valueIterator(); // @Deprecated -- not deprecated until OptimizedTagMap becomes the default Set> entrySet(); @@ -1344,11 +1348,21 @@ public boolean containsValue(Object value) { public Set keySet() { return new Keys(this); } + + @Override + public Iterator tagIterator() { + return new KeysIterator(this); + } @Override public Collection values() { return new Values(this); } + + @Override + public Iterator valueIterator() { + return new ValuesIterator(this); + } @Override public Set> entrySet() { @@ -2940,6 +2954,16 @@ public void checkWriteAccess() { public TagMap copy() { return new LegacyTagMap(this); } + + @Override + public Iterator tagIterator() { + return this.keySet().iterator(); + } + + @Override + public Iterator valueIterator() { + return this.values().iterator(); + } @Override public void fillMap(Map map) { @@ -2959,7 +2983,9 @@ public void forEach(Consumer consumer) { // TODO: optimize to take advantage of EntryReader for (Map.Entry entry : this.entrySet()) { - consumer.accept(TagMap.Entry.newAnyEntry(entry)); + entryReadingHelper.set(entry); + + consumer.accept(entryReadingHelper); } } From fbec328efec96daa436653a3b1065c3e78e680e6 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 12 Jan 2026 15:46:06 -0500 Subject: [PATCH 03/22] Adding checks for EntryIterator --- .../test/java/datadog/trace/api/TagMapTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index f48c2adc0dd..e683b506013 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -20,6 +20,8 @@ import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; +import datadog.trace.api.TagMap.EntryIterator; + public class TagMapTest { // size is chosen to make sure to stress all types of collisions in the Map static final int MANY_SIZE = 256; @@ -1005,6 +1007,14 @@ static int count(Iterator iter) { } return count; } + + static int count(EntryIterator entryIter) { + int count; + for ( count = 0; entryIter.next(); ++count ) { + // nop + } + return count; + } static final void assertBoolean(boolean expected, TagMap map, String key) { assertEquals(expected, map.getBoolean(key)); @@ -1064,8 +1074,14 @@ static final void assertSize(int size, TagMap map) { assertEquals(size, count(map)); assertEquals(size, map.keySet().size()); assertEquals(size, map.values().size()); + assertEquals(size, count(map.keySet())); + assertEquals(size, count(map.tagIterator())); + + assertEquals(size, count(map.values().iterator())); assertEquals(size, count(map.values())); + + assertEquals(size, count(map.entryIterator())); } static void assertEmptiness(TagMapScenario scenario, TagMap map) { From 2864bacbb9b9dbf2e0f8662ca39f1daf6c3d9fdf Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 12 Jan 2026 16:02:27 -0500 Subject: [PATCH 04/22] Removing EntryIterator and EntryChangeIterator EntryIterator and EntryChangeIterator are arguably redundant --- .../main/java/datadog/trace/api/TagMap.java | 211 +----------------- .../java/datadog/trace/api/TagMapTest.java | 12 - 2 files changed, 1 insertion(+), 222 deletions(-) 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 b9113138e56..224a06e6e1e 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -245,8 +245,6 @@ static Ledger ledger(int size) { Stream stream(); - EntryIterator entryIterator(); - /** * Visits each Entry in this TagMap This method is more efficient than {@link TagMap#iterator()} */ @@ -364,16 +362,6 @@ interface EntryReader { Entry entry(); } - interface EntryIterator extends EntryReader { - boolean hasNext(); - - boolean next(); - } - - interface EntryChangeIterator extends EntryIterator { - boolean isRemoval(); - } - final class Entry extends EntryChange implements Map.Entry, EntryReader { /* * Special value used for Objects that haven't been type checked yet. @@ -1818,11 +1806,6 @@ public Stream stream() { return StreamSupport.stream(spliterator(), false); } - @Override - public EntryIterator entryIterator() { - return new EntryIteratorImpl(this); - } - @Override public void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; @@ -2153,96 +2136,7 @@ public EntryReader next() { return this.nextEntryOrThrowNoSuchElement(); } } - - static final class EntryIteratorImpl extends IteratorBase implements EntryIterator { - private TagMap.Entry curEntry; - - EntryIteratorImpl(OptimizedTagMap map) { - super(map); - } - - @Override - public boolean next() { - this.curEntry = this.nextEntryOrNull(); - return (this.curEntry != null); - } - - @Override - public String tag() { - return this.curEntry.tag(); - } - - @Override - public byte type() { - return this.curEntry.type(); - } - - @Override - public boolean is(byte type) { - return this.curEntry.is(type); - } - - @Override - public boolean isNumber() { - return this.curEntry.isNumber(); - } - - @Override - public boolean isNumericPrimitive() { - return this.curEntry.isNumericPrimitive(); - } - - @Override - public boolean isObject() { - return this.curEntry.isObject(); - } - - @Override - public boolean booleanValue() { - return this.curEntry.booleanValue(); - } - - @Override - public int intValue() { - return this.curEntry.intValue(); - } - - @Override - public long longValue() { - return this.curEntry.longValue(); - } - - @Override - public float floatValue() { - return this.curEntry.floatValue(); - } - - @Override - public double doubleValue() { - return this.curEntry.doubleValue(); - } - - @Override - public Object objectValue() { - return this.curEntry.objectValue(); - } - - @Override - public String stringValue() { - return this.curEntry.stringValue(); - } - - @Override - public Entry entry() { - return this.curEntry; - } - - @Override - public Map.Entry mapEntry() { - return this.curEntry; - } - } - + /** * BucketGroup is a compromise for performance over a linked list or array * @@ -3300,11 +3194,6 @@ public Stream stream() { return StreamSupport.stream(this.spliterator(), false); } - @Override - public TagMap.EntryIterator entryIterator() { - return new EntryIteratorImpl(this); - } - private static final class IteratorImpl implements Iterator { private final Iterator> wrappedIter; private final EntryReadingHelper entryReadingHelper; @@ -3327,104 +3216,6 @@ public TagMap.EntryReader next() { return this.entryReadingHelper; } } - - private static final class EntryIteratorImpl implements EntryIterator { - private final Iterator> wrappedIter; - private Map.Entry curEntry; - - EntryIteratorImpl(LegacyTagMap legacyMap) { - this.wrappedIter = legacyMap.entrySet().iterator(); - } - - @Override - public boolean hasNext() { - return this.wrappedIter.hasNext(); - } - - @Override - public boolean next() { - boolean hasNext = this.wrappedIter.hasNext(); - if (!hasNext) return false; - - this.curEntry = this.wrappedIter.next(); - return true; - } - - @Override - public String tag() { - return this.curEntry.getKey(); - } - - @Override - public byte type() { - return TagValueConversions.typeOf(this.curEntry.getValue()); - } - - @Override - public boolean is(byte type) { - return TagValueConversions.isA(this.curEntry.getValue(), type); - } - - @Override - public boolean isNumber() { - return TagValueConversions.isNumber(this.curEntry.getValue()); - } - - @Override - public boolean isNumericPrimitive() { - return TagValueConversions.isNumericPrimitive(this.curEntry.getValue()); - } - - @Override - public boolean isObject() { - return TagValueConversions.isObject(this.curEntry.getValue()); - } - - @Override - public TagMap.Entry entry() { - return TagMap.Entry.newAnyEntry(this.curEntry); - } - - @Override - public Map.Entry mapEntry() { - return this.curEntry; - } - - @Override - public boolean booleanValue() { - return TagValueConversions.toBoolean(this.curEntry.getValue()); - } - - @Override - public int intValue() { - return TagValueConversions.toInt(this.curEntry.getValue()); - } - - @Override - public long longValue() { - return TagValueConversions.toLong(this.curEntry.getValue()); - } - - @Override - public float floatValue() { - return TagValueConversions.toFloat(this.curEntry.getValue()); - } - - @Override - public double doubleValue() { - return TagValueConversions.toDouble(this.curEntry.getValue()); - } - - @Override - public Object objectValue() { - return this.curEntry.getValue(); - } - - @Override - public String stringValue() { - return TagValueConversions.toString(this.curEntry.getValue()); - } - } } final class TagValueConversions { diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index e683b506013..28e1b6bd682 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -20,8 +20,6 @@ import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; -import datadog.trace.api.TagMap.EntryIterator; - public class TagMapTest { // size is chosen to make sure to stress all types of collisions in the Map static final int MANY_SIZE = 256; @@ -1007,14 +1005,6 @@ static int count(Iterator iter) { } return count; } - - static int count(EntryIterator entryIter) { - int count; - for ( count = 0; entryIter.next(); ++count ) { - // nop - } - return count; - } static final void assertBoolean(boolean expected, TagMap map, String key) { assertEquals(expected, map.getBoolean(key)); @@ -1080,8 +1070,6 @@ static final void assertSize(int size, TagMap map) { assertEquals(size, count(map.values().iterator())); assertEquals(size, count(map.values())); - - assertEquals(size, count(map.entryIterator())); } static void assertEmptiness(TagMapScenario scenario, TagMap map) { From 43bbea18a87a85ea755c35c5cb0b618f1793947b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 12 Jan 2026 16:03:27 -0500 Subject: [PATCH 05/22] spotless --- .../main/java/datadog/trace/api/TagMap.java | 24 +++++++++---------- .../java/datadog/trace/api/TagMapTest.java | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) 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 224a06e6e1e..53fcbe0da18 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -86,13 +86,13 @@ static Ledger ledger(int size) { /** Inefficiently implemented for optimized TagMap */ @Deprecated Set keySet(); - + Iterator tagIterator(); /** Inefficiently implemented for optimized TagMap - requires boxing primitives */ @Deprecated Collection values(); - + Iterator valueIterator(); // @Deprecated -- not deprecated until OptimizedTagMap becomes the default @@ -1336,20 +1336,20 @@ public boolean containsValue(Object value) { public Set keySet() { return new Keys(this); } - + @Override public Iterator tagIterator() { - return new KeysIterator(this); + return new KeysIterator(this); } @Override public Collection values() { return new Values(this); } - + @Override public Iterator valueIterator() { - return new ValuesIterator(this); + return new ValuesIterator(this); } @Override @@ -2136,7 +2136,7 @@ public EntryReader next() { return this.nextEntryOrThrowNoSuchElement(); } } - + /** * BucketGroup is a compromise for performance over a linked list or array * @@ -2848,15 +2848,15 @@ public void checkWriteAccess() { public TagMap copy() { return new LegacyTagMap(this); } - + @Override public Iterator tagIterator() { - return this.keySet().iterator(); + return this.keySet().iterator(); } - + @Override public Iterator valueIterator() { - return this.values().iterator(); + return this.values().iterator(); } @Override @@ -2878,7 +2878,7 @@ public void forEach(Consumer consumer) { // TODO: optimize to take advantage of EntryReader for (Map.Entry entry : this.entrySet()) { entryReadingHelper.set(entry); - + consumer.accept(entryReadingHelper); } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 28e1b6bd682..ce5a6551164 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -1064,10 +1064,10 @@ static final void assertSize(int size, TagMap map) { assertEquals(size, count(map)); assertEquals(size, map.keySet().size()); assertEquals(size, map.values().size()); - + assertEquals(size, count(map.keySet())); assertEquals(size, count(map.tagIterator())); - + assertEquals(size, count(map.values().iterator())); assertEquals(size, count(map.values())); } From 3c0997feb47b2869164dde4479d9d5de9fab872b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 16 Jan 2026 09:29:18 -0500 Subject: [PATCH 06/22] Refining handling of primitive types Fixed bug TagValueConversions.toBoolean Could cause LegacyTagMap.EntryReader to produce incorrect answers to some queries For simplicity, now treating Byte and Short as Integer. That will make calling code doing primitive handling simpler. Fleshing out tests -- more tests to come --- .../main/java/datadog/trace/api/TagMap.java | 37 ++- .../datadog/trace/api/TagMapEntryTest.java | 263 +++++++++++++----- .../trace/api/TagValueConversionsTest.java | 85 ++++++ 3 files changed, 295 insertions(+), 90 deletions(-) create mode 100644 internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java 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 82a4a36d96a..edbea0e2071 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -319,13 +319,13 @@ interface EntryReader { * Non-numeric primitive types */ public static final byte BOOLEAN = 2; - public static final byte CHAR = 3; + static final byte CHAR_RESERVED = 3; /* * Numeric constants - deliberately arranged to allow for checking by using type >= BYTE */ - public static final byte BYTE = 4; - public static final byte SHORT = 5; + static final byte BYTE_RESERVED = 4; + static final byte SHORT_RESERVED = 5; public static final byte INT = 6; public static final byte LONG = 7; public static final byte FLOAT = 8; @@ -520,7 +520,7 @@ public boolean isNumber() { } static boolean _isNumericPrimitive(byte type) { - return (type >= BYTE); + return (type >= BYTE_RESERVED); } private byte resolveAny() { @@ -3234,11 +3234,11 @@ static byte typeOf(Object value) { } else if (value instanceof Boolean) { return TagMap.EntryReader.BOOLEAN; } else if (value instanceof Short) { - return TagMap.EntryReader.SHORT; + return TagMap.EntryReader.INT; } else if (value instanceof Byte) { - return TagMap.EntryReader.BYTE; + return TagMap.EntryReader.INT; } else if (value instanceof Character) { - return TagMap.EntryReader.CHAR; + return TagMap.EntryReader.OBJECT; } else { return TagMap.EntryReader.OBJECT; } @@ -3246,20 +3246,11 @@ static byte typeOf(Object value) { static boolean isA(Object value, byte type) { switch (type) { - case TagMap.EntryReader.BYTE: - return (value instanceof Byte); - case TagMap.EntryReader.BOOLEAN: return (value instanceof Boolean); - case TagMap.EntryReader.CHAR: - return (value instanceof Character); - - case TagMap.EntryReader.SHORT: - return (value instanceof Short); - case TagMap.EntryReader.INT: - return (value instanceof Integer); + return (value instanceof Integer) || (value instanceof Short) || (value instanceof Byte); case TagMap.EntryReader.LONG: return (value instanceof Long); @@ -3292,14 +3283,16 @@ static boolean isNumericPrimitive(Object value) { } static boolean isObject(Object value) { - return (value instanceof Integer) + boolean isSupportedPrimitive = (value instanceof Integer) || (value instanceof Long) || (value instanceof Double) || (value instanceof Float) || (value instanceof Boolean) - || (value instanceof Character) || (value instanceof Short) || (value instanceof Byte); + + // Char is just treated as Object + return !isSupportedPrimitive; } static boolean toBooleanOrDefault(Object value, boolean defaultValue) { @@ -3310,7 +3303,11 @@ static boolean toBoolean(Object value) { if (value instanceof Boolean) { return (Boolean) value; } else if (value instanceof Number) { - return ((Number) value).intValue() != 0; + // NOTE: This cannot be intValue() because intValue of larger types is 0 when + // the actual value would be less than Integer.MIN_VALUE, so using doubleValue. + + // While this is a bit ugly, coerced toBoolean is uncommon + return ((Number) value).doubleValue() != 0D; } else { return false; } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 1fc401d4db3..83f7e7497a2 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -1,5 +1,6 @@ package datadog.trace.api; +import static org.junit.Assert.assertSame; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -36,11 +37,11 @@ public class TagMapEntryTest { public void isNumericPrimitive() { assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.ANY)); assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.BOOLEAN)); - assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.CHAR)); + assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.CHAR_RESERVED)); assertFalse(TagMap.Entry._isNumericPrimitive(TagMap.Entry.OBJECT)); - assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.BYTE)); - assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.SHORT)); + assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.BYTE_RESERVED)); + assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.SHORT_RESERVED)); assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.INT)); assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.LONG)); assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.FLOAT)); @@ -88,6 +89,7 @@ public void booleanEntry(boolean value) { checkValue(value, entry), checkFalse(entry::isNumericPrimitive), checkFalse(entry::isNumber), + checkFalse(entry::isObject), checkType(TagMap.Entry.BOOLEAN, entry))); } @@ -103,6 +105,7 @@ public void booleanEntry_boxed(boolean value) { checkValue(value, entry), checkFalse(entry::isNumericPrimitive), checkFalse(entry::isNumber), + checkFalse(entry::isObject), checkType(TagMap.Entry.BOOLEAN, entry))); } @@ -118,6 +121,7 @@ public void anyEntry_boolean(boolean value) { checkValue(value, entry), checkFalse(entry::isNumericPrimitive), checkFalse(entry::isNumber), + checkFalse(entry::isObject), checkType(TagMap.Entry.BOOLEAN, entry), checkValue(value, entry))); } @@ -364,15 +368,23 @@ public Thread newThread(Runnable r) { }); static final void test( - Supplier entrySupplier, byte rawType, Function checks) { + Supplier entrySupplier, byte rawType, Function checkSupplier) { + + Function combinedCheckSupplier = (entry) -> { + return multiCheck( + checkSupplier.apply(entry), + checkSame(entry, entry.entry()), + checkSame(entry, entry.mapEntry())); + }; + // repeat the test several times to exercise different orderings in this thread for (int i = 0; i < 10; ++i) { - testSingleThreaded(entrySupplier, rawType, checks); + testSingleThreaded(entrySupplier, rawType, combinedCheckSupplier); } // same for multi-threaded for (int i = 0; i < 5; ++i) { - testMultiThreaded(entrySupplier, rawType, checks); + testMultiThreaded(entrySupplier, rawType, combinedCheckSupplier); } } @@ -435,22 +447,65 @@ static final void testMultiThreaded( static final void assertChecks(Check check) { check.check(); } - + static final Check checkKey(String expected, TagMap.Entry entry) { return multiCheck(checkEquals(expected, entry::tag), checkEquals(expected, entry::getKey)); } - + + static final Supplier of(Supplier supplier, String identifier) { + return new Supplier() { + @Override + public T get() { + return supplier.get(); + } + + @Override + public String toString() { + return identifier; + } + }; + } + + static final Function of(Function func, String identifier) { + return new Function() { + @Override + public O apply(I input) { + return func.apply(input); + } + + @Override + public String toString() { + return identifier; + } + }; + } + + static final Supplier of(Function output, Supplier input) { + return of(output, input, output.toString() + "(" + input.toString() + ")"); + } + + static final Supplier of(Function output, Supplier input, String identifier) { + return of(() -> output.apply(input.get()), identifier); + } + static final Check checkIsNumericPrimitive(TagMap.Entry entry) { return multiCheck( - checkTrue(entry::isNumericPrimitive), - checkTrue(entry::isNumber), - checkInstanceOf(Number.class, entry)); + checkTrue(entry::isNumericPrimitive, "Entry::isNumericPrimitive"), + checkTrue(entry::isNumber, "Entry::isNumber"), + checkInstanceOf(Number.class, entry), + checkFalse(entry::isObject, "Entry::isObject"), + checkTrue(of(TagValueConversions::isNumber, entry::objectValue), "isNumber(Object)"), + checkTrue(of(TagValueConversions::isNumericPrimitive, entry::objectValue), "isNumericPrimitive(Object)"), + checkFalse(of(TagValueConversions::isObject, entry::objectValue), "isObject(Object)")); } static final Check checkIsBigNumber(TagMap.Entry entry) { return multiCheck( - checkFalse(entry::isNumericPrimitive), - checkTrue(entry::isNumber), + checkFalse(entry::isNumericPrimitive, "Entry::isNumericPrimitive"), + checkFalse(of(TagValueConversions::isNumericPrimitive, entry::objectValue), "isNumericPrimitive(Object)"), + checkTrue(entry::isNumber, "Entry::isNumber"), + checkTrue(entry::isObject, "Entry::isObject"), + checkTrue(of(TagValueConversions::isNumber, entry::objectValue), "isNumber(Object)"), checkInstanceOf(Number.class, entry)); } @@ -463,116 +518,184 @@ static final Check checkValue(Object expected, TagMap.Entry entry) { static final Check checkValue(boolean expected, TagMap.Entry entry) { return multiCheck( - checkEquals(expected, entry::booleanValue), - checkEquals(Boolean.valueOf(expected), entry::objectValue), - checkEquals(expected ? 1 : 0, entry::intValue), - checkEquals(expected ? 1L : 0L, entry::longValue), - checkEquals(expected ? 1D : 0D, entry::doubleValue), - checkEquals(expected ? 1F : 0F, entry::floatValue), - checkEquals(Boolean.toString(expected), entry::stringValue)); + checkEquals(expected, entry::booleanValue, "Entry::booleanValue"), + checkEquals(Boolean.valueOf(expected), entry::objectValue, "Entry::objectValue"), + checkEquals(expected ? 1 : 0, entry::intValue, "Entry::intValue"), + checkEquals(expected ? 1 : 0, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), + checkEquals(expected ? 1L : 0L, entry::longValue, "Entry::longValue"), + checkEquals(expected ? 1L : 0L, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), + checkEquals(expected ? 1D : 0D, entry::doubleValue, "Entry::doubleValue"), + checkEquals(expected ? 1D : 0D, of(TagValueConversions::toDouble, entry::objectValue), "toDouble(Object)"), + checkEquals(expected ? 1F : 0F, entry::floatValue, "Entry::floatValue"), + checkEquals(expected ? 1F : 0F, of(TagValueConversions::toFloat, entry::objectValue), "toFloat(Object)"), + checkEquals(Boolean.toString(expected), entry::stringValue, "Entry::stringValue"), + checkEquals(Boolean.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); } static final Check checkValue(int expected, TagMap.Entry entry) { return multiCheck( - checkEquals(expected, entry::intValue), - checkEquals((long) expected, entry::longValue), - checkEquals((float) expected, entry::floatValue), - checkEquals((double) expected, entry::doubleValue), - checkEquals(Integer.valueOf(expected), entry::objectValue), - checkEquals(expected != 0, entry::booleanValue), - checkEquals(Integer.toString(expected), entry::stringValue)); + checkEquals(expected, entry::intValue, "Entry::intValue"), + checkEquals(Integer.valueOf(expected), entry::objectValue, "Entry::objectValue"), + checkEquals((long) expected, entry::longValue, "Entry::longValue"), + checkEquals((long)expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), + checkEquals((float) expected, entry::floatValue, "Entry::floatValue"), + checkEquals((float)expected, of(TagValueConversions::toFloat, entry::objectValue), "toFloat(Object)"), + checkEquals((double) expected, entry::doubleValue, "Entry::doubleValue"), + checkEquals((double)expected, of(TagValueConversions::toDouble, entry::objectValue), "toDouble(Object)"), + checkEquals(expected != 0, entry::booleanValue, "Entry::booleanValue"), + checkEquals(expected != 0, of(TagValueConversions::toBoolean, entry::objectValue), "toBoolean(Object)"), + checkEquals(Integer.toString(expected), entry::stringValue, "Entry::stringValue"), + checkEquals(Integer.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); } static final Check checkValue(long expected, TagMap.Entry entry) { return multiCheck( - checkEquals(expected, entry::longValue), - checkEquals((int) expected, entry::intValue), - checkEquals((float) expected, entry::floatValue), - checkEquals((double) expected, entry::doubleValue), - checkEquals(Long.valueOf(expected), entry::objectValue), - checkEquals(expected != 0L, entry::booleanValue), - checkEquals(Long.toString(expected), entry::stringValue)); + checkEquals(expected, entry::longValue, "Entry::longValue"), + checkEquals(Long.valueOf(expected), entry::objectValue, "Entry::objectValue"), + checkEquals((int) expected, entry::intValue, "Entry::intValue"), + checkEquals((int)expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), + checkEquals((float) expected, entry::floatValue, "Entry::floatValue"), + checkEquals((float)expected, of(TagValueConversions::toFloat, entry::objectValue), "toFloat(Object)"), + checkEquals((double) expected, entry::doubleValue, "Entry::doubleValue"), + checkEquals((double)expected, of(TagValueConversions::toDouble, entry::objectValue), "toDouble(Object)"), + checkEquals(expected != 0L, entry::booleanValue, "Entry::booleanValue"), + checkEquals(expected != 0L, of(TagValueConversions::toBoolean, entry::objectValue), "toBoolean(Object)"), + checkEquals(Long.toString(expected), entry::stringValue, "Entry::stringValue"), + checkEquals(Long.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); } static final Check checkValue(double expected, TagMap.Entry entry) { return multiCheck( - checkEquals(expected, entry::doubleValue), - checkEquals((int) expected, entry::intValue), - checkEquals((long) expected, entry::longValue), - checkEquals((float) expected, entry::floatValue), - checkEquals(Double.valueOf(expected), entry::objectValue), - checkEquals(expected != 0D, entry::booleanValue), - checkEquals(Double.toString(expected), entry::stringValue)); + checkEquals(expected, entry::doubleValue, "Entry::doubleValue"), + checkEquals(Double.valueOf(expected), entry::objectValue, "Entry::objectValue"), + checkEquals((int) expected, entry::intValue, "Entry::intValue"), + checkEquals((int)expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), + checkEquals((long) expected, entry::longValue, "Entry::longValue"), + checkEquals((long)expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), + checkEquals((float) expected, entry::floatValue, "Entry::floatValue"), + checkEquals((float)expected, of(TagValueConversions::toFloat, entry::objectValue), "toFloat(Object)"), + checkEquals(expected != 0D, entry::booleanValue, "Entry::booleanValue"), + checkEquals(expected != 0D, of(TagValueConversions::toBoolean, entry::objectValue), "toBoolean(Object)"), + checkEquals(Double.toString(expected), entry::stringValue, "Entry::stringValue"), + checkEquals(Double.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); + } + + static final Check checkValue(float expected, TagMap.Entry entry) { + return multiCheck( + checkEquals(expected, entry::floatValue, "Entry::floatValue"), + checkEquals(Float.valueOf(expected), entry::objectValue, "Entry::objectValue"), + checkEquals((int) expected, entry::intValue, "Entry::intValue"), + checkEquals((int)expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), + checkEquals((long) expected, entry::longValue, "Entry::longValue"), + checkEquals((long)expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), + checkEquals((double) expected, entry::doubleValue, "Entry::doubleValue"), + checkEquals((double)expected, of(TagValueConversions::toDouble, entry::objectValue), "toDouble(Object)"), + checkEquals(expected != 0F, entry::booleanValue, "Entry::booleanValue"), + checkEquals(expected != 0F, of(TagValueConversions::toBoolean, entry::objectValue), "toBoolean(Object)"), + checkEquals(Float.toString(expected), entry::stringValue, "Entry::stringValue"), + checkEquals(Float.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); } public static Check checkNumber(Number number, TagMap.Entry entry) { return multiCheck( checkEquals(number, entry::objectValue), checkEquals(number.intValue(), entry::intValue), + checkEquals(number.intValue(), of(TagValueConversions::toInt, entry::objectValue)), checkEquals(number.longValue(), entry::longValue), + checkEquals(number.longValue(), of(TagValueConversions::toLong, entry::objectValue)), checkEquals(number.floatValue(), entry::floatValue), + checkEquals(number.floatValue(), of(TagValueConversions::toFloat, entry::objectValue)), checkEquals(number.doubleValue(), entry::doubleValue), - checkEquals(number.toString(), entry::stringValue)); - } - - static final Check checkValue(float expected, TagMap.Entry entry) { - return multiCheck( - checkEquals(expected, entry::floatValue), - checkEquals((int) expected, entry::intValue), - checkEquals((long) expected, entry::longValue), - checkEquals((double) expected, entry::doubleValue), - checkEquals(expected != 0F, entry::booleanValue), - checkEquals(Float.valueOf(expected), entry::objectValue), - checkEquals(Float.toString(expected), entry::stringValue)); + checkEquals(number.doubleValue(), of(TagValueConversions::toDouble, entry::objectValue)), + checkEquals(number.toString(), entry::stringValue), + checkEquals(number.toString(), of(TagValueConversions::toString, entry::objectValue))); } static final Check checkInstanceOf(Class klass, TagMap.Entry entry) { - return () -> - assertTrue( - klass.isAssignableFrom(entry.objectValue().getClass()), + return checkTrue( + () -> klass.isAssignableFrom(entry.objectValue().getClass()), "instanceof " + klass.getSimpleName()); } static final Check checkType(byte entryType, TagMap.Entry entry) { - return () -> assertTrue(entry.is(entryType), "type is " + entryType); + // TODO: TVC checks + return multiCheck( + checkTrue(() -> entry.is(entryType), "type is " + entryType), + checkEquals(entryType, entry::type)); } static final Check multiCheck(Check... checks) { return new MultipartCheck(checks); } + + static final Check checkSame(Object expected, Object actual) { + return () -> assertSame(expected, actual); + } static final Check checkFalse(Supplier actual) { - return () -> assertFalse(actual.get(), actual.toString()); + return checkFalse(actual, actual.toString()); + } + + static final Check checkFalse(Supplier actual, String identifier) { + return () -> assertFalse(actual.get(), identifier); } static final Check checkTrue(Supplier actual) { - return () -> assertTrue(actual.get(), actual.toString()); + return checkTrue(actual, actual.toString()); + } + + static final Check checkTrue(Supplier actual, String identifier) { + return () -> assertTrue(actual.get(), identifier); } static final Check checkEquals(float expected, Supplier actual) { - return () -> assertEquals(expected, actual.get().floatValue(), actual.toString()); + return checkEquals(expected, actual, actual.toString()); } - + + static final Check checkEquals(float expected, Supplier actual, String identifier) { + return () -> assertEquals(expected, actual.get().floatValue(), identifier); + } + static final Check checkEquals(int expected, Supplier actual) { - return () -> assertEquals(expected, actual.get().intValue(), actual.toString()); + return checkEquals(expected, actual, actual.toString()); } - + + static final Check checkEquals(int expected, Supplier actual, String identifier) { + return () -> assertEquals(expected, actual.get().intValue(), identifier); + } + static final Check checkEquals(double expected, Supplier actual) { - return () -> assertEquals(expected, actual.get().doubleValue(), actual.toString()); + return checkEquals(expected, actual, actual.toString()); + } + + static final Check checkEquals(double expected, Supplier actual, String identifier) { + return () -> assertEquals(expected, actual.get().doubleValue(), identifier); } static final Check checkEquals(long expected, Supplier actual) { - return () -> assertEquals(expected, actual.get().longValue(), actual.toString()); + return checkEquals(expected, actual, actual.toString()); + } + + static final Check checkEquals(long expected, Supplier actual, String identifier) { + return () -> assertEquals(expected, actual.get().longValue(), identifier); } static final Check checkEquals(boolean expected, Supplier actual) { - return () -> assertEquals(expected, actual.get().booleanValue(), actual.toString()); + return checkEquals(expected, actual, actual.toString()); } - - static final Check checkEquals(Object expected, Supplier actual) { - return () -> assertEquals(expected, actual.get(), actual.toString()); + + static final Check checkEquals(boolean expected, Supplier actual, String identifier) { + return () -> assertEquals(expected, actual.get().booleanValue(), identifier); } - + + static final Check checkEquals(T expected, Supplier actual) { + return checkEquals(expected, actual, actual.toString()); + } + + static final Check checkEquals(T expected, Supplier actual, String identifier) { + return () -> assertEquals(expected, actual.get(), identifier); + } + @FunctionalInterface interface Check { void check(); diff --git a/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java b/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java new file mode 100644 index 00000000000..c597b4eccd0 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java @@ -0,0 +1,85 @@ +package datadog.trace.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class TagValueConversionsTest { + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void boolean_(boolean value) { + Boolean box = Boolean.valueOf(value); + + assertEquals(TagMap.EntryReader.BOOLEAN, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.BOOLEAN)); + assertFalse(TagValueConversions.isNumericPrimitive(box)); + assertFalse(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toBoolean(box)); + assertEquals(value ? 1 : 0, TagValueConversions.toInt(box)); + assertEquals(value ? 1L : 0L, TagValueConversions.toLong(box)); + assertEquals(value ? 1F : 0F, TagValueConversions.toFloat(box)); + assertEquals(value ? 1D : 0D, TagValueConversions.toDouble(box)); + + assertEquals(Boolean.toString(value), TagValueConversions.toString(box)); + } + + @ParameterizedTest + @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + public void int_(int value) { + Integer box = Integer.valueOf(value); + + assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toInt(box)); + assertEquals((long)value, TagValueConversions.toLong(box)); + assertEquals((float)value, TagValueConversions.toFloat(box)); + assertEquals((double)value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0, TagValueConversions.toBoolean(box)); + assertEquals(Integer.toString(value), TagValueConversions.toString(box)); + } + + @ParameterizedTest + @ValueSource( + longs = { + Long.MIN_VALUE, + Integer.MIN_VALUE, + -1_048_576L, + -256L, + -128L, + -1L, + 0L, + 1L, + 128L, + 256L, + 1_048_576L, + Integer.MAX_VALUE, + Long.MAX_VALUE + }) + public void long_(long value) { + Long box = Long.valueOf(value); + + assertEquals(TagMap.EntryReader.LONG, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.LONG)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toLong(box)); + assertEquals((int)value, TagValueConversions.toInt(box)); + assertEquals((float)value, TagValueConversions.toFloat(box)); + assertEquals((double)value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0L, TagValueConversions.toBoolean(box)); + assertEquals(Long.toString(value), TagValueConversions.toString(box)); + } +} From b192679cc90e3e0fcd684441e48ddf632d4eb22b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 16 Jan 2026 09:30:50 -0500 Subject: [PATCH 07/22] Refining comment in toBoolean --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 edbea0e2071..8feba42d8ba 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -3304,7 +3304,8 @@ static boolean toBoolean(Object value) { return (Boolean) value; } else if (value instanceof Number) { // NOTE: This cannot be intValue() because intValue of larger types is 0 when - // the actual value would be less than Integer.MIN_VALUE, so using doubleValue. + // the actual value would be less than Integer.MIN_VALUE or for floating point + // types is very close to zero, so using doubleValue instead. // While this is a bit ugly, coerced toBoolean is uncommon return ((Number) value).doubleValue() != 0D; From cffac879522de27d90ebbdc3793ac2b7467c154e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 16 Jan 2026 09:39:14 -0500 Subject: [PATCH 08/22] Adding more TagValueConversionTest-s Coverage for byte, short, float, and double --- .../trace/api/TagValueConversionsTest.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java b/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java index c597b4eccd0..7aa191fc81e 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java @@ -48,6 +48,46 @@ public void int_(int value) { assertEquals(Integer.toString(value), TagValueConversions.toString(box)); } + @ParameterizedTest + @ValueSource(bytes = {Byte.MIN_VALUE, -32, -1, 0, 1, 32, Byte.MAX_VALUE}) + public void byte_(byte value) { + Byte box = Byte.valueOf(value); + + assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals((int)value, TagValueConversions.toInt(box)); + assertEquals((long)value, TagValueConversions.toLong(box)); + assertEquals((float)value, TagValueConversions.toFloat(box)); + assertEquals((double)value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0, TagValueConversions.toBoolean(box)); + assertEquals(Byte.toString(value), TagValueConversions.toString(box)); + } + + @ParameterizedTest + @ValueSource(shorts = {Short.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Short.MAX_VALUE}) + public void short_(short value) { + Short box = Short.valueOf(value); + + assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals((int)value, TagValueConversions.toInt(box)); + assertEquals((long)value, TagValueConversions.toLong(box)); + assertEquals((float)value, TagValueConversions.toFloat(box)); + assertEquals((double)value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0, TagValueConversions.toBoolean(box)); + assertEquals(Short.toString(value), TagValueConversions.toString(box)); + } + @ParameterizedTest @ValueSource( longs = { @@ -82,4 +122,45 @@ public void long_(long value) { assertEquals(value != 0L, TagValueConversions.toBoolean(box)); assertEquals(Long.toString(value), TagValueConversions.toString(box)); } + + @ParameterizedTest + @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + public void float_(float value) { + Float box = Float.valueOf(value); + + assertEquals(TagMap.EntryReader.FLOAT, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.FLOAT)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toFloat(box)); + assertEquals((int)value, TagValueConversions.toInt(box)); + assertEquals((long)value, TagValueConversions.toLong(box)); + assertEquals((double)value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0F, TagValueConversions.toBoolean(box)); + assertEquals(Float.toString(value), TagValueConversions.toString(box)); + } + + @ParameterizedTest + @ValueSource( + doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + public void double_(double value) { + Double box = Double.valueOf(value); + + assertEquals(TagMap.EntryReader.DOUBLE, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.DOUBLE)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toDouble(box)); + assertEquals((int)value, TagValueConversions.toInt(box)); + assertEquals((long)value, TagValueConversions.toLong(box)); + assertEquals((float)value, TagValueConversions.toFloat(box)); + + assertEquals(value != 0D, TagValueConversions.toBoolean(box)); + assertEquals(Double.toString(value), TagValueConversions.toString(box)); + } } From 8c51f7ff4627401717bcbd943a81dc20f4e210ab Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 16 Jan 2026 09:43:32 -0500 Subject: [PATCH 09/22] Adding Entry tests for byte and short boxes --- .../datadog/trace/api/TagMapEntryTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 83f7e7497a2..6a725187054 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -155,6 +155,36 @@ public void intEntry_boxed(int value) { checkInstanceOf(Number.class, entry), checkType(TagMap.Entry.INT, entry))); } + + @ParameterizedTest + @ValueSource(shorts = {Short.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Short.MAX_VALUE}) + public void intEntry_boxedShort(short value) { + test( + () -> TagMap.Entry.newIntEntry("foo", Short.valueOf(value)), + TagMap.Entry.INT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkIsNumericPrimitive(entry), + checkInstanceOf(Number.class, entry), + checkType(TagMap.Entry.INT, entry))); + } + + @ParameterizedTest + @ValueSource(bytes = {Byte.MIN_VALUE, -32, -1, 0, 1, 32, Byte.MAX_VALUE}) + public void intEntry_boxedByte(byte value) { + test( + () -> TagMap.Entry.newIntEntry("foo", Byte.valueOf(value)), + TagMap.Entry.INT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkIsNumericPrimitive(entry), + checkInstanceOf(Number.class, entry), + checkType(TagMap.Entry.INT, entry))); + } @ParameterizedTest @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) From 17b1255ca38be934085e653f32cac6dcca1ecd6d Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 16 Jan 2026 10:11:16 -0500 Subject: [PATCH 10/22] spotless --- .../main/java/datadog/trace/api/TagMap.java | 23 +- .../datadog/trace/api/TagMapEntryTest.java | 246 +++++++++++------- .../trace/api/TagValueConversionsTest.java | 222 ++++++++-------- 3 files changed, 279 insertions(+), 212 deletions(-) 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 8feba42d8ba..10fe96c5fca 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -3283,14 +3283,15 @@ static boolean isNumericPrimitive(Object value) { } static boolean isObject(Object value) { - boolean isSupportedPrimitive = (value instanceof Integer) - || (value instanceof Long) - || (value instanceof Double) - || (value instanceof Float) - || (value instanceof Boolean) - || (value instanceof Short) - || (value instanceof Byte); - + boolean isSupportedPrimitive = + (value instanceof Integer) + || (value instanceof Long) + || (value instanceof Double) + || (value instanceof Float) + || (value instanceof Boolean) + || (value instanceof Short) + || (value instanceof Byte); + // Char is just treated as Object return !isSupportedPrimitive; } @@ -3303,10 +3304,10 @@ static boolean toBoolean(Object value) { if (value instanceof Boolean) { return (Boolean) value; } else if (value instanceof Number) { - // NOTE: This cannot be intValue() because intValue of larger types is 0 when - // the actual value would be less than Integer.MIN_VALUE or for floating point + // NOTE: This cannot be intValue() because intValue of larger types is 0 when + // the actual value would be less than Integer.MIN_VALUE or for floating point // types is very close to zero, so using doubleValue instead. - + // While this is a bit ugly, coerced toBoolean is uncommon return ((Number) value).doubleValue() != 0D; } else { diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 6a725187054..13da8723b60 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -155,7 +155,7 @@ public void intEntry_boxed(int value) { checkInstanceOf(Number.class, entry), checkType(TagMap.Entry.INT, entry))); } - + @ParameterizedTest @ValueSource(shorts = {Short.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Short.MAX_VALUE}) public void intEntry_boxedShort(short value) { @@ -170,7 +170,7 @@ public void intEntry_boxedShort(short value) { checkInstanceOf(Number.class, entry), checkType(TagMap.Entry.INT, entry))); } - + @ParameterizedTest @ValueSource(bytes = {Byte.MIN_VALUE, -32, -1, 0, 1, 32, Byte.MAX_VALUE}) public void intEntry_boxedByte(byte value) { @@ -400,13 +400,14 @@ public Thread newThread(Runnable r) { static final void test( Supplier entrySupplier, byte rawType, Function checkSupplier) { - Function combinedCheckSupplier = (entry) -> { - return multiCheck( - checkSupplier.apply(entry), - checkSame(entry, entry.entry()), - checkSame(entry, entry.mapEntry())); - }; - + Function combinedCheckSupplier = + (entry) -> { + return multiCheck( + checkSupplier.apply(entry), + checkSame(entry, entry.entry()), + checkSame(entry, entry.mapEntry())); + }; + // repeat the test several times to exercise different orderings in this thread for (int i = 0; i < 10; ++i) { testSingleThreaded(entrySupplier, rawType, combinedCheckSupplier); @@ -477,47 +478,47 @@ static final void testMultiThreaded( static final void assertChecks(Check check) { check.check(); } - + static final Check checkKey(String expected, TagMap.Entry entry) { return multiCheck(checkEquals(expected, entry::tag), checkEquals(expected, entry::getKey)); } - + static final Supplier of(Supplier supplier, String identifier) { - return new Supplier() { - @Override - public T get() { - return supplier.get(); - } - - @Override - public String toString() { - return identifier; - } - }; - } - + return new Supplier() { + @Override + public T get() { + return supplier.get(); + } + + @Override + public String toString() { + return identifier; + } + }; + } + static final Function of(Function func, String identifier) { - return new Function() { - @Override - public O apply(I input) { - return func.apply(input); - } - - @Override - public String toString() { - return identifier; - } - }; - } - + return new Function() { + @Override + public O apply(I input) { + return func.apply(input); + } + + @Override + public String toString() { + return identifier; + } + }; + } + static final Supplier of(Function output, Supplier input) { - return of(output, input, output.toString() + "(" + input.toString() + ")"); + return of(output, input, output.toString() + "(" + input.toString() + ")"); } - + static final Supplier of(Function output, Supplier input, String identifier) { - return of(() -> output.apply(input.get()), identifier); + return of(() -> output.apply(input.get()), identifier); } - + static final Check checkIsNumericPrimitive(TagMap.Entry entry) { return multiCheck( checkTrue(entry::isNumericPrimitive, "Entry::isNumericPrimitive"), @@ -525,14 +526,18 @@ static final Check checkIsNumericPrimitive(TagMap.Entry entry) { checkInstanceOf(Number.class, entry), checkFalse(entry::isObject, "Entry::isObject"), checkTrue(of(TagValueConversions::isNumber, entry::objectValue), "isNumber(Object)"), - checkTrue(of(TagValueConversions::isNumericPrimitive, entry::objectValue), "isNumericPrimitive(Object)"), + checkTrue( + of(TagValueConversions::isNumericPrimitive, entry::objectValue), + "isNumericPrimitive(Object)"), checkFalse(of(TagValueConversions::isObject, entry::objectValue), "isObject(Object)")); } static final Check checkIsBigNumber(TagMap.Entry entry) { return multiCheck( checkFalse(entry::isNumericPrimitive, "Entry::isNumericPrimitive"), - checkFalse(of(TagValueConversions::isNumericPrimitive, entry::objectValue), "isNumericPrimitive(Object)"), + checkFalse( + of(TagValueConversions::isNumericPrimitive, entry::objectValue), + "isNumericPrimitive(Object)"), checkTrue(entry::isNumber, "Entry::isNumber"), checkTrue(entry::isObject, "Entry::isObject"), checkTrue(of(TagValueConversions::isNumber, entry::objectValue), "isNumber(Object)"), @@ -551,15 +556,28 @@ static final Check checkValue(boolean expected, TagMap.Entry entry) { checkEquals(expected, entry::booleanValue, "Entry::booleanValue"), checkEquals(Boolean.valueOf(expected), entry::objectValue, "Entry::objectValue"), checkEquals(expected ? 1 : 0, entry::intValue, "Entry::intValue"), - checkEquals(expected ? 1 : 0, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), + checkEquals( + expected ? 1 : 0, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), checkEquals(expected ? 1L : 0L, entry::longValue, "Entry::longValue"), - checkEquals(expected ? 1L : 0L, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), + checkEquals( + expected ? 1L : 0L, + of(TagValueConversions::toLong, entry::objectValue), + "toLong(Object)"), checkEquals(expected ? 1D : 0D, entry::doubleValue, "Entry::doubleValue"), - checkEquals(expected ? 1D : 0D, of(TagValueConversions::toDouble, entry::objectValue), "toDouble(Object)"), + checkEquals( + expected ? 1D : 0D, + of(TagValueConversions::toDouble, entry::objectValue), + "toDouble(Object)"), checkEquals(expected ? 1F : 0F, entry::floatValue, "Entry::floatValue"), - checkEquals(expected ? 1F : 0F, of(TagValueConversions::toFloat, entry::objectValue), "toFloat(Object)"), + checkEquals( + expected ? 1F : 0F, + of(TagValueConversions::toFloat, entry::objectValue), + "toFloat(Object)"), checkEquals(Boolean.toString(expected), entry::stringValue, "Entry::stringValue"), - checkEquals(Boolean.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); + checkEquals( + Boolean.toString(expected), + of(TagValueConversions::toString, entry::objectValue), + "toString(Object)")); } static final Check checkValue(int expected, TagMap.Entry entry) { @@ -567,15 +585,28 @@ static final Check checkValue(int expected, TagMap.Entry entry) { checkEquals(expected, entry::intValue, "Entry::intValue"), checkEquals(Integer.valueOf(expected), entry::objectValue, "Entry::objectValue"), checkEquals((long) expected, entry::longValue, "Entry::longValue"), - checkEquals((long)expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), + checkEquals( + (long) expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), checkEquals((float) expected, entry::floatValue, "Entry::floatValue"), - checkEquals((float)expected, of(TagValueConversions::toFloat, entry::objectValue), "toFloat(Object)"), + checkEquals( + (float) expected, + of(TagValueConversions::toFloat, entry::objectValue), + "toFloat(Object)"), checkEquals((double) expected, entry::doubleValue, "Entry::doubleValue"), - checkEquals((double)expected, of(TagValueConversions::toDouble, entry::objectValue), "toDouble(Object)"), + checkEquals( + (double) expected, + of(TagValueConversions::toDouble, entry::objectValue), + "toDouble(Object)"), checkEquals(expected != 0, entry::booleanValue, "Entry::booleanValue"), - checkEquals(expected != 0, of(TagValueConversions::toBoolean, entry::objectValue), "toBoolean(Object)"), + checkEquals( + expected != 0, + of(TagValueConversions::toBoolean, entry::objectValue), + "toBoolean(Object)"), checkEquals(Integer.toString(expected), entry::stringValue, "Entry::stringValue"), - checkEquals(Integer.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); + checkEquals( + Integer.toString(expected), + of(TagValueConversions::toString, entry::objectValue), + "toString(Object)")); } static final Check checkValue(long expected, TagMap.Entry entry) { @@ -583,15 +614,28 @@ static final Check checkValue(long expected, TagMap.Entry entry) { checkEquals(expected, entry::longValue, "Entry::longValue"), checkEquals(Long.valueOf(expected), entry::objectValue, "Entry::objectValue"), checkEquals((int) expected, entry::intValue, "Entry::intValue"), - checkEquals((int)expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), + checkEquals( + (int) expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), checkEquals((float) expected, entry::floatValue, "Entry::floatValue"), - checkEquals((float)expected, of(TagValueConversions::toFloat, entry::objectValue), "toFloat(Object)"), + checkEquals( + (float) expected, + of(TagValueConversions::toFloat, entry::objectValue), + "toFloat(Object)"), checkEquals((double) expected, entry::doubleValue, "Entry::doubleValue"), - checkEquals((double)expected, of(TagValueConversions::toDouble, entry::objectValue), "toDouble(Object)"), + checkEquals( + (double) expected, + of(TagValueConversions::toDouble, entry::objectValue), + "toDouble(Object)"), checkEquals(expected != 0L, entry::booleanValue, "Entry::booleanValue"), - checkEquals(expected != 0L, of(TagValueConversions::toBoolean, entry::objectValue), "toBoolean(Object)"), + checkEquals( + expected != 0L, + of(TagValueConversions::toBoolean, entry::objectValue), + "toBoolean(Object)"), checkEquals(Long.toString(expected), entry::stringValue, "Entry::stringValue"), - checkEquals(Long.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); + checkEquals( + Long.toString(expected), + of(TagValueConversions::toString, entry::objectValue), + "toString(Object)")); } static final Check checkValue(double expected, TagMap.Entry entry) { @@ -599,31 +643,53 @@ static final Check checkValue(double expected, TagMap.Entry entry) { checkEquals(expected, entry::doubleValue, "Entry::doubleValue"), checkEquals(Double.valueOf(expected), entry::objectValue, "Entry::objectValue"), checkEquals((int) expected, entry::intValue, "Entry::intValue"), - checkEquals((int)expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), + checkEquals( + (int) expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), checkEquals((long) expected, entry::longValue, "Entry::longValue"), - checkEquals((long)expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), + checkEquals( + (long) expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), checkEquals((float) expected, entry::floatValue, "Entry::floatValue"), - checkEquals((float)expected, of(TagValueConversions::toFloat, entry::objectValue), "toFloat(Object)"), + checkEquals( + (float) expected, + of(TagValueConversions::toFloat, entry::objectValue), + "toFloat(Object)"), checkEquals(expected != 0D, entry::booleanValue, "Entry::booleanValue"), - checkEquals(expected != 0D, of(TagValueConversions::toBoolean, entry::objectValue), "toBoolean(Object)"), + checkEquals( + expected != 0D, + of(TagValueConversions::toBoolean, entry::objectValue), + "toBoolean(Object)"), checkEquals(Double.toString(expected), entry::stringValue, "Entry::stringValue"), - checkEquals(Double.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); + checkEquals( + Double.toString(expected), + of(TagValueConversions::toString, entry::objectValue), + "toString(Object)")); } - + static final Check checkValue(float expected, TagMap.Entry entry) { return multiCheck( checkEquals(expected, entry::floatValue, "Entry::floatValue"), checkEquals(Float.valueOf(expected), entry::objectValue, "Entry::objectValue"), checkEquals((int) expected, entry::intValue, "Entry::intValue"), - checkEquals((int)expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), + checkEquals( + (int) expected, of(TagValueConversions::toInt, entry::objectValue), "toInt(Object)"), checkEquals((long) expected, entry::longValue, "Entry::longValue"), - checkEquals((long)expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), + checkEquals( + (long) expected, of(TagValueConversions::toLong, entry::objectValue), "toLong(Object)"), checkEquals((double) expected, entry::doubleValue, "Entry::doubleValue"), - checkEquals((double)expected, of(TagValueConversions::toDouble, entry::objectValue), "toDouble(Object)"), + checkEquals( + (double) expected, + of(TagValueConversions::toDouble, entry::objectValue), + "toDouble(Object)"), checkEquals(expected != 0F, entry::booleanValue, "Entry::booleanValue"), - checkEquals(expected != 0F, of(TagValueConversions::toBoolean, entry::objectValue), "toBoolean(Object)"), + checkEquals( + expected != 0F, + of(TagValueConversions::toBoolean, entry::objectValue), + "toBoolean(Object)"), checkEquals(Float.toString(expected), entry::stringValue, "Entry::stringValue"), - checkEquals(Float.toString(expected), of(TagValueConversions::toString, entry::objectValue), "toString(Object)")); + checkEquals( + Float.toString(expected), + of(TagValueConversions::toString, entry::objectValue), + "toString(Object)")); } public static Check checkNumber(Number number, TagMap.Entry entry) { @@ -643,59 +709,59 @@ public static Check checkNumber(Number number, TagMap.Entry entry) { static final Check checkInstanceOf(Class klass, TagMap.Entry entry) { return checkTrue( - () -> klass.isAssignableFrom(entry.objectValue().getClass()), - "instanceof " + klass.getSimpleName()); + () -> klass.isAssignableFrom(entry.objectValue().getClass()), + "instanceof " + klass.getSimpleName()); } static final Check checkType(byte entryType, TagMap.Entry entry) { - // TODO: TVC checks + // TODO: TVC checks return multiCheck( - checkTrue(() -> entry.is(entryType), "type is " + entryType), - checkEquals(entryType, entry::type)); + checkTrue(() -> entry.is(entryType), "type is " + entryType), + checkEquals(entryType, entry::type)); } static final Check multiCheck(Check... checks) { return new MultipartCheck(checks); } - + static final Check checkSame(Object expected, Object actual) { - return () -> assertSame(expected, actual); + return () -> assertSame(expected, actual); } static final Check checkFalse(Supplier actual) { return checkFalse(actual, actual.toString()); - } + } static final Check checkFalse(Supplier actual, String identifier) { - return () -> assertFalse(actual.get(), identifier); + return () -> assertFalse(actual.get(), identifier); } static final Check checkTrue(Supplier actual) { return checkTrue(actual, actual.toString()); } - + static final Check checkTrue(Supplier actual, String identifier) { - return () -> assertTrue(actual.get(), identifier); + return () -> assertTrue(actual.get(), identifier); } static final Check checkEquals(float expected, Supplier actual) { return checkEquals(expected, actual, actual.toString()); } - + static final Check checkEquals(float expected, Supplier actual, String identifier) { return () -> assertEquals(expected, actual.get().floatValue(), identifier); } - + static final Check checkEquals(int expected, Supplier actual) { return checkEquals(expected, actual, actual.toString()); } - + static final Check checkEquals(int expected, Supplier actual, String identifier) { return () -> assertEquals(expected, actual.get().intValue(), identifier); } - + static final Check checkEquals(double expected, Supplier actual) { - return checkEquals(expected, actual, actual.toString()); + return checkEquals(expected, actual, actual.toString()); } static final Check checkEquals(double expected, Supplier actual, String identifier) { @@ -705,7 +771,7 @@ static final Check checkEquals(double expected, Supplier actual, String static final Check checkEquals(long expected, Supplier actual) { return checkEquals(expected, actual, actual.toString()); } - + static final Check checkEquals(long expected, Supplier actual, String identifier) { return () -> assertEquals(expected, actual.get().longValue(), identifier); } @@ -713,19 +779,19 @@ static final Check checkEquals(long expected, Supplier actual, String iden static final Check checkEquals(boolean expected, Supplier actual) { return checkEquals(expected, actual, actual.toString()); } - + static final Check checkEquals(boolean expected, Supplier actual, String identifier) { return () -> assertEquals(expected, actual.get().booleanValue(), identifier); } - + static final Check checkEquals(T expected, Supplier actual) { return checkEquals(expected, actual, actual.toString()); } - + static final Check checkEquals(T expected, Supplier actual, String identifier) { return () -> assertEquals(expected, actual.get(), identifier); } - + @FunctionalInterface interface Check { void check(); diff --git a/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java b/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java index 7aa191fc81e..48b04419bfc 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagValueConversionsTest.java @@ -11,83 +11,83 @@ public class TagValueConversionsTest { @ParameterizedTest @ValueSource(booleans = {false, true}) public void boolean_(boolean value) { - Boolean box = Boolean.valueOf(value); - - assertEquals(TagMap.EntryReader.BOOLEAN, TagValueConversions.typeOf(box)); - assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.BOOLEAN)); - assertFalse(TagValueConversions.isNumericPrimitive(box)); - assertFalse(TagValueConversions.isNumber(box)); - assertFalse(TagValueConversions.isObject(box)); - - assertEquals(value, TagValueConversions.toBoolean(box)); - assertEquals(value ? 1 : 0, TagValueConversions.toInt(box)); - assertEquals(value ? 1L : 0L, TagValueConversions.toLong(box)); - assertEquals(value ? 1F : 0F, TagValueConversions.toFloat(box)); - assertEquals(value ? 1D : 0D, TagValueConversions.toDouble(box)); - - assertEquals(Boolean.toString(value), TagValueConversions.toString(box)); + Boolean box = Boolean.valueOf(value); + + assertEquals(TagMap.EntryReader.BOOLEAN, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.BOOLEAN)); + assertFalse(TagValueConversions.isNumericPrimitive(box)); + assertFalse(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toBoolean(box)); + assertEquals(value ? 1 : 0, TagValueConversions.toInt(box)); + assertEquals(value ? 1L : 0L, TagValueConversions.toLong(box)); + assertEquals(value ? 1F : 0F, TagValueConversions.toFloat(box)); + assertEquals(value ? 1D : 0D, TagValueConversions.toDouble(box)); + + assertEquals(Boolean.toString(value), TagValueConversions.toString(box)); } - + @ParameterizedTest @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void int_(int value) { - Integer box = Integer.valueOf(value); - - assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); - assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); - assertTrue(TagValueConversions.isNumericPrimitive(box)); - assertTrue(TagValueConversions.isNumber(box)); - assertFalse(TagValueConversions.isObject(box)); - - assertEquals(value, TagValueConversions.toInt(box)); - assertEquals((long)value, TagValueConversions.toLong(box)); - assertEquals((float)value, TagValueConversions.toFloat(box)); - assertEquals((double)value, TagValueConversions.toDouble(box)); - - assertEquals(value != 0, TagValueConversions.toBoolean(box)); - assertEquals(Integer.toString(value), TagValueConversions.toString(box)); + Integer box = Integer.valueOf(value); + + assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toInt(box)); + assertEquals((long) value, TagValueConversions.toLong(box)); + assertEquals((float) value, TagValueConversions.toFloat(box)); + assertEquals((double) value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0, TagValueConversions.toBoolean(box)); + assertEquals(Integer.toString(value), TagValueConversions.toString(box)); } - + @ParameterizedTest @ValueSource(bytes = {Byte.MIN_VALUE, -32, -1, 0, 1, 32, Byte.MAX_VALUE}) public void byte_(byte value) { - Byte box = Byte.valueOf(value); - - assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); - assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); - assertTrue(TagValueConversions.isNumericPrimitive(box)); - assertTrue(TagValueConversions.isNumber(box)); - assertFalse(TagValueConversions.isObject(box)); - - assertEquals((int)value, TagValueConversions.toInt(box)); - assertEquals((long)value, TagValueConversions.toLong(box)); - assertEquals((float)value, TagValueConversions.toFloat(box)); - assertEquals((double)value, TagValueConversions.toDouble(box)); - - assertEquals(value != 0, TagValueConversions.toBoolean(box)); - assertEquals(Byte.toString(value), TagValueConversions.toString(box)); + Byte box = Byte.valueOf(value); + + assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals((int) value, TagValueConversions.toInt(box)); + assertEquals((long) value, TagValueConversions.toLong(box)); + assertEquals((float) value, TagValueConversions.toFloat(box)); + assertEquals((double) value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0, TagValueConversions.toBoolean(box)); + assertEquals(Byte.toString(value), TagValueConversions.toString(box)); } - + @ParameterizedTest @ValueSource(shorts = {Short.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Short.MAX_VALUE}) public void short_(short value) { - Short box = Short.valueOf(value); - - assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); - assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); - assertTrue(TagValueConversions.isNumericPrimitive(box)); - assertTrue(TagValueConversions.isNumber(box)); - assertFalse(TagValueConversions.isObject(box)); - - assertEquals((int)value, TagValueConversions.toInt(box)); - assertEquals((long)value, TagValueConversions.toLong(box)); - assertEquals((float)value, TagValueConversions.toFloat(box)); - assertEquals((double)value, TagValueConversions.toDouble(box)); - - assertEquals(value != 0, TagValueConversions.toBoolean(box)); - assertEquals(Short.toString(value), TagValueConversions.toString(box)); + Short box = Short.valueOf(value); + + assertEquals(TagMap.EntryReader.INT, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.INT)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals((int) value, TagValueConversions.toInt(box)); + assertEquals((long) value, TagValueConversions.toLong(box)); + assertEquals((float) value, TagValueConversions.toFloat(box)); + assertEquals((double) value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0, TagValueConversions.toBoolean(box)); + assertEquals(Short.toString(value), TagValueConversions.toString(box)); } - + @ParameterizedTest @ValueSource( longs = { @@ -106,61 +106,61 @@ public void short_(short value) { Long.MAX_VALUE }) public void long_(long value) { - Long box = Long.valueOf(value); - - assertEquals(TagMap.EntryReader.LONG, TagValueConversions.typeOf(box)); - assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.LONG)); - assertTrue(TagValueConversions.isNumericPrimitive(box)); - assertTrue(TagValueConversions.isNumber(box)); - assertFalse(TagValueConversions.isObject(box)); - - assertEquals(value, TagValueConversions.toLong(box)); - assertEquals((int)value, TagValueConversions.toInt(box)); - assertEquals((float)value, TagValueConversions.toFloat(box)); - assertEquals((double)value, TagValueConversions.toDouble(box)); - - assertEquals(value != 0L, TagValueConversions.toBoolean(box)); - assertEquals(Long.toString(value), TagValueConversions.toString(box)); + Long box = Long.valueOf(value); + + assertEquals(TagMap.EntryReader.LONG, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.LONG)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toLong(box)); + assertEquals((int) value, TagValueConversions.toInt(box)); + assertEquals((float) value, TagValueConversions.toFloat(box)); + assertEquals((double) value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0L, TagValueConversions.toBoolean(box)); + assertEquals(Long.toString(value), TagValueConversions.toString(box)); } - + @ParameterizedTest @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void float_(float value) { - Float box = Float.valueOf(value); - - assertEquals(TagMap.EntryReader.FLOAT, TagValueConversions.typeOf(box)); - assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.FLOAT)); - assertTrue(TagValueConversions.isNumericPrimitive(box)); - assertTrue(TagValueConversions.isNumber(box)); - assertFalse(TagValueConversions.isObject(box)); - - assertEquals(value, TagValueConversions.toFloat(box)); - assertEquals((int)value, TagValueConversions.toInt(box)); - assertEquals((long)value, TagValueConversions.toLong(box)); - assertEquals((double)value, TagValueConversions.toDouble(box)); - - assertEquals(value != 0F, TagValueConversions.toBoolean(box)); - assertEquals(Float.toString(value), TagValueConversions.toString(box)); + Float box = Float.valueOf(value); + + assertEquals(TagMap.EntryReader.FLOAT, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.FLOAT)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toFloat(box)); + assertEquals((int) value, TagValueConversions.toInt(box)); + assertEquals((long) value, TagValueConversions.toLong(box)); + assertEquals((double) value, TagValueConversions.toDouble(box)); + + assertEquals(value != 0F, TagValueConversions.toBoolean(box)); + assertEquals(Float.toString(value), TagValueConversions.toString(box)); } - + @ParameterizedTest @ValueSource( doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void double_(double value) { - Double box = Double.valueOf(value); - - assertEquals(TagMap.EntryReader.DOUBLE, TagValueConversions.typeOf(box)); - assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.DOUBLE)); - assertTrue(TagValueConversions.isNumericPrimitive(box)); - assertTrue(TagValueConversions.isNumber(box)); - assertFalse(TagValueConversions.isObject(box)); - - assertEquals(value, TagValueConversions.toDouble(box)); - assertEquals((int)value, TagValueConversions.toInt(box)); - assertEquals((long)value, TagValueConversions.toLong(box)); - assertEquals((float)value, TagValueConversions.toFloat(box)); - - assertEquals(value != 0D, TagValueConversions.toBoolean(box)); - assertEquals(Double.toString(value), TagValueConversions.toString(box)); + Double box = Double.valueOf(value); + + assertEquals(TagMap.EntryReader.DOUBLE, TagValueConversions.typeOf(box)); + assertTrue(TagValueConversions.isA(box, TagMap.EntryReader.DOUBLE)); + assertTrue(TagValueConversions.isNumericPrimitive(box)); + assertTrue(TagValueConversions.isNumber(box)); + assertFalse(TagValueConversions.isObject(box)); + + assertEquals(value, TagValueConversions.toDouble(box)); + assertEquals((int) value, TagValueConversions.toInt(box)); + assertEquals((long) value, TagValueConversions.toLong(box)); + assertEquals((float) value, TagValueConversions.toFloat(box)); + + assertEquals(value != 0D, TagValueConversions.toBoolean(box)); + assertEquals(Double.toString(value), TagValueConversions.toString(box)); } } From 9d43d47ea95928a0d9d76028674b2e948c6e127f Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 28 Jan 2026 15:44:08 -0500 Subject: [PATCH 11/22] Direct TagMap.Entry support in AgentSpan / DDSpan Adding methods to AgentSpan / DDSpan that take TagMap.Entry/Reader objects directly This will enable TagMap.Entry reuse which can reduce memory allocation/GC pressure --- .../agent/test/TrackingSpanDecorator.groovy | 10 +++++++ .../main/java/datadog/trace/core/DDSpan.java | 14 +++++++++- .../datadog/trace/core/DDSpanContext.java | 26 +++++++++++++++++++ .../instrumentation/api/AgentSpan.java | 4 +++ .../instrumentation/api/ImmutableSpan.java | 11 ++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy index 70f1a2a6731..3f4a1e09a56 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy @@ -114,6 +114,11 @@ class TrackingSpanDecorator implements AgentSpan { return delegate.setTag(key, value) } + @Override + public final AgentSpan setTag(TagMap.EntryReader entry) { + return delegate.setTag(entry) + } + @Override void setRequestBlockingAction(Flow.Action.RequestBlockingAction rba) { delegate.setRequestBlockingAction(rba) @@ -184,6 +189,11 @@ class TrackingSpanDecorator implements AgentSpan { return delegate.setMetric(key, value) } + @Override + public AgentSpan setMetric(TagMap.EntryReader entry) { + return delegate.setMetric(entry) + } + @Override boolean isError() { return delegate.isError() diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java index 1b321f4536a..e8a6ce87edb 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java @@ -392,7 +392,7 @@ private boolean isExceptionReplayEnabled() { public final DDSpan setTag(final String tag, final String value) { if (value == null || value.isEmpty()) { // Remove the tag - context.setTag(tag, null); + context.removeTag(tag); } else { context.setTag(tag, value); } @@ -405,6 +405,12 @@ public final DDSpan setTag(final String tag, final boolean value) { return this; } + @Override + public final DDSpan setTag(TagMap.EntryReader entry) { + context.setTag(entry); + return this; + } + @Override public void setRequestBlockingAction(Flow.Action.RequestBlockingAction rba) { this.requestBlockingAction = rba; @@ -474,6 +480,12 @@ public DDSpan setMetric(final CharSequence metric, final double value) { return this; } + @Override + public DDSpan setMetric(TagMap.EntryReader entry) { + context.setMetric(entry); + return this; + } + @Override public DDSpan setFlag(CharSequence name, boolean value) { context.setMetric(name, value ? 1 : 0); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java index 96d6292c6ae..ee7901fa2a4 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java @@ -751,6 +751,18 @@ public void setMetric(final CharSequence key, final Number value) { } } + public void setMetric(final TagMap.EntryReader entry) { + synchronized (unsafeTags) { + unsafeTags.set(entry); + } + } + + public void removeTag(String tag) { + synchronized (unsafeTags) { + unsafeTags.remove(tag); + } + } + /** * Sets a tag to the span. Tags are not propagated to the children. * @@ -790,6 +802,20 @@ public void setTag(final String tag, final String value) { } } + public void setTag(TagMap.EntryReader entry) { + if (entry == null) return; + + // prcheck to avoid boxing + boolean intercepted = + precheckIntercept(entry.tag()) + && tagInterceptor.interceptTag(this, entry.tag(), entry.objectValue()); + if (!intercepted) { + synchronized (unsafeTags) { + unsafeTags.set(entry); + } + } + } + /* * Uses to determine if there's an opportunity to avoid primitve boxing. * If the underlying map doesn't support efficient primitives, then boxing is used. diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java index 5deb8520d3a..bfbb2c63108 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java @@ -77,6 +77,8 @@ default boolean isValid() { AgentSpan setTag(String key, Object value); + AgentSpan setTag(TagMap.EntryReader entry); + AgentSpan setAllTags(Map map); @Override @@ -91,6 +93,8 @@ default boolean isValid() { @Override AgentSpan setMetric(CharSequence key, double value); + AgentSpan setMetric(TagMap.EntryReader metricEntry); + @Override AgentSpan setSpanType(final CharSequence type); diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ImmutableSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ImmutableSpan.java index a7dcab6cebf..1f857a6e7df 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ImmutableSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ImmutableSpan.java @@ -1,5 +1,6 @@ package datadog.trace.bootstrap.instrumentation.api; +import datadog.trace.api.TagMap; import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.interceptor.MutableSpan; import java.util.Map; @@ -41,6 +42,11 @@ public AgentSpan setTag(String key, String value) { return this; } + @Override + public AgentSpan setTag(TagMap.EntryReader entry) { + return this; + } + @Override public AgentSpan setTag(String key, CharSequence value) { return this; @@ -76,6 +82,11 @@ public AgentSpan setMetric(CharSequence key, double value) { return this; } + @Override + public AgentSpan setMetric(TagMap.EntryReader entry) { + return this; + } + @Override public AgentSpan setSpanType(CharSequence type) { return this; From 26cab2216019105ad174317e1981e1f8a7da0a60 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 28 Jan 2026 15:49:00 -0500 Subject: [PATCH 12/22] Adding public create methods to TagMap.Entry Methods are intended to be used to create TagMap.Entry objects for repeatedly used values Overloads are provided for all the supported types to be easier for developers not familiar with TagMap internals. Internally, TagMap still uses the more explicit newEntry methods. --- .../main/java/datadog/trace/api/TagMap.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) 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 10fe96c5fca..9ffb9ffc5ba 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -368,6 +368,30 @@ final class Entry extends EntryChange implements Map.Entry, Entr * These objects might be primitive box objects. */ static final byte ANY = 0; + + public static final Entry create(String tag, Object value) { + return TagMap.Entry.newAnyEntry(tag, value); + } + + public static final Entry create(String tag, CharSequence value) { + return TagMap.Entry.newObjectEntry(tag, value); + } + + public static final Entry create(String tag, int value) { + return TagMap.Entry.newIntEntry(tag, value); + } + + public static final Entry create(String tag, long value) { + return TagMap.Entry.newLongEntry(tag, value); + } + + public static final Entry create(String tag, float value) { + return TagMap.Entry.newFloatEntry(tag, value); + } + + public static final Entry create(String tag, double value) { + return TagMap.Entry.newDoubleEntry(tag, value); + } static Entry newAnyEntry(Map.Entry entry) { return newAnyEntry(entry.getKey(), entry.getValue()); From f6ceef12aeebc081b83eb1f393f59e2958b71226 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 28 Jan 2026 15:54:28 -0500 Subject: [PATCH 13/22] spotless --- .../src/main/java/datadog/trace/api/TagMap.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 9ffb9ffc5ba..7d84259674e 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -368,27 +368,27 @@ final class Entry extends EntryChange implements Map.Entry, Entr * These objects might be primitive box objects. */ static final byte ANY = 0; - + public static final Entry create(String tag, Object value) { return TagMap.Entry.newAnyEntry(tag, value); } - + public static final Entry create(String tag, CharSequence value) { return TagMap.Entry.newObjectEntry(tag, value); } - + public static final Entry create(String tag, int value) { return TagMap.Entry.newIntEntry(tag, value); } - + public static final Entry create(String tag, long value) { return TagMap.Entry.newLongEntry(tag, value); } - + public static final Entry create(String tag, float value) { return TagMap.Entry.newFloatEntry(tag, value); } - + public static final Entry create(String tag, double value) { return TagMap.Entry.newDoubleEntry(tag, value); } From 40847ac6b2b6e63bf41070c9bba0053fce4dfde0 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 29 Jan 2026 11:10:04 -0500 Subject: [PATCH 14/22] Fixed merge Removing statics that were previously moved to TagValueConversions --- .../main/java/datadog/trace/api/TagMap.java | 139 ------------------ 1 file changed, 139 deletions(-) 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 cc2113934da..bed2e17b5ee 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -3240,145 +3240,6 @@ public TagMap.EntryReader next() { return this.entryReadingHelper; } } - - static boolean isA(Object value, byte type) { - switch (type) { - case TagMap.EntryReader.BOOLEAN: - return (value instanceof Boolean); - - case TagMap.EntryReader.INT: - return (value instanceof Integer) || (value instanceof Short) || (value instanceof Byte); - - case TagMap.EntryReader.LONG: - return (value instanceof Long); - - case TagMap.EntryReader.FLOAT: - return (value instanceof Float); - - case TagMap.EntryReader.DOUBLE: - return (value instanceof Double); - - case TagMap.EntryReader.OBJECT: - return true; - - default: - return false; - } - } - - static boolean isNumber(Object value) { - return (value instanceof Number); - } - - static boolean isNumericPrimitive(Object value) { - return (value instanceof Integer) - || (value instanceof Long) - || (value instanceof Double) - || (value instanceof Float) - || (value instanceof Short) - || (value instanceof Byte); - } - - static boolean isObject(Object value) { - boolean isSupportedPrimitive = - (value instanceof Integer) - || (value instanceof Long) - || (value instanceof Double) - || (value instanceof Float) - || (value instanceof Boolean) - || (value instanceof Short) - || (value instanceof Byte); - - // Char is just treated as Object - return !isSupportedPrimitive; - } - - static boolean toBooleanOrDefault(Object value, boolean defaultValue) { - return (value == null) ? defaultValue : toBoolean(value); - } - - static boolean toBoolean(Object value) { - if (value instanceof Boolean) { - return (Boolean) value; - } else if (value instanceof Number) { - // NOTE: This cannot be intValue() because intValue of larger types is 0 when - // the actual value would be less than Integer.MIN_VALUE or for floating point - // types is very close to zero, so using doubleValue instead. - - // While this is a bit ugly, coerced toBoolean is uncommon - return ((Number) value).doubleValue() != 0D; - } else { - return false; - } - } - - static int toIntOrDefault(Object value, int defaultValue) { - return (value == null) ? defaultValue : toInt(value); - } - - static int toInt(Object value) { - if (value instanceof Integer) { - return (Integer) value; - } else if (value instanceof Number) { - return ((Number) value).intValue(); - } else if (value instanceof Boolean) { - return (Boolean) value ? 1 : 0; - } else { - return 0; - } - } - - static long toLong(Object value) { - if (value instanceof Long) { - return (Long) value; - } else if (value instanceof Number) { - return ((Number) value).longValue(); - } else if (value instanceof Boolean) { - return (Boolean) value ? 1L : 0L; - } else { - return 0L; - } - } - - static long toLongOrDefault(Object value, long defaultValue) { - return (value == null) ? defaultValue : toLong(value); - } - - static float toFloat(Object value) { - if (value instanceof Float) { - return (Float) value; - } else if (value instanceof Number) { - return ((Number) value).floatValue(); - } else if (value instanceof Boolean) { - return (Boolean) value ? 1F : 0F; - } else { - return 0F; - } - } - - static float toFloatOrDefault(Object value, float defaultValue) { - return (value == null) ? defaultValue : toFloat(value); - } - - static double toDouble(Object value) { - if (value instanceof Double) { - return (Double) value; - } else if (value instanceof Number) { - return ((Number) value).doubleValue(); - } else if (value instanceof Boolean) { - return (Boolean) value ? 1D : 0D; - } else { - return 0D; - } - } - - static double toDoubleOrDefault(Object value, double defaultValue) { - return (value == null) ? defaultValue : toDouble(value); - } - - static String toString(Object value) { - return value.toString(); - } } final class TagValueConversions { From d96ebfbd78093e64af4d0c2214d6e131272eeff6 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 29 Jan 2026 11:21:52 -0500 Subject: [PATCH 15/22] Clarifying comments --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 3 +++ 1 file changed, 3 insertions(+) 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 bed2e17b5ee..ca8367a28ff 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -370,10 +370,13 @@ final class Entry extends EntryChange implements Map.Entry, Entr static final byte ANY = 0; public static final Entry create(String tag, Object value) { + // NOTE: From the static typing, it is possible that value is a primitive box, so need to call + // Any variant return TagMap.Entry.newAnyEntry(tag, value); } public static final Entry create(String tag, CharSequence value) { + // NOTE: From the static typing, know that value is not a primitive box return TagMap.Entry.newObjectEntry(tag, value); } From 9f644485531228339c72a456d228eab554a83562 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 29 Jan 2026 11:42:13 -0500 Subject: [PATCH 16/22] Adding tests for TagMap.Entry.create - tests exposed missing TagMap.Entry.create for boolean - added explanatory strings to some asserts --- .../main/java/datadog/trace/api/TagMap.java | 4 + .../datadog/trace/api/TagMapEntryTest.java | 128 ++++++++++++++++-- 2 files changed, 122 insertions(+), 10 deletions(-) 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 ca8367a28ff..9c31ac76784 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -379,6 +379,10 @@ public static final Entry create(String tag, CharSequence value) { // NOTE: From the static typing, know that value is not a primitive box return TagMap.Entry.newObjectEntry(tag, value); } + + public static final Entry create(String tag, boolean value) { + return TagMap.Entry.newBooleanEntry(tag, value); + } public static final Entry create(String tag, int value) { return TagMap.Entry.newIntEntry(tag, value); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 13da8723b60..73875515e3c 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -5,10 +5,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import datadog.trace.api.TagMap.Entry; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -17,10 +18,13 @@ import java.util.concurrent.ThreadFactory; import java.util.function.Function; import java.util.function.Supplier; + import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import datadog.trace.api.TagMap.Entry; + /** * Since TagMap.Entry is thread safe and has involves complicated multi-thread type resolution code, * this test uses a different approach to stress ordering different combinations. @@ -47,6 +51,21 @@ public void isNumericPrimitive() { assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.FLOAT)); assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.DOUBLE)); } + + @Test + public void objectEntry_via_create() { + test( + () -> TagMap.Entry.create("foo", "bar"), + TagMap.Entry.OBJECT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue("bar", entry), + checkEquals("bar", entry::stringValue), + checkFalse(entry::isNumber), + checkTrue(entry::isObject), + checkType(TagMap.Entry.OBJECT, entry))); + } @Test public void objectEntry() { @@ -58,8 +77,25 @@ public void objectEntry() { checkKey("foo", entry), checkValue("bar", entry), checkEquals("bar", entry::stringValue), + checkFalse(entry::isNumber), + checkTrue(entry::isObject), + checkType(TagMap.Entry.OBJECT, entry))); + } + + @Test + public void anyEntry_via_create() { + Map map = new HashMap<>(); + + test( + () -> TagMap.Entry.create("foo", map), + TagMap.Entry.ANY, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(map, entry), checkTrue(entry::isObject), - checkFalse(entry::isNumber))); + checkFalse(entry::isNumber), + checkType(TagMap.Entry.OBJECT, entry))); } @Test @@ -73,15 +109,14 @@ public void anyEntry_object() { checkValue("bar", entry), checkTrue(entry::isObject), checkFalse(entry::isNumber), - checkKey("foo", entry), - checkValue("bar", entry))); + checkType(TagMap.EntryReader.OBJECT, entry))); } @ParameterizedTest @ValueSource(booleans = {false, true}) - public void booleanEntry(boolean value) { + public void booleanEntry_via_create(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", value), + () -> TagMap.Entry.create("foo", value), TagMap.Entry.BOOLEAN, (entry) -> multiCheck( @@ -125,6 +160,21 @@ public void anyEntry_boolean(boolean value) { checkType(TagMap.Entry.BOOLEAN, entry), checkValue(value, entry))); } + + @ParameterizedTest + @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + public void intEntry_via_create(int value) { + test( + () -> TagMap.Entry.create("foo", value), + TagMap.Entry.INT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkIsNumericPrimitive(entry), + checkInstanceOf(Number.class, entry), + checkType(TagMap.Entry.INT, entry))); + } @ParameterizedTest @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) @@ -202,6 +252,35 @@ public void anyEntry_int(int value) { checkValue(value, entry))); } + @ParameterizedTest + @ValueSource( + longs = { + Long.MIN_VALUE, + Integer.MIN_VALUE, + -1_048_576L, + -256L, + -128L, + -1L, + 0L, + 1L, + 128L, + 256L, + 1_048_576L, + Integer.MAX_VALUE, + Long.MAX_VALUE + }) + public void longEntry_via_create(long value) { + test( + () -> TagMap.Entry.create("foo", value), + TagMap.Entry.LONG, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.LONG, entry))); + } + @ParameterizedTest @ValueSource( longs = { @@ -289,6 +368,20 @@ public void anyEntry_long(long value) { checkTrue(() -> entry.is(TagMap.Entry.LONG)), checkValue(value, entry))); } + + @ParameterizedTest + @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + public void floatEntry_via_create(float value) { + test( + () -> TagMap.Entry.create("foo", value), + TagMap.Entry.FLOAT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.FLOAT, entry))); + } @ParameterizedTest @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) @@ -331,6 +424,21 @@ public void anyEntry_float(float value) { checkTrue(entry::isNumericPrimitive), checkType(TagMap.Entry.FLOAT, entry))); } + + @ParameterizedTest + @ValueSource( + doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + public void doubleEntry_via_create(double value) { + test( + () -> TagMap.Entry.create("foo", value), + TagMap.Entry.DOUBLE, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkIsNumericPrimitive(entry), + checkType(TagMap.Entry.DOUBLE, entry))); + } @ParameterizedTest @ValueSource( @@ -422,7 +530,7 @@ static final void test( static final void testSingleThreaded( Supplier entrySupplier, byte rawType, Function checkSupplier) { Entry entry = entrySupplier.get(); - assertEquals(rawType, entry.rawType); + assertEquals(rawType, entry.rawType, "rawType"); Check checks = checkSupplier.apply(entry); checks.check(); @@ -431,7 +539,7 @@ static final void testSingleThreaded( static final void testMultiThreaded( Supplier entrySupplier, byte rawType, Function checkSupplier) { Entry sharedEntry = entrySupplier.get(); - assertEquals(rawType, sharedEntry.rawType); + assertEquals(rawType, sharedEntry.rawType, "rawType"); Check checks = checkSupplier.apply(sharedEntry); @@ -716,8 +824,8 @@ static final Check checkInstanceOf(Class klass, TagMap.Entry entry) { static final Check checkType(byte entryType, TagMap.Entry entry) { // TODO: TVC checks return multiCheck( - checkTrue(() -> entry.is(entryType), "type is " + entryType), - checkEquals(entryType, entry::type)); + checkTrue(() -> entry.is(entryType), "Entry::is(type) type=" + entryType), + checkEquals(entryType, entry::type, "Entry::type check")); } static final Check multiCheck(Check... checks) { From b7327b50c6f0f0f9b847e76cceda579a8d17fd71 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 29 Jan 2026 11:42:40 -0500 Subject: [PATCH 17/22] spotless --- .../main/java/datadog/trace/api/TagMap.java | 2 +- .../datadog/trace/api/TagMapEntryTest.java | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) 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 9c31ac76784..68e3022d6c3 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -379,7 +379,7 @@ public static final Entry create(String tag, CharSequence value) { // NOTE: From the static typing, know that value is not a primitive box return TagMap.Entry.newObjectEntry(tag, value); } - + public static final Entry create(String tag, boolean value) { return TagMap.Entry.newBooleanEntry(tag, value); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 73875515e3c..b5bb8dae2dd 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import datadog.trace.api.TagMap.Entry; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -18,13 +19,10 @@ import java.util.concurrent.ThreadFactory; import java.util.function.Function; import java.util.function.Supplier; - import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import datadog.trace.api.TagMap.Entry; - /** * Since TagMap.Entry is thread safe and has involves complicated multi-thread type resolution code, * this test uses a different approach to stress ordering different combinations. @@ -51,7 +49,7 @@ public void isNumericPrimitive() { assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.FLOAT)); assertTrue(TagMap.Entry._isNumericPrimitive(TagMap.Entry.DOUBLE)); } - + @Test public void objectEntry_via_create() { test( @@ -81,11 +79,11 @@ public void objectEntry() { checkTrue(entry::isObject), checkType(TagMap.Entry.OBJECT, entry))); } - + @Test public void anyEntry_via_create() { - Map map = new HashMap<>(); - + Map map = new HashMap<>(); + test( () -> TagMap.Entry.create("foo", map), TagMap.Entry.ANY, @@ -160,7 +158,7 @@ public void anyEntry_boolean(boolean value) { checkType(TagMap.Entry.BOOLEAN, entry), checkValue(value, entry))); } - + @ParameterizedTest @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void intEntry_via_create(int value) { @@ -280,7 +278,7 @@ public void longEntry_via_create(long value) { checkTrue(entry::isNumericPrimitive), checkType(TagMap.Entry.LONG, entry))); } - + @ParameterizedTest @ValueSource( longs = { @@ -368,7 +366,7 @@ public void anyEntry_long(long value) { checkTrue(() -> entry.is(TagMap.Entry.LONG)), checkValue(value, entry))); } - + @ParameterizedTest @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void floatEntry_via_create(float value) { @@ -424,7 +422,7 @@ public void anyEntry_float(float value) { checkTrue(entry::isNumericPrimitive), checkType(TagMap.Entry.FLOAT, entry))); } - + @ParameterizedTest @ValueSource( doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) From 6fa193af94863f3e07bfecac8a278cb7988058f0 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 29 Jan 2026 11:44:13 -0500 Subject: [PATCH 18/22] Comments --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 68e3022d6c3..15b2a557039 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -376,7 +376,7 @@ public static final Entry create(String tag, Object value) { } public static final Entry create(String tag, CharSequence value) { - // NOTE: From the static typing, know that value is not a primitive box + // NOTE: From the static typing, we know that value is not a primitive box return TagMap.Entry.newObjectEntry(tag, value); } From 531f63b5da0d1536eb3810ed9283aeffac57a03d Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 29 Jan 2026 16:42:49 -0500 Subject: [PATCH 19/22] Adding test for TagMap.Entry setters --- .../java/datadog/trace/core/SpanTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java diff --git a/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java b/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java new file mode 100644 index 00000000000..1a1b73fc860 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java @@ -0,0 +1,26 @@ +package datadog.trace.core; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import datadog.trace.api.TagMap; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; + +public class SpanTest { + static final CoreTracer TRACER = CoreTracer.builder().build(); + + @Test + public void setTag_Entry() { + AgentSpan span = TRACER.startSpan("foo", "foo"); + span.setTag(TagMap.Entry.create("message", "hello")); + + assertEquals("hello", span.getTag("message")); + } + + @Test + public void setMetric_Entry() { + AgentSpan span = TRACER.startSpan("foo", "foo"); + span.setMetric(TagMap.Entry.create("metric", 20L)); + + assertEquals(Long.valueOf(20L), span.getTag("metric")); + } +} From db10ed5700c775aad93b821f8450a44745f2e236 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 30 Jan 2026 11:08:25 -0500 Subject: [PATCH 20/22] Adding missing import --- dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java b/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java index 1a1b73fc860..6b7f617699f 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java @@ -5,6 +5,8 @@ import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import org.junit.jupiter.api.Test; + public class SpanTest { static final CoreTracer TRACER = CoreTracer.builder().build(); From 37d100914271ee66d4d9a0149e2239c5a4197be4 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 30 Jan 2026 11:09:19 -0500 Subject: [PATCH 21/22] spotless --- .../java/datadog/trace/core/SpanTest.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java b/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java index 6b7f617699f..628efa3fdd4 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/SpanTest.java @@ -4,25 +4,24 @@ import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; - import org.junit.jupiter.api.Test; public class SpanTest { static final CoreTracer TRACER = CoreTracer.builder().build(); - + @Test public void setTag_Entry() { - AgentSpan span = TRACER.startSpan("foo", "foo"); - span.setTag(TagMap.Entry.create("message", "hello")); - - assertEquals("hello", span.getTag("message")); + AgentSpan span = TRACER.startSpan("foo", "foo"); + span.setTag(TagMap.Entry.create("message", "hello")); + + assertEquals("hello", span.getTag("message")); } - + @Test public void setMetric_Entry() { - AgentSpan span = TRACER.startSpan("foo", "foo"); - span.setMetric(TagMap.Entry.create("metric", 20L)); - - assertEquals(Long.valueOf(20L), span.getTag("metric")); + AgentSpan span = TRACER.startSpan("foo", "foo"); + span.setMetric(TagMap.Entry.create("metric", 20L)); + + assertEquals(Long.valueOf(20L), span.getTag("metric")); } } From 6f2160f16bedd017e76a0d7c490e9974f5e5a487 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 30 Jan 2026 14:24:43 -0500 Subject: [PATCH 22/22] Groovy codeNarc --- .../datadog/trace/agent/test/TrackingSpanDecorator.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy index 3f4a1e09a56..7db4f70a272 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/TrackingSpanDecorator.groovy @@ -115,7 +115,7 @@ class TrackingSpanDecorator implements AgentSpan { } @Override - public final AgentSpan setTag(TagMap.EntryReader entry) { + AgentSpan setTag(TagMap.EntryReader entry) { return delegate.setTag(entry) } @@ -190,7 +190,7 @@ class TrackingSpanDecorator implements AgentSpan { } @Override - public AgentSpan setMetric(TagMap.EntryReader entry) { + AgentSpan setMetric(TagMap.EntryReader entry) { return delegate.setMetric(entry) }