package com.alibaba.fastjson.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.PropertyNamingStrategy;
import com.alibaba.fastjson.annotation.JSONCreator;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONPOJOBuilder;
import com.alibaba.fastjson.annotation.JSONType;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class JavaBeanInfo {

    public final Class<?> clazz;
    public final Class<?> builderClass;
    public final Constructor<?> defaultConstructor;
    public final Constructor<?> creatorConstructor;
    public final Method factoryMethod;
    public final Method buildMethod;

    public final int defaultConstructorParameterSize;

    public final FieldInfo[] fields;
    public final FieldInfo[] sortedFields;

    public final int parserFeatures;

    public final JSONType jsonType;

    public final String typeName;
    public final String typeKey;

    public String[] orders;

    public Type[] creatorConstructorParameterTypes;
    public String[] creatorConstructorParameters;

    public boolean kotlin;
    public Constructor<?> kotlinDefaultConstructor;

    public JavaBeanInfo(Class<?> clazz, //
                        Class<?> builderClass, //
                        Constructor<?> defaultConstructor, //
                        Constructor<?> creatorConstructor, //
                        Method factoryMethod, //
                        Method buildMethod, //
                        JSONType jsonType, //
                        List<FieldInfo> fieldList) {
        this.clazz = clazz;
        this.builderClass = builderClass;
        this.defaultConstructor = defaultConstructor;
        this.creatorConstructor = creatorConstructor;
        this.factoryMethod = factoryMethod;
        this.parserFeatures = TypeUtils.getParserFeatures(clazz);
        this.buildMethod = buildMethod;

        this.jsonType = jsonType;
        if (jsonType != null) {
            String typeName = jsonType.typeName();
            String typeKey = jsonType.typeKey();
            this.typeKey = typeKey.length() > 0 ? typeKey : null;

            if (typeName.length() != 0) {
                this.typeName = typeName;
            } else {
                this.typeName = clazz.getName();
            }
            String[] orders = jsonType.orders();
            this.orders = orders.length == 0 ? null : orders;
        } else {
            this.typeName = clazz.getName();
            this.typeKey = null;
            this.orders = null;
        }

        fields = new FieldInfo[fieldList.size()];
        fieldList.toArray(fields);

        FieldInfo[] sortedFields = new FieldInfo[fields.length];
        if (orders != null) {
            LinkedHashMap<String, FieldInfo> map = new LinkedHashMap<String, FieldInfo>(fieldList.size());
            for (FieldInfo field : fields) {
                map.put(field.name, field);
            }
            int i = 0;
            for (String item : orders) {
                FieldInfo field = map.get(item);
                if (field != null) {
                    sortedFields[i++] = field;
                    map.remove(item);
                }
            }
            for (FieldInfo field : map.values()) {
                sortedFields[i++] = field;
            }
        } else {
            System.arraycopy(fields, 0, sortedFields, 0, fields.length);
            Arrays.sort(sortedFields);
        }

        if (Arrays.equals(fields, sortedFields)) {
            sortedFields = fields;
        }
        this.sortedFields = sortedFields;

        if (defaultConstructor != null) {
            defaultConstructorParameterSize = defaultConstructor.getParameterTypes().length;
        } else if (factoryMethod != null) {
            defaultConstructorParameterSize = factoryMethod.getParameterTypes().length;
        } else {
            defaultConstructorParameterSize = 0;
        }

        if (creatorConstructor != null) {
            this.creatorConstructorParameterTypes = creatorConstructor.getParameterTypes();


            kotlin = TypeUtils.isKotlin(clazz);
            if (kotlin) {
                this.creatorConstructorParameters = TypeUtils.getKoltinConstructorParameters(clazz);
                try {
                    this.kotlinDefaultConstructor = clazz.getConstructor();
                } catch (Throwable ex) {
                    // skip
                }

                Annotation[][] paramAnnotationArrays = TypeUtils.getParameterAnnotations(creatorConstructor);
                for (int i = 0; i < creatorConstructorParameters.length && i < paramAnnotationArrays.length; ++i) {
                    Annotation[] paramAnnotations = paramAnnotationArrays[i];
                    JSONField fieldAnnotation = null;
                    for (Annotation paramAnnotation : paramAnnotations) {
                        if (paramAnnotation instanceof JSONField) {
                            fieldAnnotation = (JSONField) paramAnnotation;
                            break;
                        }
                    }
                    if (fieldAnnotation != null) {
                        String fieldAnnotationName = fieldAnnotation.name();
                        if (fieldAnnotationName.length() > 0) {
                            creatorConstructorParameters[i] = fieldAnnotationName;
                        }
                    }
                }
            } else {
                boolean match;
                if (creatorConstructorParameterTypes.length != fields.length) {
                    match = false;
                } else {
                    match = true;
                    for (int i = 0; i < creatorConstructorParameterTypes.length; i++) {
                        if (creatorConstructorParameterTypes[i] != fields[i].fieldClass) {
                            match = false;
                            break;
                        }
                    }
                }

                if (!match) {
                    this.creatorConstructorParameters = ASMUtils.lookupParameterNames(creatorConstructor);
                }
            }
        }
    }

    private static FieldInfo getField(List<FieldInfo> fieldList, String propertyName) {
        for (FieldInfo item : fieldList) {
            if (item.name.equals(propertyName)) {
                return item;
            }

            Field field = item.field;
            if (field != null && item.getAnnotation() != null && field.getName().equals(propertyName)) {
                return item;
            }
        }
        return null;
    }


    static boolean add(List<FieldInfo> fieldList, FieldInfo field) {
        for (int i = fieldList.size() - 1; i >= 0; --i) {
            FieldInfo item = fieldList.get(i);

            if (item.name.equals(field.name)) {
                if (item.getOnly && !field.getOnly) {
                    continue;
                }

                if (item.fieldClass.isAssignableFrom(field.fieldClass)) {
                    fieldList.set(i, field);
                    return true;
                }

                int result = item.compareTo(field);

                if (result < 0) {
                    fieldList.set(i, field);
                    return true;
                } else {
                    return false;
                }
            }
        }
        fieldList.add(field);

        return true;
    }

    public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy) {
        return build(clazz, type, propertyNamingStrategy, false, TypeUtils.compatibleWithJavaBean, false);
    }

    private static Map<TypeVariable, Type> buildGenericInfo(Class<?> clazz) {
        Class<?> childClass = clazz;
        Class<?> currentClass = clazz.getSuperclass();
        if (currentClass == null) {
            return null;
        }

        Map<TypeVariable, Type> typeVarMap = null;

        //analyse the whole generic info from the class inheritance
        for (; currentClass != null && currentClass != Object.class; childClass = currentClass, currentClass = currentClass.getSuperclass()) {
            if (childClass.getGenericSuperclass() instanceof ParameterizedType) {
                Type[] childGenericParentActualTypeArgs = ((ParameterizedType) childClass.getGenericSuperclass()).getActualTypeArguments();
                TypeVariable[] currentTypeParameters = currentClass.getTypeParameters();
                for (int i = 0; i < childGenericParentActualTypeArgs.length; i++) {
                    //if the child class's generic super class actual args is defined in the child class type parameters
                    if (typeVarMap == null) {
                        typeVarMap = new HashMap<TypeVariable, Type>();
                    }

                    if (typeVarMap.containsKey(childGenericParentActualTypeArgs[i])) {
                        Type actualArg = typeVarMap.get(childGenericParentActualTypeArgs[i]);
                        typeVarMap.put(currentTypeParameters[i], actualArg);
                    } else {
                        typeVarMap.put(currentTypeParameters[i], childGenericParentActualTypeArgs[i]);
                    }
                }
            }
        }

        return typeVarMap;
    }


    public static JavaBeanInfo build(Class<?> clazz //
            , Type type //
            , PropertyNamingStrategy propertyNamingStrategy //
            , boolean fieldBased //
            , boolean compatibleWithJavaBean
    ) {
        return build(clazz, type, propertyNamingStrategy, fieldBased, compatibleWithJavaBean, false);
    }

    public static JavaBeanInfo build(Class<?> clazz //
            , Type type //
            , PropertyNamingStrategy propertyNamingStrategy //
            , boolean fieldBased //
            , boolean compatibleWithJavaBean
            , boolean jacksonCompatible
    ) {
        JSONType jsonType = TypeUtils.getAnnotation(clazz,JSONType.class);
        if (jsonType != null) {
            PropertyNamingStrategy jsonTypeNaming = jsonType.naming();
            if (jsonTypeNaming != null && jsonTypeNaming != PropertyNamingStrategy.CamelCase) {
                propertyNamingStrategy = jsonTypeNaming;
            }
        }

        Class<?> builderClass = getBuilderClass(clazz, jsonType);

        Field[] declaredFields = clazz.getDeclaredFields();
        Method[] methods = clazz.getMethods();
        Map<TypeVariable, Type> genericInfo = buildGenericInfo(clazz);

        boolean kotlin = TypeUtils.isKotlin(clazz);
        Constructor[] constructors = clazz.getDeclaredConstructors();

        Constructor<?> defaultConstructor = null;
        if ((!kotlin) || constructors.length == 1) {
            if (builderClass == null) {
                defaultConstructor = getDefaultConstructor(clazz, constructors);
            } else {
                defaultConstructor = getDefaultConstructor(builderClass, builderClass.getDeclaredConstructors());
            }
        }

        Constructor<?> creatorConstructor = null;
        Method buildMethod = null;
        Method factoryMethod = null;

        List<FieldInfo> fieldList = new ArrayList<FieldInfo>();

        if (fieldBased) {
            for (Class<?> currentClass = clazz; currentClass != null; currentClass = currentClass.getSuperclass()) {
                Field[] fields = currentClass.getDeclaredFields();

                computeFields(clazz, type, propertyNamingStrategy, fieldList, fields);
            }

            if (defaultConstructor != null) {
                TypeUtils.setAccessible(defaultConstructor);
            }

            return new JavaBeanInfo(clazz, builderClass, defaultConstructor, null, factoryMethod, buildMethod, jsonType, fieldList);
        }

        boolean isInterfaceOrAbstract = clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers());
        if ((defaultConstructor == null && builderClass == null) || isInterfaceOrAbstract) {

            Type mixInType = JSON.getMixInAnnotations(clazz);
            if (mixInType instanceof Class) {
                Constructor<?>[] mixInConstructors = ((Class<?>) mixInType).getConstructors();
                Constructor<?> mixInCreator = getCreatorConstructor(mixInConstructors);
                if (mixInCreator != null) {
                    try {
                        creatorConstructor = clazz.getConstructor(mixInCreator.getParameterTypes());
                    } catch (NoSuchMethodException e) {
                        // skip
                    }
                }
            }

            if (creatorConstructor == null) {
                creatorConstructor = getCreatorConstructor(constructors);
            }

            if (creatorConstructor != null && !isInterfaceOrAbstract) { // 基于标记 JSONCreator 注解的构造方法
                TypeUtils.setAccessible(creatorConstructor);

                Class<?>[] types = creatorConstructor.getParameterTypes();

                String[] lookupParameterNames = null;
                if (types.length > 0) {
                    Annotation[][] paramAnnotationArrays = TypeUtils.getParameterAnnotations(creatorConstructor);
                    for (int i = 0; i < types.length && i < paramAnnotationArrays.length; ++i) {
                        Annotation[] paramAnnotations = paramAnnotationArrays[i];
                        JSONField fieldAnnotation = null;
                        for (Annotation paramAnnotation : paramAnnotations) {
                            if (paramAnnotation instanceof JSONField) {
                                fieldAnnotation = (JSONField) paramAnnotation;
                                break;
                            }
                        }

                        Class<?> fieldClass = types[i];
                        Type fieldType = creatorConstructor.getGenericParameterTypes()[i];

                        String fieldName = null;
                        Field field = null;
                        int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
                        if (fieldAnnotation != null) {
                            field = TypeUtils.getField(clazz, fieldAnnotation.name(), declaredFields);
                            ordinal = fieldAnnotation.ordinal();
                            serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                            parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
                            fieldName = fieldAnnotation.name();
                        }

                        if (fieldName == null || fieldName.length() == 0) {
                            if (lookupParameterNames == null) {
                                lookupParameterNames = ASMUtils.lookupParameterNames(creatorConstructor);
                            }
                            fieldName = lookupParameterNames[i];
                        }

                        if (field == null) {
                            if (lookupParameterNames == null) {
                                if (kotlin) {
                                    lookupParameterNames = TypeUtils.getKoltinConstructorParameters(clazz);
                                } else {
                                    lookupParameterNames = ASMUtils.lookupParameterNames(creatorConstructor);
                                }
                            }

                            if (lookupParameterNames.length > i) {
                                String parameterName = lookupParameterNames[i];
                                field = TypeUtils.getField(clazz, parameterName, declaredFields);
                            }
                        }

                        FieldInfo fieldInfo = new FieldInfo(fieldName, clazz, fieldClass, fieldType, field,
                                ordinal, serialzeFeatures, parserFeatures);
                        add(fieldList, fieldInfo);
                    }
                }

                //return new JavaBeanInfo(clazz, builderClass, null, creatorConstructor, null, null, jsonType, fieldList);
            } else if ((factoryMethod = getFactoryMethod(clazz, methods, jacksonCompatible)) != null) {
                TypeUtils.setAccessible(factoryMethod);

                String[] lookupParameterNames = null;
                Class<?>[] types = factoryMethod.getParameterTypes();
                if (types.length > 0) {
                    Annotation[][] paramAnnotationArrays = TypeUtils.getParameterAnnotations(factoryMethod);
                    for (int i = 0; i < types.length; ++i) {
                        Annotation[] paramAnnotations = paramAnnotationArrays[i];
                        JSONField fieldAnnotation = null;
                        for (Annotation paramAnnotation : paramAnnotations) {
                            if (paramAnnotation instanceof JSONField) {
                                fieldAnnotation = (JSONField) paramAnnotation;
                                break;
                            }
                        }
                        if (fieldAnnotation == null && !(jacksonCompatible && TypeUtils.isJacksonCreator(factoryMethod))) {
                            throw new JSONException("illegal json creator");
                        }

                        String fieldName = null;
                        int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;

                        if (fieldAnnotation != null) {
                            fieldName = fieldAnnotation.name();
                            ordinal = fieldAnnotation.ordinal();
                            serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                            parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
                        }

                        if (fieldName == null || fieldName.length() == 0) {
                            if (lookupParameterNames == null) {
                                lookupParameterNames = ASMUtils.lookupParameterNames(factoryMethod);
                            }
                            fieldName = lookupParameterNames[i];
                        }

                        Class<?> fieldClass = types[i];
                        Type fieldType = factoryMethod.getGenericParameterTypes()[i];

                        Field field = TypeUtils.getField(clazz, fieldName, declaredFields);
                        FieldInfo fieldInfo = new FieldInfo(fieldName, clazz, fieldClass, fieldType, field,
                                ordinal, serialzeFeatures, parserFeatures);
                        add(fieldList, fieldInfo);
                    }

                    return new JavaBeanInfo(clazz, builderClass, null, null, factoryMethod, null, jsonType, fieldList);
                }
            } else if (!isInterfaceOrAbstract) {
                String className = clazz.getName();

                String[] paramNames = null;
                if (kotlin && constructors.length > 0) {
                    paramNames = TypeUtils.getKoltinConstructorParameters(clazz);
                    creatorConstructor = TypeUtils.getKotlinConstructor(constructors, paramNames);
                    TypeUtils.setAccessible(creatorConstructor);
                } else {

                    for (Constructor constructor : constructors) {
                        Class<?>[] parameterTypes = constructor.getParameterTypes();

                        if (className.equals("org.springframework.security.web.authentication.WebAuthenticationDetails")) {
                            if (parameterTypes.length == 2 && parameterTypes[0] == String.class && parameterTypes[1] == String.class) {
                                creatorConstructor = constructor;
                                creatorConstructor.setAccessible(true);
                                paramNames = ASMUtils.lookupParameterNames(constructor);
                                break;
                            } else {
                                continue;
                            }
                        }

                        if (className.equals("org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken")) {
                            if (parameterTypes.length == 3
                                    && parameterTypes[0] == Object.class
                                    && parameterTypes[1] == Object.class
                                    && parameterTypes[2] == Collection.class) {
                                creatorConstructor = constructor;
                                creatorConstructor.setAccessible(true);
                                paramNames = new String[] {"principal", "credentials", "authorities"};
                                break;
                            } else {
                                continue;
                            }
                        }

                        if (className.equals("org.springframework.security.core.authority.SimpleGrantedAuthority")) {
                            if (parameterTypes.length == 1
                                    && parameterTypes[0] == String.class) {
                                creatorConstructor = constructor;
                                paramNames = new String[] {"authority"};
                                break;
                            } else {
                                continue;
                            }
                        }

                        //


                        boolean is_public = (constructor.getModifiers() & Modifier.PUBLIC) != 0;
                        if (!is_public) {
                            continue;
                        }
                        String[] lookupParameterNames = ASMUtils.lookupParameterNames(constructor);
                        if (lookupParameterNames == null || lookupParameterNames.length == 0) {
                            continue;
                        }

                        if (creatorConstructor != null
                                && paramNames != null && lookupParameterNames.length <= paramNames.length) {
                            continue;
                        }

                        paramNames = lookupParameterNames;
                        creatorConstructor = constructor;
                    }
                }

                Class<?>[] types = null;
                if (paramNames != null) {
                    types = creatorConstructor.getParameterTypes();
                }

                if (paramNames != null
                        && types.length == paramNames.length) {
                    Annotation[][] paramAnnotationArrays = TypeUtils.getParameterAnnotations(creatorConstructor);
                    for (int i = 0; i < types.length; ++i) {
                        Annotation[] paramAnnotations = paramAnnotationArrays[i];
                        String paramName = paramNames[i];

                        JSONField fieldAnnotation = null;
                        for (Annotation paramAnnotation : paramAnnotations) {
                            if (paramAnnotation instanceof JSONField) {
                                fieldAnnotation = (JSONField) paramAnnotation;
                                break;
                            }
                        }

                        Class<?> fieldClass = types[i];
                        Type fieldType = creatorConstructor.getGenericParameterTypes()[i];
                        Field field = TypeUtils.getField(clazz, paramName, declaredFields);
                        if (field != null) {
                            if (fieldAnnotation == null) {
                                fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);
                            }
                        }
                        final int ordinal, serialzeFeatures, parserFeatures;
                        if (fieldAnnotation == null) {
                            ordinal = 0;
                            serialzeFeatures = 0;

                            if ("org.springframework.security.core.userdetails.User".equals(className)
                                    && "password".equals(paramName)) {
                                parserFeatures = Feature.InitStringFieldAsEmpty.mask;
                            } else {
                                parserFeatures = 0;
                            }
                        } else {
                            String nameAnnotated = fieldAnnotation.name();
                            if (nameAnnotated.length() != 0) {
                                paramName = nameAnnotated;
                            }
                            ordinal = fieldAnnotation.ordinal();
                            serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                            parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
                        }
                        FieldInfo fieldInfo = new FieldInfo(paramName, clazz, fieldClass, fieldType, field,
                                ordinal, serialzeFeatures, parserFeatures);
                        add(fieldList, fieldInfo);
                    }

                    if ((!kotlin) && !clazz.getName().equals("javax.servlet.http.Cookie")) {
                        return new JavaBeanInfo(clazz, builderClass, null, creatorConstructor, null, null, jsonType, fieldList);
                    }
                } else {
                    throw new JSONException("default constructor not found. " + clazz);
                }
            }
        }

        if (defaultConstructor != null) {
            TypeUtils.setAccessible(defaultConstructor);
        }

        if (builderClass != null) {
            String withPrefix = null;

            JSONPOJOBuilder builderAnno = TypeUtils.getAnnotation(builderClass, JSONPOJOBuilder.class);
            if (builderAnno != null) {
                withPrefix = builderAnno.withPrefix();
            }

            if (withPrefix == null) {
                withPrefix = "with";
            }

            for (Method method : builderClass.getMethods()) {
                if (Modifier.isStatic(method.getModifiers())) {
                    continue;
                }

                if (!(method.getReturnType().equals(builderClass))) {
                    continue;
                }

                int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;

                JSONField annotation = TypeUtils.getAnnotation(method, JSONField.class);

                if (annotation == null) {
                    annotation = TypeUtils.getSuperMethodAnnotation(clazz, method);
                }

                if (annotation != null) {
                    if (!annotation.deserialize()) {
                        continue;
                    }

                    ordinal = annotation.ordinal();
                    serialzeFeatures = SerializerFeature.of(annotation.serialzeFeatures());
                    parserFeatures = Feature.of(annotation.parseFeatures());

                    if (annotation.name().length() != 0) {
                        String propertyName = annotation.name();
                        add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, ordinal, serialzeFeatures, parserFeatures,
                                annotation, null, null, genericInfo));
                        continue;
                    }
                }

                String methodName = method.getName();
                StringBuilder properNameBuilder;
                if (methodName.startsWith("set") && methodName.length() > 3) {
                    properNameBuilder = new StringBuilder(methodName.substring(3));
                } else {
                    if (withPrefix.length() == 0){
                        properNameBuilder = new StringBuilder(methodName);
                    } else {
                        if (!methodName.startsWith(withPrefix)) {
                            continue;
                        }

                        if (methodName.length() <= withPrefix.length()) {
                            continue;
                        }
                        
                        properNameBuilder = new StringBuilder(methodName.substring(withPrefix.length()));
                    }
                }

                char c0 = properNameBuilder.charAt(0);
                if (withPrefix.length() != 0 && !Character.isUpperCase(c0)) {
                    continue;
                }

                properNameBuilder.setCharAt(0, Character.toLowerCase(c0));

                String propertyName = properNameBuilder.toString();

                add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, ordinal, serialzeFeatures, parserFeatures,
                        annotation, null, null, genericInfo));
            }

            if (builderClass != null) {
                JSONPOJOBuilder builderAnnotation = TypeUtils.getAnnotation(builderClass, JSONPOJOBuilder.class);

                String buildMethodName = null;
                if (builderAnnotation != null) {
                    buildMethodName = builderAnnotation.buildMethod();
                }

                if (buildMethodName == null || buildMethodName.length() == 0) {
                    buildMethodName = "build";
                }

                try {
                    buildMethod = builderClass.getMethod(buildMethodName);
                } catch (NoSuchMethodException e) {
                    // skip
                } catch (SecurityException e) {
                    // skip
                }

                if (buildMethod == null) {
                    try {
                        buildMethod = builderClass.getMethod("create");
                    } catch (NoSuchMethodException e) {
                        // skip
                    } catch (SecurityException e) {
                        // skip
                    }
                }

                if (buildMethod == null) {
                    throw new JSONException("buildMethod not found.");
                }

                TypeUtils.setAccessible(buildMethod);
            }
        }

        for (Method method : methods) { //
            int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
            String methodName = method.getName();

            if (Modifier.isStatic(method.getModifiers())) {
                continue;
            }

            // support builder set
            Class<?> returnType = method.getReturnType();
            if (!(returnType.equals(Void.TYPE) || returnType.equals(method.getDeclaringClass()))) {
                continue;
            }

            if (method.getDeclaringClass() == Object.class) {
                continue;
            }

            Class<?>[] types = method.getParameterTypes();

            if (types.length == 0 || types.length > 2) {
                continue;
            }

            JSONField annotation = TypeUtils.getAnnotation(method, JSONField.class);
            if (annotation != null
                    && types.length == 2
                    && types[0] == String.class
                    && types[1] == Object.class) {
                add(fieldList, new FieldInfo("", method, null, clazz, type, ordinal,
                        serialzeFeatures, parserFeatures, annotation, null, null, genericInfo));
                continue;
            }

            if (types.length != 1) {
                continue;
            }

            if (annotation == null) {
                annotation = TypeUtils.getSuperMethodAnnotation(clazz, method);
            }

            if (annotation == null && methodName.length() < 4) {
                continue;
            }

            if (annotation != null) {
                if (!annotation.deserialize()) {
                    continue;
                }

                ordinal = annotation.ordinal();
                serialzeFeatures = SerializerFeature.of(annotation.serialzeFeatures());
                parserFeatures = Feature.of(annotation.parseFeatures());

                if (annotation.name().length() != 0) {
                    String propertyName = annotation.name();
                    add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, ordinal, serialzeFeatures, parserFeatures,
                            annotation, null, null, genericInfo));
                    continue;
                }
            }

            if (annotation == null && !methodName.startsWith("set") || builderClass != null) { // TODO "set"的判断放在 JSONField 注解后面，意思是允许非 setter 方法标记 JSONField 注解？
                continue;
            }

            char c3 = methodName.charAt(3);

            String propertyName;
            Field field = null;
            // 用于存储KotlinBean中所有的get方法, 方便后续判断
            List<String> getMethodNameList = null;

            if (kotlin) {
                getMethodNameList = new ArrayList();
                for (int i = 0; i < methods.length; i++) {
                    if (methods[i].getName().startsWith("get")) {
                        getMethodNameList.add(methods[i].getName());
                    }
                }
            }

            if (Character.isUpperCase(c3) //
                    || c3 > 512 // for unicode method name
                    ) {
                // 这里本身的逻辑是通过setAbc这类方法名解析出成员变量名为abc或者Abc, 但是在kotlin中, isAbc, abc成员变量的set方法都是setAbc
                // 因此如果是kotlin的话还需要进行不一样的判断, 判断的方式是通过get方法进行判断, isAbc的get方法名为isAbc(), abc的get方法名为getAbc()
                if (kotlin) {
                    String getMethodName = "g" + methodName.substring(1);
                    propertyName = TypeUtils.getPropertyNameByMethodName(getMethodName);
                } else {
                    if (TypeUtils.compatibleWithJavaBean) {
                        propertyName = TypeUtils.decapitalize(methodName.substring(3));
                    } else {
                        propertyName = TypeUtils.getPropertyNameByMethodName(methodName);
                    }
                }

            } else if (c3 == '_') {
                // 这里本身的逻辑是通过set_abc这类方法名解析出成员变量名为abc, 但是在kotlin中, is_abc和_abc成员变量的set方法都是set_abc
                // 因此如果是kotlin的话还需要进行不一样的判断, 判断的方式是通过get方法进行判断, is_abc的get方法名为is_abc(), _abc的get方法名为get_abc()
                if (kotlin) {
                    String getMethodName = "g" + methodName.substring(1);
                    if (getMethodNameList.contains(getMethodName)) {
                        propertyName = methodName.substring(3);
                    } else {
                        propertyName = "is" + methodName.substring(3);
                    }
                    field = TypeUtils.getField(clazz, propertyName, declaredFields);
                } else {
                    propertyName = methodName.substring(4);
                    field = TypeUtils.getField(clazz, propertyName, declaredFields);
                    if (field == null) {
                        String temp = propertyName;
                        propertyName = methodName.substring(3);
                        field = TypeUtils.getField(clazz, propertyName, declaredFields);
                        if (field == null) {
                            propertyName = temp; //减少修改代码带来的影响
                        }
                    }
                }

            } else if (c3 == 'f') {
                propertyName = methodName.substring(3);
            } else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))) {
                propertyName = TypeUtils.decapitalize(methodName.substring(3));
            } else {
                propertyName = methodName.substring(3);
                field = TypeUtils.getField(clazz, propertyName, declaredFields);
                if (field == null) {
                    continue;
                }
            }

            if (field == null) {
                field = TypeUtils.getField(clazz, propertyName, declaredFields);
            }

            if (field == null && types[0] == boolean.class) {
                String isFieldName = "is" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
                field = TypeUtils.getField(clazz, isFieldName, declaredFields);
            }

            JSONField fieldAnnotation = null;
            if (field != null) {
                fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);

                if (fieldAnnotation != null) {
                    if (!fieldAnnotation.deserialize()) {
                        continue;
                    }

                    ordinal = fieldAnnotation.ordinal();
                    serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                    parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

                    if (fieldAnnotation.name().length() != 0) {
                        propertyName = fieldAnnotation.name();
                        add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal,
                                serialzeFeatures, parserFeatures, annotation, fieldAnnotation, null, genericInfo));
                        continue;
                    }
                }

            }

            if (propertyNamingStrategy != null) {
                propertyName = propertyNamingStrategy.translate(propertyName);
            }

            add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures,
                    annotation, fieldAnnotation, null, genericInfo));
        }

        Field[] fields = clazz.getFields();
        computeFields(clazz, type, propertyNamingStrategy, fieldList, fields);

        for (Method method : clazz.getMethods()) { // getter methods
            String methodName = method.getName();
            if (methodName.length() < 4) {
                continue;
            }

            if (Modifier.isStatic(method.getModifiers())) {
                continue;
            }

            if (builderClass == null && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3))) {
                if (method.getParameterTypes().length != 0) {
                    continue;
                }

                if (Collection.class.isAssignableFrom(method.getReturnType()) //
                        || Map.class.isAssignableFrom(method.getReturnType()) //
                        || AtomicBoolean.class == method.getReturnType() //
                        || AtomicInteger.class == method.getReturnType() //
                        || AtomicLong.class == method.getReturnType() //
                        ) {
                    String propertyName;
                    Field collectionField = null;

                    JSONField annotation = TypeUtils.getAnnotation(method, JSONField.class);
                    if (annotation != null && annotation.deserialize()) {
                        continue;
                    }

                    if (annotation != null && annotation.name().length() > 0) {
                        propertyName = annotation.name();
                    } else {
                        propertyName = TypeUtils.getPropertyNameByMethodName(methodName);

                        Field field = TypeUtils.getField(clazz, propertyName, declaredFields);
                        if (field != null) {
                            JSONField fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);
                            if (fieldAnnotation != null && !fieldAnnotation.deserialize()) {
                                continue;
                            }

                            if (Collection.class.isAssignableFrom(method.getReturnType())
                                || Map.class.isAssignableFrom(method.getReturnType())) {
                                collectionField = field;
                            }
                        }
                    }

                    if (propertyNamingStrategy != null) {
                        propertyName = propertyNamingStrategy.translate(propertyName);
                    }

                    FieldInfo fieldInfo = getField(fieldList, propertyName);
                    if (fieldInfo != null) {
                        continue;
                    }

                    add(fieldList, new FieldInfo(propertyName, method, collectionField, clazz, type, 0, 0, 0, annotation, null, null, genericInfo));
                }
            }
        }

        if (fieldList.size() == 0) {
            if (TypeUtils.isXmlField(clazz)) {
                fieldBased = true;
            }

            if (fieldBased) {
                for (Class<?> currentClass = clazz; currentClass != null; currentClass = currentClass.getSuperclass()) {
                    computeFields(clazz, type, propertyNamingStrategy, fieldList, declaredFields);
                }
            }
        }

        return new JavaBeanInfo(clazz, builderClass, defaultConstructor, creatorConstructor, factoryMethod, buildMethod, jsonType, fieldList);
    }

    private static void computeFields(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy, List<FieldInfo> fieldList, Field[] fields) {
        Map<TypeVariable, Type> genericInfo = buildGenericInfo(clazz);

        for (Field field : fields) { // public static fields
            int modifiers = field.getModifiers();
            if ((modifiers & Modifier.STATIC) != 0) {
                continue;
            }

            if ((modifiers & Modifier.FINAL) != 0) {
                Class<?> fieldType = field.getType();
                boolean supportReadOnly = Map.class.isAssignableFrom(fieldType)
                        || Collection.class.isAssignableFrom(fieldType)
                        || AtomicLong.class.equals(fieldType) //
                        || AtomicInteger.class.equals(fieldType) //
                        || AtomicBoolean.class.equals(fieldType);
                if (!supportReadOnly) {
                    continue;
                }
            }

            boolean contains = false;
            for (FieldInfo item : fieldList) {
                if (item.name.equals(field.getName())) {
                    contains = true;
                    break; // 已经是 contains = true，无需继续遍历
                }
            }

            if (contains) {
                continue;
            }

            int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
            String propertyName = field.getName();

            JSONField fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);

            if (fieldAnnotation != null) {
                if (!fieldAnnotation.deserialize()) {
                    continue;
                }

                ordinal = fieldAnnotation.ordinal();
                serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

                if (fieldAnnotation.name().length() != 0) {
                    propertyName = fieldAnnotation.name();
                }
            }

            if (propertyNamingStrategy != null) {
                propertyName = propertyNamingStrategy.translate(propertyName);
            }

            add(fieldList, new FieldInfo(propertyName, null, field, clazz, type, ordinal, serialzeFeatures, parserFeatures, null,
                    fieldAnnotation, null, genericInfo));
        }
    }

    static Constructor<?> getDefaultConstructor(Class<?> clazz, final Constructor<?>[] constructors) {
        if (Modifier.isAbstract(clazz.getModifiers())) {
            return null;
        }

        Constructor<?> defaultConstructor = null;

        for (Constructor<?> constructor : constructors) {
            if (constructor.getParameterTypes().length == 0) {
                defaultConstructor = constructor;
                break;
            }
        }

        if (defaultConstructor == null) {
            if (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
                Class<?>[] types;
                for (Constructor<?> constructor : constructors) {
                    if ((types = constructor.getParameterTypes()).length == 1
                            && types[0].equals(clazz.getDeclaringClass())) {
                        defaultConstructor = constructor;
                        break;
                    }
                }
            }
        }

        return defaultConstructor;
    }

    public static Constructor<?> getCreatorConstructor(Constructor[] constructors) {
        Constructor<?> creatorConstructor = null;

        for (Constructor<?> constructor : constructors) {
            JSONCreator annotation = constructor.getAnnotation(JSONCreator.class);
            if (annotation != null) {
                if (creatorConstructor != null) {
                    throw new JSONException("multi-JSONCreator");
                }

                creatorConstructor = constructor;
                // 不应该break，否则多个构造方法上存在 JSONCreator 注解时，并不会触发上述异常抛出
            }
        }
        if (creatorConstructor != null) {
            return creatorConstructor;
        }

        for (Constructor constructor : constructors) {
            Annotation[][] paramAnnotationArrays = TypeUtils.getParameterAnnotations(constructor);

            if (paramAnnotationArrays.length == 0) {
                continue;
            }
            boolean match = true;
            for (Annotation[] paramAnnotationArray : paramAnnotationArrays) {
                boolean paramMatch = false;
                for (Annotation paramAnnotation : paramAnnotationArray) {
                    if (paramAnnotation instanceof JSONField) {
                        paramMatch = true;
                        break;
                    }
                }
                if (!paramMatch) {
                    match = false;
                    break;
                }
            }

            if (match) {
                if (creatorConstructor != null) {
                    throw new JSONException("multi-JSONCreator");
                }

                creatorConstructor = constructor;
            }
        }

        return creatorConstructor;
    }

    private static Method getFactoryMethod(Class<?> clazz, Method[] methods, boolean jacksonCompatible) {
        Method factoryMethod = null;

        for (Method method : methods) {
            if (!Modifier.isStatic(method.getModifiers())) {
                continue;
            }

            if (!clazz.isAssignableFrom(method.getReturnType())) {
                continue;
            }

            JSONCreator annotation = TypeUtils.getAnnotation(method, JSONCreator.class);
            if (annotation != null) {
                if (factoryMethod != null) {
                    throw new JSONException("multi-JSONCreator");
                }

                factoryMethod = method;
                // 不应该break，否则多个静态工厂方法上存在 JSONCreator 注解时，并不会触发上述异常抛出
            }
        }

        if (factoryMethod == null && jacksonCompatible) {
            for (Method method : methods) {
                if (TypeUtils.isJacksonCreator(method)) {
                    factoryMethod = method;
                    break;
                }
            }
        }
        return factoryMethod;
    }

    /**
     * @deprecated
     */
    public static Class<?> getBuilderClass(JSONType type) {
        return getBuilderClass(null, type);
    }

    public static Class<?> getBuilderClass(Class<?> clazz, JSONType type) {
        if (clazz != null && clazz.getName().equals("org.springframework.security.web.savedrequest.DefaultSavedRequest")) {
            return TypeUtils.loadClass("org.springframework.security.web.savedrequest.DefaultSavedRequest$Builder");
        }

        if (type == null) {
            return null;
        }

        Class<?> builderClass = type.builder();

        if (builderClass == Void.class) {
            return null;
        }

        return builderClass;
    }
}
