package org.javarad.utils.reflection;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Introductions:
 */
public class ReflectUtils {
    private static final Logger logger = LogManager.getLogger();

    private ReflectUtils(){
    }

    private static ConcurrentHashMap<String,Class> clazzMap = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String,Method> methodMap = new ConcurrentHashMap<>();

    private static ConcurrentHashMap<String,PropertyDescriptor[]> propertiesMap = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String,Method> writeMethodMap = new ConcurrentHashMap<>();

    public static void copyProperties(Object source, Object dest, boolean ignoreNull) throws IntrospectionException, IllegalArgumentException, IllegalAccessException,
            InvocationTargetException {
        PropertyDescriptor[] sourcePdList = getPropertyDescriptors(source.getClass());
        if(sourcePdList != null) {
            for (PropertyDescriptor pd : sourcePdList) {
                Method readMethod = null;
                try {
                    readMethod = pd.getReadMethod();
                } catch (Exception e) {
                }

                Method writeMethod = getWriteMethod(dest.getClass(), pd.getName());
                if (readMethod == null || writeMethod==null || !readMethod.getReturnType().equals(writeMethod.getParameterTypes()[0])) {
                    continue;
                }

                Object val = readMethod.invoke(source);
                if (val != null || !ignoreNull) {
                    writeMethod.invoke(dest, val);
                }
            }
        }
    }

    public static Method getWriteMethod(Class destClazz, String pdName) throws IntrospectionException {
        String key = destClazz.getName()+pdName.toLowerCase();
        Method writeMethod = writeMethodMap.get(key);
        if(writeMethod == null){
            PropertyDescriptor[] destPdList = getPropertyDescriptors(destClazz);
            if(destPdList != null){
                for (PropertyDescriptor pd : destPdList) {
                    Method write = pd.getWriteMethod();
                    if(write != null)
                        writeMethodMap.put(destClazz.getName()+pd.getName().toLowerCase(), write);
                }
                writeMethod = writeMethodMap.get(key);
            }
        }
        return writeMethod;
    }

    public static PropertyDescriptor[] getPropertyDescriptors(Class sourceClazz) throws IntrospectionException {
        String mapKey = sourceClazz.getName();
        PropertyDescriptor[] getPdList = propertiesMap.get(mapKey);
        if(getPdList == null) {
            BeanInfo beanInfo = Introspector.getBeanInfo(sourceClazz);
            getPdList = beanInfo.getPropertyDescriptors();
            propertiesMap.put(mapKey, getPdList);
        }
        return getPdList;
    }

    public static Class getClazz(String className) throws ClassNotFoundException {
        Class clazz = clazzMap.get(className);
        if(clazz == null){
            clazz = Class.forName(className);
            clazzMap.put(className, clazz);
        }
        return clazz;
    }

    public static Method getMethod(Class clazz, String methodName, Class... args) {
        String methodMapKey = getMethodMapKey(clazz.getName(), methodName, args);
        Method method = methodMap.get(methodMapKey);
        if(method == null){
            try{
                method = clazz.getMethod(methodName, args);
            }catch (NoSuchMethodException ex){
                logger.info("{} don't hava a [{}] method", clazz.getSimpleName(), methodName);
            }
            if(method != null){
                methodMap.put(methodMapKey, method);
            }
        }
        return method;
    }

    private static String getMethodMapKey(String className,String methodName, Class... args){
        if(args==null || args.length==0){
            return className+methodName;
        }else {
            StringBuffer methodMapKey = new StringBuffer(className).append('.').append(methodName);
            for(Class clazz : args){
                methodMapKey.append('_').append(clazz.toString());
            }
            return methodMapKey.toString();
        }
    }}

@@ -1596,17 +1596,16 @@
       if
- 
 (readMet
@@ -1612,18 +1612,18 @@
 hod 
-=
+!
 = null 
-%7C%7C
+&&
  wri
@@ -1634,53 +1634,132 @@
 thod
-=
+!
 =null
- %7C%7C !readMethod.getReturnType().equals(
+) %7B%0A                    Class readReturnType = readMethod.getReturnType();%0A                    Class writeParamType = 
 writ
@@ -1792,12 +1792,9 @@
 )%5B0%5D
-)) %7B
+;
 %0A   
@@ -1814,17 +1814,266 @@
     
-continue;
+if (readReturnType.equals(writeParamType) %7C%7C (readReturnType.getSimpleName().substring(0,3).equalsIgnoreCase(writeParamType.getSimpleName().substring(0, 3)) && (readReturnType.isPrimitive() %7C%7C writeParamType.isPrimitive())))//.substring(0,3) for int==Integer
 %0A   
@@ -2077,35 +2077,41 @@
                 
-%7D%0A%0A
+    %7B%0A   
                 
@@ -2110,16 +2110,21 @@
         
+     
 Object v
@@ -2167,24 +2167,32 @@
             
+        
 if (val != n
@@ -2198,16 +2198,17 @@
 null %7C%7C 
+(
 !ignoreN
@@ -2210,20 +2210,62 @@
 noreNull
-) %7B%0A
+ && !writeParamType.isPrimitive())) %7B%0A        
         
@@ -2315,33 +2315,82 @@
                 
-%7D
+        %7D%0A                    %7D%0A                %7D%0A
 %0A            %7D%0A 

fix copyProperties