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

com.alibaba.fastjson2.util.BeanUtils Maven / Gradle / Ivy

Go to download

Fastjson is a JSON processor (JSON parser + JSON generator) written in Java

There is a newer version: 2.0.53.android8
Show newest version
package com.alibaba.fastjson2.util;

import com.alibaba.fastjson2.*;
import com.alibaba.fastjson2.annotation.JSONField;
import com.alibaba.fastjson2.annotation.JSONType;
import com.alibaba.fastjson2.codec.BeanInfo;
import com.alibaba.fastjson2.codec.FieldInfo;
import com.alibaba.fastjson2.modules.ObjectCodecProvider;
import com.alibaba.fastjson2.reader.ObjectReader;
import com.alibaba.fastjson2.writer.ObjectWriter;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.*;
import java.util.function.Consumer;

import static com.alibaba.fastjson2.util.JDKUtils.ANDROID_SDK_INT;
import static com.alibaba.fastjson2.util.JDKUtils.JVM_VERSION;

/**
 * @author Bob Lee
 * @author Jesse Wilson
 * @author Shaojin Wen
 * @author poo0054
 */
public abstract class BeanUtils {
    static final Type[] EMPTY_TYPE_ARRAY = new Type[]{};

    static final ConcurrentMap fieldCache = new ConcurrentHashMap<>();
    static final ConcurrentMap> fieldMapCache = new ConcurrentHashMap<>();
    static final ConcurrentMap declaredFieldCache = new ConcurrentHashMap<>();
    static final ConcurrentMap methodCache = new ConcurrentHashMap<>();
    static final ConcurrentMap constructorCache = new ConcurrentHashMap<>();

    private static volatile Class RECORD_CLASS;
    private static volatile Method RECORD_GET_RECORD_COMPONENTS;
    private static volatile Method RECORD_COMPONENT_GET_NAME;

    public static final String SUPER = "$super$";

    // com.alibaba.fastjson2.util.BeanUtilsTest.buildIgnores
    static final long[] IGNORE_CLASS_HASH_CODES = {
            -9214723784238596577L,
            -9030616758866828325L,
            -8335274122997354104L,
            -6963030519018899258L,
            -4863137578837233966L,
            -3653547262287832698L,
            -2819277587813726773L,
            -2669552864532011468L,
            -2458634727370886912L,
            -2291619803571459675L,
            -1811306045128064037L,
            -864440709753525476L,
            -779604756358333743L,
            8731803887940231L,
            1616814008855344660L,
            2164749833121980361L,
            2688642392827789427L,
            3724195282986200606L,
            3742915795806478647L,
            3977020351318456359L,
            4882459834864833642L,
            6033839080488254886L,
            7981148566008458638L,
            8344106065386396833L
    };

    public static String[] getRecordFieldNames(Class recordType) {
        if (JVM_VERSION < 14 && ANDROID_SDK_INT < 33) {
            return new String[0];
        }

        try {
            if (RECORD_GET_RECORD_COMPONENTS == null) {
                RECORD_GET_RECORD_COMPONENTS = Class.class.getMethod("getRecordComponents");
            }

            if (RECORD_COMPONENT_GET_NAME == null) {
                Class c = Class.forName("java.lang.reflect.RecordComponent");
                RECORD_COMPONENT_GET_NAME = c.getMethod("getName");
            }

            final Object[] components = (Object[]) RECORD_GET_RECORD_COMPONENTS.invoke(recordType);
            final String[] names = new String[components.length];
            for (int i = 0; i < components.length; i++) {
                names[i] = (String) RECORD_COMPONENT_GET_NAME.invoke(components[i]);
            }

            return names;
        } catch (Exception e) {
            throw new RuntimeException(String.format(
                    "Failed to access Methods needed to support `java.lang.Record`: (%s) %s",
                    e.getClass().getName(), e.getMessage()), e);
        }
    }

    public static void fields(Class objectClass, Consumer fieldReaders) {
        if (TypeUtils.isProxy(objectClass)) {
            Class superclass = objectClass.getSuperclass();
            fields(superclass, fieldReaders);
            return;
        }

        Field[] fields = fieldCache.get(objectClass);
        if (fields == null) {
            fields = objectClass.getFields();
            fieldCache.putIfAbsent(objectClass, fields);
        }

        boolean enumClass = Enum.class.isAssignableFrom(objectClass);
        for (Field field : fields) {
            int modifiers = field.getModifiers();
            if (Modifier.isStatic(modifiers) && !enumClass) {
                continue;
            }

            fieldReaders.accept(field);
        }
    }

    public static Method getMethod(Class objectClass, String methodName) {
        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }

        return null;
    }

    public static Method fluentSetter(Class objectClass, String methodName, Class paramType) {
        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

        for (Method method : methods) {
            if (method.getName().equals(methodName)
                    && method.getReturnType() == objectClass
                    && method.getParameterCount() == 1
                    && method.getParameterTypes()[0] == paramType
            ) {
                return method;
            }
        }

        return null;
    }

    public static Method getMethod(Class objectClass, Method signature) {
        if (objectClass == null
                || objectClass == Object.class
                || objectClass == Serializable.class
        ) {
            return null;
        }

        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

        for (Method method : methods) {
            if (!method.getName().equals(signature.getName())) {
                continue;
            }
            if (method.getParameterCount() != signature.getParameterCount()) {
                continue;
            }
            Class[] parameterTypes0 = method.getParameterTypes();
            Class[] parameterTypes1 = signature.getParameterTypes();
            boolean paramMatch = true;
            for (int i = 0; i < parameterTypes0.length; i++) {
                if (!parameterTypes0[i].equals(parameterTypes1[i])) {
                    paramMatch = false;
                    break;
                }
            }
            if (paramMatch) {
                return method;
            }
        }

        return null;
    }

    public static Field getDeclaredField(Class objectClass, String fieldName) {
        Map fieldMap = fieldMapCache.get(objectClass);
        if (fieldMap == null) {
            Map map = new HashMap<>();
            declaredFields(objectClass, field -> map.put(field.getName(), field));

            fieldMapCache.putIfAbsent(objectClass, map);
            fieldMap = fieldMapCache.get(objectClass);
        }

        return fieldMap.get(fieldName);
    }

    public static Method getSetter(Class objectClass, String methodName) {
        Method[] methods = new Method[1];
        setters(objectClass, e -> {
            if (!methodName.equals(e.getName())) {
                return;
            }

            methods[0] = e;
        });
        return methods[0];
    }

    /**
     * ignore static fields
     */
    public static void declaredFields(Class objectClass, Consumer fieldConsumer) {
        if (objectClass == null || fieldConsumer == null) {
            return;
        }

        if (ignore(objectClass) || objectClass.getName().contains("$$Lambda") || JdbcSupport.isStruct(objectClass)) {
            return;
        }

        if (TypeUtils.isProxy(objectClass)) {
            Class superclass = objectClass.getSuperclass();
            declaredFields(superclass, fieldConsumer);
            return;
        }

        Class superClass = objectClass.getSuperclass();

        boolean protobufMessageV3 = false;
        if (superClass != null
                && superClass != Object.class
        ) {
            protobufMessageV3 = "com.google.protobuf.GeneratedMessageV3".equals(superClass.getName());
            if (!protobufMessageV3) {
                declaredFields(superClass, fieldConsumer);
            }
        }

        Field[] fields = declaredFieldCache.get(objectClass);
        if (fields == null) {
            Field[] declaredFields;
            try {
                declaredFields = objectClass.getDeclaredFields();
                declaredFieldCache.put(objectClass, declaredFields);
            } catch (Throwable ignored) {
                declaredFields = new Field[0];
            }

            boolean allMatch = true;
            for (Field field : declaredFields) {
                int modifiers = field.getModifiers();
                if (Modifier.isStatic(modifiers)) {
                    allMatch = false;
                    break;
                }
            }

            if (allMatch) {
                fields = declaredFields;
            } else {
                boolean isEnum = Enum.class.isAssignableFrom(objectClass);
                List list = new ArrayList<>(declaredFields.length);
                for (Field field : declaredFields) {
                    if (!isEnum && Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    list.add(field);
                }
                fields = list.toArray(new Field[list.size()]);
            }

            fieldCache.putIfAbsent(objectClass, fields);
        }

        for (Field field : fields) {
            int modifiers = field.getModifiers();
            Class fieldClass = field.getType();
            if ((modifiers & Modifier.STATIC) != 0) {
                continue;
            }

            if (BeanUtils.ignore(fieldClass)) {
                continue;
            }

            if (protobufMessageV3) {
                String fieldName = field.getName();
                if ("cardsmap_".equals(fieldName)
                        && "com.google.protobuf.MapField".equals(fieldClass.getName())) {
                    return;
                }
            }

            Class declaringClass = field.getDeclaringClass();
            if (declaringClass == AbstractMap.class
                    || declaringClass == HashMap.class
                    || declaringClass == LinkedHashMap.class
                    || declaringClass == TreeMap.class
                    || declaringClass == ConcurrentHashMap.class
            ) {
                continue;
            }

            fieldConsumer.accept(field);
        }
    }

    public static void staticMethod(Class objectClass, Consumer methodConsumer) {
        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

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

            methodConsumer.accept(method);
        }
    }

    public static Method buildMethod(Class objectClass, String methodName) {
        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

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

            if (method.getParameterCount() != 0) {
                continue;
            }

            if (method.getName().equals(methodName)) {
                return method;
            }
        }

        return null;
    }

    public static void constructor(Class objectClass, Consumer constructorConsumer) {
        Constructor[] constructors = constructorCache.get(objectClass);
        if (constructors == null) {
            constructors = objectClass.getDeclaredConstructors();
            constructorCache.putIfAbsent(objectClass, constructors);
        }

        boolean record = isRecord(objectClass);
        for (Constructor constructor : constructors) {
            if (record && constructor.getParameterCount() == 0) {
                continue;
            }
            constructorConsumer.accept(constructor);
        }
    }

    public static Constructor[] getConstructor(Class objectClass) {
        Constructor[] constructors = constructorCache.get(objectClass);
        if (constructors == null) {
            constructors = objectClass.getDeclaredConstructors();
            constructorCache.putIfAbsent(objectClass, constructors);
        }

        return constructors;
    }

    public static boolean hasPublicDefaultConstructor(Class objectClass) {
        Constructor constructor = getDefaultConstructor(objectClass, false);
        return constructor != null && Modifier.isPublic(constructor.getModifiers());
    }

    public static Constructor getDefaultConstructor(Class objectClass, boolean includeNoneStaticMember) {
        if ((objectClass == StackTraceElement.class && JVM_VERSION >= 9) || (isRecord(objectClass))) {
            return null;
        }

        Constructor[] constructors = constructorCache.get(objectClass);
        if (constructors == null) {
            constructors = objectClass.getDeclaredConstructors();
            constructorCache.putIfAbsent(objectClass, constructors);
        }

        for (Constructor constructor : constructors) {
            if (constructor.getParameterCount() == 0) {
                return constructor;
            }
        }

        if (!includeNoneStaticMember) {
            return null;
        }

        Class declaringClass = objectClass.getDeclaringClass();
        if (declaringClass != null) {
            for (Constructor constructor : constructors) {
                if (constructor.getParameterCount() == 1) {
                    Class firstParamType = constructor.getParameterTypes()[0];
                    if (declaringClass.equals(firstParamType)) {
                        return constructor;
                    }
                }
            }
        }

        return null;
    }

    public static void setters(Class objectClass, Consumer methodConsumer) {
        setters(objectClass, null, null, methodConsumer);
    }

    public static void setters(Class objectClass, Class mixin, Consumer methodConsumer) {
        setters(objectClass, null, mixin, methodConsumer);
    }

    public static void setters(Class objectClass, BeanInfo beanInfo, Class mixin, Consumer methodConsumer) {
        if (ignore(objectClass)) {
            return;
        }

        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

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

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

            String methodName = method.getName();

            boolean methodSkip = false;
            switch (methodName) {
                case "equals":
                case "hashCode":
                case "toString":
                    methodSkip = true;
                    break;
                case "copy":
                    if (beanInfo != null && beanInfo.kotlin) {
                        methodSkip = true;
                    }
                    break;
                default:
                    break;
            }

            if (methodSkip) {
                continue;
            }

            int paramCount = method.getParameterCount();
            Class returnType = method.getReturnType();

            // read only getter
            if (paramCount == 0) {
                if (methodName.length() <= 3 || !methodName.startsWith("get")) {
                    continue;
                }

                if (returnType == AtomicInteger.class
                        || returnType == AtomicLong.class
                        || returnType == AtomicBoolean.class
                        || returnType == AtomicIntegerArray.class
                        || returnType == AtomicLongArray.class
                        || returnType == AtomicReference.class
                        || Collection.class.isAssignableFrom(returnType)
                        || Map.class.isAssignableFrom(returnType)
                ) {
                    methodConsumer.accept(method);
                    continue;
                }
            }

            if (paramCount == 2
                    && method.getReturnType() == Void.TYPE
                    && method.getParameterTypes()[0] == String.class
            ) {
                Annotation[] annotations = getAnnotations(method);

                AtomicBoolean unwrapped = new AtomicBoolean(false);
                for (Annotation annotation : annotations) {
                    Class annotationType = annotation.annotationType();
                    JSONField jsonField = findAnnotation(annotation, JSONField.class);
                    if (jsonField != null) {
                        if (jsonField.unwrapped()) {
                            unwrapped.set(true);
                            break;
                        }
                        continue;
                    }

                    switch (annotationType.getName()) {
                        case "com.fasterxml.jackson.annotation.JsonAnySetter":
                            if (JSONFactory.isUseJacksonAnnotation()) {
                                unwrapped.set(true);
                            }
                            break;
                        case "com.alibaba.fastjson.annotation.JSONField": {
                            BeanUtils.annotationMethods(annotation.getClass(), m -> {
                                String name = m.getName();
                                try {
                                    if ("unwrapped".equals(name)) {
                                        Object result = m.invoke(annotation);
                                        if ((Boolean) result) {
                                            unwrapped.set(true);
                                        }
                                    }
                                } catch (Throwable ignored) {
                                    // ignored
                                }
                            });
                            break;
                        }
                        default:
                            break;
                    }
                }

                if (unwrapped.get()) {
                    methodConsumer.accept(method);
                }
                continue;
            }

            if (paramCount != 1) {
                continue;
            }

            final int methodNameLength = methodName.length();
            boolean nameMatch = methodNameLength > 3 && (methodName.startsWith("set") || returnType == objectClass);
            if (!nameMatch) {
                if (mixin != null) {
                    Method mixinMethod = getMethod(mixin, method);
                    if (mixinMethod != null) {
                        Annotation[] annotations = getAnnotations(mixinMethod);
                        for (Annotation annotation : annotations) {
                            if (annotation.annotationType() == JSONField.class) {
                                JSONField jsonField = (JSONField) annotation;
                                if (!jsonField.unwrapped()) {
                                    nameMatch = true;
                                }
                                break;
                            }
                        }
                    }
                }
            }

            if (!nameMatch) {
                Annotation[] annotations = getAnnotations(method);
                for (Annotation annotation : annotations) {
                    if (annotation.annotationType() == JSONField.class) {
                        JSONField jsonField = (JSONField) annotation;
                        if (!jsonField.unwrapped()) {
                            nameMatch = true;
                        }
                        break;
                    }
                }
            }

            if (!nameMatch) {
                continue;
            }

            methodConsumer.accept(method);
        }
    }

    public static void setters(Class objectClass, boolean checkPrefix, Consumer methodConsumer) {
        if (ignore(objectClass)) {
            return;
        }

        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

        for (Method method : methods) {
            int paramType = method.getParameterCount();

            // read only getter
            if (paramType == 0) {
                String methodName = method.getName();
                if (checkPrefix && (methodName.length() <= 3 || !methodName.startsWith("get"))) {
                    continue;
                }

                Class returnType = method.getReturnType();
                if (returnType == AtomicInteger.class
                        || returnType == AtomicLong.class
                        || returnType == AtomicBoolean.class
                        || returnType == AtomicIntegerArray.class
                        || returnType == AtomicLongArray.class
                        || Collection.class.isAssignableFrom(returnType)
                ) {
                    methodConsumer.accept(method);
                    continue;
                }
            }

            if (paramType != 1) {
                continue;
            }

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

            String methodName = method.getName();
            final int methodNameLength = methodName.length();
            if (checkPrefix && (methodNameLength <= 3 || !methodName.startsWith("set"))) {
                continue;
            }

            methodConsumer.accept(method);
        }
    }

    public static void annotationMethods(Class objectClass, Consumer methodConsumer) {
        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

        for (Method method : methods) {
            if (method.getParameterCount() != 0) {
                continue;
            }
            Class declaringClass = method.getDeclaringClass();
            if (declaringClass == Object.class) {
                continue;
            }

            switch (method.getName()) {
                case "toString":
                case "hashCode":
                case "annotationType":
                    continue;
                default:
                    break;
            }

            methodConsumer.accept(method);
        }
    }

    public static boolean isWriteEnumAsJavaBean(Class clazz) {
        Annotation[] annotations = getAnnotations(clazz);
        for (Annotation annotation : annotations) {
            JSONType jsonType = findAnnotation(annotation, JSONType.class);
            if (jsonType != null) {
                return jsonType.writeEnumAsJavaBean();
            }

            Class annotationType = annotation.annotationType();
            String name = annotationType.getName();
            BeanInfo beanInfo = new BeanInfo(JSONFactory.getDefaultObjectWriterProvider());
            switch (name) {
                case "com.alibaba.fastjson.annotation.JSONType":
                    BeanUtils.annotationMethods(annotationType, method -> BeanUtils.processJSONType1x(beanInfo, annotation, method));
                    break;
                case "com.fasterxml.jackson.annotation.JsonFormat":
                    boolean useJacksonAnnotation = JSONFactory.isUseJacksonAnnotation();
                    if (useJacksonAnnotation) {
                        processJacksonJsonFormat(beanInfo, annotation);
                    }
                    break;
            }
            if (beanInfo.writeEnumAsJavaBean) {
                return true;
            }
        }

        return false;
    }

    public static String[] getEnumAnnotationNames(Class enumClass) {
        Enum[] enumConstants = (Enum[]) enumClass.getEnumConstants();
        String[] annotationNames = new String[enumConstants.length];
        BeanUtils.fields(enumClass, field -> {
            String fieldName = field.getName();
            for (int i = 0; i < enumConstants.length; i++) {
                Enum e = enumConstants[i];
                final int enumIndex = i;
                String enumName = e.name();
                if (fieldName.equals(enumName)) {
                    for (Annotation annotation : field.getAnnotations()) {
                        Class annotationType = annotation.annotationType();
                        String annotationTypeName = annotationType.getName();
                        if ("com.alibaba.fastjson2.annotation.JSONField".equals(annotationTypeName)
                                || "com.alibaba.fastjson.annotation.JSONField".equals(annotationTypeName)) {
                            BeanUtils.annotationMethods(annotationType, m -> {
                                String name = m.getName();
                                try {
                                    Object result = m.invoke(annotation);
                                    if ("name".equals(name)) {
                                        String annotationName = (String) result;
                                        if (annotationName.length() != 0 && !annotationName.equals(enumName)) {
                                            annotationNames[enumIndex] = annotationName;
                                        }
                                    }
                                } catch (Exception ignored) {
                                    // ignored
                                }
                            });
                        }
                    }
                    break;
                }
            }
        });

        int nulls = 0;
        for (String annotationName : annotationNames) {
            if (annotationName == null) {
                nulls++;
            }
        }

        if (nulls == annotationNames.length) {
            return null;
        }

        return annotationNames;
    }

    public static Member getEnumValueField(Class enumClass, ObjectCodecProvider mixinProvider) {
        if (enumClass == null) {
            return null;
        }

        Class[] interfaces = enumClass.getInterfaces();

        Method[] methods = methodCache.get(enumClass);
        if (methods == null) {
            methods = enumClass.getMethods();
            methodCache.putIfAbsent(enumClass, methods);
        }

        Member valueMember = null;
        for (Method method : methods) {
            if (method.getReturnType() == Void.class) {
                continue;
            }

            if (method.getParameterCount() != 0) {
                continue;
            }

            Class declaringClass = method.getDeclaringClass();
            if (declaringClass == Enum.class || declaringClass == Object.class) {
                continue;
            }

            String methodName = method.getName();
            if ("values".equals(methodName)) {
                continue;
            }

            if (isJSONField(method)) {
                return method;
            }

            if (methodName.startsWith("get")) {
                String fieldName = BeanUtils.getterName(methodName, null);
                Field field = BeanUtils.getDeclaredField(enumClass, fieldName);
                if (field != null && isJSONField(field)) {
                    if (valueMember == null) {
                        valueMember = method;
                    } else if (valueMember.getName().equals(method.getName())) {
                        // Using Subclasses #2682
                        if (valueMember instanceof Method) {
                            Method valueMethod = (Method) valueMember;
                            if (valueMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
                                valueMember = method;
                            }
                        }
                    } else {
                        // multi annotation
                        return null;
                    }
                    continue;
                }
            }

            AtomicReference memberRef = new AtomicReference<>();
            for (Class enumInterface : interfaces) {
                getters(enumInterface, e -> {
                    if (e.getName().equals(methodName)) {
                        if (isJSONField(e)) {
                            memberRef.set(method);
                        }
                    }
                });

                Class mixIn;
                if (mixinProvider != null) {
                    mixIn = mixinProvider.getMixIn(enumInterface);
                } else {
                    mixIn = JSONFactory.getDefaultObjectWriterProvider().getMixIn(enumInterface);
                }

                if (mixIn != null) {
                    getters(mixIn, e -> {
                        if (e.getName().equals(methodName)) {
                            if (isJSONField(e)) {
                                memberRef.set(method);
                            }
                        }
                    });
                }
            }
            Member refMember = memberRef.get();
            if (refMember != null) {
                if (valueMember == null) {
                    valueMember = refMember;
                } else if (!valueMember.getName().equals(refMember.getName())) {
                    // multi annotation
                    return null;
                }
            }
        }

        if (valueMember != null) {
            return valueMember;
        }

        Field[] fields = fieldCache.get(enumClass);
        if (fields == null) {
            fields = enumClass.getFields();
            fieldCache.putIfAbsent(enumClass, fields);
        }

        Member member = null;
        Enum[] enumConstants = (Enum[]) enumClass.getEnumConstants();
        for (Field field : fields) {
            boolean found = false;
            if (enumConstants != null) {
                String fieldName = field.getName();
                for (Enum e : enumConstants) {
                    if (fieldName.equals(e.name())) {
                        found = true;
                        break;
                    }
                }
            }

            if (isJSONField(field)) {
                if (!found) {
                    member = field;
                    break;
                }
            }
        }

        return member;
    }

    public static void getters(Class objectClass, Consumer methodConsumer) {
        getters(objectClass, null, methodConsumer);
    }

    public static void getters(Class objectClass, Class mixinSource, Consumer methodConsumer) {
        getters(objectClass, mixinSource, false, methodConsumer);
    }

    public static void getters(Class objectClass, Class mixinSource, boolean kotlin, Consumer methodConsumer) {
        if (objectClass == null) {
            return;
        }

        if (Proxy.isProxyClass(objectClass)) {
            Class[] interfaces = objectClass.getInterfaces();
            if (interfaces.length == 1) {
                getters(interfaces[0], methodConsumer);
                return;
            }
        }

        if (ignore(objectClass)) {
            return;
        }

        Class superClass = objectClass.getSuperclass();
        if (TypeUtils.isProxy(objectClass)) {
            getters(superClass, methodConsumer);
            return;
        }

        boolean record = isRecord(objectClass);
        boolean jdbcStruct = JdbcSupport.isStruct(objectClass);

        String[] recordFieldNames = null;
        if (record) {
            recordFieldNames = getRecordFieldNames(objectClass);
        }

        Method[] methods = methodCache.get(objectClass);
        if (methods == null) {
            methods = getMethods(objectClass);
            methodCache.putIfAbsent(objectClass, methods);
        }

        boolean protobufMessageV3 = superClass != null && "com.google.protobuf.GeneratedMessageV3".equals(superClass.getName());

        for (Method method : methods) {
            int paramType = method.getParameterCount();
            if (paramType != 0) {
                continue;
            }

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

            Class returnClass = method.getReturnType();
            if (returnClass == Void.class || returnClass == void.class || ignore(returnClass)) {
                continue;
            }

            Class declaringClass = method.getDeclaringClass();
            if (declaringClass == Enum.class || declaringClass == Object.class) {
                continue;
            }

            String methodName = method.getName();
            if (jdbcStruct) {
                if (!"getSQLTypeName".equals(methodName) && !"getAttributes".equals(methodName)) {
                    continue;
                }
            }

            boolean methodSkip = false;
            switch (methodName) {
                case "isInitialized":
                case "getInitializationErrorString":
                case "getSerializedSize":
                    if (protobufMessageV3) {
                        methodSkip = true;
                    }
                    break;
                case "equals":
                case "hashCode":
                case "toString":
                    methodSkip = true;
                    break;
                default:
                    break;
            }

            if (methodSkip) {
                continue;
            }

            if (protobufMessageV3) {
                if ((methodName.endsWith("Type") || methodName.endsWith("Bytes"))
                        && "com.google.protobuf.ByteString".equals(returnClass.getName())) {
                    continue;
                }
            }

            // skip thrift isSetXXX
            if (methodName.startsWith("isSet") && returnClass == boolean.class) {
                boolean setterFound = false, unsetFound = false, getterFound = false;
                String setterName = BeanUtils.getterName(methodName, null);
                String getterName = "g" + setterName.substring(1);

                String unsetName = "un" + setterName;
                for (Method m : methods) {
                    if (m.getName().equals(setterName)
                            && m.getParameterCount() == 1
                            && m.getReturnType() == void.class) {
                        setterFound = true;
                    } else if (m.getName().equals(getterName)
                            && m.getParameterCount() == 0) {
                        getterFound = true;
                    } else if (m.getName().equals(unsetName)
                            && m.getParameterCount() == 0
                            && m.getReturnType() == void.class) {
                        unsetFound = true;
                    }
                }

                if (setterFound && unsetFound && getterFound
                        && findAnnotation(method, JSONField.class) == null) {
                    continue;
                }
            }

            if (record) {
                boolean match = false;
                for (String recordFieldName : recordFieldNames) {
                    if (methodName.equals(recordFieldName)) {
                        match = true;
                        break;
                    }
                }

                if (match) {
                    methodConsumer.accept(method);
                    continue;
                }
            }

            final int methodNameLength = methodName.length();
            boolean nameMatch = methodNameLength > 3 && methodName.startsWith("get");
            if (nameMatch) {
                char firstChar = methodName.charAt(3);
                if (firstChar >= 'a' && firstChar <= 'z' && methodNameLength == 4) {
                    nameMatch = false;
                }
            } else if (returnClass == boolean.class || returnClass == Boolean.class || kotlin) {
                nameMatch = methodNameLength > 2 && methodName.startsWith("is");
                if (nameMatch) {
                    char firstChar = methodName.charAt(2);
                    if (firstChar >= 'a' && firstChar <= 'z' && methodNameLength == 3) {
                        nameMatch = false;
                    }
                }
            }

            if (!nameMatch) {
                if (isJSONField(method)) {
                    nameMatch = true;
                }
            }

            if (!nameMatch && mixinSource != null) {
                Method mixinMethod = getMethod(mixinSource, method);
                if (mixinMethod != null) {
                    if (isJSONField(mixinMethod)) {
                        nameMatch = true;
                    }
                }
            }

            if (!nameMatch
                    && objectClass != returnClass
                    && (!methodName.startsWith("build"))
                    && fluentSetter(objectClass, methodName, returnClass) != null) {
                nameMatch = true;
            }

            if (!nameMatch) {
                continue;
            }

            if (protobufMessageV3) {
                if (method.getDeclaringClass() == superClass) {
                    continue;
                }
                Class returnType = method.getReturnType();
                boolean ignore = false;
                switch (methodName) {
                    case "getUnknownFields":
                    case "getSerializedSize":
                    case "getParserForType":
                    case "getMessageBytes":
                    case "getDefaultInstanceForType":
                        ignore = returnType.getName().startsWith("com.google.protobuf.") || returnType == objectClass;
                        break;
                    default:
                        break;
                }

                if (ignore) {
                    continue;
                }
            }

            methodConsumer.accept(method);
        }
    }

    private static Method[] getMethods(Class objectClass) {
        Method[] methods;
        try {
            methods = objectClass.getMethods();
        } catch (NoClassDefFoundError ignored) {
            methods = new Method[0];
        }
        return methods;
    }

    private static boolean isJSONField(AnnotatedElement element) {
        Annotation[] annotations = element.getAnnotations();
        for (Annotation annotation : annotations) {
            String annotationTypeName = annotation.annotationType().getName();
            switch (annotationTypeName) {
                case "com.alibaba.fastjson.annotation.JSONField":
                case "com.alibaba.fastjson2.annotation.JSONField":
                    return true;
                case "com.fasterxml.jackson.annotation.JsonValue":
                case "com.fasterxml.jackson.annotation.JsonRawValue":
                case "com.fasterxml.jackson.annotation.JsonProperty":
                case "com.fasterxml.jackson.annotation.JsonUnwrapped":
                    if (JSONFactory.isUseJacksonAnnotation()) {
                        return true;
                    }
                    break;
                default:
                    break;
            }
        }
        return false;
    }

    static boolean ignore(Class objectClass) {
        if (objectClass == null) {
            return true;
        }

        return Arrays.binarySearch(
                IGNORE_CLASS_HASH_CODES,
                Fnv.hashCode64(
                        objectClass.getName()
                )
        ) >= 0;
    }

    public static boolean isRecord(Class objectClass) {
        Class superclass = objectClass.getSuperclass();
        if (superclass == null) {
            return false;
        }

        if (RECORD_CLASS == null) {
            String superclassName = superclass.getName();
            if ("java.lang.Record".equals(superclassName)) {
                RECORD_CLASS = superclass;
                return true;
            } else {
                return false;
            }
        }

        return superclass == RECORD_CLASS;
    }

    public static String setterName(String methodName, String namingStrategy) {
        if (namingStrategy == null) {
            namingStrategy = "CamelCase";
        }

        int methodNameLength = methodName.length();
        if (methodNameLength <= 3) {
            return methodName;
        }

        int prefixLength = methodName.startsWith("set") ? 3 : 0;

        switch (namingStrategy) {
            case "NeverUseThisValueExceptDefaultValue":
            case "CamelCase": {
                char[] chars = new char[methodNameLength - prefixLength];
                methodName.getChars(prefixLength, methodNameLength, chars, 0);
                char c0 = chars[0];
                boolean c1UCase = chars.length > 1 && chars[1] >= 'A' && chars[1] <= 'Z';
                if (c0 >= 'A' && c0 <= 'Z' && !c1UCase) {
                    chars[0] = (char) (c0 + 32);
                }
                return new String(chars);
            }
            case "PascalCase":
                return pascal(methodName, methodNameLength, prefixLength);
            case "SnakeCase": {
                return snakeCase(methodName, prefixLength);
            }
            case "UpperCaseWithUnderScores": {
                return underScores(methodName, prefixLength, true);
            }
            case "UpperCase": {
                char[] chars = new char[methodNameLength - prefixLength];
                methodName.getChars(prefixLength, methodNameLength, chars, 0);
                char c0 = chars[0];
                for (int i = 0; i < chars.length; i++) {
                    char ch = chars[i];
                    if (ch >= 'a' && c0 <= 'z') {
                        chars[i] = (char) (ch - 32);
                    }
                }
                return new String(chars);
            }
            case "CamelCase1x": {
                char[] chars = new char[methodNameLength - prefixLength];
                methodName.getChars(prefixLength, methodNameLength, chars, 0);
                char c0 = chars[0];
                if (c0 >= 'A' && c0 <= 'Z') {
                    chars[0] = (char) (c0 + 32);
                }
                return new String(chars);
            }
            case "UpperCamelCaseWithSpaces":
                return upperCamelWith(methodName, prefixLength, ' ');
            case "UpperCamelCaseWithUnderScores":
                return upperCamelWith(methodName, prefixLength, '_');
            case "UpperCamelCaseWithDashes":
                return upperCamelWith(methodName, prefixLength, '-');
            case "UpperCamelCaseWithDots":
                return upperCamelWith(methodName, prefixLength, '.');
            case "KebabCase": {
                StringBuilder buf = new StringBuilder();
                final int firstIndex = prefixLength;

                for (int i = prefixLength; i < methodName.length(); ++i) {
                    char ch = methodName.charAt(i);
                    if (ch >= 'A' && ch <= 'Z') {
                        ch = (char) (ch + 32);
                        if (i > firstIndex) {
                            buf.append('-');
                        }
                    }
                    buf.append(ch);
                }
                return buf.toString();
            }
            case "UpperCaseWithDashes":
                return dashes(methodName, prefixLength, true);
            case "UpperCaseWithDots":
                return dots(methodName, prefixLength, true);
            case "LowerCase":
                return methodName.substring(prefixLength).toLowerCase();
            case "LowerCaseWithUnderScores":
                return underScores(methodName, prefixLength, false);
            case "LowerCaseWithDashes":
                return dashes(methodName, prefixLength, false);
            case "LowerCaseWithDots":
                return dots(methodName, prefixLength, false);
            default:
                throw new JSONException("TODO : " + namingStrategy);
        }
    }

    public static String setterName(String methodName, int prefixLength) {
        int methodNameLength = methodName.length();
        char[] chars = new char[methodNameLength - prefixLength];
        methodName.getChars(prefixLength, methodNameLength, chars, 0);
        char c0 = chars[0];
        boolean c1UCase = chars.length > 1 && chars[1] >= 'A' && chars[1] <= 'Z';
        if (c0 >= 'A' && c0 <= 'Z' && !c1UCase) {
            chars[0] = (char) (c0 + 32);
        }
        return new String(chars);
    }

    public static String getterName(Method method, String namingStrategy) {
        return getterName(method, false, namingStrategy);
    }

    public static String getterName(Method method, boolean kotlin, String namingStrategy) {
        String methodName = method.getName();
        if (methodName.startsWith("is")) {
            Class returnType = method.getReturnType();
            if ((returnType != Boolean.class && returnType != boolean.class) || kotlin) {
                return methodName;
            }
        }

        String fieldName = getterName(methodName, namingStrategy);

        if (fieldName.length() > 2
                && fieldName.charAt(0) >= 'A' && fieldName.charAt(0) <= 'Z'
                && fieldName.charAt(1) >= 'A' && fieldName.charAt(1) <= 'Z'
        ) {
            char[] chars = fieldName.toCharArray();
            chars[0] = (char) (chars[0] + 32);
            String fieldName1 = new String(chars);
            Field field = BeanUtils.getDeclaredField(method.getDeclaringClass(), fieldName1);
            if (field != null && Modifier.isPublic(field.getModifiers())) {
                fieldName = field.getName();
            }
        }

        return fieldName;
    }

    public static Field getField(Class objectClass, Method method) {
        String methodName = method.getName();
        final int len = methodName.length();
        Class returnType = method.getReturnType();

        boolean is = false, get = false, set = false;
        if (len > 2) {
            char c0 = methodName.charAt(0);
            char c1 = methodName.charAt(1);
            char c2 = methodName.charAt(2);

            if (c0 == 'i' && c1 == 's') {
                is = returnType == Boolean.class || returnType == boolean.class;
            } else if (c0 == 'g' && c1 == 'e' && c2 == 't') {
                get = len > 3;
            } else if (c0 == 's' && c1 == 'e' && c2 == 't') {
                set = len > 3 && method.getParameterCount() == 1;
            }
        }

        Field[] fields = new Field[2];
        if (is || get || set) {
            Class type = (is || get) ? returnType : method.getParameterTypes()[0];
            int prefix = is ? 2 : 3;
            char[] chars = new char[len - prefix];
            methodName.getChars(prefix, len, chars, 0);
            char c0 = chars[0];
            declaredFields(objectClass, field -> {
                if (field.getDeclaringClass() != method.getDeclaringClass()) {
                    return;
                }

                String fieldName = field.getName();
                int fieldNameLength = fieldName.length();
                if (fieldNameLength == len - prefix
                        && (field.getType() == type || type.isAssignableFrom(field.getType()))) {
                    if (c0 >= 'A' && c0 <= 'Z' && (c0 + 32) == fieldName.charAt(0)
                            && fieldName.regionMatches(1, methodName, prefix + 1, fieldNameLength - 1)
                    ) {
                        fields[0] = field;
                    } else if (fieldName.regionMatches(0, methodName, prefix, fieldNameLength)) {
                        fields[1] = field;
                    }
                } else if (boolean.class == field.getType() && methodName.equals(fieldName)) {
                    fields[0] = field;
                }
            });
        }

        Field field = fields[0] != null ? fields[0] : fields[1];
        if (Throwable.class.isAssignableFrom(objectClass)) {
            if (returnType == String.class && (field == null && "getMessage".equals(methodName) || field == null && "getLocalizedMessage".equals(methodName))) {
                field = getDeclaredField(objectClass, "detailMessage");
            } else if (returnType == Throwable[].class && "getSuppressed".equals(methodName)) {
                field = getDeclaredField(objectClass, "suppressedExceptions");
            }
        }

        return field;
    }

    public static String getterName(String methodName, String namingStrategy) {
        if (namingStrategy == null) {
            namingStrategy = "CamelCase";
        }

        final int methodNameLength = methodName.length();
        boolean is = methodName.startsWith("is");
        boolean get = methodName.startsWith("get");

        final int prefixLength;
        if (is) {
            prefixLength = 2;
        } else if (get) {
            prefixLength = 3;
        } else {
            prefixLength = 0;
        }

        if (methodNameLength == prefixLength) {
            return methodName;
        }

        switch (namingStrategy) {
            case "NeverUseThisValueExceptDefaultValue":
            case "CamelCase": {
                char[] chars = new char[methodNameLength - prefixLength];
                methodName.getChars(prefixLength, methodNameLength, chars, 0);
                char c0 = chars[0];
                boolean c1UCase = chars.length > 1 && chars[1] >= 'A' && chars[1] <= 'Z';
                if (c0 >= 'A' && c0 <= 'Z' && !c1UCase) {
                    chars[0] = (char) (c0 + 32);
                }
                return new String(chars);
            }
            case "CamelCase1x": {
                char[] chars = new char[methodNameLength - prefixLength];
                methodName.getChars(prefixLength, methodNameLength, chars, 0);
                char c0 = chars[0];
                if (c0 >= 'A' && c0 <= 'Z') {
                    chars[0] = (char) (c0 + 32);
                }
                return new String(chars);
            }
            case "PascalCase": {
                return pascal(methodName, methodNameLength, prefixLength);
            }
            case "SnakeCase": {
                return snakeCase(methodName, prefixLength);
            }
            case "UpperCaseWithUnderScores": {
                return underScores(methodName, prefixLength, true);
            }
            case "UpperCamelCaseWithSpaces":
                return upperCamelWith(methodName, prefixLength, ' ');
            case "UpperCase":
                return methodName.substring(prefixLength).toUpperCase();
            case "UpperCaseWithDashes":
                return dashes(methodName, prefixLength, true);
            case "UpperCaseWithDots":
                return dots(methodName, prefixLength, true);
            case "KebabCase": {
                StringBuilder buf = new StringBuilder();
                final int firstIndex;
                if (is) {
                    firstIndex = 2;
                } else if (get) {
                    firstIndex = 3;
                } else {
                    firstIndex = 0;
                }

                for (int i = firstIndex; i < methodName.length(); ++i) {
                    char ch = methodName.charAt(i);
                    if (ch >= 'A' && ch <= 'Z') {
                        ch = (char) (ch + 32);
                        if (i > firstIndex) {
                            buf.append('-');
                        }
                    }
                    buf.append(ch);
                }
                return buf.toString();
            }
            case "UpperCamelCaseWithUnderScores":
                return upperCamelWith(methodName, prefixLength, '_');
            case "UpperCamelCaseWithDashes":
                return upperCamelWith(methodName, prefixLength, '-');
            case "UpperCamelCaseWithDots":
                return upperCamelWith(methodName, prefixLength, '.');
            case "LowerCase":
                return methodName.substring(prefixLength).toLowerCase();
            case "LowerCaseWithUnderScores":
                return underScores(methodName, prefixLength, false);
            case "LowerCaseWithDashes":
                return dashes(methodName, prefixLength, false);
            case "LowerCaseWithDots":
                return dots(methodName, prefixLength, false);
            default:
                throw new JSONException("TODO : " + namingStrategy);
        }
    }

    private static String pascal(String methodName, int methodNameLength, int prefixLength) {
        char[] chars = new char[methodNameLength - prefixLength];
        methodName.getChars(prefixLength, methodNameLength, chars, 0);
        char c0 = chars[0];
        if (c0 >= 'a' && c0 <= 'z' && chars.length > 1) {
            chars[0] = (char) (c0 - 32);
        } else if (c0 == '_' && chars.length > 2) {
            char c1 = chars[1];
            if (c1 >= 'a' && c1 <= 'z' && chars[2] >= 'a' && chars[2] <= 'z') {
                chars[1] = (char) (c1 - 32);
            }
        }
        return new String(chars);
    }

    public static String fieldName(String methodName, String namingStrategy) {
        if (namingStrategy == null) {
            namingStrategy = "CamelCase";
        }
        if (methodName == null || methodName.isEmpty()) {
            return methodName;
        }

        switch (namingStrategy) {
            case "NoChange":
            case "NeverUseThisValueExceptDefaultValue":
            case "CamelCase": {
                char c0 = methodName.charAt(0);
                char c1 = methodName.length() > 1 ? methodName.charAt(1) : '\0';
                if (c0 >= 'A' && c0 <= 'Z'
                        && methodName.length() > 1
                        && (c1 < 'A' || c1 > 'Z')) {
                    char[] chars = methodName.toCharArray();
                    chars[0] = (char) (c0 + 32);
                    return new String(chars);
                }
                return methodName;
            }
            case "CamelCase1x": {
                char c0 = methodName.charAt(0);
                if (c0 >= 'A' && c0 <= 'Z' && methodName.length() > 1) {
                    char[] chars = methodName.toCharArray();
                    chars[0] = (char) (c0 + 32);
                    return new String(chars);
                }
                return methodName;
            }
            case "PascalCase": {
                char c0 = methodName.charAt(0);
                char c1;
                if (c0 >= 'a' && c0 <= 'z'
                        && methodName.length() > 1
                        && (c1 = methodName.charAt(1)) >= 'a'
                        && c1 <= 'z') {
                    char[] chars = methodName.toCharArray();
                    chars[0] = (char) (c0 - 32);
                    return new String(chars);
                } else if (c0 == '_'
                        && methodName.length() > 1
                        && (c1 = methodName.charAt(1)) >= 'a'
                        && c1 <= 'z') {
                    char[] chars = methodName.toCharArray();
                    chars[1] = (char) (c1 - 32);
                    return new String(chars);
                }
                return methodName;
            }
            case "SnakeCase":
                return snakeCase(methodName, 0);
            case "UpperCaseWithUnderScores":
                return underScores(methodName, 0, true);
            case "LowerCaseWithUnderScores":
                return underScores(methodName, 0, false);
            case "UpperCaseWithDashes":
                return dashes(methodName, 0, true);
            case "LowerCaseWithDashes":
                return dashes(methodName, 0, false);
            case "UpperCaseWithDots":
                return dots(methodName, 0, true);
            case "LowerCaseWithDots":
                return dots(methodName, 0, false);
            case "UpperCase":
                return methodName.toUpperCase();
            case "LowerCase":
                return methodName.toLowerCase();
            case "UpperCamelCaseWithSpaces":
                return upperCamelWith(methodName, 0, ' ');
            case "UpperCamelCaseWithUnderScores":
                return upperCamelWith(methodName, 0, '_');
            case "UpperCamelCaseWithDashes":
                return upperCamelWith(methodName, 0, '-');
            case "UpperCamelCaseWithDots":
                return upperCamelWith(methodName, 0, '.');
            case "KebabCase": {
                StringBuilder buf = new StringBuilder();
                for (int i = 0; i < methodName.length(); ++i) {
                    char ch = methodName.charAt(i);
                    if (ch >= 'A' && ch <= 'Z') {
                        ch = (char) (ch + 32);
                        if (i > 0) {
                            buf.append('-');
                        }
                    }
                    buf.append(ch);
                }
                return buf.toString();
            }
            default:
                throw new JSONException("TODO : " + namingStrategy);
        }
    }

    static String snakeCase(String methodName, int prefixLength) {
        final int methodNameLength = methodName.length();

        char[] buf = TypeUtils.CHARS_UPDATER.getAndSet(TypeUtils.CACHE, null);
        if (buf == null) {
            buf = new char[128];
        }
        try {
            int off = 0;
            for (int i = prefixLength; i < methodNameLength; ++i) {
                char ch = methodName.charAt(i);
                if (ch >= 'A' && ch <= 'Z') {
                    ch = (char) (ch + 32);
                    if (i > prefixLength) {
                        buf[off++] = '_';
                    }
                }
                buf[off++] = ch;
            }
            return new String(buf, 0, off);
        } finally {
            TypeUtils.CHARS_UPDATER.set(TypeUtils.CACHE, buf);
        }
    }

    static String upperCamelWith(String methodName, int prefixLength, char separator) {
        final int methodNameLength = methodName.length();

        char[] buf = TypeUtils.CHARS_UPDATER.getAndSet(TypeUtils.CACHE, null);
        if (buf == null) {
            buf = new char[128];
        }
        try {
            int off = 0;
            for (int i = prefixLength; i < methodNameLength; ++i) {
                char ch = methodName.charAt(i);
                char c1;
                if (i == prefixLength) {
                    if (ch >= 'a' && ch <= 'z'
                            && i + 1 < methodNameLength
                            && (c1 = methodName.charAt(i + 1)) >= 'a'
                            && c1 <= 'z') {
                        ch = (char) (ch - 32);
                    } else if (ch == '_' && i + 1 < methodNameLength
                            && (c1 = methodName.charAt(i + 1)) >= 'a'
                            && c1 <= 'z') {
                        buf[off++] = ch;
                        ch = (char) (c1 - 32);
                        ++i;
                    }
                } else if (ch >= 'A' && ch <= 'Z'
                        && i + 1 < methodNameLength
                        && ((c1 = methodName.charAt(i + 1)) < 'A' || c1 > 'Z')) {
                    if (i > prefixLength) {
                        buf[off++] = separator;
                    }
                } else if (ch >= 'A' && ch <= 'Z'
                        && i > prefixLength
                        && i + 1 < methodNameLength
                        && (c1 = methodName.charAt(i + 1)) >= 'A'
                        && c1 <= 'Z'
                        && (c1 = methodName.charAt(i - 1)) >= 'a'
                        && c1 <= 'z') {
                    buf[off++] = separator;
                }
                buf[off++] = ch;
            }
            return new String(buf, 0, off);
        } finally {
            TypeUtils.CHARS_UPDATER.set(TypeUtils.CACHE, buf);
        }
    }

    static String underScores(String methodName, int prefixLength, boolean upper) {
        final int methodNameLength = methodName.length();

        char[] buf = TypeUtils.CHARS_UPDATER.getAndSet(TypeUtils.CACHE, null);
        if (buf == null) {
            buf = new char[128];
        }
        try {
            int off = 0;
            for (int i = prefixLength; i < methodNameLength; ++i) {
                char ch = methodName.charAt(i);
                if (upper) {
                    if (ch < 'A' || ch > 'Z') {
                        if (ch >= 'a' && ch <= 'z') {
                            ch -= 32;
                        }
                    } else {
                        if (i > prefixLength) {
                            buf[off++] = '_';
                        }
                    }
                } else {
                    if (ch >= 'A' && ch <= 'Z') {
                        if (i > prefixLength) {
                            buf[off++] = '_';
                        }
                        ch = (char) (ch + 32);
                    }
                }
                buf[off++] = ch;
            }
            return new String(buf, 0, off);
        } finally {
            TypeUtils.CHARS_UPDATER.set(TypeUtils.CACHE, buf);
        }
    }

    static String dashes(String methodName, int prefixLength, boolean upper) {
        final int methodNameLength = methodName.length();

        char[] buf = TypeUtils.CHARS_UPDATER.getAndSet(TypeUtils.CACHE, null);
        if (buf == null) {
            buf = new char[128];
        }
        try {
            int off = 0;
            for (int i = prefixLength; i < methodNameLength; ++i) {
                char ch = methodName.charAt(i);
                if (upper) {
                    if (ch >= 'A' && ch <= 'Z') {
                        if (i > prefixLength) {
                            buf[off++] = '-';
                        }
                    } else {
                        if (ch >= 'a' && ch <= 'z') {
                            ch -= 32;
                        }
                    }
                } else {
                    if (ch >= 'A' && ch <= 'Z') {
                        if (i > prefixLength) {
                            buf[off++] = '-';
                        }
                        ch = (char) (ch + 32);
                    }
                }
                buf[off++] = ch;
            }
            return new String(buf, 0, off);
        } finally {
            TypeUtils.CHARS_UPDATER.set(TypeUtils.CACHE, buf);
        }
    }

    static String dots(String methodName, int prefixLength, boolean upper) {
        final int methodNameLength = methodName.length();

        char[] buf = TypeUtils.CHARS_UPDATER.getAndSet(TypeUtils.CACHE, null);
        if (buf == null) {
            buf = new char[128];
        }
        try {
            int off = 0;
            for (int i = prefixLength; i < methodNameLength; ++i) {
                char ch = methodName.charAt(i);
                if (upper) {
                    if (ch >= 'A' && ch <= 'Z') {
                        if (i > prefixLength) {
                            buf[off++] = '.';
                        }
                    } else {
                        if (ch >= 'a' && ch <= 'z') {
                            ch -= 32;
                        }
                    }
                } else {
                    if (ch >= 'A' && ch <= 'Z') {
                        if (i > prefixLength) {
                            buf[off++] = '.';
                        }
                        ch = (char) (ch + 32);
                    }
                }
                buf[off++] = ch;
            }
            return new String(buf, 0, off);
        } finally {
            TypeUtils.CHARS_UPDATER.set(TypeUtils.CACHE, buf);
        }
    }

    public static Type getFieldType(TypeReference typeReference, Class raw, Member field, Type fieldType) {
        final Class declaringClass = field == null ? null : field.getDeclaringClass();

        while (raw != Object.class) {
            Type type = typeReference == null ? null : typeReference.getType();
            if (declaringClass == raw) {
                return resolve(type, declaringClass, fieldType);
            }
            Type superType = raw.getGenericSuperclass();
            // interface has no generic super class
            if (superType == null) {
                break;
            }
            typeReference = TypeReference.get(resolve(type, raw, superType));
            raw = typeReference.getRawType();
        }
        return null;
    }

    public static Type getParamType(
            TypeReference type,
            Class raw,
            Class declaringClass,
            Parameter field,
            Type fieldType
    ) {
        while (raw != Object.class) {
            if (declaringClass == raw) {
                return resolve(type.getType(), declaringClass, fieldType);
            }
            type = TypeReference.get(resolve(type.getType(), raw, raw.getGenericSuperclass()));
            raw = type.getRawType();
        }
        return null;
    }

    /**
     * Returns a new parameterized type, applying {@code typeArguments} to
     * {@code rawType} and enclosed by {@code ownerType}.
     *
     * @return a {@link java.io.Serializable serializable} parameterized type.
     */
    public static ParameterizedType newParameterizedTypeWithOwner(
            Type ownerType, Type rawType, Type... typeArguments) {
        return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
    }

    /**
     * Returns an array type whose elements are all instances of
     * {@code componentType}.
     *
     * @return a {@link java.io.Serializable serializable} generic array type.
     */
    public static GenericArrayType arrayOf(Type componentType) {
        return new GenericArrayTypeImpl(componentType);
    }

    /**
     * Returns a type that represents an unknown type that extends {@code bound}.
     * For example, if {@code bound} is {@code CharSequence.class}, this returns
     * {@code ? extends CharSequence}. If {@code bound} is {@code Object.class},
     * this returns {@code ?}, which is shorthand for {@code ? extends Object}.
     */
    public static WildcardType subtypeOf(Type bound) {
        return new WildcardTypeImpl(
                bound instanceof WildcardType
                        ? ((WildcardType) bound).getUpperBounds()
                        : new Type[]{bound},
                EMPTY_TYPE_ARRAY);
    }

    /**
     * Returns a type that represents an unknown supertype of {@code bound}. For
     * example, if {@code bound} is {@code String.class}, this returns {@code ?
     * super String}.
     */
    public static WildcardType supertypeOf(Type bound) {
        return new WildcardTypeImpl(
                new Type[]{Object.class},
                bound instanceof WildcardType
                        ? ((WildcardType) bound).getLowerBounds()
                        : new Type[]{bound}
        );
    }

    /**
     * Returns a type that is functionally equal but not necessarily equal
     * according to {@link Object#equals(Object) Object.equals()}. The returned
     * type is {@link java.io.Serializable}.
     */
    public static Type canonicalize(Type type) {
        if (type instanceof Class) {
            Class c = (Class) type;
            return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;
        } else if (type instanceof ParameterizedType) {
            ParameterizedType p = (ParameterizedType) type;
            return new ParameterizedTypeImpl(p.getOwnerType(),
                    p.getRawType(), p.getActualTypeArguments());
        } else if (type instanceof GenericArrayType) {
            GenericArrayType g = (GenericArrayType) type;
            return new GenericArrayTypeImpl(g.getGenericComponentType());
        } else if (type instanceof WildcardType) {
            WildcardType w = (WildcardType) type;
            return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
        } else {
            // type is either serializable as-is or unsupported
            return type;
        }
    }

    public static Class getRawType(Type type) {
        if (type instanceof Class) {
            // type is a normal class.
            return (Class) type;
        } else if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;

            // I'm not exactly sure why getRawType() returns Type instead of Class.
            // Neal isn't either but suspects some pathological case related
            // to nested classes exists.
            Type rawType = parameterizedType.getRawType();
            checkArgument(rawType instanceof Class);
            return (Class) rawType;
        } else if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            return Array.newInstance(getRawType(componentType), 0).getClass();
        } else if (type instanceof TypeVariable) {
            // we could use the variable's bounds, but that won't work if there are multiple.
            // having a raw type that's more general than necessary is okay
            return Object.class;
        } else if (type instanceof WildcardType) {
            return getRawType(((WildcardType) type).getUpperBounds()[0]);
        } else {
            String className = type == null ? "null" : type.getClass().getName();
            throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
                    + "GenericArrayType, but <" + type + "> is of type " + className);
        }
    }

    static boolean equal(Object a, Object b) {
        return Objects.equals(a, b);
    }

    /**
     * Returns true if {@code a} and {@code b} are equal.
     */
    public static boolean equals(Type a, Type b) {
        if (a == b) {
            // also handles (a == null && b == null)
            return true;
        } else if (a instanceof Class) {
            // Class already specifies equals().
            return a.equals(b);
        } else if (a instanceof ParameterizedType) {
            if (!(b instanceof ParameterizedType)) {
                return false;
            }

            // TODO: save a .clone() call
            ParameterizedType pa = (ParameterizedType) a;
            ParameterizedType pb = (ParameterizedType) b;
            return equal(pa.getOwnerType(), pb.getOwnerType())
                    && pa.getRawType().equals(pb.getRawType())
                    && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments());
        } else if (a instanceof GenericArrayType) {
            if (!(b instanceof GenericArrayType)) {
                return false;
            }

            GenericArrayType ga = (GenericArrayType) a;
            GenericArrayType gb = (GenericArrayType) b;
            return equals(ga.getGenericComponentType(), gb.getGenericComponentType());
        } else if (a instanceof WildcardType) {
            if (!(b instanceof WildcardType)) {
                return false;
            }

            WildcardType wa = (WildcardType) a;
            WildcardType wb = (WildcardType) b;
            return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds())
                    && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());
        } else if (a instanceof TypeVariable) {
            if (!(b instanceof TypeVariable)) {
                return false;
            }
            TypeVariable va = (TypeVariable) a;
            TypeVariable vb = (TypeVariable) b;
            return va.getGenericDeclaration() == vb.getGenericDeclaration()
                    && va.getName().equals(vb.getName());
        } else {
            // This isn't a type we support. Could be a generic array type, wildcard type, etc.
            return false;
        }
    }

    static int hashCodeOrZero(Object o) {
        return o != null ? o.hashCode() : 0;
    }

    public static String typeToString(Type type) {
        return type instanceof Class ? ((Class) type).getName() : type.toString();
    }

    /**
     * Returns the generic supertype for {@code supertype}. For example, given a class {@code
     * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the
     * result when the supertype is {@code Collection.class} is {@code Collection}.
     */
    static Type getGenericSupertype(Type context, Class rawType, Class toResolve) {
        if (toResolve == rawType) {
            return context;
        }

        // we skip searching through interfaces if unknown is an interface
        if (toResolve.isInterface()) {
            Class[] interfaces = rawType.getInterfaces();
            for (int i = 0, length = interfaces.length; i < length; i++) {
                if (interfaces[i] == toResolve) {
                    return rawType.getGenericInterfaces()[i];
                } else if (toResolve.isAssignableFrom(interfaces[i])) {
                    return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
                }
            }
        }

        // check our supertypes
        if (rawType != null && !rawType.isInterface()) {
            while (rawType != Object.class) {
                Class rawSupertype = rawType.getSuperclass();
                if (rawSupertype == toResolve) {
                    return rawType.getGenericSuperclass();
                } else if (toResolve.isAssignableFrom(rawSupertype)) {
                    return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve);
                }
                rawType = rawSupertype;
            }
        }

        // we can't resolve this further
        return toResolve;
    }

    public static Type resolve(Type context, Class contextRawType, Type toResolve) {
        return resolve(context, contextRawType, toResolve, new HashMap<>());
    }

    private static Type resolve(Type context, Class contextRawType, Type toResolve,
                                Map, Type> visitedTypeVariables) {
        // this implementation is made a little more complicated in an attempt to avoid object-creation
        TypeVariable resolving = null;
        while (true) {
            if (toResolve instanceof TypeVariable) {
                TypeVariable typeVariable = (TypeVariable) toResolve;
                Type previouslyResolved = visitedTypeVariables.get(typeVariable);
                if (previouslyResolved != null) {
                    // cannot reduce due to infinite recursion
                    return (previouslyResolved == Void.TYPE) ? toResolve : previouslyResolved;
                }

                // Insert a placeholder to mark the fact that we are in the process of resolving this type
                visitedTypeVariables.put(typeVariable, Void.TYPE);
                if (resolving == null) {
                    resolving = typeVariable;
                }

                toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
                if (toResolve == typeVariable) {
                    break;
                }
            } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) {
                Class original = (Class) toResolve;
                Type componentType = original.getComponentType();
                Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
                toResolve = equal(componentType, newComponentType)
                        ? original
                        : arrayOf(newComponentType);
                break;
            } else if (toResolve instanceof GenericArrayType) {
                GenericArrayType original = (GenericArrayType) toResolve;
                Type componentType = original.getGenericComponentType();
                Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
                toResolve = equal(componentType, newComponentType)
                        ? original
                        : arrayOf(newComponentType);
                break;
            } else if (toResolve instanceof ParameterizedType) {
                ParameterizedType original = (ParameterizedType) toResolve;
                Type ownerType = original.getOwnerType();
                Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables);
                boolean changed = !equal(newOwnerType, ownerType);

                Type[] args = original.getActualTypeArguments();
                for (int i = 0, length = args.length; i < length; i++) {
                    Type arg = args[i];
                    if (arg == String.class) {
                        continue;
                    }
                    Type resolvedTypeArgument = resolve(context, contextRawType, arg, visitedTypeVariables);
                    if (!equal(resolvedTypeArgument, arg)) {
                        if (!changed) {
                            args = args.clone();
                            changed = true;
                        }
                        args[i] = resolvedTypeArgument;
                    }
                }

                toResolve = changed
                        ? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
                        : original;
                break;
            } else if (toResolve instanceof WildcardType) {
                WildcardType original = (WildcardType) toResolve;
                Type[] originalLowerBound = original.getLowerBounds();
                Type[] originalUpperBound = original.getUpperBounds();

                if (originalLowerBound.length == 1) {
                    Type lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
                    if (lowerBound != originalLowerBound[0]) {
                        toResolve = supertypeOf(lowerBound);
                        break;
                    }
                } else if (originalUpperBound.length == 1) {
                    Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
                    if (upperBound != originalUpperBound[0]) {
                        toResolve = subtypeOf(upperBound);
                        break;
                    }
                }
                break;
            } else {
                break;
            }
        }
        // ensure that any in-process resolution gets updated with the final result
        if (resolving != null) {
            visitedTypeVariables.put(resolving, toResolve);
        }
        return toResolve;
    }

    static Type resolveTypeVariable(Type context, Class contextRawType, TypeVariable unknown) {
        Class declaredByRaw = declaringClassOf(unknown);

        // we can't reduce this further
        if (declaredByRaw == null) {
            return unknown;
        }

        Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);
        if (declaredBy instanceof ParameterizedType) {
            int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
            return ((ParameterizedType) declaredBy).getActualTypeArguments()[index];
        }

        return unknown;
    }

    private static int indexOf(Object[] array, Object toFind) {
        for (int i = 0, length = array.length; i < length; i++) {
            if (toFind.equals(array[i])) {
                return i;
            }
        }
        throw new NoSuchElementException();
    }

    /**
     * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by
     * a class.
     */
    private static Class declaringClassOf(TypeVariable typeVariable) {
        GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
        return genericDeclaration instanceof Class
                ? (Class) genericDeclaration
                : null;
    }

    static void checkNotPrimitive(Type type) {
        checkArgument(!(type instanceof Class) || !((Class) type).isPrimitive());
    }

    /**
     * Find the first annotation of {@code annotationType} that is either
     * directly present, meta-present, or indirectly
     * present on the supplied {@code element}.
     *
     * 

If the element is a class and the annotation is neither directly * present nor meta-present on the class, this method will additionally search on * interfaces implemented by the class before finding an annotation that is indirectly * present on the class. * * @param element the element on which to search for the annotation * @param annotationType the annotation type of need to search * @param the annotation * @return the searched annotation type */ public static A findAnnotation(AnnotatedElement element, Class annotationType) { if (annotationType == null) { throw new NullPointerException("annotationType must not be null"); } boolean inherited = annotationType.isAnnotationPresent(Inherited.class); return findAnnotation(element, annotationType, inherited, new HashSet<>()); } /** * If the {@code annotation}'s annotationType is not {@code annotationType}, then to find the * first annotation of {@code annotationType} that is either * directly present, meta-present, or indirectly * present on the supplied {@code element}. * * @param annotation annotation * @param annotationType the annotation type of need to search * @param the searched annotation type * @return the searched annotation */ @SuppressWarnings("unchecked") public static A findAnnotation(Annotation annotation, Class annotationType) { if (annotation == null) { throw new NullPointerException("annotation must not be null"); } if (annotationType == null) { throw new NullPointerException("annotationType must not be null"); } Class annotationTypeClass = annotation.annotationType(); if (annotationTypeClass == annotationType) { return (A) annotation; } boolean inherited = annotationType.isAnnotationPresent(Inherited.class); return findAnnotation(annotationTypeClass, annotationType, inherited, new HashSet<>()); } /** * Find the first annotation of {@code annotationType} that is either * directly present, meta-present, or indirectly * present on the supplied {@code element}. * *

If the element is a class and the annotation is neither directly * present nor meta-present on the class, this method will additionally search on * interfaces implemented by the class before finding an annotation that is indirectly * present on the class. * * @param element the element on which to search for the annotation * @param annotationType the annotation type of need to search * @param inherited whether has {@link Inherited} * @param visited this annotation whether visited * @param the annotation type * @return the searched annotation */ private static A findAnnotation( AnnotatedElement element, Class annotationType, boolean inherited, Set visited ) { if (element == null || annotationType == null) { return null; } A annotation = element.getDeclaredAnnotation(annotationType); if (annotation != null) { return annotation; } Annotation[] declaredAnnotations = element.getDeclaredAnnotations(); A directMetaAnnotation = findMetaAnnotation(annotationType, declaredAnnotations, inherited, visited); if (directMetaAnnotation != null) { return directMetaAnnotation; } if (element instanceof Class) { Class clazz = (Class) element; for (Class ifc : clazz.getInterfaces()) { if (ifc != Annotation.class) { A annotationOnInterface = findAnnotation(ifc, annotationType, inherited, visited); if (annotationOnInterface != null) { return annotationOnInterface; } } } if (inherited) { Class superclass = clazz.getSuperclass(); if (superclass != null && superclass != Object.class) { A annotationOnSuperclass = findAnnotation(superclass, annotationType, true, visited); if (annotationOnSuperclass != null) { return annotationOnSuperclass; } } } } return findMetaAnnotation(annotationType, getAnnotations(element), inherited, visited); } /** * Find meta-present on indirectly present annotations. * * @param annotationType the annotation type of need to search * @param candidates annotations for candidates * @param inherited whether has {@link Inherited} * @param visited this annotation whether visited * @param the annotation type * @return the searched annotation */ private static A findMetaAnnotation( Class annotationType, Annotation[] candidates, boolean inherited, Set visited ) { for (Annotation candidateAnnotation : candidates) { Class candidateAnnotationType = candidateAnnotation.annotationType(); String name = candidateAnnotationType.getName(); boolean isInJavaLangAnnotationPackage = name.startsWith("java.lang.annotation") || name.startsWith("kotlin."); if (!isInJavaLangAnnotationPackage && visited.add(candidateAnnotation)) { A metaAnnotation = findAnnotation(candidateAnnotationType, annotationType, inherited, visited); if (metaAnnotation != null) { return metaAnnotation; } } } return null; } public static Annotation[] getAnnotations(AnnotatedElement element) { try { return element.getDeclaredAnnotations(); } catch (Throwable ignored) { return new Annotation[0]; } } static final class ParameterizedTypeImpl implements ParameterizedType, Serializable { private final Type ownerType; private final Type rawType; private final Type[] typeArguments; public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { // require an owner type if the raw type needs it if (rawType instanceof Class) { Class rawTypeAsClass = (Class) rawType; boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers()) || rawTypeAsClass.getEnclosingClass() == null; checkArgument(ownerType != null || isStaticOrTopLevelClass); } this.ownerType = ownerType == null ? null : canonicalize(ownerType); this.rawType = canonicalize(rawType); this.typeArguments = typeArguments.clone(); for (int t = 0, length = this.typeArguments.length; t < length; t++) { checkNotPrimitive(this.typeArguments[t]); this.typeArguments[t] = canonicalize(this.typeArguments[t]); } } @Override public Type[] getActualTypeArguments() { return typeArguments.clone(); } @Override public Type getRawType() { return rawType; } @Override public Type getOwnerType() { return ownerType; } @Override public boolean equals(Object other) { return other instanceof ParameterizedType && BeanUtils.equals(this, (ParameterizedType) other); } @Override public int hashCode() { return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType); } @Override public String toString() { int length = typeArguments.length; if (length == 0) { return typeToString(rawType); } StringBuilder stringBuilder = new StringBuilder(30 * (length + 1)); stringBuilder.append(typeToString(rawType)).append("<").append(typeToString(typeArguments[0])); for (int i = 1; i < length; i++) { stringBuilder.append(", ").append(typeToString(typeArguments[i])); } return stringBuilder.append(">").toString(); } private static final long serialVersionUID = 0; } public static final class GenericArrayTypeImpl implements GenericArrayType, Serializable { private final Type componentType; public GenericArrayTypeImpl(Type componentType) { this.componentType = canonicalize(componentType); } @Override public Type getGenericComponentType() { return componentType; } @Override public boolean equals(Object o) { return o instanceof GenericArrayType && BeanUtils.equals(this, (GenericArrayType) o); } @Override public int hashCode() { return componentType.hashCode(); } @Override public String toString() { return typeToString(componentType) + "[]"; } private static final long serialVersionUID = 0; } /** * The WildcardType interface supports multiple upper bounds and multiple * lower bounds. We only support what the Java 6 language needs - at most one * bound. If a lower bound is set, the upper bound must be Object.class. */ static final class WildcardTypeImpl implements WildcardType, Serializable { private final Type upperBound; private final Type lowerBound; public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { checkArgument(lowerBounds.length <= 1); checkArgument(upperBounds.length == 1); if (lowerBounds.length == 1) { checkNotPrimitive(lowerBounds[0]); checkArgument(upperBounds[0] == Object.class); this.lowerBound = canonicalize(lowerBounds[0]); this.upperBound = Object.class; } else { checkNotPrimitive(upperBounds[0]); this.lowerBound = null; this.upperBound = canonicalize(upperBounds[0]); } } @Override public Type[] getUpperBounds() { return new Type[]{upperBound}; } @Override public Type[] getLowerBounds() { return lowerBound != null ? new Type[]{lowerBound} : EMPTY_TYPE_ARRAY; } @Override public boolean equals(Object other) { return other instanceof WildcardType && BeanUtils.equals(this, (WildcardType) other); } @Override public int hashCode() { // this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()); return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode()); } @Override public String toString() { if (lowerBound != null) { return "? super " + typeToString(lowerBound); } else if (upperBound == Object.class) { return "?"; } else { return "? extends " + typeToString(upperBound); } } private static final long serialVersionUID = 0; } static void checkArgument(boolean condition) { if (!condition) { throw new IllegalArgumentException(); } } public static void processJacksonJsonIgnore(FieldInfo fieldInfo, Annotation annotation) { fieldInfo.ignore = true; Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); if ("value".equals(name)) { fieldInfo.ignore = (boolean) (Boolean) result; } } catch (Throwable ignored) { // ignored } }); } public static boolean isNoneStaticMemberClass(Class objectClass, Class memberClass) { if (memberClass == null || memberClass.isPrimitive() || memberClass == String.class || memberClass == List.class ) { return false; } Class enclosingClass = memberClass.getEnclosingClass(); if (enclosingClass == null) { return false; } if (objectClass != null && !objectClass.equals(enclosingClass)) { return false; } Constructor[] constructors = constructorCache.get(memberClass); if (constructors == null) { constructors = memberClass.getDeclaredConstructors(); constructorCache.putIfAbsent(memberClass, constructors); } if (constructors.length == 0) { return false; } Constructor firstConstructor = constructors[0]; if (firstConstructor.getParameterCount() == 0) { return false; } Class[] parameterTypes = firstConstructor.getParameterTypes(); return enclosingClass.equals(parameterTypes[0]); } public static void setNoneStaticMemberClassParent(Object object, Object parent) { Class objectClass = object.getClass(); Field[] fields = declaredFieldCache.get(objectClass); if (fields == null) { Field[] declaredFields = objectClass.getDeclaredFields(); boolean allMatch = true; for (Field field : declaredFields) { int modifiers = field.getModifiers(); if (Modifier.isStatic(modifiers)) { allMatch = false; break; } } if (allMatch) { fields = declaredFields; } else { List list = new ArrayList<>(declaredFields.length); for (Field field : declaredFields) { int modifiers = field.getModifiers(); if (Modifier.isStatic(modifiers)) { continue; } list.add(field); } fields = list.toArray(new Field[list.size()]); } fieldCache.putIfAbsent(objectClass, fields); } Field this0 = null; for (Field field : fields) { if ("this$0".equals(field.getName())) { this0 = field; } } if (this0 != null) { this0.setAccessible(true); try { this0.set(object, parent); } catch (IllegalAccessException e) { throw new JSONException("setNoneStaticMemberClassParent error, class " + objectClass); } } } public static void cleanupCache(Class objectClass) { if (objectClass == null) { return; } fieldCache.remove(objectClass); fieldMapCache.remove(objectClass); declaredFieldCache.remove(objectClass); methodCache.remove(objectClass); constructorCache.remove(objectClass); } public static void cleanupCache(ClassLoader classLoader) { for (Iterator> it = fieldCache.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = it.next(); Class entryKey = entry.getKey(); if (entryKey.getClassLoader() == classLoader) { it.remove(); } } for (Iterator>> it = fieldMapCache.entrySet().iterator(); it.hasNext(); ) { Map.Entry> entry = it.next(); Class entryKey = entry.getKey(); if (entryKey.getClassLoader() == classLoader) { it.remove(); } } for (Iterator> it = declaredFieldCache.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = it.next(); Class entryKey = entry.getKey(); if (entryKey.getClassLoader() == classLoader) { it.remove(); } } for (Iterator> it = methodCache.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = it.next(); Class entryKey = entry.getKey(); if (entryKey.getClassLoader() == classLoader) { it.remove(); } } for (Iterator> it = constructorCache.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = it.next(); Class entryKey = entry.getKey(); if (entryKey.getClassLoader() == classLoader) { it.remove(); } } } public static void processJSONType1x(BeanInfo beanInfo, Annotation jsonType1x, Method method) { try { Object result = method.invoke(jsonType1x); switch (method.getName()) { case "seeAlso": { Class[] classes = (Class[]) result; if (classes.length != 0) { beanInfo.seeAlso = classes; } break; } case "typeName": { String typeName = (String) result; if (!typeName.isEmpty()) { beanInfo.typeName = typeName; } break; } case "typeKey": { String typeKey = (String) result; if (!typeKey.isEmpty()) { beanInfo.typeKey = typeKey; } break; } case "rootName": { String rootName = (String) result; if (!rootName.isEmpty()) { beanInfo.rootName = rootName; } break; } case "alphabetic": { Boolean alphabetic = (Boolean) result; if (!alphabetic) { beanInfo.alphabetic = false; } break; } case "serializeFeatures": case "serialzeFeatures": { Enum[] serializeFeatures = (Enum[]) result; for (Enum feature : serializeFeatures) { switch (feature.name()) { case "WriteMapNullValue": beanInfo.writerFeatures |= JSONWriter.Feature.WriteNulls.mask; break; case "WriteNullListAsEmpty": beanInfo.writerFeatures |= JSONWriter.Feature.WriteNullListAsEmpty.mask; break; case "WriteNullStringAsEmpty": beanInfo.writerFeatures |= JSONWriter.Feature.WriteNullStringAsEmpty.mask; break; case "WriteNullNumberAsZero": beanInfo.writerFeatures |= JSONWriter.Feature.WriteNullNumberAsZero.mask; break; case "WriteNullBooleanAsFalse": beanInfo.writerFeatures |= JSONWriter.Feature.WriteNullBooleanAsFalse.mask; break; case "BrowserCompatible": beanInfo.writerFeatures |= JSONWriter.Feature.BrowserCompatible.mask; break; case "WriteClassName": beanInfo.writerFeatures |= JSONWriter.Feature.WriteClassName.mask; break; case "WriteNonStringValueAsString": beanInfo.writerFeatures |= JSONWriter.Feature.WriteNonStringValueAsString.mask; break; case "WriteEnumUsingToString": beanInfo.writerFeatures |= JSONWriter.Feature.WriteEnumUsingToString.mask; break; case "NotWriteRootClassName": beanInfo.writerFeatures |= JSONWriter.Feature.NotWriteRootClassName.mask; break; case "IgnoreErrorGetter": beanInfo.writerFeatures |= JSONWriter.Feature.IgnoreErrorGetter.mask; break; default: break; } } break; } case "serializeEnumAsJavaBean": { boolean serializeEnumAsJavaBean = (Boolean) result; if (serializeEnumAsJavaBean) { beanInfo.writeEnumAsJavaBean = true; } break; } case "naming": { Enum naming = (Enum) result; beanInfo.namingStrategy = naming.name(); break; } case "ignores": { String[] fields = (String[]) result; if (fields.length != 0) { if (beanInfo.ignores == null) { beanInfo.ignores = fields; } else { LinkedHashSet ignoresSet = new LinkedHashSet<>(); ignoresSet.addAll(Arrays.asList(beanInfo.ignores)); ignoresSet.addAll(Arrays.asList(fields)); beanInfo.ignores = ignoresSet.toArray(new String[ignoresSet.size()]); } } break; } case "includes": { String[] fields = (String[]) result; if (fields.length != 0) { beanInfo.includes = fields; } break; } case "orders": { String[] fields = (String[]) result; if (fields.length != 0) { beanInfo.orders = fields; } break; } case "serializer": { Class serializerClass = (Class) result; if (ObjectWriter.class.isAssignableFrom(serializerClass)) { beanInfo.writeEnumAsJavaBean = true; beanInfo.serializer = serializerClass; } break; } case "deserializer": { Class deserializerClass = (Class) result; if (ObjectReader.class.isAssignableFrom(deserializerClass)) { beanInfo.deserializer = deserializerClass; } break; } default: break; } } catch (Throwable ignored) { // ignored } } public static void processJacksonJsonFormat(FieldInfo fieldInfo, Annotation annotation) { Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); switch (name) { case "pattern": { String pattern = (String) result; if (pattern.length() != 0) { fieldInfo.format = pattern; } break; } case "shape": { String shape = ((Enum) result).name(); if ("STRING".equals(shape)) { fieldInfo.features |= JSONWriter.Feature.WriteNonStringValueAsString.mask; } else if ("NUMBER".equals(shape)) { fieldInfo.format = "millis"; } break; } case "locale": { String locale = (String) result; if (!locale.isEmpty() && !"##default".equals(locale)) { fieldInfo.locale = Locale.forLanguageTag(locale); } break; } default: break; } } catch (Throwable ignored) { // ignored } }); } public static void processJacksonJsonFormat(BeanInfo beanInfo, Annotation annotation) { Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); if ("pattern".equals(name)) { String pattern = (String) result; if (!pattern.isEmpty()) { beanInfo.format = pattern; } } else if ("shape".equals(name)) { String shape = ((Enum) result).name(); if ("NUMBER".equals(shape)) { beanInfo.format = "millis"; } else if ("OBJECT".equals(shape)) { beanInfo.writeEnumAsJavaBean = true; } } else if ("locale".equals(name)) { String locale = (String) result; if (!locale.isEmpty() && !"##default".equals(locale)) { beanInfo.locale = Locale.forLanguageTag(locale); } } } catch (Throwable ignored) { // ignored } }); } public static void processJacksonJsonInclude(BeanInfo beanInfo, Annotation annotation) { Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); if ("value".equals(name)) { String include = ((Enum) result).name(); switch (include) { case "ALWAYS": beanInfo.writerFeatures |= JSONWriter.Feature.WriteNulls.mask; break; case "NON_DEFAULT": beanInfo.writerFeatures |= JSONWriter.Feature.NotWriteDefaultValue.mask; break; case "NON_EMPTY": beanInfo.writerFeatures |= JSONWriter.Feature.NotWriteEmptyArray.mask; break; default: break; } } } catch (Throwable ignored) { // ignored } }); } public static void processJacksonJsonInclude(FieldInfo fieldInfo, Annotation annotation) { Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); if ("value".equals(name)) { String include = ((Enum) result).name(); switch (include) { case "ALWAYS": fieldInfo.features |= JSONWriter.Feature.WriteNulls.mask; break; case "NON_DEFAULT": fieldInfo.features |= JSONWriter.Feature.NotWriteDefaultValue.mask; break; case "NON_EMPTY": fieldInfo.features |= JSONWriter.Feature.NotWriteEmptyArray.mask; fieldInfo.features |= JSONWriter.Feature.IgnoreEmpty.mask; break; default: break; } } } catch (Throwable ignored) { // ignored } }); } public static void processJacksonJsonUnwrapped(FieldInfo fieldInfo, Annotation annotation) { Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); if ("enabled".equals(name)) { boolean value = (Boolean) result; if (value) { fieldInfo.features = FieldInfo.UNWRAPPED_MASK; } } } catch (Throwable ignored) { // ignored } }); } public static void processJacksonJsonTypeName(BeanInfo beanInfo, Annotation annotation) { Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); if ("value".equals(name)) { String value = (String) result; if (!value.isEmpty()) { beanInfo.typeName = value; } } } catch (Throwable ignored) { // ignored } }); } public static void processJacksonJsonSubTypesType(BeanInfo beanInfo, int index, Annotation annotation) { Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); switch (name) { case "value": { Class value = (Class) result; beanInfo.seeAlso[index] = value; break; } case "name": { String value = (String) result; beanInfo.seeAlsoNames[index] = value; break; } default: break; } } catch (Throwable ignored) { // ignored } }); } public static void processGsonSerializedName(FieldInfo fieldInfo, Annotation annotation) { Class annotationClass = annotation.getClass(); BeanUtils.annotationMethods(annotationClass, m -> { String name = m.getName(); try { Object result = m.invoke(annotation); switch (name) { case "value": String value = (String) result; if (!value.isEmpty()) { fieldInfo.fieldName = value; } break; case "alternate": String[] alternate = (String[]) result; if (alternate.length != 0) { fieldInfo.alternateNames = alternate; } break; default: break; } } catch (Throwable ignored) { // ignored } }); } public static boolean isExtendedMap(Class objectClass) { if (objectClass == HashMap.class || objectClass == LinkedHashMap.class || objectClass == TreeMap.class || "".equals(objectClass.getSimpleName()) ) { return false; } Class superclass = objectClass.getSuperclass(); if (superclass != HashMap.class && superclass != LinkedHashMap.class && superclass != TreeMap.class ) { return false; } Constructor defaultConstructor = getDefaultConstructor(objectClass, false); if (defaultConstructor != null) { return false; } List fields = new ArrayList<>(); BeanUtils.declaredFields(objectClass, field -> { int modifiers = field.getModifiers(); if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers) || field.getDeclaringClass().isAssignableFrom(superclass) || "this$0".equals(field.getName()) ) { return; } fields.add(field); }); return !fields.isEmpty(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy