Skip to content

Commit

Permalink
Refactor/container adapter register (#151)
Browse files Browse the repository at this point in the history
* feat(ContainerAdapterRegister): add an adapter and adapter manager for converting an object into a container (GitHub #149)

* refactor(DynamicContainerOperatorProxyMethodFactory): replace local container adapters with global container adapters (GitHub #149)
Createsequence authored Sep 10, 2023
1 parent f9a24cf commit 17ce3a5
Showing 7 changed files with 276 additions and 83 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cn.crane4j.core.support;

import cn.crane4j.core.container.Container;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.Optional;

/**
* Container adapter register.
*
* @author huangchengxing
* @see DefaultContainerAdapterRegister
* @since 2.2.0
*/
public interface ContainerAdapterRegister {

/**
* Get target type.
*
* @param targetType target type
* @return {@link Adapter} instance.
*/
@Nullable
Adapter getAdapter(Class<?> targetType);

/**
* Register adapter.
*
* @param targetType target type
* @param adapter adapter
*/
void registerAdapter(Class<?> targetType, Adapter adapter);

/**
* Wrap target to {@link Container} if possible.
*
* @param namespace namespace of container
* @param target target
* @param <T> key type of container
* @return {@link Container} instant if possible, null otherwise
*/
@SuppressWarnings("unchecked")
@Nullable
default <T> Container<T> wrapIfPossible(String namespace, Object target) {
return (Container<T>)Optional.ofNullable(target)
.map(Object::getClass)
.map(this::getAdapter)
.map(adapter -> adapter.wrapIfPossible(namespace, target))
.orElse(null);
}

/**
* An adapter for wrap object to {@link Container}.
*
* @author huangchengxing
*/
@FunctionalInterface
interface Adapter {

/**
* Wrap target to {@link Container} if possible.
*
* @param namespace namespace of container
* @param target target
* @return {@link Container} instant if possible, null otherwise
*/
@Nullable
Container<Object> wrapIfPossible(String namespace, @NonNull Object target);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package cn.crane4j.core.support;

import cn.crane4j.core.container.Container;
import cn.crane4j.core.container.Containers;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
* Default implementation for {@link DefaultContainerAdapterRegister}.
*
* @author huangchengxing
* @since 2.2.0
*/
public class DefaultContainerAdapterRegister implements ContainerAdapterRegister {

/**
* default global instance.
*/
public static final DefaultContainerAdapterRegister INSTANCE = new DefaultContainerAdapterRegister();

/**
* registered adapters
*/
protected final Map<Class<?>, Adapter> registeredAdapters = new LinkedHashMap<>();

/**
* Create instance with default adapters.
*/
public DefaultContainerAdapterRegister() {
initDefaultAdapters();
}

/**
* Init default adapters.
*/
@SuppressWarnings("unchecked")
protected void initDefaultAdapters() {
registerAdapter(Map.class, (n, t) -> Containers.forMap(n, (Map<Object, ?>) t));
registerAdapter(Container.class, (n, t) -> (Container<Object>)t);
registerAdapter(DataProvider.class, (n, t) -> Containers.forLambda(n, (DataProvider<Object, Object>) t));
}

/**
* Get target type.
*
* @param targetType target type
* @return {@link Adapter} instance.
*/
@Nullable
@Override
public Adapter getAdapter(Class<?> targetType) {
return findAdaptor(targetType);
}

/**
* Register adapter.
*
* @param targetType target type
* @param adapter adapter
*/
@Override
public void registerAdapter(Class<?> targetType, Adapter adapter) {
registeredAdapters.put(targetType, adapter);
}

@Nullable
private Adapter findAdaptor(Class<?> targetType) {
Adapter adapter = registeredAdapters.get(targetType);
if (Objects.nonNull(adapter)) {
return adapter;
}
return registeredAdapters.entrySet().stream()
.filter(e -> e.getKey().isAssignableFrom(targetType))
.findFirst()
.map(Map.Entry::getValue)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
@@ -2,12 +2,10 @@

import cn.crane4j.annotation.ContainerParam;
import cn.crane4j.core.container.Container;
import cn.crane4j.core.container.ImmutableMapContainer;
import cn.crane4j.core.container.LambdaContainer;
import cn.crane4j.core.executor.BeanOperationExecutor;
import cn.crane4j.core.parser.BeanOperations;
import cn.crane4j.core.support.AnnotationFinder;
import cn.crane4j.core.support.DataProvider;
import cn.crane4j.core.support.ContainerAdapterRegister;
import cn.crane4j.core.support.Grouped;
import cn.crane4j.core.support.MethodInvoker;
import cn.crane4j.core.support.ParameterNameFinder;
@@ -25,7 +23,6 @@
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -50,22 +47,24 @@ public class DynamicContainerOperatorProxyMethodFactory implements OperatorProxy
private final ConverterManager converterManager;
private final ParameterNameFinder parameterNameFinder;
private final AnnotationFinder annotationFinder;
private final Map<Class<?>, ContainerParameterAdaptorProvider> adaptorProviders;
private final ContainerAdapterRegister containerAdapterRegister;

/**
* Create a {@link DynamicContainerOperatorProxyMethodFactory} instance.
*
*
* @param converterManager converter manager
* @param parameterNameFinder parameter name finder
* @param annotationFinder annotation finder
* @param containerAdapterRegister container adapter register
*/
public DynamicContainerOperatorProxyMethodFactory(
ConverterManager converterManager, ParameterNameFinder parameterNameFinder, AnnotationFinder annotationFinder) {
ConverterManager converterManager, ParameterNameFinder parameterNameFinder,
AnnotationFinder annotationFinder, ContainerAdapterRegister containerAdapterRegister) {
this.converterManager = converterManager;
this.parameterNameFinder = parameterNameFinder;
this.annotationFinder = annotationFinder;
this.adaptorProviders = new LinkedHashMap<>();
initAdaptorProvider();
this.containerAdapterRegister = containerAdapterRegister;
}

/**
@@ -79,53 +78,6 @@ public int getSort() {
return ORDER;
}

@SuppressWarnings("unchecked")
private void initAdaptorProvider() {
// adapt for map
adaptorProviders.put(Map.class, (n, p) ->
arg -> ImmutableMapContainer.forMap(n, (Map<Object, ?>) arg)
);
// adapt for container
adaptorProviders.put(Container.class, (n, p) ->
arg -> (Container<Object>) arg
);
// adapt for data provider
adaptorProviders.put(DataProvider.class, (n, p) ->
arg -> LambdaContainer.forLambda(n, (DataProvider<Object, Object>) arg)
);
}

@Nullable
private Function<Object, Container<Object>> findAdaptor(
String namespace, String parameterName, Parameter parameter, Method method) {
Class<?> parameterType = parameter.getType();
ContainerParameterAdaptorProvider provider = adaptorProviders.get(parameterType);
if (Objects.nonNull(provider)) {
return provider.getAdaptor(namespace, parameter);
}
Optional<ContainerParameterAdaptorProvider> optional = adaptorProviders.entrySet().stream()
.filter(e -> e.getKey().isAssignableFrom(parameterType))
.findFirst()
.map(Map.Entry::getValue);
if (!optional.isPresent()) {
log.warn("cannot find adaptor provider for type [{}] of param [{}] in method [{}]", parameterType, parameterName, method);
return null;
}
return optional.get().getAdaptor(namespace, parameter);
}

/**
* Add adaptor provider for specific type.
*
* @param type type
* @param adaptorProvider adaptor provider
*/
public void addAdaptorProvider(
Class<?> type, ContainerParameterAdaptorProvider adaptorProvider) {
Objects.requireNonNull(adaptorProvider, "adaptorProvider name must not null");
adaptorProviders.put(type, adaptorProvider);
}

/**
* Get operator proxy method.
*
@@ -143,7 +95,7 @@ public MethodInvoker get(BeanOperations beanOperations, Method method, BeanOpera
return null;
}
// has other argument but no adapters were found
Function<Object, Container<Object>>[] adaptors = resolveContainerParameterAdaptors(method, parameterNameMap);
ContainerParameterAdapter[] adaptors = resolveContainerParameterAdaptors(method, parameterNameMap);
if (Arrays.stream(adaptors).allMatch(Objects::isNull)) {
return null;
}
@@ -152,28 +104,39 @@ public MethodInvoker get(BeanOperations beanOperations, Method method, BeanOpera
}

@NonNull
private Function<Object, Container<Object>>[] resolveContainerParameterAdaptors(
private ContainerParameterAdapter[] resolveContainerParameterAdaptors(
Method method, Map<String, Parameter> parameterNameMap) {
// resolve parameter adaptors
@SuppressWarnings("unchecked")
Function<Object, Container<Object>>[] adaptors = new Function[parameterNameMap.size()];
ContainerParameterAdapter[] adaptors = new ContainerParameterAdapter[parameterNameMap.size()];
AtomicInteger index = new AtomicInteger(0);
parameterNameMap.forEach((name, param) -> {
parameterNameMap.forEach((n, p) -> {
int curr = index.getAndIncrement();
// first argument must is target which need operate
if (curr == 0) {
return;
}
// adapt as container
String namespace = Optional.ofNullable(annotationFinder.getAnnotation(param, ContainerParam.class))
String namespace = Optional.ofNullable(annotationFinder.getAnnotation(p, ContainerParam.class))
.map(ContainerParam::value)
.orElse(name);
.orElse(n);
// this parameter may not need to be adapted to a container when provider return null
adaptors[curr] = findAdaptor(namespace, name, param, method);
adaptors[curr] = findAdaptor(namespace, n, p, method);
});
return adaptors;
}

@Nullable
private ContainerParameterAdapter findAdaptor(
String namespace, String parameterName, Parameter parameter, Method method) {
Class<?> parameterType = parameter.getType();
ContainerAdapterRegister.Adapter adapter = containerAdapterRegister.getAdapter(parameterType);
if (Objects.isNull(adapter)) {
log.warn("cannot find adaptor provider for type [{}] of param [{}] in method [{}]", parameterType, parameterName, method);
return null;
}
return new ContainerParameterAdapter(namespace, adapter);
}

/**
* Dynamic container method invoker.
*
@@ -184,7 +147,7 @@ protected static class DynamicContainerMethodInvoker implements MethodInvoker {

private final BeanOperations operations;
private final BeanOperationExecutor beanOperationExecutor;
private final Function<Object, Container<Object>>[] adaptors;
private final ContainerParameterAdapter[] adaptors;

/**
* Invoke method.
@@ -208,7 +171,8 @@ public Object invoke(Object target, Object... args) {
// has any temporary containers
Map<String, Container<Object>> temporaryContainers = IntStream.rangeClosed(0, args.length - 1)
.filter(i -> Objects.nonNull(args[i]) && Objects.nonNull(adaptors[i]))
.mapToObj(i -> adaptors[i].apply(args[i]))
.mapToObj(i -> adaptors[i].wrap(args[i]))
.filter(Objects::nonNull)
.collect(Collectors.toMap(Container::getNamespace, Function.identity()));
beanOperationExecutor.execute(
targets, operations, new BeanOperationExecutor.Options.DynamicContainerOption(Grouped.alwaysMatch(), temporaryContainers)
@@ -218,21 +182,17 @@ public Object invoke(Object target, Object... args) {
}

/**
* Provider of container parameter adaptor.
* Adapter for adapt invoke argument to container.
*
* @author huangchengxing
* @since 2.2.0
*/
@FunctionalInterface
public interface ContainerParameterAdaptorProvider {

/**
* Get container parameter adaptor by given namespace and parameter.
*
* @param namespace namespace of container
* @param parameter method parameter
* @return functional interface for adapting argument to container instance
*/
@Nullable
Function<Object, Container<Object>> getAdaptor(String namespace, Parameter parameter);
@RequiredArgsConstructor
protected static class ContainerParameterAdapter {
private final String namespace;
private final ContainerAdapterRegister.Adapter adapter;
public Container<Object> wrap(Object target) {
return adapter.wrapIfPossible(namespace, target);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cn.crane4j.core.support;

import cn.crane4j.core.container.Container;
import cn.crane4j.core.container.Containers;
import cn.crane4j.core.container.LambdaContainer;
import org.junit.Assert;
import org.junit.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* test for {@link DefaultContainerAdapterRegister}
*
* @author huangchengxing
*/
public class DefaultContainerAdapterRegisterTest {

private DefaultContainerAdapterRegister register = new DefaultContainerAdapterRegister();

@Test
public void testRegisterAdapter() {
DefaultContainerAdapterRegister register = new DefaultContainerAdapterRegister();
Assert.assertNull(register.getAdapter(Void.TYPE));
ContainerAdapterRegister.Adapter adapter = (ns, t) -> Containers.empty();
register.registerAdapter(Void.TYPE, adapter);
Assert.assertSame(adapter, register.getAdapter(Void.TYPE));
}

@Test
public void testAdaptFunction() {
ContainerAdapterRegister.Adapter functionAdapter = register.getAdapter(DataProvider.class);
Assert.assertNotNull(functionAdapter);
DataProvider<Object, Object> dp = ids -> Collections.emptyMap();
Assert.assertEquals(functionAdapter, register.getAdapter(dp.getClass()));
Container<Object> functionContainer = register.wrapIfPossible("test", dp);
Assert.assertNotNull(functionContainer);
Assert.assertEquals("test", functionContainer.getNamespace());
}
@Test
public void testAdaptContainer() {
ContainerAdapterRegister.Adapter containerAdapter = register.getAdapter(LambdaContainer.class);
Assert.assertNotNull(containerAdapter);
Assert.assertEquals(containerAdapter, register.getAdapter(Container.class));
Container<Object> container = Containers.empty();
Assert.assertSame(container, register.wrapIfPossible(Container.EMPTY_CONTAINER_NAMESPACE, container));
}

@Test
public void testAdaptMap() {
ContainerAdapterRegister.Adapter mapAdapter = register.getAdapter(LinkedHashMap.class);
Assert.assertNotNull(mapAdapter);
Assert.assertSame(mapAdapter, register.getAdapter(Map.class));
Map<String, Object> map = new HashMap<>();
Assert.assertEquals(map, register.wrapIfPossible("test", map).get(Collections.emptyList()));
}
}
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
import cn.crane4j.core.parser.BeanOperations;
import cn.crane4j.core.support.Crane4jGlobalConfiguration;
import cn.crane4j.core.support.DataProvider;
import cn.crane4j.core.support.DefaultContainerAdapterRegister;
import cn.crane4j.core.support.MethodInvoker;
import cn.crane4j.core.support.SimpleAnnotationFinder;
import cn.crane4j.core.support.SimpleCrane4jGlobalConfiguration;
@@ -49,7 +50,8 @@ public void init() {
beanOperations = operationParser.parse(Foo.class);
operationExecutor = configuration.getBeanOperationExecutor(BeanOperationExecutor.class);
proxyMethodFactory = new DynamicContainerOperatorProxyMethodFactory(
configuration.getConverterManager(), new SimpleParameterNameFinder(), new SimpleAnnotationFinder()
configuration.getConverterManager(), new SimpleParameterNameFinder(),
new SimpleAnnotationFinder(), DefaultContainerAdapterRegister.INSTANCE
);
}

Original file line number Diff line number Diff line change
@@ -25,7 +25,9 @@
import cn.crane4j.core.parser.handler.strategy.SimplePropertyMappingStrategyManager;
import cn.crane4j.core.parser.operation.AssembleOperation;
import cn.crane4j.core.support.AnnotationFinder;
import cn.crane4j.core.support.ContainerAdapterRegister;
import cn.crane4j.core.support.Crane4jGlobalConfiguration;
import cn.crane4j.core.support.DefaultContainerAdapterRegister;
import cn.crane4j.core.support.OperateTemplate;
import cn.crane4j.core.support.ParameterNameFinder;
import cn.crane4j.core.support.SimpleTypeResolver;
@@ -238,6 +240,11 @@ public MethodInvokerContainerCreator methodInvokerContainerCreator(PropertyOpera
return new MethodInvokerContainerCreator(propertyOperator, converterManager);
}

@Bean
public DefaultContainerAdapterRegister defaultContainerAdapterRegister() {
return DefaultContainerAdapterRegister.INSTANCE;
}

@Order
@Bean
public DefaultMethodContainerFactory defaultMethodContainerFactory(
@@ -248,8 +255,11 @@ public DefaultMethodContainerFactory defaultMethodContainerFactory(
@Order
@Bean
public DynamicContainerOperatorProxyMethodFactory dynamicContainerOperatorProxyMethodFactory(
ConverterManager converterManager, ParameterNameFinder parameterNameFinder, AnnotationFinder annotationFinder) {
return new DynamicContainerOperatorProxyMethodFactory(converterManager, parameterNameFinder, annotationFinder);
ConverterManager converterManager, ParameterNameFinder parameterNameFinder,
AnnotationFinder annotationFinder, ContainerAdapterRegister containerAdapterRegister) {
return new DynamicContainerOperatorProxyMethodFactory(
converterManager, parameterNameFinder, annotationFinder, containerAdapterRegister
);
}

@Order(Ordered.LOWEST_PRECEDENCE - 1)
Original file line number Diff line number Diff line change
@@ -32,7 +32,9 @@
import cn.crane4j.core.parser.handler.strategy.SimplePropertyMappingStrategyManager;
import cn.crane4j.core.parser.operation.AssembleOperation;
import cn.crane4j.core.support.AnnotationFinder;
import cn.crane4j.core.support.ContainerAdapterRegister;
import cn.crane4j.core.support.Crane4jGlobalConfiguration;
import cn.crane4j.core.support.DefaultContainerAdapterRegister;
import cn.crane4j.core.support.OperateTemplate;
import cn.crane4j.core.support.ParameterNameFinder;
import cn.crane4j.core.support.SimpleTypeResolver;
@@ -244,6 +246,11 @@ public SpringParameterNameFinder springParameterNameFinder() {
return new SpringParameterNameFinder(new DefaultParameterNameDiscoverer());
}

@Bean
public DefaultContainerAdapterRegister defaultContainerAdapterRegister() {
return DefaultContainerAdapterRegister.INSTANCE;
}

@ConditionalOnMissingBean
@Bean
public AutoOperateAnnotatedElementResolver autoOperateMethodAnnotatedElementResolver(
@@ -444,8 +451,11 @@ public DefaultOperatorProxyMethodFactory defaultProxyMethodFactory(ConverterMana
@Order
@Bean
public DynamicContainerOperatorProxyMethodFactory dynamicContainerOperatorProxyMethodFactory(
ConverterManager converterManager, ParameterNameFinder parameterNameFinder, AnnotationFinder annotationFinder) {
return new DynamicContainerOperatorProxyMethodFactory(converterManager, parameterNameFinder, annotationFinder);
ConverterManager converterManager, ParameterNameFinder parameterNameFinder,
AnnotationFinder annotationFinder, ContainerAdapterRegister containerAdapterRegister) {
return new DynamicContainerOperatorProxyMethodFactory(
converterManager, parameterNameFinder, annotationFinder, containerAdapterRegister
);
}

// endregion

0 comments on commit 17ce3a5

Please sign in to comment.