All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.e6tech.elements.common.reflection.Reflection Maven / Gradle / Ivy

There is a newer version: 2.7.9
Show newest version
/*
 * Copyright 2015-2019 Futeh Kao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.e6tech.elements.common.reflection;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import net.e6tech.elements.common.logging.Logger;
import net.e6tech.elements.common.resources.Provision;
import net.e6tech.elements.common.util.SystemException;
import net.e6tech.elements.common.util.TextSubstitution;
import net.e6tech.elements.common.util.datastructure.Pair;
import net.e6tech.elements.common.util.lambda.Each;

import java.beans.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.util.Locale.ENGLISH;

/**
 * Created by futeh.
 */
@SuppressWarnings({"unchecked", "squid:S134", "squid:S1149", "squid:S1141", "squid:MethodCyclomaticComplexity", "squid:S3776"})
public class Reflection {
    @SuppressWarnings("squid:S1185")
    static final class PrivateSecurityManager extends SecurityManager {
        @Override
        protected Class[] getClassContext() {
            return super.getClassContext();
        }
    }

    private static Set convertibleTypes = new HashSet();
    static {
        convertibleTypes.add(Boolean.TYPE);
        convertibleTypes.add(Boolean.class);
        convertibleTypes.add(Double.TYPE);
        convertibleTypes.add(Double.class);
        convertibleTypes.add(Float.TYPE);
        convertibleTypes.add(Float.class);
        convertibleTypes.add(Integer.TYPE);
        convertibleTypes.add(Integer.class);
        convertibleTypes.add(Long.TYPE);
        convertibleTypes.add(Long.class);
        convertibleTypes.add(Short.TYPE);
        convertibleTypes.add(Short.class);
        convertibleTypes.add(BigDecimal.class);
        convertibleTypes.add(BigInteger.class);
    }

    private static final PrivateSecurityManager securityManager = new PrivateSecurityManager();

    private static LoadingCache methodPropertyDescriptors = CacheBuilder.newBuilder()
            .maximumSize(10000)
            .initialCapacity(500)
            .concurrencyLevel(Provision.cacheBuilderConcurrencyLevel)
            .expireAfterWrite(120 * 60 * 1000L, TimeUnit.MILLISECONDS)
            .build(new CacheLoader() {
                public PropertyDescriptor load(Method method) throws IntrospectionException {
                    String name = method.getName();
                    Parameter[] parameters = method.getParameters();
                    String property ;
                    if (name.startsWith("set")) {
                        if (parameters.length != 1)
                            throw new IllegalArgumentException("" + method.getName() + " is not a setter");
                        property = name.substring(3);
                    } else if (name.startsWith("get")) {
                        if (parameters.length != 0)
                            throw new IllegalArgumentException("" + method.getName() + " is not a getter");
                        property = name.substring(3);
                    } else if (name.startsWith("is")) {
                        if (parameters.length != 0)
                            throw new IllegalArgumentException("" + method.getName() + " is not a getter");
                        property = name.substring(2);
                    } else {
                        throw new IllegalArgumentException("" + method.getName() + " is not an property accessor");
                    }

                    boolean lowerCase = true;
                    if (property.length() > 1 && Character.isUpperCase(property.charAt(1)))
                        lowerCase = false;
                    if (lowerCase)
                        property = property.substring(0, 1).toLowerCase(ENGLISH) + property.substring(1);
                    return new PropertyDescriptor(property, method.getDeclaringClass());
                }
            });

    private static LoadingCache, PropertyDescriptor> propertyDescriptors = CacheBuilder.newBuilder()
            .maximumSize(10000)
            .initialCapacity(500)
            .concurrencyLevel(Provision.cacheBuilderConcurrencyLevel)
            .expireAfterWrite(120 * 60 * 1000L, TimeUnit.MILLISECONDS)
            .build(new CacheLoader, PropertyDescriptor>() {
                @Override
                public PropertyDescriptor load(Pair key) throws Exception {
                    Class cls = key.key();
                    String property = key.value();
                    PropertyDescriptor descriptor;
                    try {
                        descriptor = new PropertyDescriptor(property, cls,
                                "is" + TextSubstitution.capitalize(property), null);
                    } catch (IntrospectionException e) {
                        throw new SystemException(cls.getName() + "." + property, e);
                    }
                    return descriptor;
                }
            });

    private static LoadingCache parametrizedTypes = CacheBuilder.newBuilder()
            .maximumSize(10000)
            .initialCapacity(100)
            .concurrencyLevel(Provision.cacheBuilderConcurrencyLevel)
            .expireAfterWrite(120 * 60 * 1000L, TimeUnit.MILLISECONDS)
            .build(new CacheLoader() {
                @Override
                public Type[] load(Class clazz) throws Exception {
                    Class cls = clazz;
                    Type[] types = null;
                    while (!cls.equals(Object.class)) {
                        try {
                            Type genericSuper = cls.getGenericSuperclass();
                            if (genericSuper instanceof ParameterizedType) {
                                ParameterizedType parametrizedType = (ParameterizedType) genericSuper;
                                types = parametrizedType.getActualTypeArguments();
                                break;
                            }
                        } catch (Exception th) {
                            logger.warn(th.getMessage(), th);
                        }
                        cls = cls.getSuperclass();
                    }
                    if (types == null)
                        throw new IllegalArgumentException("No parametrized types found");
                    return types;
                }
            });

    static Logger logger = Logger.getLogger();

    private Reflection() {
    }

    // should use weak hash map
    public static PropertyDescriptor propertyDescriptor(Method method) {
        try {
            return methodPropertyDescriptors.get(method);
        } catch (ExecutionException e) {
            throw new SystemException(e.getCause());
        }
    }

    public static Class getCallingClass() {
        return privateGetCallingClass(1);
    }

    public static Class getCallingClass(int index) {
        return privateGetCallingClass(index + 1);
    }

    @SuppressWarnings("squid:S1872")
    public static Class privateGetCallingClass(int index) {
        Class[] trace = securityManager.getClassContext(); // trace[0]
                                                              // trace[1] is Reflection because of this call.
                                                              // trace[2] is the caller who wants the calling class
                                                              // trace[3] is the caller.
        String thisClassName = Reflection.class.getName();

        int i;
        for (i = 0; i < trace.length; i++) {
            if (thisClassName.equals(trace[i].getName()))
                break;
        }

        // trace[i] = Reflection this method
        // trace[i+1] = Reflection.getCallingClass method
        // trace[i+2] = caller
        // trace[i+3] = caller's caller
        if (i >= trace.length || i + 2 + index >= trace.length) {
            throw new IllegalStateException("Failed to find caller in the stack");
        }

        return trace[i + 2 + index];
    }

    public static Class[] getClassContext() {
        return securityManager.getClassContext();
    }

    public static  Optional mapCallingStackTrace(Function, ? extends V> mapper) {
        Throwable th = new Throwable();
        StackTraceElement[] trace = th.getStackTrace();
        String thisClassName = Reflection.class.getName();

        int i;
        for (i = 0; i < trace.length; i++) {
            if (thisClassName.equals(trace[i].getClassName()))
                break;
        }

        Each.Mutator mutator = Each.create();
        for (int j = i + 2; j < trace.length; j++) {
            mutator.setValue(trace[j]);
            V v = mapper.apply(mutator.each());
            if (v != null)
                return Optional.of(v);
        }

        return Optional.empty();
    }

    public static void printStackTrace(StringBuilder builder, String indent, int start, int end) {
        StackTraceElement[] elements = new Throwable().getStackTrace();
        int i = start + 1; // skip 1 for this printStackTrace call
        while (i < end && i < elements.length) {
            builder.append("\n");
            if (indent != null)
                builder.append(indent);
            builder.append(elements[i].getClassName());
            builder.append(".");
            builder.append(elements[i].getMethodName());
            builder.append("(");
            builder.append(elements[i].getFileName());
            builder.append(":");
            builder.append(elements[i].getLineNumber());
            builder.append(")");
            i ++;
        }
    }

    public static  T newInstance(String className, ClassLoader loader) {
        try {
            return (T) loadClass(className, loader).getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new SystemException(e);
        }
    }

    public static Class loadClass(String className, ClassLoader classLoader) {
        ClassLoader loader = classLoader;
        if (loader == null) {
            loader = Thread.currentThread().getContextClassLoader();
        }
        if (loader == null) {
            loader = getCallingClass().getClassLoader();
        }
        try {
            return loader.loadClass(className);
        } catch (Exception e) {
            throw new SystemException(e);
        }
    }


    public static Map, Annotation>> getAnnotationsByName(Class cls) {
        return getAnnotations(cls).entrySet().stream()
                .filter(x -> x.getKey() instanceof NamedSignature)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    public static Map, Annotation>> getAnnotations(Class cls) {
        List classes = collectClass(cls);

        Map, Map, Annotation>> annotations = new HashMap<>(50);
        Map, Annotation> classAnnotations = new HashMap<>(50);
        for (Class c : classes) {
            for (Method method : c.getDeclaredMethods()) {
                Map, Annotation> map = Accessor.getAnnotations(method);
                MethodSignature signature = new MethodSignature(method);
                annotations.putIfAbsent(signature, map);
            }

            Field[] fields = c.getDeclaredFields();
            for (Field field : fields) {
                Map, Annotation> map = Accessor.getAnnotations(field);
                FieldSignature signature = new FieldSignature(field);
                if (!annotations.containsKey(signature)) {
                    annotations.put(signature, map);
                    NamedSignature named = new NamedSignature(field.getName());
                    annotations.put(named, map);
                }
            }

            // unlike method and field, class signature aggregates all annotations including it super classes.
            ClassSignature classSignature = new ClassSignature(cls);
            for (Annotation a : c.getAnnotations()) {
                classAnnotations.putIfAbsent(a.annotationType(), a);
            }
            annotations.put(classSignature, classAnnotations);
        }

        for (Class c : classes) {
            try {
                for (PropertyDescriptor desc : Introspector.getBeanInfo(c).getPropertyDescriptors()) {
                    Map, Annotation> map = Accessor.getAnnotations(desc);
                    PropertySignature signature = new PropertySignature(desc);
                    if (!annotations.containsKey(signature)) {
                        annotations.put(signature, map);
                        NamedSignature named = new NamedSignature(desc.getName());
                        Map, Annotation> namedMap = annotations.computeIfAbsent(named, key -> new HashMap<>());
                        for (Map.Entry, Annotation> entry : map.entrySet()) {
                            namedMap.putIfAbsent(entry.getKey(), entry.getValue());
                        }
                    }
                }
            } catch (IntrospectionException e) {
                throw new SystemException(e);
            }
        }

        return annotations.entrySet().stream()
                .filter(x -> x.getValue().size() > 0)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    private static List collectClass(Class cls) {
        LinkedHashSet set = new LinkedHashSet<>();
        Class tmp =  cls;
        while (tmp != null && !tmp.equals(Object.class)) {
            set.add(tmp);
            Class[] interfaces = tmp.getInterfaces();
            for (Class intf : interfaces) {
                collectInterfaces(intf, set);
            }
            tmp = tmp.getSuperclass();
        }
        return new ArrayList<>(set);
    }


    // this needs to be breadth first
    private static void collectInterfaces(Class intf, Set set) {
        if (!set.contains(intf))
            set.add(intf);
        else
            return;
        Class[] interfaces = intf.getInterfaces();
        for (Class i : interfaces) {
            collectInterfaces(i, set);
        }
    }

    public static BeanInfo getBeanInfo(Class cls) {
        try {
            return Introspector.getBeanInfo(cls);
        } catch (IntrospectionException e) {
            throw new SystemException(e);
        }
    }

    public static void forEachAnnotatedAccessor(Class objectClass,
                                                Class annotationClass,
                                                Consumer consumer) {
        Class cls = objectClass;
        BeanInfo beanInfo = getBeanInfo(cls);
        PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor prop : props) {
            if (prop.getReadMethod() != null && prop.getReadMethod().getAnnotation(annotationClass) != null) {
                consumer.accept(prop.getReadMethod());
            } else if (prop.getWriteMethod() != null && prop.getWriteMethod().getAnnotation(annotationClass) != null) {
                consumer.accept(prop.getWriteMethod());
            }
        }

        while (!cls.equals(Object.class)) {
            Field[] fields = cls.getDeclaredFields();
            for (Field field : fields) {
                if (field.getAnnotation(annotationClass) != null) {
                    consumer.accept(field);
                }
            }
            cls = cls.getSuperclass();
        }
    }

    public static  V getProperty(Object object, String property) {
        try {
            Class cls = null;
            if (object instanceof  Class) {
                cls = (Class) object;
            } else {
                cls = object.getClass();
            }

            PropertyDescriptor descriptor =  getPropertyDescriptor(cls, property);
            if (descriptor == null || descriptor.getReadMethod() == null)
                return null;
            return (V) descriptor.getReadMethod().invoke(object);
        } catch (Exception e) {
            throw new SystemException(object.getClass().getName() + "." + property, e);
        }
    }

    public static PropertyDescriptor getPropertyDescriptor(Class cls, String property) {
        try {
            return propertyDescriptors.get(new Pair<>(cls, property));
        } catch (ExecutionException e) {
            throw new SystemException(e.getCause());
        }
    }

    public static  V getField(Object object, String fieldName) {
        Field field = getField(object.getClass(), fieldName);
        try {
            return (V) field.get(object);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    public static void setField(Object object, String fieldName, Object value) {
        Field field = getField(object.getClass(), fieldName);
        try {
            field.set(object, value);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    public static Field getField(Class clazz, String fieldName) {
        Class cls = clazz;
        while (cls != null && !cls.equals(Object.class)) {
            try {
                Field field = cls.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field;
            } catch (Exception e) {
                Logger.suppress(e);
            }
            try {
                cls = cls.getSuperclass();
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }
        throw new IllegalStateException("No bankId defined");
    }

    public static Class getParametrizedType(Class clazz, int index) {
        Type[] types;
        try {
            types = parametrizedTypes.get(clazz);
        } catch (ExecutionException e) {
            throw new SystemException(e.getCause());
        }

        if (types.length <= index) {
            throw new IllegalArgumentException("No parametrized type at index=" + index);
        } else if (types[index] instanceof Class) {
            return (Class) types[index];
        } else if (types[index] instanceof ParameterizedType &&  ((ParameterizedType) types[index]).getRawType() instanceof Class) {
            return (Class)((ParameterizedType) types[index]).getRawType();
        } else {
            return null;
        }
    }

    public static  List newInstance(Class cls, List objectList) {
        return newInstance(cls, objectList, null);
    }

    @SuppressWarnings("squid:S1168")
    public static  List newInstance(Class cls, List objectList, CopyListener listener) {
        if (objectList == null)
            return null;

        List list = new ArrayList<>();
        for (Object o : objectList) {
            T target = (new Replicator()).newInstance(cls, o, listener);
            list.add(target);
        }
        return list;
    }

    public static  T newInstance(Class cls, Object object) {
        return (new Replicator()).newInstance(cls, object, new HashMap<>(), null);
    }

    public static  T newInstance(Class cls, Object object, CopyListener listener) {
        return (new Replicator()).newInstance(cls, object, new HashMap<>(), listener);
    }

    public static  T copyInstance(T target, Object object) {
        (new Replicator()).copy(target, object, new HashMap<>(), null);
        return target;
    }

    public static  T copyInstance(T target, Object object, CopyListener listener) {
        (new Replicator()).copy(target, object, new HashMap<>(), listener);
        return target;
    }

    public static boolean compare(Object target, Object object) {
        return (new Replicator()).compare(target, object);
    }

    public static class Replicator {
        private Map> targetPropertiesDescriptor = new HashMap<>();
        private Map propertyDescriptors = new HashMap<>();

        private synchronized Map getTargetProperties(Class cls) {
            return targetPropertiesDescriptor.computeIfAbsent(cls, key -> {
                HashMap descriptors = new HashMap<>();
                PropertyDescriptor[] props = getBeanInfo(key).getPropertyDescriptors();
                for (PropertyDescriptor prop : props) {
                    descriptors.put(prop.getName(), prop);
                }
                return descriptors;
            });
        }

        private synchronized PropertyDescriptor[] getPropertyDescriptors(Class cls) {
            return propertyDescriptors.computeIfAbsent(cls, key -> getBeanInfo(key).getPropertyDescriptors());
        }

        public synchronized Map> getTargetPropertiesDescriptor() {
            return targetPropertiesDescriptor;
        }

        public synchronized void setTargetPropertiesDescriptor(Map> targetPropertiesDescriptor) {
            this.targetPropertiesDescriptor = targetPropertiesDescriptor;
        }

        public synchronized Map getPropertyDescriptors() {
            return propertyDescriptors;
        }

        public synchronized void setPropertyDescriptors(Map propertyDescriptors) {
            this.propertyDescriptors = propertyDescriptors;
        }

        public  T newInstance(Class cls, Object object) {
            return (new Replicator()).newInstance(cls, object, new HashMap<>(), null);
        }

        public  T newInstance(Class cls, Object object, CopyListener listener) {
            return (new Replicator()).newInstance(cls, object, new HashMap<>(), listener);
        }

        private  T newInstance(Type toType, Object object, Map seen, CopyListener listener) {
            if (object == null)
                return null;

            T buildin = null;
            if (toType instanceof Class) {
                buildin = (T) convertBuiltinType((Class) toType, object);
                if (buildin != null)
                    return buildin;
            }

            Integer hashCode = null;
            if (!object.getClass().isPrimitive()) {
                hashCode = System.identityHashCode(object);
                if (seen.get(hashCode) != null)
                    return (T) seen.get(hashCode);
            }

            T target = null;
            if (toType instanceof Class) {
                Class cls = (Class) toType;
                if (Enum.class.isAssignableFrom((Class)toType)) {
                    target = (T) Enum.valueOf(cls, object.toString());
                } else if (Enum.class.isAssignableFrom(object.getClass()) && String.class.isAssignableFrom(cls)) {
                    target = (T) ((Enum) object).name();
                } else if (Collection.class.isAssignableFrom(cls)) {
                    Collection collection = newCollection(cls);
                    target = (T) collection;
                    if (object instanceof Collection) {
                        Collection c = (Collection) object;
                        for (Object o : c) {
                            collection.add(o);
                        }
                    } else {
                        throw new IllegalStateException("Do not know how to convert " + object.getClass() + " to " + toType);
                    }
                } else {
                    try {
                        target = (T) cls.getDeclaredConstructor().newInstance();
                    } catch (Exception e) {
                        throw new SystemException(e);
                    }
                    copy(target, object, seen, listener);
                }
            } else {
                ParameterizedType parametrized = (ParameterizedType) toType;
                Class enclosedType = (Class) parametrized.getRawType();
                Type type = parametrized.getActualTypeArguments()[0];
                if (Collection.class.isAssignableFrom(enclosedType)) {
                    Collection collection = newCollection(enclosedType);
                    target = (T) collection;
                    if (object instanceof Collection) {
                        Collection c = (Collection) object;
                        for (Object o : c) {
                            Object converted = newInstance(type ,o, seen, listener);
                            collection.add(converted);
                        }
                    } else {
                        throw new IllegalStateException("Do not know how to convert " + object.getClass() + " to " + toType);
                    }
                } else {
                    try {
                        target = (T) enclosedType.getDeclaredConstructor().newInstance();
                    } catch (Exception e) {
                        throw new SystemException(e);
                    }
                    copy(target, object, seen, listener);
                }
            }

            if (hashCode != null)
                seen.put(hashCode, target);
            return target;
        }

        protected Collection newCollection(Class cls) {
            Collection collection = null;
            if (List.class.isAssignableFrom(cls)) {
                collection = new ArrayList<>();
            } else if (Set.class.isAssignableFrom(cls)) {
                collection = new LinkedHashSet<>();
            } else {
                collection = new ArrayList<>();
            }
            return collection;
        }

        @SuppressWarnings("all")
        protected Object convertBuiltinType(Class type, Object object) {
            if (String.class.isAssignableFrom(type)) {
                return object.toString();
            }

            if (!convertibleTypes.contains(type))
                return null;

            if (type == Boolean.TYPE || type == Boolean.class)
                return new Boolean(object.toString());
            else if (type == Double.TYPE || type == Double.class) {
                return new Double(object.toString());
            } else if (type == Float.TYPE || type == Float.class) {
                return new Float(object.toString());
            } else if (type == Integer.TYPE || type == Integer.class) {
                return new Integer(object.toString());
            } else if (type == Long.TYPE || type == Long.class) {
                return new Long(object.toString());
            } else if (type == Short.TYPE || type == Short.class) {
                return new Short(object.toString());
            } else if (type == BigDecimal.class) {
                return new BigDecimal(object.toString());
            } else if (type == BigInteger.class) {
                return new BigInteger(object.toString());
            }
            return null;
        }

        public void copy(Object target, Object object, CopyListener copyListener) {
            copy(target, object, new HashMap<>(), copyListener);
        }

        public void copy(Object target, Object object) {
            copy(target, object, new HashMap<>(), null);
        }

        @SuppressWarnings("squid:S135")
        private void copy(Object target, Object object, Map seen, CopyListener copyListener) {
            if (target == null || object == null)
                return;

            for (PropertyDescriptor prop : getPropertyDescriptors(object.getClass())) {
                if (prop.getReadMethod() != null) {
                    PropertyDescriptor targetDesc = getTargetProperties(target.getClass()).get(prop.getName());
                    if (targetDesc == null)
                        continue;

                    Method setter = targetDesc.getWriteMethod();
                    if (setter == null)
                        continue;

                    if (setter.getAnnotation(DoNotAccept.class) != null) {
                        continue;
                    }

                    Method getter = targetDesc.getReadMethod();
                    if (getter != null && getter.getAnnotation(DoNotAccept.class) != null) {
                        continue;
                    }

                    try {
                        boolean annotated = (prop.getReadMethod().getAnnotation(DoNotCopy.class) != null);
                        if (!annotated && prop.getWriteMethod() != null) {
                            annotated = (prop.getWriteMethod().getAnnotation(DoNotCopy.class) != null);
                        }
                        if (!annotated) {
                            try {
                                boolean handled = false;
                                if (copyListener != null) {
                                    handled = copyListener.copy(target, targetDesc, object, prop);
                                }
                                if (!handled) {
                                    Object value = prop.getReadMethod().invoke(object);

                                    if (!(value instanceof Collection) &&
                                            setter.getParameterTypes()[0].isAssignableFrom(prop.getReadMethod().getReturnType())) {
                                        setter.invoke(target, value);
                                    } else {
                                        try {
                                            Object converted = newInstance(setter.getGenericParameterTypes()[0], value, seen, copyListener);
                                            setter.invoke(target, converted);
                                        } catch (Exception ex) {
                                            logger.warn("Error copying " + value + " to " + setter.getDeclaringClass() + "::" + setter.getName(), ex);
                                        }
                                    }
                                }
                            } catch (PropertyVetoException ex) {
                                Logger.suppress(ex);
                            }
                        }
                    } catch (Exception e) {
                        throw new SystemException(e);
                    }
                }
            }
        }

        public boolean compare(Object target, Object object) {
            Stack stack = new Stack<>();
            if (target != null)
                stack.push(target.getClass().getName());
            return compare(target, object, new HashSet<>(), stack);
        }

        private boolean compare(Object target, Object object, Set seen, Stack stack) {
            if (target == null && object == null)
                return true;
            if (target == null || object == null)
                return false;
            // at this point target and object are not null
            int hashCode = System.identityHashCode(target);
            int hashCode2 = System.identityHashCode(object);
            String compared = Integer.toString(hashCode) + ":" + hashCode2;
            if (seen.contains(compared))
                return true;
            if (target.getClass().isAssignableFrom(object.getClass())) {
                if (!target.equals(object)) {
                    if (logger.isDebugEnabled()) {
                        StringBuilder builder = new StringBuilder();
                        builder.append("Comparison failed at ");
                        boolean first = true;
                        for (String element : stack) {
                            if (first)
                                first = false;
                            else
                                builder.append(".");
                            builder.append(element);
                        }
                        logger.debug(builder.toString());
                    }
                    return false;
                }
                return true;
            }

            if (!object.getClass().isPrimitive()) {
                seen.add(compared);
            }

            for (PropertyDescriptor prop : getPropertyDescriptors(target.getClass())) {
                if (prop.getReadMethod() != null && !"class".equals(prop.getName())) {
                    try {
                        boolean annotated = (prop.getReadMethod().getAnnotation(DoNotCopy.class) != null);
                        if (!annotated && prop.getWriteMethod() != null) {
                            annotated = (prop.getWriteMethod().getAnnotation(DoNotCopy.class) != null);
                        }
                        if (!annotated) {
                            Method objectGetter = null;
                            try {
                                PropertyDescriptor objectProp = new PropertyDescriptor(prop.getName(), object.getClass(),
                                        "is" + capitalize(prop.getName()), null);
                                objectGetter = objectProp.getReadMethod();
                            } catch (IntrospectionException e) {
                                Logger.suppress(e);
                            }
                            if (objectGetter == null)
                                continue;
                            Object value = objectGetter.invoke(object);
                            Object targetFieldValue = prop.getReadMethod().invoke(target);
                            stack.push(prop.getName());
                            if (!compare(targetFieldValue, value, seen, stack)) {
                                return false;
                            }
                            stack.pop();
                        }
                    } catch (Exception e) {
                        throw new SystemException(e);
                    }
                }
            }
            return true;
        }

        public static String capitalize(String name) {
            return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy