Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
3ab5862
Set known tags by id on TagMap and spans
dougqh Oct 9, 2026
4bf02bb
Ignore unknown ids when clearing a tag by id; fix a comment wrap
dougqh Oct 9, 2026
a2829d1
Precheck and reuse the box in long-valued setters
dougqh Oct 9, 2026
2d87cde
Keep peer.port off the id path
dougqh Oct 9, 2026
7a017a5
Merge set-by-id (#12715) onto master
dougqh Oct 9, 2026
0401a43
Mark intercepted tags with an id bit, via a tracer overlay
dougqh Oct 9, 2026
8949a57
Dispatch TagInterceptor on tag ids
dougqh Oct 9, 2026
cfe2368
Set tags on spans by id first
dougqh Oct 9, 2026
38c9947
Look up tag names by serial in an array, not a switch
dougqh Oct 9, 2026
3545c15
Point stale comments at the tracer overlay and the intercepted bit
dougqh Oct 9, 2026
1642cc9
Index the split-by-tags table by serial, sized to every known tag
dougqh Oct 9, 2026
b9dd573
Check for a known tag id with a range check, not a name lookup
dougqh Oct 9, 2026
75d3fbc
Pin interceptTag(long) above C2's inlining size
dougqh Oct 9, 2026
ecb4f73
Tighten comments on the id-keyed interceptor
dougqh Oct 9, 2026
3f65031
Treat overlay tags as intercepted; tidy from review
dougqh Oct 9, 2026
69d7849
Reject shared-name tag ids on the id-keyed TagMap and span paths
dougqh Oct 9, 2026
46c3b47
Test rejection of shared-name tag ids
dougqh Oct 9, 2026
ad2fa97
Build custom tags by name and known tags by id
dougqh Oct 10, 2026
3a269e1
Resolve direction-dependent tag names by the span's kind
dougqh Oct 9, 2026
d509119
Key shared-name tags by direction now that resolution knows it
dougqh Oct 10, 2026
628da33
Merge master into id-tag-interceptor (#12817)
dougqh Oct 10, 2026
cc43136
Merge id-tag-interceptor (#12817) into tag-registry-direction-resolution
dougqh Oct 10, 2026
dac7060
Give the tracer core a restricted, trusted path into TagMap
dougqh Oct 10, 2026
fbab551
Mark TagMap.Internals @NoEscape
dougqh Oct 10, 2026
8d47c7f
Merge id-tag-interceptor (#12817) into tag-registry-direction-resolution
dougqh Oct 10, 2026
92fb191
Point Internals' trust note at isKnown, the span's check here
dougqh Oct 10, 2026
06dec0f
Store each direction-dependent tag once and apply direction on read
dougqh Oct 13, 2026
d469c66
Name direction-dependent tags by span kind in OTLP export
dougqh Oct 13, 2026
748a8af
Apply last write wins between a tag and its direction-scoped alias
dougqh Oct 13, 2026
44826c6
Carry the direction role in tag ids so plain setTag pays nothing extra
dougqh Oct 13, 2026
dc06725
Move alias superseding off the setTag store fast path
dougqh Oct 13, 2026
44c9c2f
Cover SpanTags and the KnownTagCodec direction helpers in internal-api
dougqh Oct 13, 2026
d3d6ee4
Route alias-target writes only on spans holding a pending alias
dougqh Oct 13, 2026
a48e145
Merge tag-registry-direction-resolution into the single-representatio…
dougqh Oct 13, 2026
85c222d
Cover the remaining TagMap.Internals setKnown overloads
dougqh Oct 13, 2026
f08c1c3
Add datadog.apicontract to CODEOWNERS
dougqh Oct 13, 2026
f796149
Merge branch 'master' into dougqh/id-tag-interceptor
dougqh Oct 13, 2026
22d0fd1
Give peer.port one direction-neutral id
dougqh Oct 13, 2026
ba715de
Name peer.port by the span's kind in OTLP export
dougqh Oct 13, 2026
e55a358
Merge id-tag-interceptor (#12817, before the peer.port move) into tag…
dougqh Oct 13, 2026
01ea7f5
Merge id-tag-interceptor (#12817) into tag-registry-direction-resolution
dougqh Oct 13, 2026
584310c
Let C2 inline the constructors of TagMap's nested classes
dougqh Oct 11, 2026
3842b16
Remove TagMap's remaining synthetic accessors and guard against them
dougqh Oct 11, 2026
6e6077e
Merge id-tag-interceptor (#12817) into tag-registry-direction-resolution
dougqh Oct 13, 2026
64d6cf3
Merge master into id-tag-interceptor
dougqh Oct 14, 2026
bb0bbb0
Cover TagMap.Internals, KnownTagCodec and EntryReader paths
dougqh Oct 14, 2026
fb6410e
Format the tag-registry generator to its spotless rules
dougqh Oct 14, 2026
6593371
Merge id-tag-interceptor (#12817) into tag-registry-direction-resolution
dougqh Oct 14, 2026
0d7b732
Drop #12817's direction stand-in tests, covered by SpanTagsTest
dougqh Oct 14, 2026
97a508b
Order the tag-id carve bits by position
dougqh Oct 14, 2026
fa54aca
Merge master into id-tag-interceptor
dougqh Oct 14, 2026
4bb2c75
Merge id-tag-interceptor (#12817) into tag-registry-direction-resolution
dougqh Oct 14, 2026
1c88d5e
Re-apply split-by-tags language by its id
dougqh Oct 14, 2026
5ba0a09
Say what a tag id's intercepted bit does and doesn't decide
dougqh Oct 14, 2026
1a084b2
Merge #12817's final commits into tag-registry-direction-resolution
dougqh Oct 15, 2026
a24cb0d
Merge remote-tracking branch 'origin/master' into dougqh/tag-registry…
dougqh Oct 15, 2026
32ad2a3
Restore the conventions' one-tag wording for peer.port
dougqh Oct 15, 2026
306b312
Tidy the direction-resolution code from review
dougqh Oct 15, 2026
96df27a
Keep the alias slow paths to spans that really hold a pending alias
dougqh Oct 15, 2026
a5c6556
Make a removal before the span's kind supersede every meaning
dougqh Oct 15, 2026
d7356bc
Extend the pending-alias gate to reads, removals and entry routing
dougqh Oct 15, 2026
c517b18
Encapsulate direction handling in a package-private DirectionalTags
dougqh Oct 15, 2026
ab4b1ac
Write and read known tags by id without building an Entry per call
dougqh Oct 16, 2026
351145d
Resolve aliases when the kind is set, and route them as their tag
dougqh Oct 16, 2026
6aa6d4f
Close three more gaps between the write paths and direction handling
dougqh Oct 16, 2026
dabddda
Let a Datadog tag name double as another tag's direction-scoped alias
dougqh Oct 16, 2026
b1107a3
Drop the OTLP export check for a tag written by id on the wrong side
dougqh Oct 16, 2026
66a069c
Merge remote-tracking branch 'origin/master' into dougqh/tag-registry…
dougqh Oct 16, 2026
d7f0ffc
Resolve direction-scoped names at the OpenTelemetry boundary, not in …
dougqh Oct 16, 2026
416663b
Merge master (#12882 slot coloring) into tag-registry-direction-resol…
dougqh Oct 19, 2026
3742a7e
Resolve held OTel attributes as soon as the span kind is set
dougqh Oct 19, 2026
9e6bd6c
Ignore null OTel attribute keys again
dougqh Oct 19, 2026
810d174
Merge branch 'master' into dougqh/tag-registry-direction-resolution
dougqh Oct 20, 2026
ac13674
Merge branch 'master' into dougqh/tag-registry-direction-resolution
dougqh Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ object KnownTagsEmitter {
fun otelNameC(ddName: String) = otelNameOfConst.getValue(ddName)

val keyOfOrder = reg.tags.map { it.ddName } // stable emit order
// OpenTelemetry names that denote a different tag depending on the span's direction. keyOf
// ignores direction (a shared Datadog name such as server.port resolves to its own tag);
// directionalKeyOf(name, direction) switches on each one's tag per direction.
val directional = sortedMapOf<String, MutableMap<TagConventions.Direction, String>>()
for (t in reg.tags) {
for ((direction, otel) in t.scopedOtelNames) {
directional.getOrPut(otel) { mutableMapOf() }[direction] = t.ddName
}
}
return buildString {
// Public API first (name + encoded id couplets), so readers see the useful parts up top; the
// serial ids and keyOf/resolver machinery follow below. Derivation is in the trailing comment.
Expand Down Expand Up @@ -95,6 +104,34 @@ object KnownTagsEmitter {

// Serial numbers (globalSerial per tag) — public, so a switch on KnownTagCodec.serialNum outside
// this package (the resolver here, TagInterceptor in core) can name them as case labels.
// Per-direction aliases: each OpenTelemetry name that names a tag on spans of one direction, as
// that tag's id. Code that knows its direction writes in OpenTelemetry terms by id
// (Outbound.SERVER_ADDRESS_ID is PEER_HOSTNAME_ID) without knowing the Datadog name, and
// without a lookup. Direction-free renames are repeated in each, so one class covers a span.
for (direction in TagConventions.Direction.entries) {
val aliases = sortedMapOf<String, String>()
for (t in reg.tags) {
val otel = t.otelName ?: t.scopedOtelNames[direction] ?: continue
aliases[otel] = t.ddName
}
if (aliases.isEmpty()) continue
val holder = DIRECTION_CLASS.getValue(direction)
appendLine(" /** OpenTelemetry names on ${DIRECTION_SPANS.getValue(direction)} spans, as the ids of the tags they name. */")
appendLine(" public static final class $holder {")
val local = HashSet<String>()
for ((otel, ddName) in aliases) {
var constant = withSuffix(sanitize(otel), "_ID")
var n = 2
while (!local.add(constant)) constant = "${withSuffix(sanitize(otel), "_ID")}_${n++}"
appendLine(" /** {@code ${escape(otel)}}: {@code ${escape(ddName)}}. */")
appendLine(" public static final long $constant = $className.${idC(ddName)};")
}
appendLine()
appendLine(" private $holder() {}")
appendLine(" }")
appendLine()
}

appendLine(" // ---- serial numbers ----")
for (t in reg.tags) {
appendLine(" public static final int ${serialC(t.ddName)} = ${t.serial};")
Expand Down Expand Up @@ -221,6 +258,30 @@ object KnownTagsEmitter {
int slot = StringIndex.EmbeddingSupport.indexOf(KEYOF_HASHES, KEYOF_KEYS, name);
return slot < 0 ? 0L : KEYOF_IDS[slot];
}

@Override
public long directionalKeyOf(String name, int direction) {
switch (name) {
Comment thread
dougqh marked this conversation as resolved.
""".trimIndent()
)
for ((name, byDirection) in directional) {
appendLine(" case \"${escape(name)}\":")
appendLine(" switch (direction) {")
for (d in TagConventions.Direction.entries) {
val tag = byDirection[d] ?: continue
appendLine(" case KnownTagCodec.DIRECTION_${d.name}:")
appendLine(" return ${idC(tag)};")
}
appendLine(" default:")
appendLine(" return 0L;")
appendLine(" }")
}
appendLine(
"""
default:
return 0L;
}
}
};

private $className() {}
Expand All @@ -230,6 +291,20 @@ object KnownTagsEmitter {
}
}

private val DIRECTION_CLASS =
mapOf(
TagConventions.Direction.INBOUND to "Inbound",
TagConventions.Direction.OUTBOUND to "Outbound",
TagConventions.Direction.NONE to "Internal",
)

private val DIRECTION_SPANS =
mapOf(
TagConventions.Direction.INBOUND to "server and consumer",
TagConventions.Direction.OUTBOUND to "client and producer",
TagConventions.Direction.NONE to "internal",
)

private fun hex(id: Long): String = "0x%016XL".format(Locale.ROOT, id)

private fun escape(value: String): String = String(JsonStringEncoder.getInstance().quoteAsString(value))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class TagConventions private constructor(
val name: String
get() = identity.label

/** The Datadog-namespace name, shared by the declarations of a name declared per direction. */
/** The Datadog-namespace name, shared by the tags of a name declared per direction. */
val ddName: String
get() = identity.ddName
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,32 @@ class TagRegistry private constructor(val tags: List<Tag>) {
}

validateOtelNames(tags)
return TagRegistry(tags)
return TagRegistry(claimSharedNames(tags))
}

/**
* A tag whose Datadog name is also another tag's direction-scoped OpenTelemetry name keeps that
* name in every direction the other tag doesn't claim it: `server.port` is its own tag (the
* server's port), except on outbound spans, where it is `peer.port`. The Datadog name still
* resolves to the tag without a direction; only OpenTelemetry-side resolution by direction maps
* it to the other tag.
*/
private fun claimSharedNames(tags: List<Tag>): List<Tag> {
val claimedBy = HashMap<String, MutableSet<TagConventions.Direction>>()
for (t in tags) {
for ((direction, otel) in t.scopedOtelNames) {
claimedBy.getOrPut(otel) { mutableSetOf() }.add(direction)
}
}
return tags.map { t ->
val claimed = claimedBy[t.ddName] ?: return@map t
require(t.otelName == null && t.scopedOtelNames.isEmpty()) {
"'${t.name}' is another tag's OpenTelemetry name on ${claimed.joinToString { it.yamlKey }} " +
"spans, so it cannot be renamed itself"
}
val own = TagConventions.Direction.entries.filter { it !in claimed }.associateWith { t.ddName }
t.copy(scopedOtelNames = own.toSortedMap())
}
}

/** Requirement levels whose tags may share a slot with each other when a span type overflows. */
Expand Down Expand Up @@ -207,10 +232,10 @@ class TagRegistry private constructor(val tags: List<Tag>) {
private fun strongestRequirement(levels: List<String>): String = levels.minBy { REQUIREMENT_STRENGTH.indexOf(it).let { i -> if (i < 0) Int.MAX_VALUE else i } }

/**
* Rejects OpenTelemetry names that collide with any Datadog name. Within each direction,
* a rename must also belong to a single tag. Direction-free renames reserve their name
* in every direction. Scoped renames may share a name across directions: `server.address`
* maps to `http.hostname` inbound and `peer.hostname` outbound.
* Within each direction, a rename must belong to a single tag. A direction-free rename can't
* be a Datadog name too, since without a direction its name would resolve to two tags. A scoped
* rename may share a name across directions -- `server.address` is `http.hostname` inbound and
* `peer.hostname` outbound -- and may be another tag's Datadog name (see [claimSharedNames]).
*/
private fun validateOtelNames(tags: List<Tag>) {
val canonical = tags.map { it.ddName }.toSet()
Expand All @@ -219,7 +244,7 @@ class TagRegistry private constructor(val tags: List<Tag>) {
val byDirection =
t.otelName?.let { otel -> TagConventions.Direction.entries.associateWith { otel } } ?: t.scopedOtelNames
for ((direction, otel) in byDirection) {
require(otel !in canonical) {
require(otel != t.ddName && (t.otelName == null || otel !in canonical)) {
"OpenTelemetry name '$otel' (of '${t.name}') collides with canonical tag name '$otel'"
}
val prev = owner.put(direction to otel, t.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ object TagRegistryGenerator {

# DIRECTION-SCOPED OPENTELEMETRY NAMES. Each applies only on spans of the given direction, so
# it is not in the tables above: a tag is emitted under it when its span has that direction,
# but name resolution does not use it until it knows the direction.
# and a value written under it is resolved to the tag once the span's direction is known.
""".trimIndent()
)
val scoped =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ class TagRegistryGeneratorPluginTest {
String nameOf(long id);
String openTelemetryNameOf(long id, int direction);
long keyOf(String name);
long directionalKeyOf(String name, int direction);
}
public static final int DIRECTION_UNKNOWN = -1;
public static final int DIRECTION_INBOUND = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,85 @@ class TagRegistryGeneratorTest {
assertThat(conventions.resolve("http.server").map { it.name }).contains("peer.port@inbound")
}

@Test
fun `a Datadog name can be another tag's OpenTelemetry name in some directions`() {
val domain =
"""
span_types:
http.server:
span-kind: server
tags: [{dd-name: server.port, type: int}]
http.client: {span-kind: client, include: [outbound_peer]}
mixins:
outbound_peer: {span-kind: client, tags: [{dd-name: peer.port, type: int, otel-name: server.port}]}
"""
val output = File(directory, "generated")

TagRegistryGenerator.generate(directory.conventionsFile(domain), output)

val tags = TagRegistry.build(tagConventions(domain)).tags.associateBy { it.name }
assertThat(tags.getValue("server.port").scopedOtelNames)
.isEqualTo(
mapOf(TagConventions.Direction.INBOUND to "server.port", TagConventions.Direction.NONE to "server.port")
)
val source = contents(output).getValue("java/datadog/trace/api/KnownTags.java")
assertThat(source.substringAfter("case \"server.port\":").substringBefore("default:"))
.contains(
"case KnownTagCodec.DIRECTION_INBOUND:",
"return SERVER_PORT_ID;",
"case KnownTagCodec.DIRECTION_OUTBOUND:",
"return PEER_PORT_ID;",
"case KnownTagCodec.DIRECTION_NONE:",
)
// Without a direction the Datadog name is its own tag; only directionalKeyOf maps it elsewhere.
assertThat(source.substringAfter("KEYOF_VALUES = {").substringBefore("};"))
.contains("SERVER_PORT_ID,")
.doesNotContain("SENTINEL")
}

@Test
fun `each direction gets its OpenTelemetry names as the ids of the tags they name`() {
val yaml = directory.conventionsFile(
"""
span_types:
base: {abstract: true, tags: [{dd-name: http.method, otel-name: http.request.method}]}
http.server:
span-kind: server
tags:
- {dd-name: http.hostname, otel-name: server.address}
- {dd-name: server.port, type: int}
http.client: {span-kind: client, include: [outbound_peer]}
mixins:
outbound_peer:
span-kind: client
tags:
- {dd-name: peer.hostname, otel-name: server.address}
- {dd-name: peer.port, type: int, otel-name: server.port}
"""
)
val output = File(directory, "generated")

TagRegistryGenerator.generate(yaml, output)

val source = contents(output).getValue("java/datadog/trace/api/KnownTags.java")
fun holder(name: String) = source.substringAfter("public static final class $name {").substringBefore("private $name() {}")
assertThat(holder("Outbound"))
.contains(
"SERVER_ADDRESS_ID = KnownTags.PEER_HOSTNAME_ID;",
"SERVER_PORT_ID = KnownTags.PEER_PORT_ID;",
"HTTP_REQUEST_METHOD_ID = KnownTags.HTTP_METHOD_ID;",
)
assertThat(holder("Inbound"))
.contains(
"SERVER_ADDRESS_ID = KnownTags.HTTP_HOSTNAME_ID;",
"SERVER_PORT_ID = KnownTags.SERVER_PORT_ID;",
"HTTP_REQUEST_METHOD_ID = KnownTags.HTTP_METHOD_ID;",
)
assertThat(holder("Internal"))
.contains("SERVER_PORT_ID = KnownTags.SERVER_PORT_ID;")
.doesNotContain("SERVER_ADDRESS_ID")
}

@Test
fun `a Datadog name declared per direction is one tag`() {
val yaml = directory.conventionsFile(
Expand Down Expand Up @@ -434,6 +513,8 @@ class TagRegistryGeneratorTest {
@TableTest(
"""
scenario | domain | message
scoped rename to own name | span_types: {a: {span-kind: client, tags: [{dd-name: x, otel-name: x}]}} | collides with canonical
shared name renamed itself | span_types: {a: {span-kind: server, tags: [{dd-name: x, otel-name: o}]}, b: {span-kind: client, tags: [{dd-name: y, otel-name: x}]}} | so it cannot be renamed itself
same name in one direction | span_types: {a: {span-kind: server, tags: [{dd-name: x, otel-name: o}]}, b: {span-kind: consumer, tags: [{dd-name: y, otel-name: o}]}} | claimed by both 'x' and 'y' on inbound spans
neutral contradicts scoped | span_types: {a: {span-kind: server, tags: [{dd-name: x, otel-name: o, span-kind-neutral: true}]}, b: {span-kind: client, tags: [{dd-name: y, otel-name: o}]}} | on outbound spans
map-form otel-name | span_types: {a: {span-kind: client, tags: [{dd-name: x, otel-name: {outbound: o}}]}} | invalid otel-name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static java.util.Collections.emptyMap;
import static java.util.Locale.ROOT;

import datadog.trace.api.KnownTagCodec;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.SpanAttributes;
import datadog.trace.bootstrap.instrumentation.api.Tags;
Expand Down Expand Up @@ -48,6 +49,18 @@ public final class OtelConventions {

private OtelConventions() {}

/**
* The tag to store an attribute under on spans of {@code direction}. A direction-scoped
* OpenTelemetry name is stored as the Datadog tag it denotes there: {@code server.address} is
* {@code peer.hostname} on a client span and {@code http.hostname} on a server span. Every other
* name, and a direction-scoped one that denotes no tag in {@code direction}, is stored as
* written.
*/
static String attributeTag(String key, int direction) {
long tagId = KnownTagCodec.directionalKeyOf(key, direction);
return tagId == 0 ? key : KnownTagCodec.nameOf(tagId);
}

/**
* Convert OpenTelemetry {@link SpanKind} to {@link Tags#SPAN_KIND} value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@
import static datadog.opentelemetry.shim.trace.OtelConventions.applyNamingConvention;
import static datadog.opentelemetry.shim.trace.OtelConventions.applyReservedAttribute;
import static datadog.opentelemetry.shim.trace.OtelConventions.applySpanEventExceptionAttributes;
import static datadog.opentelemetry.shim.trace.OtelConventions.attributeTag;
import static datadog.opentelemetry.shim.trace.OtelConventions.convertEventAttributes;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
import static io.opentelemetry.api.trace.StatusCode.ERROR;
import static io.opentelemetry.api.trace.StatusCode.OK;
import static io.opentelemetry.api.trace.StatusCode.UNSET;

import datadog.context.ContextScope;
import datadog.trace.api.KnownTagCodec;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
import datadog.trace.bootstrap.instrumentation.api.AttachableWrapper;
import datadog.trace.bootstrap.instrumentation.api.ErrorPriorities;
import datadog.trace.bootstrap.instrumentation.api.ResourceNamePriorities;
import datadog.trace.bootstrap.instrumentation.api.SpanWrapper;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.WithAgentSpan;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
Expand All @@ -33,11 +36,17 @@
@ParametersAreNonnullByDefault
public class OtelSpan implements Span, WithAgentSpan, SpanWrapper {
private final AgentSpan delegate;

/** The direction of the span's kind, fixed at start; it decides direction-scoped names. */
private final int direction;

private StatusCode statusCode = UNSET;
private volatile boolean recording = true;

public OtelSpan(AgentSpan delegate) {
this.delegate = delegate;
Object kind = delegate.getTag(Tags.SPAN_KIND);
this.direction = KnownTagCodec.directionOfKind(kind == null ? null : kind.toString());
if (delegate instanceof AttachableWrapper) {
((AttachableWrapper) delegate).attachWrapper(this);
}
Expand Down Expand Up @@ -69,7 +78,7 @@ public <T> Span setAttribute(AttributeKey<T> key, T value) {
}
break;
default:
this.delegate.setTag(key.getKey(), value);
this.delegate.setTag(attributeTag(key.getKey(), this.direction), value);
break;
}
}
Expand Down
Loading
Loading