org.rx.util.BeanMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rxlib Show documentation
Show all versions of rxlib Show documentation
A set of utilities for Java
package org.rx.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.rx.annotation.ErrorCode;
import org.rx.annotation.Mapping;
import org.rx.bean.FlagsEnum;
import org.rx.bean.Tuple;
import org.rx.core.Linq;
import org.rx.core.Reflects;
import org.rx.core.Strings;
import org.rx.exception.ApplicationException;
import org.rx.exception.InvalidException;
import org.springframework.cglib.beans.BeanCopier;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import static org.rx.core.Extends.*;
import static org.rx.core.Sys.proxy;
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanMapper {
@RequiredArgsConstructor
private static class MapConfig {
private final BeanCopier copier;
private final ConcurrentHashMap mappings = new ConcurrentHashMap<>();
private FlagsEnum flags;
}
public static final BeanMapper DEFAULT = new BeanMapper();
static final Mapping[] empty = new Mapping[0];
public static Map convertFromObjectString(String str, boolean root) {
String startFlag = root ? "(" : "{", endFlag = root ? ")" : "}";
int s = Strings.indexOf(str, startFlag);
if (s == -1) {
return Collections.emptyMap();
}
int e = Strings.lastIndexOf(str, endFlag);
if (e == -1) {
return Collections.emptyMap();
}
return Linq.from(Strings.split(str.substring(s + 1, e), ", ")).select(p -> {
int i = Strings.indexOf(p, "=");
if (i == -1) {
throw new InvalidException("Parse error {}", p);
}
String k = p.substring(0, i);
String v = p.substring(i + 1);
return Tuple.of(k, Strings.startsWith(v, "{") ? convertFromObjectString(v, false) : v);
}).toMap(p -> p.left, p -> p.right);
}
private final Map config = new ConcurrentHashMap<>();
private final FlagsEnum flags = BeanMapFlag.LOG_ON_MISS_MAPPING.flags();
@ErrorCode
public T define(@NonNull Class type) {
require(type, type.isInterface());
return proxy(type, (m, p) -> {
if (Reflects.OBJECT_METHODS.contains(m)) {
return p.fastInvokeSuper();
}
Object[] args = p.arguments;
Object target = null;
if (m.isDefault()) {
target = Reflects.invokeDefaultMethod(m, p.getProxyObject(), args);
}
boolean noreturn = m.getReturnType() == void.class;
if (args.length >= 2) {
MapConfig config = setMappings(args[0].getClass(), noreturn ? args[1].getClass() : m.getReturnType(), type, p.getProxyObject(), m);
map(args[0], ifNull(target, args[1]), config.flags, m);
return noreturn ? null : args[1];
}
if (args.length == 1) {
if (noreturn) {
return null;
}
MapConfig config = setMappings(args[0].getClass(), m.getReturnType(), type, p.getProxyObject(), m);
if (target == null) {
target = Reflects.newInstance(m.getReturnType());
}
return map(args[0], target, config.flags, m);
}
throw new ApplicationException(values(m.getName()));
});
}
private MapConfig setMappings(Class> from, Class> to, Class> type, Object instance, Method method) {
MapConfig config = getConfig(from, to);
config.mappings.computeIfAbsent(method, k -> {
try {
Method defMethod = type.getDeclaredMethod("getFlags");
if (defMethod.isDefault()) {
config.flags = Reflects.invokeDefaultMethod(defMethod, instance);
}
} catch (Exception e) {
log.warn("BeanMapper.setMappings {}", e.toString());
}
return method.getAnnotationsByType(Mapping.class);
});
return config;
}
private MapConfig getConfig(Class> from, Class> to) {
return config.computeIfAbsent(Objects.hash(from, to), k -> new MapConfig(BeanCopier.create(from, to, true)));
}
public T map(Object source, Class targetType) {
return map(source, (T) Reflects.newInstance(targetType));
}
public T map(Object source, T target) {
return map(source, target, null);
}
public T map(Object source, T target, FlagsEnum flags) {
return map(source, target, flags, null);
}
private T map(@NonNull Object source, @NonNull T target, FlagsEnum flags, Method method) {
if (flags == null) {
flags = this.flags;
}
boolean skipNull = flags.has(BeanMapFlag.SKIP_NULL);
Class> from = source.getClass(), to = target.getClass();
final Linq toProperties = Reflects.getProperties(to);
TreeSet copiedNames = new TreeSet<>();
Map