package com.firefly.utils;

import com.firefly.utils.classproxy.JavaReflectionProxyFactory;
import com.firefly.utils.classproxy.JavassistReflectionProxyFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public abstract class ReflectUtils {

    private static final ConcurrentHashMap<Class<?>, Map<String, Method>> getterCache = new ConcurrentHashMap<>(256);
    private static final ConcurrentHashMap<Class<?>, Map<String, Method>> setterCache = new ConcurrentHashMap<>(256);
    private static final ConcurrentHashMap<Class<?>, Map<String, Field>> propertyCache = new ConcurrentHashMap<>(256);

    public static ProxyFactory defaultProxyFactory = ServiceUtils.loadService(ProxyFactory.class, JavaReflectionProxyFactory.INSTANCE);

    public interface BeanMethodFilter {
        boolean accept(String propertyName, Method method);
    }

    public interface BeanFieldFilter {
        boolean accept(String propertyName, Field field);
    }

    public interface MethodProxy {

        Method method();

        /**
         * Executes this method
         *
         * @param obj  The instance of object that contains this method
         * @param args The parameters of this method
         * @return Return value of this method
         */
        Object invoke(Object obj, Object... args);
    }

    public interface FieldProxy {

        Field field();

        Object get(Object obj);

        void set(Object obj, Object value);
    }

    public interface ArrayProxy {
        int size(Object array);

        Object get(Object array, int index);

        void set(Object array, int index, Object value);
    }

    public interface ProxyFactory {
        ArrayProxy getArrayProxy(Class<?> clazz);

        FieldProxy getFieldProxy(Field field);

        MethodProxy getMethodProxy(Method method);
    }

    public static void setProperty(Object obj, String property, Object value) throws Throwable {
        getFields(obj.getClass()).get(property).set(obj, value);
    }

    public static Object getProperty(Object obj, String property) throws Throwable {
        return getFields(obj.getClass()).get(property).get(obj);
    }

    /**
     * Invokes a object's "setter" method by property name
     *
     * @param obj      The instance of a object
     * @param property The property name of this object
     * @param value    The parameter of "setter" method that you want to set
     * @throws Throwable A runtime exception
     */
    public static void set(Object obj, String property, Object value) throws Throwable {
        getSetterMethod(obj.getClass(), property).invoke(obj, value);
    }

    /**
     * Invokes a object's "getter" method by property name
     *
     * @param obj      The instance of a object
     * @param property The property name of this object
     * @return The value of this property
     * @throws Throwable A runtime exception
     */
    public static Object get(Object obj, String property) throws Throwable {
        return getGetterMethod(obj.getClass(), property).invoke(obj);
    }

    public static Object arrayGet(Object array, int index) {
        return getArrayProxy(array.getClass()).get(array, index);
    }

    public static void arraySet(Object array, int index, Object value) {
        getArrayProxy(array.getClass()).set(array, index, value);
    }

    public static int arraySize(Object array) {
        return getArrayProxy(array.getClass()).size(array);
    }

    public static ArrayProxy getArrayProxy(Class<?> clazz) {
        return defaultProxyFactory.getArrayProxy(clazz);
    }

    public static FieldProxy getFieldProxy(Field field) {
        return defaultProxyFactory.getFieldProxy(field);
    }

    public static MethodProxy getMethodProxy(Method method) {
        return defaultProxyFactory.getMethodProxy(method);
    }

    /**
     * Gets the all interface names of this class
     *
     * @param c The class of one object
     * @return Returns the all interface names
     */
    public static String[] getInterfaceNames(Class<?> c) {
        Class<?>[] interfaces = c.getInterfaces();
        List<String> names = new ArrayList<>();
        for (Class<?> i : interfaces) {
            names.add(i.getName());
        }
        return names.toArray(new String[0]);
    }

    public static String getPropertyName(Method method) {
        String methodName = method.getName();
        int index = (methodName.charAt(0) == 'i' ? 2 : 3);
        char c = methodName.charAt(index);
        if (Character.isLowerCase(c)) {
            return methodName.substring(index);
        } else {
            return Character.toLowerCase(methodName.charAt(index)) + methodName.substring(index + 1);
        }
    }

    public static Method getSetterMethod(Class<?> clazz, String propertyName) {
        return getSetterMethods(clazz).get(propertyName);
    }

    public static Map<String, Method> getSetterMethods(Class<?> clazz) {
        return setterCache.computeIfAbsent(clazz, key -> getSetterMethods(key, null));
    }

    public static Map<String, Method> getSetterMethods(Class<?> clazz, BeanMethodFilter filter) {
        Map<String, Method> setMethodMap = new HashMap<>();
        Method[] methods = clazz.getMethods();

        for (Method method : methods) {
            method.setAccessible(true);
            if (method.getName().length() < 4
                    || !method.getName().startsWith("set")
                    || Modifier.isStatic(method.getModifiers())
                    || !method.getReturnType().equals(Void.TYPE)
                    || method.getParameterTypes().length != 1)
                continue;

            String propertyName = getPropertyName(method);
            if (filter == null || filter.accept(propertyName, method))
                setMethodMap.put(propertyName, method);
        }
        return setMethodMap;
    }

    public static Method getGetterMethod(Class<?> clazz, String propertyName) {
        return getGetterMethods(clazz).get(propertyName);
    }

    public static Map<String, Method> getGetterMethods(Class<?> clazz) {
        return getterCache.computeIfAbsent(clazz, key -> getGetterMethods(key, null));
    }

    public static Map<String, Method> getGetterMethods(Class<?> clazz, BeanMethodFilter filter) {
        Map<String, Method> getMethodMap = new HashMap<>();
        Method[] methods = clazz.getMethods();
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            method.setAccessible(true);
            String methodName = method.getName();

            if (Modifier.isStatic(method.getModifiers()))
                continue;
            if (Modifier.isAbstract(method.getModifiers()))
                continue;
            if (method.getName().equals("getClass"))
                continue;
            if (!(method.getName().startsWith("is") || method.getName().startsWith("get")))
                continue;
            if (method.getParameterTypes().length != 0)
                continue;
            if (method.getReturnType() == void.class)
                continue;
            int index = (methodName.charAt(0) == 'i' ? 2 : 3);
            if (methodName.length() < index + 1)
                continue;

            String propertyName = getPropertyName(method);
            if (filter == null || filter.accept(propertyName, method))
                getMethodMap.put(propertyName, method);
        }

        return getMethodMap;
    }

    public static Map<String, Field> getFields(Class<?> clazz) {
        return propertyCache.computeIfAbsent(clazz, key -> getFields(key, null));
    }

    public static Map<String, Field> getFields(Class<?> clazz, BeanFieldFilter filter) {
        Map<String, Field> fieldMap = new HashMap<>();
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (Modifier.isStatic(field.getModifiers()))
                continue;

            String propertyName = field.getName();
            if (filter == null || filter.accept(propertyName, field))
                fieldMap.put(propertyName, field);
        }
        return fieldMap;
    }

    public static void copy(Object src, Object dest) {
        Map<String, Method> getterMethodMap = ReflectUtils.getGetterMethods(src.getClass());
        Map<String, Method> setterMethodMap = ReflectUtils.getSetterMethods(dest.getClass());

        for (Map.Entry<String, Method> entry : setterMethodMap.entrySet()) {
            Method getter = getterMethodMap.get(entry.getKey());
            if (getter == null)
                continue;

            try {
                Object obj = getter.invoke(src);
                if (obj != null) {
                    entry.getValue().invoke(dest, obj);
                }
            } catch (Throwable t) {
                System.err.println("copy object exception, " + t.getMessage());
            }
        }
    }
}

@@ -25,72 +25,8 @@
 s;%0A%0A
-import com.firefly.utils.classproxy.JavaReflectionProxyFactory;%0A
 impo
@@ -755,16 +755,21 @@
 ss, Java
+ssist
 Reflecti

set proxy factory JavassistReflectionProxyFactory