package com.brightgenerous.bean;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import com.brightgenerous.bean.AbstractBean.Fill;
import com.brightgenerous.bean.AbstractBean.Fill.Null;
import com.brightgenerous.bean.AbstractBean.Primary;
public class BeanUtils {
private static final Map<Class<?>, LinkedHashSet<Field>> classPirmarys = new ConcurrentHashMap<>();
private static final Map<Class<?>, LinkedHashMap<Field, Entry<Class<?>, Constructor<?>>>> classFills = new ConcurrentHashMap<>();
private static final Map<Class<?>, Constructor<?>> classConstructor = new ConcurrentHashMap<>();
private static final Object[] empty = new Object[0];
private static final Comparator<? extends AbstractBean> comparator = new SerializableComparator<AbstractBean>() {
private static final long serialVersionUID = 5555378345381027585L;
@Override
public int compare(AbstractBean bean1, AbstractBean bean2) {
return comparePrimary(bean1, bean2);
}
};
private static final Comparator<? extends AbstractBean> eachComparator = new SerializableComparator<AbstractBean>() {
private static final long serialVersionUID = 5555378345381027585L;
@Override
public int compare(AbstractBean bean1, AbstractBean bean2) {
return comparePrimary(bean1, bean2, true);
}
};
private BeanUtils() {
}
public static <T extends AbstractBean> Comparator<T> primaryComparator() {
return primaryComparator(false);
}
public static <T extends AbstractBean> Comparator<T> primaryComparator(boolean eachField) {
return (Comparator<T>) (eachField ? eachComparator : comparator);
}
public static <T extends AbstractBean> T defaultIfPrimaryNull(T bean, T def, T... defs) {
if (notPrimaryNull(bean)) {
return bean;
}
if (notPrimaryNull(def)) {
return def;
}
if (defs != null) {
for (T d : defs) {
if (notPrimaryNull(d)) {
return d;
}
}
}
return null;
}
public static <T extends AbstractBean> boolean allPrimaryNull(T bean, T... beans) {
if (notPrimaryNull(bean)) {
return false;
}
if ((beans != null) && (0 < beans.length)) {
for (T b : beans) {
if (notPrimaryNull(b)) {
return false;
}
}
}
return true;
}
public static <T extends AbstractBean> boolean allNotPrimaryNull(T bean, T... beans) {
if (primaryNull(bean)) {
return false;
}
if (beans == null) {
return false;
}
if (0 < beans.length) {
for (T b : beans) {
if (primaryNull(b)) {
return false;
}
}
}
return true;
}
public static <T extends AbstractBean> boolean anyPrimaryNull(T bean, T... beans) {
return !allNotPrimaryNull(bean, beans);
}
public static <T extends AbstractBean> boolean anyNotPrimaryNull(T bean, T... beans) {
return !allPrimaryNull(bean, beans);
}
public static boolean primaryNull(AbstractBean bean) {
if (bean == null) {
return true;
}
LinkedHashSet<Field> fields = getPrimaryFields(bean);
if (fields.isEmpty()) {
return true;
}
try {
for (Field field : fields) {
Object obj;
if ((obj = field.get(bean)) == null) {
return true;
}
if (obj instanceof AbstractBean) {
if (primaryNull((AbstractBean) obj)) {
return true;
}
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return false;
}
public static boolean notPrimaryNull(AbstractBean bean) {
return !primaryNull(bean);
}
public static boolean equalsPrimary(AbstractBean bean1, AbstractBean bean2) {
return equalsPrimary(bean1, bean2, false);
}
public static boolean equalsPrimary(AbstractBean bean1, AbstractBean bean2, boolean eachField) {
try {
return comparePrimary(bean1, bean2, eachField) == 0;
} catch (ClassCastException e) {
}
return false;
}
public static boolean notEqualsPrimary(AbstractBean bean1, AbstractBean bean2) {
return !equalsPrimary(bean1, bean2);
}
public static boolean notEqualsPrimary(AbstractBean bean1, AbstractBean bean2, boolean eachField) {
return !equalsPrimary(bean1, bean2, eachField);
}
public static int comparePrimary(AbstractBean bean1, AbstractBean bean2) {
return comparePrimary(bean1, bean2, false);
}
public static int comparePrimary(AbstractBean bean1, AbstractBean bean2, boolean eachField) {
if (bean1 == bean2) {
return 0;
}
if (eachField) {
if (bean1 == null) {
return 1;
}
if (bean2 == null) {
return -1;
}
} else {
if (bean1 == null) {
return primaryNull(bean2) ? 0 : 1;
}
if (bean2 == null) {
return primaryNull(bean1) ? 0 : -1;
}
}
checkComperable(bean1, bean2);
Object[] values1 = getPrimaryValues(bean1);
Object[] values2 = getPrimaryValues(bean2);
final int length1 = values1.length;
final int length2 = values2.length;
boolean null1 = false;
boolean null2 = false;
int compare = 0;
for (int i = 0; i < Math.min(length1, length2); i++) {
Object value1 = values1[i];
Object value2 = values2[i];
int c = 0;
if ((c = compareValue(value1, value2, eachField)) != 0) {
if (eachField) {
return c;
}
if (compare == 0) {
compare = c;
}
}
null1 |= value1 == null;
null2 |= value2 == null;
}
if (eachField) {
return length1 - length2;
}
if (null1 && null2) {
return 0;
}
if (length1 != length2) {
if (!null1 && (length2 < length1)) {
for (int i = length2; i < length1; i++) {
null1 |= values1[i] == null;
}
}
if (!null2 && (length1 < length2)) {
for (int i = length1; i < length2; i++) {
null2 |= values2[i] == null;
}
}
}
if (null1 && null2) {
return 0;
}
if (null1) {
return 1;
}
if (null2) {
return -1;
}
if (compare != 0) {
return compare;
}
return length1 - length2;
}
private static int compareValue(Object obj1, Object obj2, boolean eachField) {
if (obj1 == obj2) {
return 0;
}
if (obj1 == null) {
return 1;
}
if (obj2 == null) {
return -1;
}
checkComperable(obj1, obj2);
if ((obj1 instanceof AbstractBean) && (obj2 instanceof AbstractBean)) {
return comparePrimary((AbstractBean) obj1, (AbstractBean) obj2, eachField);
}
return ((Comparable<Object>) obj1).compareTo(obj2);
}
private static void checkComperable(Object obj1, Object obj2) {
Class<?> clazz1 = obj1.getClass();
Class<?> clazz2 = obj2.getClass();
if (clazz1.equals(clazz2) || clazz1.isAssignableFrom(clazz2)
|| clazz2.isAssignableFrom(clazz1)) {
return;
}
throw new ClassCastException(String.format("The arguments is not assignable %s with %s",
clazz1, clazz2));
}
private static Object[] getPrimaryValues(AbstractBean bean) {
LinkedHashSet<Field> fields = getPrimaryFields(bean);
if (fields.isEmpty()) {
return empty;
}
Object[] ret = new Object[fields.size()];
try {
int i = 0;
for (Field field : fields) {
ret[i] = field.get(bean);
i++;
}
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return ret;
}
public static LinkedHashMap<String, Class<?>> getPrimaryClassMapShallow(Class<?> clazz) {
Set<Entry<String[], Class<?>>> set = getPrimaryClassMap(clazz, false);
if (set == null) {
return null;
}
LinkedHashMap<String, Class<?>> ret = new LinkedHashMap<>();
for (Entry<String[], Class<?>> e : set) {
String[] key = e.getKey();
if (key.length != 1) {
throw new IllegalStateException();
}
ret.put(key[0], e.getValue());
}
return ret;
}
public static LinkedHashSet<Entry<String[], Class<?>>> getPrimaryClassMap(Class<?> clazz) {
return getPrimaryClassMap(clazz, true);
}
public static LinkedHashSet<Entry<String[], Class<?>>> getPrimaryClassMap(Class<?> clazz,
boolean deep) {
if (clazz == null) {
return null;
}
LinkedHashSet<Field> fields = getPrimaryFields(clazz);
if (fields.isEmpty()) {
return new LinkedHashSet<>();
}
LinkedHashSet<Entry<String[], Class<?>>> ret = new LinkedHashSet<>();
try {
for (Field field : fields) {
Class<?> c = field.getType();
if (deep && AbstractBean.class.isAssignableFrom(c)) {
Set<Entry<String[], Class<?>>> set = getPrimaryClassMap(c, deep);
for (Entry<String[], Class<?>> e : set) {
String[] k = e.getKey();
Class<?> v = e.getValue();
String[] ks = new String[k.length + 1];
ks[0] = field.getName();
for (int i = 0; i < k.length; i++) {
ks[i + 1] = k[i];
}
ret.add(new SimpleImmutableEntry<String[], Class<?>>(ks, v));
}
} else {
String[] ks = new String[] { field.getName() };
ret.add(new SimpleImmutableEntry<String[], Class<?>>(ks, c));
}
}
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
}
return ret;
}
public static LinkedHashMap<String, Object> getPrimaryValueMapShallow(AbstractBean bean) {
Set<Entry<String[], Object>> set = getPrimaryValueMap(bean, false);
if (set == null) {
return null;
}
LinkedHashMap<String, Object> ret = new LinkedHashMap<>();
for (Entry<String[], Object> e : set) {
String[] key = e.getKey();
if (key.length != 1) {
throw new IllegalStateException();
}
ret.put(key[0], e.getValue());
}
return ret;
}
public static Set<Entry<String[], Object>> getPrimaryValueMap(AbstractBean bean) {
return getPrimaryValueMap(bean, true);
}
public static Set<Entry<String[], Object>> getPrimaryValueMap(AbstractBean bean, boolean deep) {
if (bean == null) {
return null;
}
LinkedHashSet<Field> fields = getPrimaryFields(bean);
if (fields.isEmpty()) {
return Collections.EMPTY_SET;
}
Set<Entry<String[], Object>> ret = new HashSet<>();
try {
for (Field field : fields) {
Object val = field.get(bean);
if (deep && (val instanceof AbstractBean)) {
Set<Entry<String[], Object>> set = getPrimaryValueMap((AbstractBean) val, deep);
for (Entry<String[], Object> e : set) {
String[] k = e.getKey();
Object v = e.getValue();
String[] ks = new String[k.length + 1];
ks[0] = field.getName();
for (int i = 0; i < k.length; i++) {
ks[i + 1] = k[i];
}
ret.add(new SimpleImmutableEntry<>(ks, v));
}
} else {
String[] ks = new String[] { field.getName() };
ret.add(new SimpleImmutableEntry<>(ks, val));
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return ret;
}
private static LinkedHashSet<Field> getPrimaryFields(AbstractBean bean) {
return getPrimaryFields(bean.getClass());
}
private static LinkedHashSet<Field> getPrimaryFields(Class<?> clazz) {
LinkedHashSet<Field> ret = classPirmarys.get(clazz);
if (ret == null) {
synchronized (clazz) {
ret = classPirmarys.get(clazz);
if (ret == null) {
ret = loadPrimarys(clazz);
classPirmarys.put(clazz, ret);
}
}
}
return ret;
}
private static LinkedHashSet<Field> loadPrimarys(Class<?> clazz) {
LinkedHashSet<Field> ret = new LinkedHashSet<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getAnnotation(Primary.class) != null) {
checkAsPrimary(field);
if (!Modifier.isPublic(field.getModifiers()) && !field.isAccessible()) {
field.setAccessible(true);
}
ret.add(field);
}
}
for (Field field : clazz.getFields()) {
if (field.getAnnotation(Primary.class) != null) {
checkAsPrimary(field);
if (!Modifier.isPublic(field.getModifiers()) && !field.isAccessible()) {
field.setAccessible(true);
}
ret.add(field);
}
}
return ret;
}
private static void checkAsPrimary(Field field) {
Class<?> type = field.getType();
if (AbstractBean.class.isAssignableFrom(type)) {
return;
}
if (type.isPrimitive() || Boolean.class.isAssignableFrom(type)
|| Number.class.isAssignableFrom(type) || Character.class.isAssignableFrom(type)
|| CharSequence.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type)) {
return;
}
throw new RuntimeException(String.format("The field %s is illegal type as primary, %s",
field, type));
}
private static LinkedHashMap<Field, Entry<Class<?>, Constructor<?>>> getFillFields(
AbstractBean bean) {
LinkedHashMap<Field, Entry<Class<?>, Constructor<?>>> ret;
{
Class<?> clazz = bean.getClass();
ret = classFills.get(clazz);
if (ret == null) {
synchronized (clazz) {
ret = classFills.get(clazz);
if (ret == null) {
ret = loadFills(clazz);
classFills.put(clazz, ret);
}
}
}
}
return ret;
}
private static LinkedHashMap<Field, Entry<Class<?>, Constructor<?>>> loadFills(Class<?> clazz) {
LinkedHashMap<Field, Entry<Class<?>, Constructor<?>>> ret = new LinkedHashMap<>();
for (Field field : clazz.getDeclaredFields()) {
Fill fill;
if ((fill = field.getAnnotation(Fill.class)) != null) {
checkAsFill(field);
if (!Modifier.isPublic(field.getModifiers()) && !field.isAccessible()) {
field.setAccessible(true);
}
ret.put(field, getFillConstructor(fill, field));
}
}
for (Field field : clazz.getFields()) {
Fill fill;
if ((fill = field.getAnnotation(Fill.class)) != null) {
if (ret.containsKey(field)) {
continue;
}
checkAsFill(field);
if (!Modifier.isPublic(field.getModifiers()) && !field.isAccessible()) {
field.setAccessible(true);
}
ret.put(field, getFillConstructor(fill, field));
}
}
return ret;
}
private static Entry<Class<?>, Constructor<?>> getFillConstructor(Fill fill, Field field) {
Class<?> clazz = null;
{
Class<?> val = fill.value();
if ((val != null) && !val.equals(Null.class)) {
clazz = val;
}
}
if (clazz == null) {
clazz = field.getType();
}
return new SimpleImmutableEntry<Class<?>, Constructor<?>>(clazz, getConstructor(clazz));
}
private static final Map<Class<?>, Class<?>> clazzImpls;
static {
clazzImpls = new ConcurrentHashMap<>();
clazzImpls.put(Map.class, LinkedHashMap.class);
clazzImpls.put(SortedMap.class, TreeMap.class);
clazzImpls.put(NavigableMap.class, TreeMap.class);
clazzImpls.put(ConcurrentMap.class, ConcurrentSkipListMap.class);
clazzImpls.put(ConcurrentNavigableMap.class, ConcurrentSkipListMap.class);
clazzImpls.put(List.class, LinkedList.class);
clazzImpls.put(Queue.class, LinkedList.class);
clazzImpls.put(Deque.class, LinkedList.class);
clazzImpls.put(BlockingQueue.class, LinkedBlockingQueue.class);
clazzImpls.put(BlockingDeque.class, LinkedBlockingDeque.class);
clazzImpls.put(Set.class, LinkedHashSet.class);
clazzImpls.put(SortedSet.class, TreeSet.class);
clazzImpls.put(NavigableSet.class, TreeSet.class);
}
private static Constructor<?> getConstructor(Class<?> clazz) {
Constructor<?> ret = classConstructor.get(clazz);
if (ret == null) {
synchronized (clazz) {
ret = classConstructor.get(clazz);
if (ret == null) {
Class<?> impl = null;
if (clazz.isInterface()) {
impl = clazzImpls.get(clazz);
if (impl == null) {
throw new RuntimeException(String.format(
"The class %s is illegal type as fill, %s", clazz));
}
} else {
impl = clazz;
}
try {
ret = impl.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
if (!Modifier.isPublic(ret.getModifiers()) && !ret.isAccessible()) {
ret.setAccessible(true);
}
classConstructor.put(clazz, ret);
}
}
}
return ret;
}
private static void checkAsFill(Field field) {
Class<?> type = field.getType();
if (type.isAnnotation() || type.isAnonymousClass() || type.isArray() || type.isEnum()
|| type.isPrimitive()) {
throw new RuntimeException(String.format("The field %s is illegal type as fill, %s",
field, type));
}
}
public static void fill(AbstractBean bean) {
if (bean == null) {
return;
}
Map<Class<?>, Object> instances = new HashMap<>();
instances.put(bean.getClass(), bean);
fill(bean, instances);
}
private static void fill(AbstractBean bean, Map<Class<?>, Object> instances) {
try {
for (Entry<Field, Entry<Class<?>, Constructor<?>>> e : getFillFields(bean).entrySet()) {
Field field = e.getKey();
if (field.get(bean) != null) {
return;
}
Entry<Class<?>, Constructor<?>> cc = e.getValue();
Class<?> ic = cc.getKey();
Object child = instances.get(ic);
if (child == null) {
Constructor<?> cons = cc.getValue();
child = cons.newInstance();
if (child instanceof AbstractBean) {
Map<Class<?>, Object> tmp = new HashMap<>(instances);
tmp.put(ic, child);
fill((AbstractBean) child, tmp);
}
}
field.set(bean, child);
}
} catch (InstantiationException | IllegalAccessException | SecurityException
| IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}