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<User> 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));
}
}
}