ObjectReaderProvider.java

package com.alibaba.fastjson2.reader;

import com.alibaba.fastjson2.*;
import com.alibaba.fastjson2.JSONReader.AutoTypeBeforeHandler;
import com.alibaba.fastjson2.codec.BeanInfo;
import com.alibaba.fastjson2.codec.FieldInfo;
import com.alibaba.fastjson2.function.FieldBiConsumer;
import com.alibaba.fastjson2.function.FieldConsumer;
import com.alibaba.fastjson2.modules.ObjectCodecProvider;
import com.alibaba.fastjson2.modules.ObjectReaderAnnotationProcessor;
import com.alibaba.fastjson2.modules.ObjectReaderModule;
import com.alibaba.fastjson2.support.LambdaMiscCodec;
import com.alibaba.fastjson2.util.BeanUtils;
import com.alibaba.fastjson2.util.Fnv;
import com.alibaba.fastjson2.util.JDKUtils;
import com.alibaba.fastjson2.util.TypeUtils;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import static com.alibaba.fastjson2.JSONFactory.*;
import static com.alibaba.fastjson2.util.Fnv.MAGIC_HASH_CODE;
import static com.alibaba.fastjson2.util.Fnv.MAGIC_PRIME;
import static com.alibaba.fastjson2.util.TypeUtils.loadClass;

/**
 * ObjectReaderProvider is responsible for providing and managing ObjectReader instances
 * for deserializing JSON data into Java objects. It handles object creation, caching,
 * type conversion, and auto-type support.
 *
 * <p>This provider supports various features including:
 * <ul>
 *   <li>Object reader caching for performance optimization</li>
 *   <li>Auto-type support with security controls</li>
 *   <li>Type conversion between different Java types</li>
 *   <li>Mixin support for modifying serialization behavior</li>
 *   <li>Module-based extensibility</li>
 * </ul>
 *
 * <p>Example usage:
 * <pre>
 * // Get default provider
 * ObjectReaderProvider provider = JSONFactory.getDefaultObjectReaderProvider();
 *
 * // Get object reader for a specific type
 * ObjectReader&lt;User&gt; reader = provider.getObjectReader(User.class);
 *
 * // Parse JSON string using the reader
 * User user = reader.readObject(JSONReader.of(jsonString));
 *
 * // Register custom type converter
 * provider.registerTypeConvert(String.class, Integer.class, Integer::valueOf);
 * </pre>
 *
 * @since 2.0.0
 */
public class ObjectReaderProvider
        implements ObjectCodecProvider {
    static final ClassLoader FASTJSON2_CLASS_LOADER = JSON.class.getClassLoader();
    public static final boolean SAFE_MODE;
    static final String[] DENYS;
    static final String[] AUTO_TYPE_ACCEPT_LIST;

    static AutoTypeBeforeHandler DEFAULT_AUTO_TYPE_BEFORE_HANDLER;
    static Consumer<Class> DEFAULT_AUTO_TYPE_HANDLER;
    static boolean DEFAULT_AUTO_TYPE_HANDLER_INIT_ERROR;

    static ObjectReaderCachePair readerCache;

    private static final class ObjectReaderCachePair {
        final long hashCode;
        final ObjectReader reader;
        volatile int missCount;

        public ObjectReaderCachePair(long hashCode, ObjectReader reader) {
            this.hashCode = hashCode;
            this.reader = reader;
        }
    }

    static {
        {
            String property = System.getProperty(PROPERTY_DENY_PROPERTY);
            if (property == null) {
                property = JSONFactory.Conf.getProperty(PROPERTY_DENY_PROPERTY);
            }
            if (property != null && property.length() > 0) {
                DENYS = property.split(",");
            } else {
                DENYS = new String[0];
            }
        }

        {
            String property = System.getProperty(PROPERTY_AUTO_TYPE_ACCEPT);
            if (property == null) {
                property = JSONFactory.Conf.getProperty(PROPERTY_AUTO_TYPE_ACCEPT);
            }
            if (property != null && property.length() > 0) {
                AUTO_TYPE_ACCEPT_LIST = property.split(",");
            } else {
                AUTO_TYPE_ACCEPT_LIST = new String[0];
            }
        }

        {
            String property = System.getProperty(PROPERTY_AUTO_TYPE_BEFORE_HANDLER);
            if (property == null || property.isEmpty()) {
                property = JSONFactory.Conf.getProperty(PROPERTY_AUTO_TYPE_BEFORE_HANDLER);
            }

            if (property != null) {
                property = property.trim();
            }

            if (property != null && !property.isEmpty()) {
                Class handlerClass = TypeUtils.loadClass(property);
                if (handlerClass != null) {
                    try {
                        DEFAULT_AUTO_TYPE_BEFORE_HANDLER = (AutoTypeBeforeHandler) handlerClass.newInstance();
                    } catch (Exception ignored) {
                        DEFAULT_AUTO_TYPE_HANDLER_INIT_ERROR = true;
                        // skip
                    }
                }
            }
        }

        {
            String property = System.getProperty(PROPERTY_AUTO_TYPE_HANDLER);
            if (property == null || property.isEmpty()) {
                property = JSONFactory.Conf.getProperty(PROPERTY_AUTO_TYPE_HANDLER);
            }

            if (property != null) {
                property = property.trim();
            }

            if (property != null && !property.isEmpty()) {
                Class handlerClass = TypeUtils.loadClass(property);
                if (handlerClass != null) {
                    try {
                        DEFAULT_AUTO_TYPE_HANDLER = (Consumer<Class>) handlerClass.newInstance();
                    } catch (Exception ignored) {
                        DEFAULT_AUTO_TYPE_HANDLER_INIT_ERROR = true;
                        // skip
                    }
                }
            }
        }

        {
            String property = System.getProperty("fastjson.parser.safeMode");
            if (property == null || property.isEmpty()) {
                property = JSONFactory.Conf.getProperty("fastjson.parser.safeMode");
            }

            if (property == null || property.isEmpty()) {
                property = System.getProperty("fastjson2.parser.safeMode");
            }
            if (property == null || property.isEmpty()) {
                property = JSONFactory.Conf.getProperty("fastjson2.parser.safeMode");
            }

            if (property != null) {
                property = property.trim();
            }

            SAFE_MODE = "true".equals(property);
        }
    }

    final ConcurrentMap<Type, ObjectReader> cache = new ConcurrentHashMap<>();
    final ConcurrentMap<Type, ObjectReader> cacheFieldBased = new ConcurrentHashMap<>();
    final ConcurrentMap<Integer, ConcurrentHashMap<Long, ObjectReader>> tclHashCaches = new ConcurrentHashMap<>();
    final ConcurrentMap<Long, ObjectReader> hashCache = new ConcurrentHashMap<>();
    final ConcurrentMap<Class, Class> mixInCache = new ConcurrentHashMap<>();

    final LRUAutoTypeCache autoTypeList = new LRUAutoTypeCache(1024);

    private final ConcurrentMap<Type, Map<Type, Function>> typeConverts = new ConcurrentHashMap<>();

    final ObjectReaderCreator creator;
    final List<ObjectReaderModule> modules = new CopyOnWriteArrayList<>();

    boolean disableReferenceDetect = JSONFactory.isDisableReferenceDetect();
    boolean disableArrayMapping = JSONFactory.isDisableArrayMapping();
    boolean disableJSONB = JSONFactory.isDisableJSONB();
    boolean disableAutoType = JSONFactory.isDisableAutoType();
    boolean disableSmartMatch = JSONFactory.isDisableSmartMatch();

    private long[] acceptHashCodes;

    private AutoTypeBeforeHandler autoTypeBeforeHandler = DEFAULT_AUTO_TYPE_BEFORE_HANDLER;
    private Consumer<Class> autoTypeHandler = DEFAULT_AUTO_TYPE_HANDLER;
    PropertyNamingStrategy namingStrategy;

    {
        long[] hashCodes;
        if (AUTO_TYPE_ACCEPT_LIST == null) {
            hashCodes = new long[1];
        } else {
            hashCodes = new long[AUTO_TYPE_ACCEPT_LIST.length + 1];
            for (int i = 0; i < AUTO_TYPE_ACCEPT_LIST.length; i++) {
                hashCodes[i] = Fnv.hashCode64(AUTO_TYPE_ACCEPT_LIST[i]);
            }
        }

        hashCodes[hashCodes.length - 1] = -6293031534589903644L;

        Arrays.sort(hashCodes);
        acceptHashCodes = hashCodes;

        hashCache.put(ObjectArrayReader.TYPE_HASH_CODE, ObjectArrayReader.INSTANCE);
        final long STRING_CLASS_NAME_HASH = -4834614249632438472L; // Fnv.hashCode64(String.class.getName());
        hashCache.put(STRING_CLASS_NAME_HASH, ObjectReaderImplString.INSTANCE);
        hashCache.put(Fnv.hashCode64(TypeUtils.getTypeName(HashMap.class)), ObjectReaderImplMap.INSTANCE);
    }

    /**
     * Registers an ObjectReader for the specified hash code if it is not already registered.
     * This method handles both thread-local and global caching.
     *
     * @param hashCode the hash code for which to register the ObjectReader
     * @param objectReader the ObjectReader to register
     */
    public void registerIfAbsent(long hashCode, ObjectReader objectReader) {
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        if (tcl != null && tcl != JSON.class.getClassLoader()) {
            int tclHash = System.identityHashCode(tcl);
            ConcurrentHashMap<Long, ObjectReader> tclHashCache = tclHashCaches.get(tclHash);
            if (tclHashCache == null) {
                tclHashCaches.putIfAbsent(tclHash, new ConcurrentHashMap<>());
                tclHashCache = tclHashCaches.get(tclHash);
            }

            tclHashCache.putIfAbsent(hashCode, objectReader);
        }

        hashCache.putIfAbsent(hashCode, objectReader);
    }

    /**
     * Adds a type name to the auto-type accept list. Types in this list are allowed
     * for auto-type deserialization.
     *
     * @param name the type name to add to the accept list
     */
    public void addAutoTypeAccept(String name) {
        if (name != null && name.length() != 0) {
            long hash = Fnv.hashCode64(name);
            if (Arrays.binarySearch(this.acceptHashCodes, hash) < 0) {
                long[] hashCodes = new long[this.acceptHashCodes.length + 1];
                hashCodes[hashCodes.length - 1] = hash;
                System.arraycopy(this.acceptHashCodes, 0, hashCodes, 0, this.acceptHashCodes.length);
                Arrays.sort(hashCodes);
                this.acceptHashCodes = hashCodes;
            }
        }
    }

    @Deprecated
    public void addAutoTypeDeny(String name) {
    }

    /**
     * Gets the auto-type handler that is invoked when a type is auto-resolved.
     *
     * @return the auto-type handler, or null if none is set
     */
    public Consumer<Class> getAutoTypeHandler() {
        return autoTypeHandler;
    }

    /**
     * Sets the auto-type handler that will be invoked when a type is auto-resolved.
     *
     * @param autoTypeHandler the auto-type handler to set
     */
    public void setAutoTypeHandler(Consumer<Class> autoTypeHandler) {
        this.autoTypeHandler = autoTypeHandler;
    }

    /**
     * Gets the mixin source class for the specified target class.
     *
     * @param target the target class
     * @return the mixin source class, or null if no mixin is registered for the target
     */
    public Class getMixIn(Class target) {
        return mixInCache.get(target);
    }

    /**
     * Clears all mixin mappings.
     */
    public void cleanupMixIn() {
        mixInCache.clear();
    }

    /**
     * Registers a mixin mapping between a target class and a mixin source class.
     * Mixin allows modifying the serialization/deserialization behavior of a class
     * by applying annotations from another class.
     *
     * @param target the target class to which the mixin will be applied
     * @param mixinSource the source class from which annotations will be copied, or null to remove the mixin
     */
    public void mixIn(Class target, Class mixinSource) {
        if (mixinSource == null) {
            mixInCache.remove(target);
        } else {
            mixInCache.put(target, mixinSource);
        }
        cache.remove(target);
        cacheFieldBased.remove(target);
    }

    /**
     * Registers a subtype class for see-also support. This allows the provider to
     * recognize and handle subtypes of the specified superclass.
     *
     * @param subTypeClass the subtype class to register
     */
    public void registerSeeAlsoSubType(Class subTypeClass) {
        registerSeeAlsoSubType(subTypeClass, null);
    }

    /**
     * Registers a subtype class with a specific name for see-also support.
     *
     * @param subTypeClass the subtype class to register
     * @param subTypeClassName the name of the subtype class, or null to use the class's simple name
     * @throws JSONException if the superclass is null
     */
    public void registerSeeAlsoSubType(Class subTypeClass, String subTypeClassName) {
        Class superClass = subTypeClass.getSuperclass();
        if (superClass == null) {
            throw new JSONException("superclass is null");
        }

        ObjectReader objectReader = getObjectReader(superClass);
        if (objectReader instanceof ObjectReaderSeeAlso) {
            ObjectReaderSeeAlso readerSeeAlso = (ObjectReaderSeeAlso) objectReader;
            ObjectReaderSeeAlso readerSeeAlsoNew = readerSeeAlso.addSubType(subTypeClass, subTypeClassName);
            if (readerSeeAlsoNew != readerSeeAlso) {
                if (cache.containsKey(superClass)) {
                    cache.put(superClass, readerSeeAlsoNew);
                } else {
                    cacheFieldBased.put(subTypeClass, readerSeeAlsoNew);
                }
            }
        }
    }

    /**
     * Registers an ObjectReader for the specified type. If an ObjectReader is already
     * registered for the type, it will be replaced.
     *
     * @param type the type for which to register the ObjectReader
     * @param objectReader the ObjectReader to register, or null to unregister
     * @param fieldBased whether the ObjectReader is field-based
     * @return the previous ObjectReader for the type, or null if there was no previous ObjectReader
     */
    public ObjectReader register(Type type, ObjectReader objectReader, boolean fieldBased) {
        ConcurrentMap<Type, ObjectReader> cache = fieldBased ? this.cacheFieldBased : this.cache;
        if (objectReader == null) {
            return cache.remove(type);
        }

        return cache.put(type, objectReader);
    }

    /**
     * Registers an ObjectReader for the specified type using method-based reading.
     * If an ObjectReader is already registered for the type, it will be replaced.
     *
     * @param type the type for which to register the ObjectReader
     * @param objectReader the ObjectReader to register, or null to unregister
     * @return the previous ObjectReader for the type, or null if there was no previous ObjectReader
     */
    public ObjectReader register(Type type, ObjectReader objectReader) {
        return register(type, objectReader, false);
    }

    /**
     * Registers an ObjectReader for the specified type using method-based reading
     * if it is not already registered.
     *
     * @param type the type for which to register the ObjectReader
     * @param objectReader the ObjectReader to register
     * @return the previous ObjectReader for the type, or null if there was no previous ObjectReader
     */
    public ObjectReader registerIfAbsent(Type type, ObjectReader objectReader) {
        return registerIfAbsent(type, objectReader, false);
    }

    /**
     * Registers an ObjectReader for the specified type if it is not already registered.
     *
     * @param type the type for which to register the ObjectReader
     * @param objectReader the ObjectReader to register
     * @param fieldBased whether the ObjectReader is field-based
     * @return the previous ObjectReader for the type, or null if there was no previous ObjectReader
     */
    public ObjectReader registerIfAbsent(Type type, ObjectReader objectReader, boolean fieldBased) {
        ConcurrentMap<Type, ObjectReader> cache = fieldBased ? this.cacheFieldBased : this.cache;
        return cache.putIfAbsent(type, objectReader);
    }

    /**
     * Unregisters the ObjectReader for the specified type using method-based reading.
     *
     * @param type the type for which to unregister the ObjectReader
     * @return the unregistered ObjectReader, or null if there was no ObjectReader for the type
     */
    public ObjectReader unregisterObjectReader(Type type) {
        return unregisterObjectReader(type, false);
    }

    /**
     * Unregisters the ObjectReader for the specified type.
     *
     * @param type the type for which to unregister the ObjectReader
     * @param fieldBased whether the ObjectReader is field-based
     * @return the unregistered ObjectReader, or null if there was no ObjectReader for the type
     */
    public ObjectReader unregisterObjectReader(Type type, boolean fieldBased) {
        ConcurrentMap<Type, ObjectReader> cache = fieldBased ? this.cacheFieldBased : this.cache;
        return cache.remove(type);
    }

    /**
     * Unregisters the specified ObjectReader for the given type using method-based reading,
     * but only if the currently registered reader matches the specified reader.
     *
     * @param type the type for which to unregister the ObjectReader
     * @param reader the ObjectReader to unregister
     * @return true if the ObjectReader was unregistered, false otherwise
     */
    public boolean unregisterObjectReader(Type type, ObjectReader reader) {
        return unregisterObjectReader(type, reader, false);
    }

    /**
     * Unregisters the specified ObjectReader for the given type, but only if the currently
     * registered reader matches the specified reader.
     *
     * @param type the type for which to unregister the ObjectReader
     * @param reader the ObjectReader to unregister
     * @param fieldBased whether the ObjectReader is field-based
     * @return true if the ObjectReader was unregistered, false otherwise
     */
    public boolean unregisterObjectReader(Type type, ObjectReader reader, boolean fieldBased) {
        ConcurrentMap<Type, ObjectReader> cache = fieldBased ? this.cacheFieldBased : this.cache;
        return cache.remove(type, reader);
    }

    /**
     * Registers an ObjectReaderModule. If the module is already registered, this method
     * does nothing and returns false.
     *
     * @param module the module to register
     * @return true if the module was registered, false if it was already registered
     */
    public boolean register(ObjectReaderModule module) {
        for (int i = modules.size() - 1; i >= 0; i--) {
            if (modules.get(i) == module) {
                return false;
            }
        }

        module.init(this);

        modules.add(0, module);
        return true;
    }

    /**
     * Unregisters an ObjectReaderModule.
     *
     * @param module the module to unregister
     * @return true if the module was unregistered, false if it was not registered
     */
    public boolean unregister(ObjectReaderModule module) {
        return modules.remove(module);
    }

    /**
     * Cleans up cached ObjectReaders and mixin mappings associated with the specified class.
     *
     * @param objectClass the class for which to clean up cached ObjectReaders
     */
    public void cleanup(Class objectClass) {
        mixInCache.remove(objectClass);
        cache.remove(objectClass);
        cacheFieldBased.remove(objectClass);
        for (ConcurrentHashMap<Long, ObjectReader> tlc : tclHashCaches.values()) {
            for (Iterator<Map.Entry<Long, ObjectReader>> it = tlc.entrySet().iterator(); it.hasNext(); ) {
                Map.Entry<Long, ObjectReader> entry = it.next();
                ObjectReader reader = entry.getValue();
                if (reader.getObjectClass() == objectClass) {
                    it.remove();
                }
            }
        }
        BeanUtils.cleanupCache(objectClass);
    }

    /**
     * Clears all cached ObjectReaders and mixin mappings.
     *
     * @since 2.0.53
     */
    public void clear() {
        mixInCache.clear();
        cache.clear();
        cacheFieldBased.clear();
    }

    static boolean match(Type objectType, ObjectReader objectReader, ClassLoader classLoader) {
        Class<?> objectClass = TypeUtils.getClass(objectType);
        if (objectClass != null && objectClass.getClassLoader() == classLoader) {
            return true;
        }

        if (objectType instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) objectType;
            Type rawType = paramType.getRawType();
            if (match(rawType, objectReader, classLoader)) {
                return true;
            }

            for (Type argType : paramType.getActualTypeArguments()) {
                if (match(argType, objectReader, classLoader)) {
                    return true;
                }
            }
        }

        if (objectReader instanceof ObjectReaderImplMapTyped) {
            ObjectReaderImplMapTyped mapTyped = (ObjectReaderImplMapTyped) objectReader;
            Class valueClass = mapTyped.valueClass;
            if (valueClass != null && valueClass.getClassLoader() == classLoader) {
                return true;
            }
            Class keyClass = TypeUtils.getClass(mapTyped.keyType);
            return keyClass != null && keyClass.getClassLoader() == classLoader;
        } else if (objectReader instanceof ObjectReaderImplList) {
            ObjectReaderImplList list = (ObjectReaderImplList) objectReader;
            return list.itemClass != null && list.itemClass.getClassLoader() == classLoader;
        } else if (objectReader instanceof ObjectReaderImplOptional) {
            Class itemClass = ((ObjectReaderImplOptional) objectReader).itemClass;
            return itemClass != null && itemClass.getClassLoader() == classLoader;
        } else if (objectReader instanceof ObjectReaderAdapter) {
            FieldReader[] fieldReaders = ((ObjectReaderAdapter<?>) objectReader).fieldReaders;
            for (FieldReader fieldReader : fieldReaders) {
                if (fieldReader.fieldClass != null && fieldReader.fieldClass.getClassLoader() == classLoader) {
                    return true;
                }
                Type fieldType = fieldReader.fieldType;
                if (fieldType instanceof ParameterizedType) {
                    if (match(fieldType, null, classLoader)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * Cleans up cached ObjectReaders associated with the specified ClassLoader.
     * This method removes all cached readers that are related to classes loaded
     * by the given ClassLoader.
     *
     * @param classLoader the ClassLoader for which to clean up cached ObjectReaders
     */
    public void cleanup(ClassLoader classLoader) {
        mixInCache.entrySet().removeIf(
                entry -> entry.getKey().getClassLoader() == classLoader
        );

        cache.entrySet().removeIf(
                entry -> match(entry.getKey(), entry.getValue(), classLoader)
        );

        cacheFieldBased.entrySet().removeIf(
                entry -> match(entry.getKey(), entry.getValue(), classLoader)
        );

        int tclHash = System.identityHashCode(classLoader);
        tclHashCaches.remove(tclHash);

        BeanUtils.cleanupCache(classLoader);
    }

    /**
     * Gets the ObjectReaderCreator used by this provider. If a context-specific creator
     * is available, it will be returned; otherwise, the default creator for this provider
     * will be returned.
     *
     * @return the ObjectReaderCreator
     */
    public ObjectReaderCreator getCreator() {
        ObjectReaderCreator contextCreator = JSONFactory.getContextReaderCreator();
        if (contextCreator != null) {
            return contextCreator;
        }
        return this.creator;
    }

    /**
     * Constructs an ObjectReaderProvider with the default ObjectReaderCreator based on
     * system configuration. The creator selection follows this priority:
     * 1. ASM creator (default, if not Android or GraalVM)
     * 2. Reflection/Lambda creator (fallback)
     *
     * <p>The provider is initialized with the base module and all registered modules.
     */
    public ObjectReaderProvider() {
        ObjectReaderCreator creator = null;
        switch (JSONFactory.CREATOR) {
            case "reflect":
            case "lambda":
                creator = ObjectReaderCreator.INSTANCE;
                break;
            case "asm":
            default:
                try {
                    if (!JDKUtils.ANDROID && !JDKUtils.GRAAL) {
                        creator = ObjectReaderCreatorASM.INSTANCE;
                    }
                } catch (Throwable ignored) {
                    // ignored
                }
                if (creator == null) {
                    creator = ObjectReaderCreator.INSTANCE;
                }
                break;
        }
        this.creator = creator;

        modules.add(new ObjectReaderBaseModule(this));
        init();
    }

    /**
     * Constructs an ObjectReaderProvider with the specified ObjectReaderCreator.
     *
     * @param creator the ObjectReaderCreator to use for creating ObjectReader instances
     */
    public ObjectReaderProvider(ObjectReaderCreator creator) {
        this.creator = creator;
        modules.add(new ObjectReaderBaseModule(this));
        init();
    }

    void init() {
        for (ObjectReaderModule module : modules) {
            module.init(this);
        }
    }

    /**
     * Gets the type converter function that can convert values from one type to another.
     *
     * @param from the source type
     * @param to the target type
     * @return the converter function for the type pair, or null if no converter is registered
     */
    public Function getTypeConvert(Type from, Type to) {
        Map<Type, Function> map = typeConverts.get(from);
        if (map == null) {
            return null;
        }
        return map.get(to);
    }

    /**
     * Registers a type converter function that can convert values from one type to another.
     *
     * @param from the source type
     * @param to the target type
     * @param typeConvert the function to convert from source type to target type
     * @return the previous converter function for the type pair, or null if there was no previous converter
     */
    public Function registerTypeConvert(Type from, Type to, Function typeConvert) {
        Map<Type, Function> map = typeConverts.get(from);
        if (map == null) {
            typeConverts.putIfAbsent(from, new ConcurrentHashMap<>());
            map = typeConverts.get(from);
        }
        return map.put(to, typeConvert);
    }

    /**
     * Gets an ObjectReader by its hash code. This method first checks thread-local cache,
     * then global cache for performance optimization.
     *
     * @param hashCode the hash code of the ObjectReader to retrieve
     * @return the ObjectReader associated with the hash code, or null if not found
     */
    public ObjectReader getObjectReader(long hashCode) {
        ObjectReaderCachePair pair = readerCache;
        if (pair != null) {
            if (pair.hashCode == hashCode) {
                return pair.reader;
            } else {
                if (pair.missCount++ > 16) {
                    readerCache = null;
                }
            }
        }

        Long hashCodeObj = hashCode;
        ObjectReader objectReader = null;
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        if (tcl != null && tcl != FASTJSON2_CLASS_LOADER) {
            int tclHash = System.identityHashCode(tcl);
            ConcurrentHashMap<Long, ObjectReader> tclHashCache = tclHashCaches.get(tclHash);
            if (tclHashCache != null) {
                objectReader = tclHashCache.get(hashCodeObj);
            }
        }

        if (objectReader == null) {
            objectReader = hashCache.get(hashCodeObj);
        }

        if (objectReader != null && readerCache == null) {
            readerCache = new ObjectReaderCachePair(hashCode, objectReader);
        }

        return objectReader;
    }

    /**
     * Gets an ObjectReader for the specified type name, expected class, and features.
     * This method handles auto-type resolution and ObjectReader caching.
     *
     * @param typeName the name of the type
     * @param expectClass the expected class type
     * @param features the JSON reader features
     * @return the ObjectReader for the specified type, or null if the type cannot be resolved
     */
    public ObjectReader getObjectReader(String typeName, Class<?> expectClass, long features) {
        Class<?> autoTypeClass = checkAutoType(typeName, expectClass, features);
        if (autoTypeClass == null) {
            return null;
        }
        boolean fieldBased = (features & JSONReader.Feature.FieldBased.mask) != 0;
        ObjectReader objectReader = getObjectReader(autoTypeClass, fieldBased);

        if (autoTypeClass != expectClass) {
            registerIfAbsent(Fnv.hashCode64(typeName), objectReader);
        }
        return objectReader;
    }

    final void afterAutoType(String typeName, Class type) {
        if (autoTypeHandler != null) {
            autoTypeHandler.accept(type);
        }

        synchronized (autoTypeList) {
            autoTypeList.putIfAbsent(typeName, new Date());
        }
    }

    /**
     * Checks and resolves the class for auto-type support. This method handles security
     * validation and class loading for auto-type deserialization.
     *
     * @param typeName the name of the type to check
     * @param expectClass the expected class type
     * @param features the JSON reader features
     * @return the resolved Class, or null if the type cannot be resolved or is not allowed
     * @throws JSONException if the type is not supported or security checks fail
     */
    public Class<?> checkAutoType(String typeName, Class<?> expectClass, long features) {
        if (typeName == null || typeName.isEmpty()) {
            return null;
        }

        if (autoTypeBeforeHandler != null) {
            Class<?> resolvedClass = autoTypeBeforeHandler.apply(typeName, expectClass, features);
            if (resolvedClass != null) {
                afterAutoType(typeName, resolvedClass);
                return resolvedClass;
            }
        }

        if (SAFE_MODE) {
            return null;
        }

        int typeNameLength = typeName.length();
        if (typeNameLength >= 192) {
            throw new JSONException("autoType is not support. " + typeName);
        }

        if (typeName.charAt(0) == '[') {
            String componentTypeName = typeName.substring(1);
            checkAutoType(componentTypeName, null, features); // blacklist check for componentType
        }

        if (expectClass != null && expectClass.getName().equals(typeName)) {
            afterAutoType(typeName, expectClass);
            return expectClass;
        }

        boolean autoTypeSupport = (features & JSONReader.Feature.SupportAutoType.mask) != 0;
        Class<?> clazz;

        if (autoTypeSupport) {
            long hash = MAGIC_HASH_CODE;
            for (int i = 0; i < typeNameLength; ++i) {
                char ch = typeName.charAt(i);
                if (ch == '$') {
                    ch = '.';
                }
                hash ^= ch;
                hash *= MAGIC_PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    clazz = loadClass(typeName);
                    if (clazz != null) {
                        if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
                            throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                        }

                        afterAutoType(typeName, clazz);
                        return clazz;
                    }
                }
            }
        }

        if (!autoTypeSupport) {
            long hash = MAGIC_HASH_CODE;
            for (int i = 0; i < typeNameLength; ++i) {
                char ch = typeName.charAt(i);
                if (ch == '$') {
                    ch = '.';
                }
                hash ^= ch;
                hash *= MAGIC_PRIME;

                // white list
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    clazz = loadClass(typeName);

                    if (clazz != null && expectClass != null && !expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }

                    afterAutoType(typeName, clazz);
                    return clazz;
                }
            }
        }

        if (!autoTypeSupport) {
            return null;
        }

        clazz = TypeUtils.getMapping(typeName);

        if (clazz != null) {
            if (expectClass != null
                    && expectClass != Object.class
                    && clazz != java.util.HashMap.class
                    && !expectClass.isAssignableFrom(clazz)
            ) {
                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
            }

            afterAutoType(typeName, clazz);
            return clazz;
        }

        clazz = loadClass(typeName);

        if (clazz != null) {
            if (ClassLoader.class.isAssignableFrom(clazz) || JDKUtils.isSQLDataSourceOrRowSet(clazz)) {
                throw new JSONException("autoType is not support. " + typeName);
            }

            if (expectClass != null) {
                if (expectClass.isAssignableFrom(clazz)) {
                    afterAutoType(typeName, clazz);
                    return clazz;
                } else {
                    if ((features & JSONReader.Feature.IgnoreAutoTypeNotMatch.mask) != 0) {
                        return expectClass;
                    }

                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                }
            }
        }

        afterAutoType(typeName, clazz);
        return clazz;
    }

    /**
     * Gets the list of registered ObjectReader modules.
     *
     * @return the list of modules
     */
    public List<ObjectReaderModule> getModules() {
        return modules;
    }

    /**
     * Gets bean information for the specified class by delegating to registered modules.
     *
     * @param beanInfo the BeanInfo object to populate with bean information
     * @param objectClass the class for which to get bean information
     */
    public void getBeanInfo(BeanInfo beanInfo, Class objectClass) {
        for (int i = 0; i < modules.size(); i++) {
            ObjectReaderModule module = modules.get(i);
            module.getBeanInfo(beanInfo, objectClass);
        }
    }

    /**
     * Gets field information for the specified field of a class.
     *
     * @param fieldInfo the FieldInfo object to populate with field information
     * @param objectClass the class containing the field
     * @param field the field for which to get information
     */
    public void getFieldInfo(FieldInfo fieldInfo, Class objectClass, Field field) {
        for (int i = 0; i < modules.size(); i++) {
            ObjectReaderModule module = modules.get(i);
            module.getFieldInfo(fieldInfo, objectClass, field);
        }
    }

    /**
     * Gets field information for the specified constructor parameter.
     *
     * @param fieldInfo the FieldInfo object to populate with field information
     * @param objectClass the class containing the constructor
     * @param constructor the constructor containing the parameter
     * @param paramIndex the index of the parameter in the constructor
     * @param parameter the parameter for which to get information
     */
    public void getFieldInfo(
            FieldInfo fieldInfo,
            Class objectClass,
            Constructor constructor,
            int paramIndex,
            Parameter parameter
    ) {
        for (int i = 0; i < modules.size(); i++) {
            ObjectReaderAnnotationProcessor annotationProcessor = modules.get(i).getAnnotationProcessor();
            if (annotationProcessor != null) {
                annotationProcessor.getFieldInfo(fieldInfo, objectClass, constructor, paramIndex, parameter);
            }
        }
    }

    /**
     * Gets field information for the specified method parameter.
     *
     * @param fieldInfo the FieldInfo object to populate with field information
     * @param objectClass the class containing the method
     * @param method the method containing the parameter
     * @param paramIndex the index of the parameter in the method
     * @param parameter the parameter for which to get information
     */
    public void getFieldInfo(
            FieldInfo fieldInfo,
            Class objectClass,
            Method method,
            int paramIndex,
            Parameter parameter) {
        for (int i = 0; i < modules.size(); i++) {
            ObjectReaderAnnotationProcessor annotationProcessor = modules.get(i).getAnnotationProcessor();
            if (annotationProcessor != null) {
                annotationProcessor.getFieldInfo(fieldInfo, objectClass, method, paramIndex, parameter);
            }
        }
    }

    /**
     * Gets an ObjectReader for the specified type. If an ObjectReader for the type
     * is already cached, it will be returned directly. Otherwise, a new ObjectReader
     * will be created and cached.
     *
     * @param objectType the type for which to get an ObjectReader
     * @return the ObjectReader for the specified type
     */
    public ObjectReader getObjectReader(Type objectType) {
        return getObjectReader(objectType, false);
    }

    /**
     * Creates a value consumer creator for byte array values.
     *
     * @param objectClass the class for which to create the value consumer creator
     * @param fieldReaderArray the field readers to use
     * @return a function that creates ByteArrayValueConsumer instances
     */
    public Function<Consumer, ByteArrayValueConsumer> createValueConsumerCreator(
            Class objectClass,
            FieldReader[] fieldReaderArray
    ) {
        return creator.createByteArrayValueConsumerCreator(objectClass, fieldReaderArray);
    }

    /**
     * Creates a value consumer creator for char array values.
     *
     * @param objectClass the class for which to create the value consumer creator
     * @param fieldReaderArray the field readers to use
     * @return a function that creates CharArrayValueConsumer instances
     */
    public Function<Consumer, CharArrayValueConsumer> createCharArrayValueConsumerCreator(
            Class objectClass,
            FieldReader[] fieldReaderArray
    ) {
        return creator.createCharArrayValueConsumerCreator(objectClass, fieldReaderArray);
    }

    /**
     * Gets an ObjectReader for the specified type with field-based option.
     * If an ObjectReader for the type is already cached, it will be returned directly.
     * Otherwise, a new ObjectReader will be created and cached.
     *
     * @param objectType the type for which to get an ObjectReader
     * @param fieldBased whether to use field-based reading (true) or method-based reading (false)
     * @return the ObjectReader for the specified type
     */
    public ObjectReader getObjectReader(Type objectType, boolean fieldBased) {
        if (objectType == null) {
            objectType = Object.class;
        }

        ObjectReader objectReader = fieldBased
                ? cacheFieldBased.get(objectType)
                : cache.get(objectType);

        if (objectReader == null && objectType instanceof WildcardType) {
            Type[] upperBounds = ((WildcardType) objectType).getUpperBounds();
            if (upperBounds.length == 1) {
                Type upperBoundType = upperBounds[0];
                objectReader = fieldBased ? cacheFieldBased.get(upperBoundType) : cache.get(upperBoundType);
            }
        }

        return objectReader != null
                ? objectReader
                : getObjectReaderInternal(objectType, fieldBased);
    }

    private ObjectReader getObjectReaderInternal(Type objectType, boolean fieldBased) {
        ObjectReader objectReader = null;

        for (ObjectReaderModule module : modules) {
            objectReader = module.getObjectReader(this, objectType);
            if (objectReader != null) {
                ObjectReader previous = fieldBased
                        ? cacheFieldBased.putIfAbsent(objectType, objectReader)
                        : cache.putIfAbsent(objectType, objectReader);

                if (previous != null) {
                    objectReader = previous;
                }
                return objectReader;
            }
        }

        if (objectType instanceof TypeVariable) {
            Type[] bounds = ((TypeVariable<?>) objectType).getBounds();
            if (bounds.length > 0) {
                Type bound = bounds[0];
                if (bound instanceof Class) {
                    ObjectReader boundObjectReader = getObjectReader(bound, fieldBased);
                    if (boundObjectReader != null) {
                        ObjectReader previous = getPreviousObjectReader(fieldBased, objectType, boundObjectReader);
                        if (previous != null) {
                            boundObjectReader = previous;
                        }
                        return boundObjectReader;
                    }
                }
            }
        }

        if (objectType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) objectType;
            Type rawType = parameterizedType.getRawType();
            Type[] typeArguments = parameterizedType.getActualTypeArguments();
            if (rawType instanceof Class) {
                Class rawClass = (Class) rawType;

                boolean generic = false;
                for (Class clazz = rawClass; clazz != Object.class; clazz = clazz.getSuperclass()) {
                    if (clazz.getTypeParameters().length > 0) {
                        generic = true;
                        break;
                    }
                }
                if (typeArguments.length == 0 || !generic) {
                    ObjectReader rawClassReader = getObjectReader(rawClass, fieldBased);
                    if (rawClassReader != null) {
                        ObjectReader previous = getPreviousObjectReader(fieldBased, objectType, rawClassReader);
                        if (previous != null) {
                            rawClassReader = previous;
                        }
                        return rawClassReader;
                    }
                }
                if (typeArguments.length == 1 && ArrayList.class.isAssignableFrom(rawClass)) {
                    return ObjectReaderImplList.of(objectType, rawClass, 0);
                }

                if (typeArguments.length == 2 && Map.class.isAssignableFrom(rawClass)) {
                    return ObjectReaderImplMap.of(objectType, (Class) rawType, 0);
                }
            }
        }

        Class<?> objectClass = TypeUtils.getMapping(objectType);

        String className = objectClass.getName();
        if (!fieldBased) {
            if ("com.google.common.collect.ArrayListMultimap".equals(className)) {
                objectReader = ObjectReaderImplMap.of(null, objectClass, 0);
            }
        }

        if (objectReader == null) {
            ObjectReaderCreator creator = getCreator();
            objectReader = creator.createObjectReader(objectClass, objectType, fieldBased, this);
        }

        ObjectReader previous = getPreviousObjectReader(fieldBased, objectType, objectReader);
        if (previous != null) {
            objectReader = previous;
        }

        return objectReader;
    }

    private ObjectReader getPreviousObjectReader(boolean fieldBased, Type objectType, ObjectReader boundObjectReader) {
        return fieldBased
                ? cacheFieldBased.putIfAbsent(objectType, boundObjectReader)
                : cache.putIfAbsent(objectType, boundObjectReader);
    }

    /**
     * Gets the auto-type before handler that is invoked before type resolution.
     *
     * @return the auto-type before handler, or null if none is set
     */
    public AutoTypeBeforeHandler getAutoTypeBeforeHandler() {
        return autoTypeBeforeHandler;
    }

    /**
     * Sets the auto-type before handler that will be invoked before type resolution.
     *
     * @param autoTypeBeforeHandler the auto-type before handler to set
     */
    public void setAutoTypeBeforeHandler(AutoTypeBeforeHandler autoTypeBeforeHandler) {
        this.autoTypeBeforeHandler = autoTypeBeforeHandler;
    }

    public Map<String, Date> getAutoTypeList() {
        return autoTypeList;
    }

    private static final class LRUAutoTypeCache
            extends LinkedHashMap<String, Date> {
        private final int maxSize;

        public LRUAutoTypeCache(int maxSize) {
            super(16, 0.75f, false);
            this.maxSize = maxSize;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<String, Date> eldest) {
            return this.size() > this.maxSize;
        }
    }

    /**
     * Gets field information for the specified method of a class. This method also
     * handles setter methods by attempting to find corresponding fields.
     *
     * @param fieldInfo the FieldInfo object to populate with field information
     * @param objectClass the class containing the method
     * @param method the method for which to get information
     */
    public void getFieldInfo(FieldInfo fieldInfo, Class objectClass, Method method) {
        for (int i = 0; i < modules.size(); i++) {
            ObjectReaderAnnotationProcessor annotationProcessor = modules.get(i).getAnnotationProcessor();
            if (annotationProcessor == null) {
                continue;
            }
            annotationProcessor.getFieldInfo(fieldInfo, objectClass, method);
        }

        if (fieldInfo.fieldName == null && fieldInfo.alternateNames == null) {
            String methodName = method.getName();
            if (methodName.startsWith("set")) {
                String findName = methodName.substring(3);
                Field field = BeanUtils.getDeclaredField(objectClass, findName);
                if (field != null) {
                    fieldInfo.alternateNames = new String[]{findName};
                }
            }
        }
    }

    /**
     * Creates an object creator (supplier) for the specified class and reader features.
     *
     * @param objectClass the class for which to create an object creator
     * @param readerFeatures the reader features to use
     * @param <T> the type of the object
     * @return a supplier function that creates new instances of the object
     * @throws JSONException if no default constructor is found for the class
     */
    public <T> Supplier<T> createObjectCreator(Class<T> objectClass, long readerFeatures) {
        boolean fieldBased = (readerFeatures & JSONReader.Feature.FieldBased.mask) != 0;
        ObjectReader objectReader = fieldBased
                ? cacheFieldBased.get(objectClass)
                : cache.get(objectClass);
        if (objectReader != null) {
            return () -> (T) objectReader.createInstance(0);
        }

        Constructor constructor = BeanUtils.getDefaultConstructor(objectClass, false);
        if (constructor == null) {
            throw new JSONException("default constructor not found : " + objectClass.getName());
        }

        return LambdaMiscCodec.createSupplier(constructor);
    }

    /**
     * Creates a FieldReader for the specified class, field name, and reader features.
     *
     * @param objectClass the class containing the field
     * @param fieldName the name of the field
     * @param readerFeatures the reader features to use
     * @return a FieldReader for the specified field, or null if the field is not found
     */
    public FieldReader createFieldReader(Class objectClass, String fieldName, long readerFeatures) {
        boolean fieldBased = (readerFeatures & JSONReader.Feature.FieldBased.mask) != 0;

        ObjectReader objectReader = fieldBased
                ? cacheFieldBased.get(objectClass)
                : cache.get(objectClass);

        if (objectReader != null) {
            return objectReader.getFieldReader(fieldName);
        }

        AtomicReference<Field> fieldRef = new AtomicReference<>();
        long nameHashLCase = Fnv.hashCode64LCase(fieldName);
        BeanUtils.fields(objectClass, field -> {
            if (nameHashLCase == Fnv.hashCode64LCase(field.getName())) {
                fieldRef.set(field);
            }
        });

        Field field = fieldRef.get();
        if (field != null) {
            return creator.createFieldReader(fieldName, null, field.getType(), field);
        }

        AtomicReference<Method> methodRef = new AtomicReference<>();
        BeanUtils.setters(objectClass, method -> {
            String setterName = BeanUtils.setterName(method.getName(), PropertyNamingStrategy.CamelCase.name());
            if (nameHashLCase == Fnv.hashCode64LCase(setterName)) {
                methodRef.set(method);
            }
        });

        Method method = methodRef.get();
        if (method != null) {
            Class<?>[] params = method.getParameterTypes();
            Class fieldClass = params[0];
            return creator.createFieldReaderMethod(objectClass, fieldName, null, fieldClass, fieldClass, method);
        }

        return null;
    }

    /**
     * Creates an ObjectReader for a custom object with specified field names, types, and consumer.
     *
     * @param names the field names
     * @param types the field types
     * @param supplier the supplier function to create new instances of the object
     * @param c the field consumer to set field values
     * @param <T> the type of the object
     * @return the created ObjectReader
     */
    public <T> ObjectReader<T> createObjectReader(
            String[] names,
            Type[] types,
            Supplier<T> supplier,
            FieldConsumer<T> c
    ) {
        return createObjectReader(names, types, null, supplier, c);
    }

    /**
     * Creates an ObjectReader for a custom object with specified field names, types, features, and consumer.
     *
     * @param names the field names
     * @param types the field types
     * @param features the field features (can be null)
     * @param supplier the supplier function to create new instances of the object
     * @param c the field consumer to set field values
     * @param <T> the type of the object
     * @return the created ObjectReader
     */
    public <T> ObjectReader<T> createObjectReader(
            String[] names,
            Type[] types,
            long[] features,
            Supplier<T> supplier,
            FieldConsumer<T> c
    ) {
        FieldReader[] fieldReaders = new FieldReader[names.length];
        for (int i = 0; i < names.length; i++) {
            Type fieldType = types[i];
            Class fieldClass = TypeUtils.getClass(fieldType);
            long feature = features != null && i < features.length ? features[i] : 0;
            fieldReaders[i] = creator.createFieldReader(
                    names[i],
                    fieldType,
                    fieldClass,
                    feature,
                    new FieldBiConsumer(i, c)
            );
        }

        return creator.createObjectReader(
                null,
                supplier,
                fieldReaders
        );
    }

    /**
     * Checks if reference detection is disabled.
     *
     * @return true if reference detection is disabled, false otherwise
     */
    public boolean isDisableReferenceDetect() {
        return disableReferenceDetect;
    }

    /**
     * Checks if auto-type support is disabled.
     *
     * @return true if auto-type support is disabled, false otherwise
     */
    public boolean isDisableAutoType() {
        return disableAutoType;
    }

    /**
     * Checks if JSONB support is disabled.
     *
     * @return true if JSONB support is disabled, false otherwise
     */
    public boolean isDisableJSONB() {
        return disableJSONB;
    }

    /**
     * Checks if array mapping is disabled.
     *
     * @return true if array mapping is disabled, false otherwise
     */
    public boolean isDisableArrayMapping() {
        return disableArrayMapping;
    }

    /**
     * Sets whether reference detection is disabled.
     *
     * @param disableReferenceDetect true to disable reference detection, false to enable it
     */
    public void setDisableReferenceDetect(boolean disableReferenceDetect) {
        this.disableReferenceDetect = disableReferenceDetect;
    }

    /**
     * Sets whether array mapping is disabled.
     *
     * @param disableArrayMapping true to disable array mapping, false to enable it
     */
    public void setDisableArrayMapping(boolean disableArrayMapping) {
        this.disableArrayMapping = disableArrayMapping;
    }

    /**
     * Sets whether JSONB support is disabled.
     *
     * @param disableJSONB true to disable JSONB support, false to enable it
     */
    public void setDisableJSONB(boolean disableJSONB) {
        this.disableJSONB = disableJSONB;
    }

    /**
     * Sets whether auto-type support is disabled.
     *
     * @param disableAutoType true to disable auto-type support, false to enable it
     */
    public void setDisableAutoType(boolean disableAutoType) {
        this.disableAutoType = disableAutoType;
    }

    /**
     * Checks if smart match is disabled.
     *
     * @return true if smart match is disabled, false otherwise
     */
    public boolean isDisableSmartMatch() {
        return disableSmartMatch;
    }

    /**
     * Sets whether smart match is disabled.
     *
     * @param disableSmartMatch true to disable smart match, false to enable it
     */
    public void setDisableSmartMatch(boolean disableSmartMatch) {
        this.disableSmartMatch = disableSmartMatch;
    }

    /**
     * Gets the property naming strategy used by this provider.
     *
     * @return the property naming strategy, or null if none is set
     * @since 2.0.52
     */
    public PropertyNamingStrategy getNamingStrategy() {
        return namingStrategy;
    }

    /**
     * Sets the property naming strategy used by this provider.
     *
     * @param namingStrategy the property naming strategy to set
     * @since 2.0.52
     */
    public void setNamingStrategy(PropertyNamingStrategy namingStrategy) {
        this.namingStrategy = namingStrategy;
    }
}