Skip to content

Commit

Permalink
refactor: reconstruct the AssemblyOperationHandler and uniformly prov…
Browse files Browse the repository at this point in the history
…ide one-to-one, one-to-many, and many-to-many mapping support for all assembly operations(GitHub #25)
  • Loading branch information
huangchengxing committed Mar 19, 2023
1 parent 8faa0c9 commit df582cf
Show file tree
Hide file tree
Showing 18 changed files with 662 additions and 284 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package cn.crane4j.core.executor.handler;

import cn.crane4j.core.container.Container;
import cn.crane4j.core.container.EmptyContainer;
import cn.crane4j.core.executor.AssembleExecution;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Collection;
import java.util.Map;

/**
* Abstract template implementation of {@link AssembleOperationHandler}.
*
* @author huangchengxing
* @param <K> key type
* @param <T> target type
*/
public abstract class AbstractAssembleOperationHandler<K, T extends AbstractAssembleOperationHandler.Target<K>> implements AssembleOperationHandler {

/**
* Perform assembly operation.
*
* @param container container
* @param executions operations to be performed
*/
@Override
public void process(Container<?> container, Collection<AssembleExecution> executions) {
Collection<T> targets = collectToEntities(executions);
if (container instanceof EmptyContainer) {
introspectForEntities(targets);
return;
}
Map<Object, Object> sources = getSourcesFromContainer(container, targets);
if (MapUtil.isEmpty(sources)) {
return;
}
for (T target : targets) {
Object source = getTheAssociatedSource(target, sources);
if (ObjectUtil.isNotEmpty(source)) {
completeMapping(source, target);
}
}
}

/**
* Split the {@link AssembleExecution} into pending objects and wrap it as {@link Target}.
*
* @param executions executions
* @return {@link Target}
*/
protected abstract Collection<T> collectToEntities(Collection<AssembleExecution> executions);

/**
* When the container is {@link EmptyContainer}, introspect the object to be processed.
*
* @param targets targets
*/
protected void introspectForEntities(Collection<T> targets) {
for (T target : targets) {
completeMapping(target.getOrigin(), target);
}
}

/**
* Obtain the corresponding data source object from the data source container based on the entity's key value.
*
* @param container container
* @param targets targets
* @return source objects
*/
protected abstract Map<Object, Object> getSourcesFromContainer(Container<?> container, Collection<T> targets);

/**
* Get the data source object associated with the target object.
*
* @param target target
* @param sources sources
* @return data source object associated with the target object
*/
protected abstract Object getTheAssociatedSource(T target, Map<Object, Object> sources);

/**
* Complete attribute mapping between the target object and the data source object.
*
* @param source source
* @param target target
*/
protected abstract void completeMapping(Object source, T target);

/**
* Target object to be processed.
*
* @param <K> key type
*/
@Getter
@RequiredArgsConstructor
protected abstract static class Target<K> {

/**
* execution
*/
private final AssembleExecution execution;

/**
* objects to be processed
*/
protected final Object origin;

/**
* Get key from target object.
*
* @return key
*/
protected abstract K getKey();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cn.crane4j.core.executor.handler;

import cn.crane4j.core.container.Container;
import cn.crane4j.core.executor.AssembleExecution;
import cn.crane4j.core.parser.PropertyMapping;
import cn.crane4j.core.support.MethodInvoker;
import cn.crane4j.core.support.reflect.PropertyOperator;
import cn.hutool.core.text.CharSequenceUtil;
import lombok.RequiredArgsConstructor;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Abstract implementation of {@link AssembleOperationHandler} based on reflection.
*
* @author huangchengxing
* @see PropertyOperator
*/
@RequiredArgsConstructor
public abstract class AbstractReflexAssembleOperationHandler extends AbstractAssembleOperationHandler<Object, KeyEntity> {

/**
* propertyOperator
*/
protected final PropertyOperator propertyOperator;

/**
* Split the {@link AssembleExecution} into pending objects and wrap it as {@link Target}.
*
* @param executions executions
* @return {@link Target}
*/
@Override
protected Collection<KeyEntity> collectToEntities(Collection<AssembleExecution> executions) {
List<KeyEntity> targets = new ArrayList<>();
for (AssembleExecution execution : executions) {
Class<?> targetType = execution.getTargetType();
String key = execution.getOperation().getKey();
MethodInvoker getter = propertyOperator.findGetter(targetType, key);
Objects.requireNonNull(getter, () -> CharSequenceUtil.format(
"cannot find getter [{}] for [{}]", key, targetType
));
execution.getTargets().stream()
.map(t -> createTarget(execution, t, getter.invoke(t)))
.forEach(targets::add);
}
return targets;
}

/**
* Create a {@link KeyEntity} instance.
*
* @param execution execution
* @param origin origin
* @param keyValue key value
* @return {@link KeyEntity}
*/
protected KeyEntity createTarget(AssembleExecution execution, Object origin, Object keyValue) {
return new KeyEntity(execution, origin, keyValue);
}

/**
* Obtain the corresponding data source object from the data source container based on the entity's key value.
*
* @param container container
* @param targets targets
* @return source objects
*/
@SuppressWarnings("unchecked")
@Override
protected Map<Object, Object> getSourcesFromContainer(Container<?> container, Collection<KeyEntity> targets) {
Set<Object> keys = targets.stream()
.map(KeyEntity::getKey)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return (Map<Object, Object>)((Container<Object>)container).get(keys);
}

/**
* Get the data source object associated with the target object.
*
* @param target target
* @param sources sources
* @return data source object associated with the target object
*/
@Override
protected Object getTheAssociatedSource(KeyEntity target, Map<Object, Object> sources) {
return sources.get(target.getKey());
}

/**
* Complete attribute mapping between the target object and the data source object.
*
* @param source source
* @param target target
*/
@Override
protected void completeMapping(Object source, KeyEntity target) {
AssembleExecution execution = target.getExecution();
Class<?> targetType = execution.getTargetType();
Set<PropertyMapping> mappings = execution.getOperation().getPropertyMappings();
for (PropertyMapping mapping : mappings) {
mappingProperty(target, source, targetType, mapping);
}
}

private void mappingProperty(KeyEntity entity, Object source, Class<?> targetType, PropertyMapping mapping) {
Object sourceValue = mapping.hasSource() ?
propertyOperator.readProperty(source.getClass(), source, mapping.getSource()) : source;
if (Objects.nonNull(sourceValue)) {
propertyOperator.writeProperty(targetType, entity.getOrigin(), mapping.getReference(), sourceValue);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* the reading and writing of beans and the requests for data source containers.
*
* @author huangchengxing
* @see ReflectAssembleOperationHandler
* @see ManyToManyReflexAssembleOperationHandler
* @see MultiKeyAssembleOperationHandler
*/
public interface AssembleOperationHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Used to extract nested pending objects from a pending target object.
*
* @author huangchengxing
* @see ReflectAssembleOperationHandler
* @see ManyToManyReflexAssembleOperationHandler
*/
public interface DisassembleOperationHandler {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cn.crane4j.core.executor.handler;

import cn.crane4j.core.executor.AssembleExecution;
import lombok.Getter;

/**
* basic implementation of {@link AbstractAssembleOperationHandler.Target}
*
* @author huangchengxing
*/
@Getter
public class KeyEntity extends AbstractAssembleOperationHandler.Target<Object> {

/**
* value of key property
*/
private final Object key;

/**
* Create a {@link KeyEntity} instance.
*
* @param execution execution
* @param target target
* @param key value of key property
*/
public KeyEntity(AssembleExecution execution, Object target, Object key) {
super(execution, target);
this.key = key;
}
}
Loading

0 comments on commit df582cf

Please sign in to comment.