package org.rx.util;
import org.rx.common.Func2;
import org.rx.common.Action2;
import org.rx.common.Func1;
import org.rx.common.NQuery;
import org.rx.security.MD5Util;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Created by wangxiaoming on 2016/3/11.
*/
public class EntityMapper {
//region NestedTypes
private static class MapEntity {
public Func2<String, String, Boolean> MembersMatcher;
public Object PostProcessor;
public HashSet<String> IgnoreNames;
}
private static class DefaultMatcher implements Func2<String, String, Boolean> {
@Override
public Boolean invoke(String arg1, String arg2) {
return arg1.equals(arg2);
}
}
//endregion
//region Fields
private static final String GET = "get", SET = "set";
private static final int getDefaultWhenNull = 1 << 0, putNewWhenNull = 1 << 1;
private static final MapEntity Default;
private static ConcurrentMap<String, MapEntity> Config;
//endregion
//region Methods
static {
Default = new MapEntity();
Default.IgnoreNames = new HashSet<>();
Default.IgnoreNames.add("getClass");
Default.MembersMatcher = new DefaultMatcher();
Config = new ConcurrentHashMap<>();
}
private static MapEntity getConfig(Class<?> tFrom, Class<?> tTo, int flags) {
String key = getKey(tFrom, tTo);
MapEntity map = Config.get(key);
if (map == null) {
if ((flags & getDefaultWhenNull) == getDefaultWhenNull) {
return Default;
}
if ((flags & putNewWhenNull) == putNewWhenNull) {
Config.putIfAbsent(key, map = new MapEntity());
map.MembersMatcher = Default.MembersMatcher;
}
}
return map;
}
private static String getKey(Class<?> tFrom, Class<?> tTo) {
return MD5Util.md5Hex(tFrom.getName() + tTo.getName());
}
public static <T> T createInstance(Class<T> type) {
try {
return type.newInstance();
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
//endregion
//region MapMethods
public synchronized static <TF, TT> void setMembersMatcher(Class<TF> tFrom, Class<TT> tTo,
Func2<String, String, Boolean> membersMatcher,
Action2<TF, TT> postProcessor) {
MapEntity map = getConfig(tFrom, tTo, putNewWhenNull);
map.MembersMatcher = membersMatcher == null ? Default.MembersMatcher : membersMatcher;
map.PostProcessor = postProcessor;
}
public synchronized static void setIgnoreMembers(Class<?> tFrom, Class<?> tTo, String... ignoreNames) {
MapEntity map = getConfig(tFrom, tTo, putNewWhenNull);
map.IgnoreNames = new HashSet<>(Arrays.asList(ignoreNames));
map.IgnoreNames.add("getClass");
}
public static <TF, TT> TT[] map(Collection<TF> from, Class<TT> toType) {
List<TT> toSet = new ArrayList<>();
for (Object o : from) {
toSet.add(map(o, toType));
}
TT[] x = (TT[]) Array.newInstance(toType, toSet.size());
toSet.toArray(x);
return x;
}
public static <TF, TT> TT map(TF from, Class<TT> toType) {
return map(from, createInstance(toType));
}
public static <TF, TT> TT map(TF from, TT to) {
return map(from, to, false);
}
public static <TF, TT> TT map(TF from, TT to, boolean skipNull) {
Class<?> tFrom = from.getClass(), tTo = to.getClass();
final MapEntity map = getConfig(tFrom, tTo, getDefaultWhenNull);
NQuery<Method> fq = new NQuery<>(tFrom.getMethods()).where(new Func1<Method, Boolean>() {
@Override
public Boolean invoke(Method arg) {
String fName = arg.getName();
return !map.IgnoreNames.contains(fName) && fName.startsWith(GET);
}
});
NQuery<Method> tq = new NQuery<>(tTo.getMethods()).where(new Func1<Method, Boolean>() {
@Override
public Boolean invoke(Method arg) {
return arg.getName().startsWith(SET);
}
});
for (Method fMethod : fq) {
String fName = fMethod.getName();
final String tName = SET + fName.substring(3);
//App.logInfo("EntityMapper Step1 %s", fName);
Method tMethod = tq.where(new Func1<Method, Boolean>() {
@Override
public Boolean invoke(Method arg) {
return map.MembersMatcher.invoke(tName, arg.getName());
}
}).firstOrDefault();
Class<?>[] tArgs;
if (tMethod == null || (tArgs = tMethod.getParameterTypes()).length != 1) {
//App.logInfo("EntityMapper %s Miss %s.%s", tTo.getSimpleName(), tFrom.getSimpleName(), tName);
continue;
}
//App.logInfo("EntityMapper Step2 %s to %s", fName, tName);
try {
Object value = fMethod.invoke(from);
if (value == null && skipNull) {
continue;
}
value = App.changeType(value, tArgs[0]);
tMethod.invoke(to, value);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
if (map.PostProcessor != null) {
Action2<TF, TT> postProcessor = (Action2<TF, TT>) map.PostProcessor;
postProcessor.invoke(from, to);
}
return to;
}
//endregion
public static void trim(Object entity) {
Class<?> type = entity.getClass();
NQuery<Method> fq = new NQuery<>(type.getMethods()).where(new Func1<Method, Boolean>() {
@Override
public Boolean invoke(Method arg) {
return arg.getName().startsWith(GET) && arg.getReturnType().equals(String.class);
}
});
for (Method method : fq) {
try {
String value = (String) method.invoke(entity);
if (App.isNullOrEmpty(value)) {
continue;
}
method = type.getMethod(SET + method.getName().substring(3), String.class);
method.invoke(entity, value.trim());
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
}
public static Object getProperty(Object entity, String propName) {
try {
Method method = entity.getClass()
.getMethod(GET + propName.substring(0, 1).toUpperCase() + propName.substring(1));
return method.invoke(entity);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
}