Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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 @@ -15,36 +15,48 @@
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;
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;

import static java.lang.String.format;

public class TypeTransformerRegistryImpl implements TypeTransformerRegistry {
private final Map<String, Class<?>> aliases = new HashMap<>();
private final List<TypeTransformer<?, ?>> transformers = new ArrayList<>();
private static final Monitor NOOP_MONITOR = new Monitor() {
};
Comment thread
mokhairymahmoud marked this conversation as resolved.
Outdated

private final Map<Class<?>, Map<Class<?>, TypeTransformer<?, ?>>> transformers = new HashMap<>();
private final Map<String, TypeTransformerRegistry> 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) {
this.transformers.add(transformer);
var registeredTransformer = transformers.computeIfAbsent(transformer.getInputType(), key -> new HashMap<>())
Comment thread
mokhairymahmoud marked this conversation as resolved.
Outdated
.put(transformer.getOutputType(), transformer);
if (registeredTransformer != null) {
monitor.warning(format("Overriding transformer registered for %s -> %s", transformer.getInputType(), transformer.getOutputType()));
}
}

@Override
Expand All @@ -54,14 +66,40 @@ public void register(TypeTransformer<?, ?> transformer) {

@Override
public @NotNull <INPUT, OUTPUT> TypeTransformer<INPUT, OUTPUT> transformerFor(@NotNull INPUT input, @NotNull Class<OUTPUT> outputType) {
return transformers.stream()
.filter(t -> t.getInputType().isInstance(input) && t.getOutputType().equals(outputType))
.findAny()
return findTransformer(input, outputType)
.map(it -> (TypeTransformer<INPUT, OUTPUT>) 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<TypeTransformer<?, ?>> findTransformer(Object input, Class<?> outputType) {
Comment thread
mokhairymahmoud marked this conversation as resolved.
Outdated
return findMostSpecificInputType(input, outputType)
.map(transformers::get)
.map(transformersByOutputType -> transformersByOutputType.get(outputType));
}

private Optional<Class<?>> findMostSpecificInputType(Object input, Class<?> outputType) {
return transformers.entrySet().stream()
.filter(entry -> entry.getKey().isInstance(input))
.filter(entry -> entry.getValue().containsKey(outputType))
.<Class<?>>map(Map.Entry::getKey)
.map(inputType -> Optional.<Class<?>>of(inputType))
.reduce(Optional.<Class<?>>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
public <INPUT, OUTPUT> Result<OUTPUT> transform(@NotNull INPUT input, @NotNull Class<OUTPUT> outputType) {
Objects.requireNonNull(input);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Amadeus - 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<INPUT, OUTPUT> implements TypeTransformer<INPUT, OUTPUT> {
private final Class<INPUT> inputType;
private final Class<OUTPUT> outputType;

public TestTypeTransformer(Class<INPUT> inputType, Class<OUTPUT> outputType) {
this.inputType = inputType;
this.outputType = outputType;
}

@Override
public Class<INPUT> getInputType() {
return inputType;
}

@Override
public Class<OUTPUT> getOutputType() {
return outputType;
}

@Override
public OUTPUT transform(INPUT input, TransformerContext context) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,12 +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() {
Expand All @@ -46,6 +51,35 @@ 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);
verify(monitor).warning(contains("Overriding transformer registered"));
}

@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;
Expand Down Expand Up @@ -96,13 +130,23 @@ void shouldTransformUsingNestedContext() {
TypeTransformer<Integer, String> typeTransformer = mock();
contextRegistry.register(typeTransformer);
registry.register(typeTransformer);
clearInvocations((Object) typeTransformer);

assertThat(nestedContextRegistry.transform(5, String.class))
.isSucceeded().isEqualTo("5");

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading