From b37181acbaea3df66af187a6f98868753cb73820 Mon Sep 17 00:00:00 2001 From: mohamed khairy Date: Wed, 29 Jul 2026 10:19:05 +0200 Subject: [PATCH 1/3] feat: change TypeTransformerRegistry to use a Map instead of Arraylist. --- .../TypeTransformerRegistryImpl.java | 32 +++++++++++--- .../edc/transform/TestTypeTransformer.java | 43 +++++++++++++++++++ .../TypeTransformerRegistryImplTest.java | 39 +++++++++++++++++ 3 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TestTypeTransformer.java diff --git a/core/common/lib/jsonld-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java b/core/common/lib/jsonld-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java index fc46f53b633..20a9cb8ccca 100644 --- a/core/common/lib/jsonld-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java +++ b/core/common/lib/jsonld-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java @@ -20,9 +20,7 @@ import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -31,7 +29,7 @@ public class TypeTransformerRegistryImpl implements TypeTransformerRegistry { private final Map> aliases = new HashMap<>(); - private final List> transformers = new ArrayList<>(); + private final Map, Map, TypeTransformer>> transformers = new HashMap<>(); private final Map contextRegistries = new HashMap<>(); private TypeTransformerRegistry parent; @@ -44,7 +42,8 @@ private TypeTransformerRegistryImpl(TypeTransformerRegistry parent) { @Override public void register(TypeTransformer transformer) { - this.transformers.add(transformer); + transformers.computeIfAbsent(transformer.getInputType(), key -> new HashMap<>()) + .put(transformer.getOutputType(), transformer); } @Override @@ -54,14 +53,33 @@ public void register(TypeTransformer transformer) { @Override public @NotNull TypeTransformer transformerFor(@NotNull INPUT input, @NotNull Class outputType) { - return transformers.stream() - .filter(t -> t.getInputType().isInstance(input) && t.getOutputType().equals(outputType)) - .findAny() + return findTransformer(input, outputType) .map(it -> (TypeTransformer) it) .or(() -> Optional.ofNullable(parent).map(p -> p.transformerFor(input, outputType))) .orElseThrow(() -> new EdcException(format("No Transformer registered that can handle %s -> %s", input.getClass(), outputType))); } + private Optional> findTransformer(Object input, Class outputType) { + var inputTypes = transformers.entrySet().stream() + .filter(entry -> entry.getKey().isInstance(input)) + .filter(entry -> entry.getValue().containsKey(outputType)) + .map(Map.Entry::getKey) + .toList(); + + var mostSpecificInputTypes = inputTypes.stream() + .filter(candidate -> inputTypes.stream() + .noneMatch(other -> !candidate.equals(other) && candidate.isAssignableFrom(other))) + .toList(); + + if (mostSpecificInputTypes.size() > 1) { + throw new EdcException(format("Ambiguous transformers registered for %s -> %s", input.getClass(), outputType)); + } + + return mostSpecificInputTypes.stream() + .findFirst() + .map(inputType -> transformers.get(inputType).get(outputType)); + } + @Override public Result transform(@NotNull INPUT input, @NotNull Class outputType) { Objects.requireNonNull(input); diff --git a/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TestTypeTransformer.java b/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TestTypeTransformer.java new file mode 100644 index 00000000000..5763b5a6611 --- /dev/null +++ b/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TestTypeTransformer.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 - 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.transform; + +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.edc.transform.spi.TypeTransformer; + +public class TestTypeTransformer implements TypeTransformer { + private final Class inputType; + private final Class outputType; + + public TestTypeTransformer(Class inputType, Class outputType) { + this.inputType = inputType; + this.outputType = outputType; + } + + @Override + public Class getInputType() { + return inputType; + } + + @Override + public Class getOutputType() { + return outputType; + } + + @Override + public OUTPUT transform(INPUT input, TransformerContext context) { + return null; + } +} diff --git a/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java b/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java index 182ccf60925..f2fb068c2d6 100644 --- a/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java +++ b/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java @@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoInteractions; @@ -46,6 +47,34 @@ void shouldReturnTheCorrectTransformer() { assertThat(transformer).isInstanceOf(StringIntegerTypeTransformer.class); } + @Test + void shouldReplaceTransformerForTheSameInputAndOutputTypes() { + var replacement = new TestTypeTransformer<>(String.class, Integer.class); + registry.register(replacement); + + assertThat(registry.transformerFor("a string", Integer.class)).isSameAs(replacement); + } + + @Test + void shouldReturnMostSpecificCompatibleTransformer() { + var objectTransformer = new TestTypeTransformer<>(Object.class, Long.class); + var charSequenceTransformer = new TestTypeTransformer<>(CharSequence.class, Long.class); + registry.register(objectTransformer); + registry.register(charSequenceTransformer); + + assertThat(registry.transformerFor("a string", Long.class)).isSameAs(charSequenceTransformer); + } + + @Test + void shouldThrowExceptionWhenCompatibleTransformersAreAmbiguous() { + registry.register(new TestTypeTransformer<>(CharSequence.class, Long.class)); + registry.register(new TestTypeTransformer<>(Comparable.class, Long.class)); + + assertThatThrownBy(() -> registry.transformerFor("a string", Long.class)) + .isInstanceOf(EdcException.class) + .hasMessageContaining("Ambiguous transformers"); + } + @Test void shouldThrowExceptionWhenTransformerDoesNotExist() { var notString = 4L; @@ -96,6 +125,7 @@ void shouldTransformUsingNestedContext() { TypeTransformer typeTransformer = mock(); contextRegistry.register(typeTransformer); registry.register(typeTransformer); + clearInvocations((Object) typeTransformer); assertThat(nestedContextRegistry.transform(5, String.class)) .isSucceeded().isEqualTo("5"); @@ -103,6 +133,15 @@ void shouldTransformUsingNestedContext() { verifyNoInteractions(typeTransformer); } + @Test + void shouldOverrideParentTransformer() { + var replacement = new TestTypeTransformer<>(String.class, Integer.class); + contextRegistry.register(replacement); + + assertThat(contextRegistry.transformerFor("a string", Integer.class)).isSameAs(replacement); + assertThat(registry.transformerFor("a string", Integer.class)).isInstanceOf(StringIntegerTypeTransformer.class); + } + } @Nested From 705fd59e9b4aae908f9ff4cd167f491c0a25169c Mon Sep 17 00:00:00 2001 From: mohamed khairy Date: Mon, 3 Aug 2026 14:47:39 +0200 Subject: [PATCH 2/3] simplified logic for the typetransformer selector. --- .../TypeTransformerRegistryImpl.java | 60 ++++++++++++------- .../edc/transform/TestTypeTransformer.java | 4 +- .../TypeTransformerRegistryImplTest.java | 7 ++- .../core/RuntimeCoreServicesExtension.java | 4 +- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/core/common/lib/jsonld-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java b/core/common/lib/jsonld-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java index 20a9cb8ccca..6c3abe7b5f4 100644 --- a/core/common/lib/jsonld-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java +++ b/core/common/lib/jsonld-lib/src/main/java/org/eclipse/edc/transform/TypeTransformerRegistryImpl.java @@ -15,6 +15,7 @@ package org.eclipse.edc.transform; import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.transform.spi.TypeTransformer; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; @@ -28,22 +29,34 @@ import static java.lang.String.format; public class TypeTransformerRegistryImpl implements TypeTransformerRegistry { - private final Map> aliases = new HashMap<>(); + private static final Monitor NOOP_MONITOR = new Monitor() { + }; + private final Map, Map, TypeTransformer>> transformers = new HashMap<>(); private final Map contextRegistries = new HashMap<>(); + private final Monitor monitor; private TypeTransformerRegistry parent; public TypeTransformerRegistryImpl() { + this(NOOP_MONITOR); + } + + public TypeTransformerRegistryImpl(Monitor monitor) { + this.monitor = monitor; } - private TypeTransformerRegistryImpl(TypeTransformerRegistry parent) { + private TypeTransformerRegistryImpl(TypeTransformerRegistryImpl parent) { this.parent = parent; + monitor = parent.monitor; } @Override public void register(TypeTransformer transformer) { - transformers.computeIfAbsent(transformer.getInputType(), key -> new HashMap<>()) - .put(transformer.getOutputType(), transformer); + var registeredTransformer = transformers.computeIfAbsent(transformer.getInputType(), key -> new HashMap<>()) + .put(transformer.getOutputType(), transformer); + if (registeredTransformer != null) { + monitor.warning(format("Overriding transformer registered for %s -> %s", transformer.getInputType(), transformer.getOutputType())); + } } @Override @@ -60,24 +73,31 @@ public void register(TypeTransformer transformer) { } private Optional> findTransformer(Object input, Class outputType) { - var inputTypes = transformers.entrySet().stream() + return findMostSpecificInputType(input, outputType) + .map(transformers::get) + .map(transformersByOutputType -> transformersByOutputType.get(outputType)); + } + + private Optional> findMostSpecificInputType(Object input, Class outputType) { + return transformers.entrySet().stream() .filter(entry -> entry.getKey().isInstance(input)) .filter(entry -> entry.getValue().containsKey(outputType)) - .map(Map.Entry::getKey) - .toList(); - - var mostSpecificInputTypes = inputTypes.stream() - .filter(candidate -> inputTypes.stream() - .noneMatch(other -> !candidate.equals(other) && candidate.isAssignableFrom(other))) - .toList(); - - if (mostSpecificInputTypes.size() > 1) { - throw new EdcException(format("Ambiguous transformers registered for %s -> %s", input.getClass(), outputType)); - } - - return mostSpecificInputTypes.stream() - .findFirst() - .map(inputType -> transformers.get(inputType).get(outputType)); + .>map(Map.Entry::getKey) + .map(inputType -> Optional.>of(inputType)) + .reduce(Optional.>empty(), (current, candidate) -> { + if (current.isEmpty()) { + return candidate; + } + + if (candidate.get().isAssignableFrom(current.get())) { + return current; + } + + if (current.get().isAssignableFrom(candidate.get())) { + return candidate; + } + throw new EdcException(format("Ambiguous transformers registered for %s -> %s", input.getClass(), outputType)); + }); } @Override diff --git a/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TestTypeTransformer.java b/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TestTypeTransformer.java index 5763b5a6611..712b9b87662 100644 --- a/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TestTypeTransformer.java +++ b/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TestTypeTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 - 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Amadeus * * This program and the accompanying materials are made available under the * terms of the Apache License, Version 2.0 which is available at @@ -8,7 +8,7 @@ * SPDX-License-Identifier: Apache-2.0 * * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * Amadeus - Initial API and Implementation * */ diff --git a/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java b/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java index f2fb068c2d6..71e6f6efc35 100644 --- a/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java +++ b/core/common/lib/jsonld-lib/src/test/java/org/eclipse/edc/transform/TypeTransformerRegistryImplTest.java @@ -15,6 +15,7 @@ package org.eclipse.edc.transform; import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.transform.spi.TypeTransformer; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.junit.jupiter.api.BeforeEach; @@ -24,13 +25,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; public class TypeTransformerRegistryImplTest { - private final TypeTransformerRegistry registry = new TypeTransformerRegistryImpl(); + private final Monitor monitor = mock(); + private final TypeTransformerRegistry registry = new TypeTransformerRegistryImpl(monitor); @BeforeEach void setUp() { @@ -53,6 +57,7 @@ void shouldReplaceTransformerForTheSameInputAndOutputTypes() { registry.register(replacement); assertThat(registry.transformerFor("a string", Integer.class)).isSameAs(replacement); + verify(monitor).warning(contains("Overriding transformer registered")); } @Test diff --git a/core/common/runtime-core/src/main/java/org/eclipse/edc/runtime/core/RuntimeCoreServicesExtension.java b/core/common/runtime-core/src/main/java/org/eclipse/edc/runtime/core/RuntimeCoreServicesExtension.java index 92dfdc1eeaa..2ca4b8ce2ed 100644 --- a/core/common/runtime-core/src/main/java/org/eclipse/edc/runtime/core/RuntimeCoreServicesExtension.java +++ b/core/common/runtime-core/src/main/java/org/eclipse/edc/runtime/core/RuntimeCoreServicesExtension.java @@ -96,8 +96,8 @@ public EventRouter eventRouter(ServiceExtensionContext context) { } @Provider - public TypeTransformerRegistry typeTransformerRegistry() { - return new TypeTransformerRegistryImpl(); + public TypeTransformerRegistry typeTransformerRegistry(ServiceExtensionContext context) { + return new TypeTransformerRegistryImpl(context.getMonitor()); } @Provider From 32c68decee6d310767e7709c5c2d93763985308a Mon Sep 17 00:00:00 2001 From: mohamed khairy Date: Mon, 3 Aug 2026 16:55:22 +0200 Subject: [PATCH 3/3] chore: empty commit