ObjectWriterProvider.java

package com.alibaba.fastjson2.writer;

import com.alibaba.fastjson2.JSONFactory;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.PropertyNamingStrategy;
import com.alibaba.fastjson2.codec.BeanInfo;
import com.alibaba.fastjson2.codec.FieldInfo;
import com.alibaba.fastjson2.modules.ObjectCodecProvider;
import com.alibaba.fastjson2.modules.ObjectWriterAnnotationProcessor;
import com.alibaba.fastjson2.modules.ObjectWriterModule;
import com.alibaba.fastjson2.util.*;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.time.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * ObjectWriterProvider is responsible for providing and managing ObjectWriter instances
 * for serializing Java objects into JSON format. It handles object writer creation, caching,
 * type conversion, and various serialization features.
 *
 * <p>This provider supports various features including:
 * <ul>
 *   <li>Object writer caching for performance optimization</li>
 *   <li>Mixin support for modifying serialization behavior</li>
 *   <li>Module-based extensibility</li>
 *   <li>Custom type writer registration</li>
 *   <li>Property naming strategy configuration</li>
 * </ul>
 *
 * <p>Example usage:
 * <pre>
 * // Get default provider
 * ObjectWriterProvider provider = JSONFactory.getDefaultObjectWriterProvider();
 *
 * // Get object writer for a specific type
 * ObjectWriter&lt;User&gt; writer = provider.getObjectWriter(User.class);
 *
 * // Serialize object to JSON string
 * User user = new User(1, "John");
 * String jsonString = writer.toJSONString(user);
 *
 * // Register custom object writer
 * provider.register(User.class, new CustomUserWriter());
 * </pre>
 *
 * @since 2.0.0
 */
public class ObjectWriterProvider
        implements ObjectCodecProvider {
    static final int TYPE_INT32_MASK = 1 << 1;
    static final int TYPE_INT64_MASK = 1 << 2;
    static final int TYPE_DECIMAL_MASK = 1 << 3;
    static final int TYPE_DATE_MASK = 1 << 4;
    static final int TYPE_ENUM_MASK = 1 << 5;
    static final int NAME_COMPATIBLE_WITH_FILED = 1 << 6; // compatibleWithFieldName 1.x

    final ConcurrentMap<Type, ObjectWriter> cache = new ConcurrentHashMap<>();
    final ConcurrentMap<Type, ObjectWriter> cacheFieldBased = new ConcurrentHashMap<>();
    final ConcurrentMap<Class, Class> mixInCache = new ConcurrentHashMap<>();
    final ObjectWriterCreator creator;
    final List<ObjectWriterModule> modules = new ArrayList<>();
    PropertyNamingStrategy namingStrategy;

    boolean disableReferenceDetect = JSONFactory.isDisableReferenceDetect();
    boolean disableArrayMapping = JSONFactory.isDisableArrayMapping();
    boolean disableJSONB = JSONFactory.isDisableJSONB();
    boolean disableAutoType = JSONFactory.isDisableAutoType();
    boolean skipTransient = JSONFactory.isDefaultSkipTransient();

    volatile long userDefineMask;
    boolean alphabetic = JSONFactory.isDefaultWriterAlphabetic();

    /**
     * Constructs an ObjectWriterProvider with default settings.
     */
    public ObjectWriterProvider() {
        this((PropertyNamingStrategy) null);
    }

    /**
     * Constructs an ObjectWriterProvider with the specified naming strategy.
     *
     * @param namingStrategy the property naming strategy to use
     */
    public ObjectWriterProvider(PropertyNamingStrategy namingStrategy) {
        init();

        ObjectWriterCreator creator = null;
        switch (JSONFactory.CREATOR) {
            case "reflect":
            case "lambda":
                creator = ObjectWriterCreator.INSTANCE;
                break;
            case "asm":
            default:
                try {
                    if (!JDKUtils.ANDROID && !JDKUtils.GRAAL) {
                        creator = ObjectWriterCreatorASM.INSTANCE;
                    }
                } catch (Throwable ignored) {
                    // ignored
                }
                if (creator == null) {
                    creator = ObjectWriterCreator.INSTANCE;
                }
                break;
        }
        this.creator = creator;
        this.namingStrategy = namingStrategy;
    }

    /**
     * Constructs an ObjectWriterProvider with the specified ObjectWriterCreator.
     *
     * @param creator the ObjectWriterCreator to use for creating ObjectWriter instances
     */
    public ObjectWriterProvider(ObjectWriterCreator creator) {
        init();
        this.creator = creator;
    }

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

    /**
     * Sets whether to use compatible field name behavior.
     *
     * @param stat true to enable compatible field name behavior, false to disable
     * @deprecated only use compatible with fastjson 1.x
     */
    public void setCompatibleWithFieldName(boolean stat) {
        if (stat) {
            userDefineMask |= NAME_COMPATIBLE_WITH_FILED;
        } else {
            userDefineMask &= ~NAME_COMPATIBLE_WITH_FILED;
        }
    }

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

    /**
     * Registers a mixin mapping between a target class and a mixin source class.
     * Mixin allows modifying the serialization 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);
    }

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

    /**
     * Gets the ObjectWriterCreator 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 ObjectWriterCreator
     */
    public ObjectWriterCreator getCreator() {
        ObjectWriterCreator contextCreator = JSONFactory.getContextWriterCreator();
        if (contextCreator != null) {
            return contextCreator;
        }
        return creator;
    }

    /**
     * Registers an ObjectWriter for the specified type using the default field-based setting.
     *
     * @param type the type for which to register the ObjectWriter
     * @param objectWriter the ObjectWriter to register, or null to unregister
     * @return the previous ObjectWriter for the type, or null if there was no previous ObjectWriter
     */
    public ObjectWriter register(Type type, ObjectWriter objectWriter) {
        boolean fieldBased = (JSONFactory.getDefaultWriterFeatures() & JSONWriter.Feature.FieldBased.mask) != 0;
        return register(type, objectWriter, fieldBased);
    }

    /**
     * Registers an ObjectWriter for the specified type.
     *
     * @param type the type for which to register the ObjectWriter
     * @param objectWriter the ObjectWriter to register, or null to unregister
     * @param fieldBased whether the ObjectWriter is field-based
     * @return the previous ObjectWriter for the type, or null if there was no previous ObjectWriter
     */
    public ObjectWriter register(Type type, ObjectWriter objectWriter, boolean fieldBased) {
        if (type == Integer.class) {
            if (objectWriter == null || objectWriter == ObjectWriterImplInt32.INSTANCE) {
                userDefineMask &= ~TYPE_INT32_MASK;
            } else {
                userDefineMask |= TYPE_INT32_MASK;
            }
        } else if (type == Long.class || type == long.class) {
            if (objectWriter == null || objectWriter == ObjectWriterImplInt64.INSTANCE) {
                userDefineMask &= ~TYPE_INT64_MASK;
            } else {
                userDefineMask |= TYPE_INT64_MASK;
            }
        } else if (type == BigDecimal.class) {
            if (objectWriter == null || objectWriter == ObjectWriterImplBigDecimal.INSTANCE) {
                userDefineMask &= ~TYPE_DECIMAL_MASK;
            } else {
                userDefineMask |= TYPE_DECIMAL_MASK;
            }
        } else if (type == Date.class) {
            if (objectWriter == null || objectWriter == ObjectWriterImplDate.INSTANCE) {
                userDefineMask &= ~TYPE_DATE_MASK;
            } else {
                userDefineMask |= TYPE_DATE_MASK;
            }
        } else if (type == Enum.class) {
            if (objectWriter == null) {
                userDefineMask &= ~TYPE_ENUM_MASK;
            } else {
                userDefineMask |= TYPE_ENUM_MASK;
            }
        }

        ConcurrentMap<Type, ObjectWriter> cache = fieldBased ? this.cacheFieldBased : this.cache;

        if (objectWriter == null) {
            return cache.remove(type);
        }

        return cache.put(type, objectWriter);
    }

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

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

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

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

    /**
     * Unregisters the specified ObjectWriter for the given type, but only if the currently
     * registered writer matches the specified writer.
     *
     * @param type the type for which to unregister the ObjectWriter
     * @param objectWriter the ObjectWriter to unregister
     * @return true if the ObjectWriter was unregistered, false otherwise
     */
    public boolean unregister(Type type, ObjectWriter objectWriter) {
        return unregister(type, objectWriter, false);
    }

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

    /**
     * Registers an ObjectWriterModule. 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(ObjectWriterModule 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 ObjectWriterModule.
     *
     * @param module the module to unregister
     * @return true if the module was unregistered, false if it was not registered
     */
    public boolean unregister(ObjectWriterModule module) {
        return modules.remove(module);
    }

    /**
     * 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);
    }

    /**
     * Initializes this provider with the base module.
     */
    public void init() {
        modules.add(new ObjectWriterBaseModule(this));
    }

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

    /**
     * Gets field information for the specified field of a class.
     *
     * @param beanInfo the BeanInfo object to populate with bean information
     * @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(BeanInfo beanInfo, FieldInfo fieldInfo, Class objectClass, Field field) {
        for (int i = 0; i < modules.size(); i++) {
            ObjectWriterModule module = modules.get(i);
            ObjectWriterAnnotationProcessor annotationProcessor = module.getAnnotationProcessor();
            if (annotationProcessor == null) {
                continue;
            }
            annotationProcessor.getFieldInfo(beanInfo, fieldInfo, objectClass, field);
        }
    }

    /**
     * Gets field information for the specified method of a class.
     *
     * @param beanInfo the BeanInfo object to populate with bean information
     * @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(BeanInfo beanInfo, FieldInfo fieldInfo, Class objectClass, Method method) {
        for (int i = 0; i < modules.size(); i++) {
            ObjectWriterModule module = modules.get(i);
            ObjectWriterAnnotationProcessor annotationProcessor = module.getAnnotationProcessor();
            if (annotationProcessor == null) {
                continue;
            }
            annotationProcessor.getFieldInfo(beanInfo, fieldInfo, objectClass, method);
        }
    }

    /**
     * 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) {
        if (namingStrategy != null && namingStrategy != PropertyNamingStrategy.NeverUseThisValueExceptDefaultValue) {
            beanInfo.namingStrategy = namingStrategy.name();
        }

        for (int i = 0; i < modules.size(); i++) {
            ObjectWriterModule module = modules.get(i);
            ObjectWriterAnnotationProcessor annotationProcessor = module.getAnnotationProcessor();
            if (annotationProcessor == null) {
                continue;
            }
            annotationProcessor.getBeanInfo(beanInfo, objectClass);
        }
    }

    /**
     * Gets an ObjectWriter for the specified type with formatting.
     *
     * @param objectType the type for which to get an ObjectWriter
     * @param format the format string to use
     * @param locale the locale to use
     * @return the ObjectWriter for the specified type
     */
    public ObjectWriter getObjectWriter(Type objectType, String format, Locale locale) {
        if (objectType == Double.class) {
            return new ObjectWriterImplDouble(new DecimalFormat(format));
        }

        if (objectType == Float.class) {
            return new ObjectWriterImplFloat(new DecimalFormat(format));
        }

        if (objectType == BigDecimal.class) {
            return new ObjectWriterImplBigDecimal(new DecimalFormat(format), null);
        }

        if (objectType == LocalDate.class) {
            return ObjectWriterImplLocalDate.of(format, null);
        }

        if (objectType == LocalDateTime.class) {
            return new ObjectWriterImplLocalDateTime(format, null);
        }

        if (objectType == LocalTime.class) {
            return new ObjectWriterImplLocalTime(format, null);
        }

        if (objectType == Date.class) {
            return new ObjectWriterImplDate(format, null);
        }

        if (objectType == OffsetDateTime.class) {
            return ObjectWriterImplOffsetDateTime.of(format, null);
        }

        if (objectType == ZonedDateTime.class) {
            return new ObjectWriterImplZonedDateTime(format, null);
        }

        return getObjectWriter(objectType);
    }

    /**
     * Gets an ObjectWriter for the specified class.
     *
     * @param objectClass the class for which to get an ObjectWriter
     * @return the ObjectWriter for the specified class
     */
    public ObjectWriter getObjectWriter(Class objectClass) {
        return getObjectWriter(objectClass, objectClass, false);
    }

    /**
     * Gets an ObjectWriter for the specified type and class.
     *
     * @param objectType the type for which to get an ObjectWriter
     * @param objectClass the class for which to get an ObjectWriter
     * @return the ObjectWriter for the specified type and class
     */
    public ObjectWriter getObjectWriter(Type objectType, Class objectClass) {
        return getObjectWriter(objectType, objectClass, false);
    }

    /**
     * Gets an ObjectWriter for the specified type.
     *
     * @param objectType the type for which to get an ObjectWriter
     * @return the ObjectWriter for the specified type
     */
    public ObjectWriter getObjectWriter(Type objectType) {
        Class objectClass = TypeUtils.getClass(objectType);
        return getObjectWriter(objectType, objectClass, false);
    }

    /**
     * Gets an ObjectWriter from the cache for the specified type and class.
     *
     * @param objectType the type for which to get an ObjectWriter
     * @param objectClass the class for which to get an ObjectWriter
     * @param fieldBased whether to use field-based writing
     * @return the ObjectWriter from the cache, or null if not found
     */
    public ObjectWriter getObjectWriterFromCache(Type objectType, Class objectClass, boolean fieldBased) {
        return fieldBased
                ? cacheFieldBased.get(objectType)
                : cache.get(objectType);
    }

    /**
     * Gets an ObjectWriter for the specified type, class, and format with field-based option.
     *
     * @param objectType the type for which to get an ObjectWriter
     * @param objectClass the class for which to get an ObjectWriter
     * @param format the format string to use
     * @param fieldBased whether to use field-based writing
     * @return the ObjectWriter for the specified type, class, and format
     */
    public ObjectWriter getObjectWriter(Type objectType, Class objectClass, String format, boolean fieldBased) {
        ObjectWriter objectWriter = getObjectWriter(objectType, objectClass, fieldBased);
        if (format != null) {
            if (objectType == LocalDateTime.class && objectWriter == ObjectWriterImplLocalDateTime.INSTANCE) {
                return ObjectWriterImplLocalDateTime.of(format, null);
            }
        }
        return objectWriter;
    }

    /**
     * Gets an ObjectWriter for the specified type, class, and field-based option.
     * If an ObjectWriter for the type is already cached, it will be returned directly.
     * Otherwise, a new ObjectWriter will be created and cached.
     *
     * @param objectType the type for which to get an ObjectWriter
     * @param objectClass the class for which to get an ObjectWriter
     * @param fieldBased whether to use field-based writing
     * @return the ObjectWriter for the specified type and class
     */
    public ObjectWriter getObjectWriter(Type objectType, Class objectClass, boolean fieldBased) {
        ObjectWriter objectWriter = fieldBased
                ? cacheFieldBased.get(objectType)
                : cache.get(objectType);
        return objectWriter != null
                ? objectWriter
                : getObjectWriterInternal(objectType, objectClass, fieldBased);
    }

    private ObjectWriter getObjectWriterInternal(Type objectType, Class objectClass, boolean fieldBased) {
        Class superclass = objectClass.getSuperclass();
        if (!objectClass.isEnum()
                && superclass != null
                && superclass.isEnum()
        ) {
            return getObjectWriter(superclass, superclass, fieldBased);
        }

        final String className = objectClass.getName();
        if (fieldBased) {
            if (superclass != null
                    && superclass != Object.class
                    && "com.google.protobuf.GeneratedMessageV3".equals(superclass.getName())) {
                fieldBased = false;
            } else {
                switch (className) {
                    case "springfox.documentation.spring.web.json.Json":
                    case "cn.hutool.json.JSONArray":
                    case "cn.hutool.json.JSONObject":
                    case "cn.hutool.core.map.CaseInsensitiveMap":
                    case "cn.hutool.core.map.CaseInsensitiveLinkedMap":
                        fieldBased = false;
                        break;
                    default:
                        break;
                }
            }
        } else {
            switch (className) {
                case "org.springframework.core.ResolvableType":
                    fieldBased = true;
                    break;
                default:
                    break;
            }
        }

        ObjectWriter objectWriter = fieldBased
                ? cacheFieldBased.get(objectType)
                : cache.get(objectType);

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

        if (TypeUtils.isProxy(objectClass)) {
            Class<?> proxyTarget = superclass;
            if (proxyTarget == Object.class) {
                Class[] interfaces = objectClass.getInterfaces();
                for (Class<?> i : interfaces) {
                    if (!TypeUtils.isProxy(i)) {
                        proxyTarget = i;
                        break;
                    }
                }
            }
            if (objectClass == objectType) {
                objectType = proxyTarget;
            }
            objectClass = proxyTarget;
            if (fieldBased) {
                fieldBased = false;
                objectWriter = cacheFieldBased.get(objectType);
            } else {
                objectWriter = cache.get(objectType);
            }
            if (objectWriter != null) {
                return objectWriter;
            }
        }

        boolean useModules = true;
        if (fieldBased) {
            if (Iterable.class.isAssignableFrom(objectClass)
                    && !Collection.class.isAssignableFrom(objectClass)) {
                useModules = false;
            }
        }

        if (useModules) {
            for (int i = 0; i < modules.size(); i++) {
                ObjectWriterModule module = modules.get(i);
                objectWriter = module.getObjectWriter(objectType, objectClass);
                if (objectWriter != null) {
                    ObjectWriter previous = fieldBased
                            ? cacheFieldBased.putIfAbsent(objectType, objectWriter)
                            : cache.putIfAbsent(objectType, objectWriter);

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

        switch (className) {
            case "com.google.common.collect.HashMultimap":
            case "com.google.common.collect.LinkedListMultimap":
            case "com.google.common.collect.LinkedHashMultimap":
            case "com.google.common.collect.ArrayListMultimap":
            case "com.google.common.collect.TreeMultimap":
                objectWriter = GuavaSupport.createAsMapWriter(objectClass);
                break;
            case "com.google.common.collect.AbstractMapBasedMultimap$RandomAccessWrappedList":
                objectWriter = ObjectWriterImplList.INSTANCE;
                break;
            case "com.alibaba.fastjson.JSONObject":
                objectWriter = ObjectWriterImplMap.of(objectClass);
                break;
            case "android.net.Uri$OpaqueUri":
            case "android.net.Uri$HierarchicalUri":
            case "android.net.Uri$StringUri":
                objectWriter = ObjectWriterImplToString.INSTANCE;
                break;
            case "com.clickhouse.data.value.UnsignedLong":
                objectWriter = new ObjectWriterImplToString(true);
            default:
                break;
        }

        if (objectWriter == null
                && (!fieldBased)
                && Map.class.isAssignableFrom(objectClass)
                && BeanUtils.isExtendedMap(objectClass)) {
            return ObjectWriterImplMap.of(objectClass);
        }

        if (objectWriter == null) {
            ObjectWriterCreator creator = getCreator();
            objectWriter = creator.createObjectWriter(
                    objectClass,
                    fieldBased ? JSONWriter.Feature.FieldBased.mask : 0,
                    this
            );
            ObjectWriter previous = fieldBased
                    ? cacheFieldBased.putIfAbsent(objectType, objectWriter)
                    : cache.putIfAbsent(objectType, objectWriter);

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

    static final int ENUM = 0x00004000;
    static final int[] PRIMITIVE_HASH_CODES;
    static final int[] NOT_REFERENCES_TYPE_HASH_CODES;

    static {
        Class<?>[] classes = new Class[]{
                boolean.class,
                Boolean.class,
                Character.class,
                char.class,
                Byte.class,
                byte.class,
                Short.class,
                short.class,
                Integer.class,
                int.class,
                Long.class,
                long.class,
                Float.class,
                float.class,
                Double.class,
                double.class,
                BigInteger.class,
                BigDecimal.class,
                String.class,
                java.util.Currency.class,
                java.util.Date.class,
                java.util.Calendar.class,
                java.util.UUID.class,
                java.util.Locale.class,
                java.time.LocalTime.class,
                java.time.LocalDate.class,
                java.time.LocalDateTime.class,
                java.time.Instant.class,
                java.time.ZoneId.class,
                java.time.ZonedDateTime.class,
                java.time.OffsetDateTime.class,
                java.time.OffsetTime.class,
                AtomicInteger.class,
                AtomicLong.class,
                String.class,
                StackTraceElement.class,
                Collections.emptyList().getClass(),
                Collections.emptyMap().getClass(),
                Collections.emptySet().getClass()
        };

        int[] codes = new int[classes.length];
        for (int i = 0; i < classes.length; i++) {
            codes[i] = System.identityHashCode(classes[i]);
        }
        Arrays.sort(codes);
        PRIMITIVE_HASH_CODES = codes;

        int[] codes2 = Arrays.copyOf(codes, codes.length + 3);
        codes2[codes2.length - 1] = System.identityHashCode(Class.class);
        codes2[codes2.length - 2] = System.identityHashCode(int[].class);
        codes2[codes2.length - 3] = System.identityHashCode(long[].class);
        Arrays.sort(codes2);
        NOT_REFERENCES_TYPE_HASH_CODES = codes2;
    }

    /**
     * Checks if the specified class is a primitive type or an enum.
     *
     * @param clazz the class to check
     * @return true if the class is a primitive type or an enum, false otherwise
     */
    public static boolean isPrimitiveOrEnum(final Class<?> clazz) {
        return Arrays.binarySearch(PRIMITIVE_HASH_CODES, System.identityHashCode(clazz)) >= 0
                || ((clazz.getModifiers() & ENUM) != 0 && clazz.getSuperclass() == Enum.class);
    }

    /**
     * Checks if the specified class is a type that should not have reference detection.
     *
     * @param clazz the class to check
     * @return true if the class should not have reference detection, false otherwise
     */
    public static boolean isNotReferenceDetect(final Class<?> clazz) {
        return Arrays.binarySearch(NOT_REFERENCES_TYPE_HASH_CODES, System.identityHashCode(clazz)) >= 0
                || ((clazz.getModifiers() & ENUM) != 0 && clazz.getSuperclass() == Enum.class);
    }

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

    /**
     * Cleans up cached ObjectWriters and mixin mappings associated with the specified class.
     *
     * @param objectClass the class for which to clean up cached ObjectWriters
     */
    public void cleanup(Class objectClass) {
        mixInCache.remove(objectClass);
        cache.remove(objectClass);
        cacheFieldBased.remove(objectClass);

        BeanUtils.cleanupCache(objectClass);
    }

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

        if (checkedMap.containsKey(objectWriter)) {
            return false;
        }

        if (objectWriter instanceof ObjectWriterImplMap) {
            ObjectWriterImplMap mapTyped = (ObjectWriterImplMap) objectWriter;
            Class valueClass = TypeUtils.getClass(mapTyped.valueType);
            if (valueClass != null && valueClass.getClassLoader() == classLoader) {
                return true;
            }
            Class keyClass = TypeUtils.getClass(mapTyped.keyType);
            return keyClass != null && keyClass.getClassLoader() == classLoader;
        } else if (objectWriter instanceof ObjectWriterImplCollection) {
            Class itemClass = TypeUtils.getClass(((ObjectWriterImplCollection) objectWriter).itemType);
            return itemClass != null && itemClass.getClassLoader() == classLoader;
        } else if (objectWriter instanceof ObjectWriterImplOptional) {
            Class itemClass = TypeUtils.getClass(((ObjectWriterImplOptional) objectWriter).valueType);
            return itemClass != null && itemClass.getClassLoader() == classLoader;
        } else if (objectWriter instanceof ObjectWriterAdapter) {
            checkedMap.put(objectWriter, null);
            List<FieldWriter> fieldWriters = ((ObjectWriterAdapter<?>) objectWriter).fieldWriters;
            for (int i = 0; i < fieldWriters.size(); i++) {
                FieldWriter fieldWriter = fieldWriters.get(i);
                if (fieldWriter instanceof FieldWriterObject) {
                    ObjectWriter initObjectWriter = ((FieldWriterObject<?>) fieldWriter).initObjectWriter;
                    if (match(null, initObjectWriter, classLoader, checkedMap)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

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

        IdentityHashMap<ObjectWriter, Object> checkedMap = new IdentityHashMap();

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

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

        BeanUtils.cleanupCache(classLoader);
    }

    /**
     * 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 alphabetic ordering is enabled.
     *
     * @return true if alphabetic ordering is enabled, false otherwise
     */
    public boolean isAlphabetic() {
        return alphabetic;
    }

    /**
     * Checks if transient fields should be skipped.
     *
     * @return true if transient fields should be skipped, false otherwise
     */
    public boolean isSkipTransient() {
        return skipTransient;
    }

    /**
     * Sets whether transient fields should be skipped.
     *
     * @param skipTransient true to skip transient fields, false to include them
     */
    public void setSkipTransient(boolean skipTransient) {
        this.skipTransient = skipTransient;
    }

    /**
     * Creates a new BeanInfo instance.
     *
     * @return a new BeanInfo instance
     */
    protected BeanInfo createBeanInfo() {
        return new BeanInfo(this);
    }

    /**
     * Configure the Enum classes as a JavaBean
     * @since 2.0.55
     * @param enumClasses enum classes
     */
    @SuppressWarnings("rawtypes")
    @SafeVarargs
    public final void configEnumAsJavaBean(Class<? extends Enum>... enumClasses) {
        for (Class<? extends Enum> enumClass : enumClasses) {
            register(enumClass, getCreator().createObjectWriter(enumClass));
        }
    }
}