com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector Maven / Gradle / Ivy
package com.fasterxml.jackson.databind.introspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.*;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.ext.Java7Support;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.AttributePropertyWriter;
import com.fasterxml.jackson.databind.ser.std.RawSerializer;
import com.fasterxml.jackson.databind.type.MapLikeType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.*;
/**
* {@link AnnotationIntrospector} implementation that handles standard
* Jackson annotations.
*/
public class JacksonAnnotationIntrospector
extends AnnotationIntrospector
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
@SuppressWarnings("unchecked")
private final static Class extends Annotation>[] ANNOTATIONS_TO_INFER_SER = (Class extends Annotation>[])
new Class>[] {
JsonSerialize.class,
JsonView.class,
JsonFormat.class,
JsonTypeInfo.class,
JsonRawValue.class,
JsonUnwrapped.class,
JsonBackReference.class,
JsonManagedReference.class
};
@SuppressWarnings("unchecked")
private final static Class extends Annotation>[] ANNOTATIONS_TO_INFER_DESER = (Class extends Annotation>[])
new Class>[] {
JsonDeserialize.class,
JsonView.class,
JsonFormat.class,
JsonTypeInfo.class,
JsonUnwrapped.class,
JsonBackReference.class,
JsonManagedReference.class,
JsonMerge.class // since 2.9
};
// NOTE: loading of Java7 dependencies is encapsulated by handlers in Java7Support,
// here we do not really need any handling; but for extra-safety use try-catch
private static final Java7Support _java7Helper;
static {
Java7Support x = null;
try {
x = Java7Support.instance();
} catch (Throwable t) { }
_java7Helper = x;
}
/**
* Since introspection of annotation types is a performance issue in some
* use cases (rare, but do exist), let's try a simple cache to reduce
* need for actual meta-annotation introspection.
*
* Non-final only because it needs to be re-created after deserialization.
*
* @since 2.7
*/
protected transient LRUMap,Boolean> _annotationsInside = new LRUMap,Boolean>(48, 48);
/*
/**********************************************************
/* Local configuration settings
/**********************************************************
*/
/**
* See {@link #setConstructorPropertiesImpliesCreator(boolean)} for
* explanation.
*
* Defaults to true.
*
* @since 2.7.4
*/
protected boolean _cfgConstructorPropertiesImpliesCreator = true;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public JacksonAnnotationIntrospector() { }
@Override
public Version version() {
return com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION;
}
protected Object readResolve() {
if (_annotationsInside == null) {
_annotationsInside = new LRUMap,Boolean>(48, 48);
}
return this;
}
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
/**
* Method for changing behavior of {@link java.beans.ConstructorProperties}:
* if set to `true`, existence DOES indicate that the given constructor should
* be considered a creator; `false` that it should NOT be considered a creator
* without explicit use of JsonCreator
annotation.
*
* Default setting is `true`
*
* @since 2.7.4
*/
public JacksonAnnotationIntrospector setConstructorPropertiesImpliesCreator(boolean b)
{
_cfgConstructorPropertiesImpliesCreator = b;
return this;
}
/*
/**********************************************************
/* General annotation properties
/**********************************************************
*/
/**
* Annotations with meta-annotation {@link JacksonAnnotationsInside}
* are considered bundles.
*/
@Override
public boolean isAnnotationBundle(Annotation ann) {
// 22-Sep-2015, tatu: Caching here has modest effect on JavaSE, and only
// mostly in degenerate cases where introspection used more often than
// it should (like recreating ObjectMapper once per read/write).
// But it may be more beneficial on platforms like Android (should verify)
Class> type = ann.annotationType();
Boolean b = _annotationsInside.get(type);
if (b == null) {
b = type.getAnnotation(JacksonAnnotationsInside.class) != null;
_annotationsInside.putIfAbsent(type, b);
}
return b.booleanValue();
}
/*
/**********************************************************
/* General annotations
/**********************************************************
*/
/**
* Since 2.6, we have supported use of {@link JsonProperty} for specifying
* explicit serialized name
*/
@Override
@Deprecated // since 2.8
public String findEnumValue(Enum> value)
{
// 11-Jun-2015, tatu: As per [databind#677], need to allow explicit naming.
// Unfortunately cannot quite use standard AnnotatedClass here (due to various
// reasons, including odd representation JVM uses); has to do for now
try {
// We know that values are actually static fields with matching name so:
Field f = value.getDeclaringClass().getField(value.name());
if (f != null) {
JsonProperty prop = f.getAnnotation(JsonProperty.class);
if (prop != null) {
String n = prop.value();
if (n != null && !n.isEmpty()) {
return n;
}
}
}
} catch (SecurityException e) {
// 17-Sep-2015, tatu: Anything we could/should do here?
} catch (NoSuchFieldException e) {
// 17-Sep-2015, tatu: should not really happen. But... can we do anything?
}
return value.name();
}
@Override // since 2.7
public String[] findEnumValues(Class> enumType, Enum>[] enumValues, String[] names) {
HashMap expl = null;
for (Field f : enumType.getDeclaredFields()) {
if (!f.isEnumConstant()) {
continue;
}
JsonProperty prop = f.getAnnotation(JsonProperty.class);
if (prop == null) {
continue;
}
String n = prop.value();
if (n.isEmpty()) {
continue;
}
if (expl == null) {
expl = new HashMap();
}
expl.put(f.getName(), n);
}
// and then stitch them together if and as necessary
if (expl != null) {
for (int i = 0, end = enumValues.length; i < end; ++i) {
String defName = enumValues[i].name();
String explValue = expl.get(defName);
if (explValue != null) {
names[i] = explValue;
}
}
}
return names;
}
@Override // since 2.11
public void findEnumAliases(Class> enumType, Enum>[] enumValues, String[][] aliasList)
{
// Main complication: discrepancy between Field that represent enum value,
// Enum abstraction; joint by name but not reference
for (Field f : enumType.getDeclaredFields()) {
if (f.isEnumConstant()) {
JsonAlias aliasAnnotation = f.getAnnotation(JsonAlias.class);
if (aliasAnnotation != null) {
String[] aliases = aliasAnnotation.value();
if (aliases.length != 0) {
final String name = f.getName();
// Find matching enum (could create Ma
for (int i = 0, end = enumValues.length; i < end; ++i) {
if (name.equals(enumValues[i].name())) {
aliasList[i] = aliases;
}
}
}
}
}
}
}
/**
* Finds the Enum value that should be considered the default value, if possible.
*
* This implementation relies on {@link JsonEnumDefaultValue} annotation to determine the default value if present.
*
* @param enumCls The Enum class to scan for the default value.
* @return null if none found or it's not possible to determine one.
* @since 2.8
*/
@Override
public Enum> findDefaultEnumValue(Class> enumCls) {
return ClassUtil.findFirstAnnotatedEnumValue(enumCls, JsonEnumDefaultValue.class);
}
/*
/**********************************************************
/* General class annotations
/**********************************************************
*/
@Override
public PropertyName findRootName(AnnotatedClass ac)
{
JsonRootName ann = _findAnnotation(ac, JsonRootName.class);
if (ann == null) {
return null;
}
String ns = ann.namespace();
if (ns != null && ns.isEmpty()) {
ns = null;
}
return PropertyName.construct(ann.value(), ns);
}
@Override
public Boolean isIgnorableType(AnnotatedClass ac) {
JsonIgnoreType ignore = _findAnnotation(ac, JsonIgnoreType.class);
return (ignore == null) ? null : ignore.value();
}
@Override // since 2.12
public JsonIgnoreProperties.Value findPropertyIgnoralByName(MapperConfig> config, Annotated a)
{
JsonIgnoreProperties v = _findAnnotation(a, JsonIgnoreProperties.class);
if (v == null) {
return JsonIgnoreProperties.Value.empty();
}
return JsonIgnoreProperties.Value.from(v);
}
// Keep around until 3.x
@Deprecated // since 2.12
@Override
public JsonIgnoreProperties.Value findPropertyIgnorals(Annotated ac) {
return findPropertyIgnoralByName(null, ac);
}
@Override
public JsonIncludeProperties.Value findPropertyInclusionByName(MapperConfig> config, Annotated a)
{
JsonIncludeProperties v = _findAnnotation(a, JsonIncludeProperties.class);
if (v == null) {
return JsonIncludeProperties.Value.all();
}
return JsonIncludeProperties.Value.from(v);
}
@Override
public Object findFilterId(Annotated a) {
JsonFilter ann = _findAnnotation(a, JsonFilter.class);
if (ann != null) {
String id = ann.value();
// Empty String is same as not having annotation, to allow overrides
if (!id.isEmpty()) {
return id;
}
}
return null;
}
@Override
public Object findNamingStrategy(AnnotatedClass ac)
{
JsonNaming ann = _findAnnotation(ac, JsonNaming.class);
return (ann == null) ? null : ann.value();
}
@Override
public String findClassDescription(AnnotatedClass ac) {
JsonClassDescription ann = _findAnnotation(ac, JsonClassDescription.class);
return (ann == null) ? null : ann.value();
}
/*
/**********************************************************
/* Property auto-detection
/**********************************************************
*/
@Override
public VisibilityChecker> findAutoDetectVisibility(AnnotatedClass ac,
VisibilityChecker> checker)
{
JsonAutoDetect ann = _findAnnotation(ac, JsonAutoDetect.class);
return (ann == null) ? checker : checker.with(ann);
}
/*
/**********************************************************
/* General member (field, method/constructor) annotations
/**********************************************************
*/
@Override
public String findImplicitPropertyName(AnnotatedMember m) {
PropertyName n = _findConstructorName(m);
return (n == null) ? null : n.getSimpleName();
}
@Override
public List findPropertyAliases(Annotated m) {
JsonAlias ann = _findAnnotation(m, JsonAlias.class);
if (ann == null) {
return null;
}
String[] strs = ann.value();
final int len = strs.length;
if (len == 0) {
return Collections.emptyList();
}
List result = new ArrayList<>(len);
for (int i = 0; i < len; ++i) {
result.add(PropertyName.construct(strs[i]));
}
return result;
}
@Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
return _isIgnorable(m);
}
@Override
public Boolean hasRequiredMarker(AnnotatedMember m)
{
JsonProperty ann = _findAnnotation(m, JsonProperty.class);
if (ann != null) {
return ann.required();
}
return null;
}
@Override
public JsonProperty.Access findPropertyAccess(Annotated m) {
JsonProperty ann = _findAnnotation(m, JsonProperty.class);
if (ann != null) {
return ann.access();
}
return null;
}
@Override
public String findPropertyDescription(Annotated ann) {
JsonPropertyDescription desc = _findAnnotation(ann, JsonPropertyDescription.class);
return (desc == null) ? null : desc.value();
}
@Override
public Integer findPropertyIndex(Annotated ann) {
JsonProperty prop = _findAnnotation(ann, JsonProperty.class);
if (prop != null) {
int ix = prop.index();
if (ix != JsonProperty.INDEX_UNKNOWN) {
return Integer.valueOf(ix);
}
}
return null;
}
@Override
public String findPropertyDefaultValue(Annotated ann) {
JsonProperty prop = _findAnnotation(ann, JsonProperty.class);
if (prop == null) {
return null;
}
String str = prop.defaultValue();
// Since annotations do not allow nulls, need to assume empty means "none"
return str.isEmpty() ? null : str;
}
@Override
public JsonFormat.Value findFormat(Annotated ann) {
JsonFormat f = _findAnnotation(ann, JsonFormat.class);
// NOTE: could also just call `JsonFormat.Value.from()` with `null`
// too, but that returns "empty" instance
return (f == null) ? null : JsonFormat.Value.from(f);
}
@Override
public ReferenceProperty findReferenceType(AnnotatedMember member)
{
JsonManagedReference ref1 = _findAnnotation(member, JsonManagedReference.class);
if (ref1 != null) {
return AnnotationIntrospector.ReferenceProperty.managed(ref1.value());
}
JsonBackReference ref2 = _findAnnotation(member, JsonBackReference.class);
if (ref2 != null) {
return AnnotationIntrospector.ReferenceProperty.back(ref2.value());
}
return null;
}
@Override
public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member)
{
JsonUnwrapped ann = _findAnnotation(member, JsonUnwrapped.class);
// if not enabled, just means annotation is not enabled; not necessarily
// that unwrapping should not be done (relevant when using chained introspectors)
if (ann == null || !ann.enabled()) {
return null;
}
String prefix = ann.prefix();
String suffix = ann.suffix();
return NameTransformer.simpleTransformer(prefix, suffix);
}
@Override // since 2.9
public JacksonInject.Value findInjectableValue(AnnotatedMember m) {
JacksonInject ann = _findAnnotation(m, JacksonInject.class);
if (ann == null) {
return null;
}
// Empty String means that we should use name of declared value class.
JacksonInject.Value v = JacksonInject.Value.from(ann);
if (!v.hasId()) {
Object id;
// slight complication; for setters, type
if (!(m instanceof AnnotatedMethod)) {
id = m.getRawType().getName();
} else {
AnnotatedMethod am = (AnnotatedMethod) m;
if (am.getParameterCount() == 0) { // getter
id = m.getRawType().getName();
} else { // setter
id = am.getRawParameterType(0).getName();
}
}
v = v.withId(id);
}
return v;
}
@Override
@Deprecated // since 2.9
public Object findInjectableValueId(AnnotatedMember m) {
JacksonInject.Value v = findInjectableValue(m);
return (v == null) ? null : v.getId();
}
@Override
public Class>[] findViews(Annotated a)
{
JsonView ann = _findAnnotation(a, JsonView.class);
return (ann == null) ? null : ann.value();
}
/**
* Specific implementation that will use following tie-breaker on
* given setter parameter types:
*
* - If either one is primitive type then either return {@code null}
* (both primitives) or one that is primitive (when only primitive)
*
* - If only one is of type {@code String}, return that setter
*
* - Otherwise return {@code null}
*
*
* Returning {@code null} will indicate that resolution could not be done.
*/
@Override // since 2.7
public AnnotatedMethod resolveSetterConflict(MapperConfig> config,
AnnotatedMethod setter1, AnnotatedMethod setter2)
{
Class> cls1 = setter1.getRawParameterType(0);
Class> cls2 = setter2.getRawParameterType(0);
// First: prefer primitives over non-primitives
// 11-Dec-2015, tatu: TODO, perhaps consider wrappers for primitives too?
if (cls1.isPrimitive()) {
if (!cls2.isPrimitive()) {
return setter1;
}
// 10-May-2021, tatu: if both primitives cannot decide
return null;
} else if (cls2.isPrimitive()) {
return setter2;
}
if (cls1 == String.class) {
if (cls2 != String.class) {
return setter1;
}
} else if (cls2 == String.class) {
return setter2;
}
return null;
}
@Override // since 2.11
public PropertyName findRenameByField(MapperConfig> config,
AnnotatedField f, PropertyName implName) {
// Nothing to report, only used by modules. But define just as documentation
return null;
}
/*
/**********************************************************
/* Annotations for Polymorphic Type handling
/**********************************************************
*/
@Override
public TypeResolverBuilder> findTypeResolver(MapperConfig> config,
AnnotatedClass ac, JavaType baseType)
{
return _findTypeResolver(config, ac, baseType);
}
@Override
public TypeResolverBuilder> findPropertyTypeResolver(MapperConfig> config,
AnnotatedMember am, JavaType baseType)
{
// As per definition of @JsonTypeInfo, should only apply to contents of container
// (collection, map) types, not container types themselves:
// 17-Apr-2016, tatu: For 2.7.4 make sure ReferenceType also included
if (baseType.isContainerType() || baseType.isReferenceType()) {
return null;
}
// No per-member type overrides (yet)
return _findTypeResolver(config, am, baseType);
}
@Override
public TypeResolverBuilder> findPropertyContentTypeResolver(MapperConfig> config,
AnnotatedMember am, JavaType containerType)
{
// First: let's ensure property is a container type: caller should have
// verified but just to be sure
if (containerType.getContentType() == null) {
throw new IllegalArgumentException("Must call method with a container or reference type (got "+containerType+")");
}
return _findTypeResolver(config, am, containerType);
}
@Override
public List findSubtypes(Annotated a)
{
JsonSubTypes t = _findAnnotation(a, JsonSubTypes.class);
if (t == null) return null;
JsonSubTypes.Type[] types = t.value();
// 02-Aug-2022, tatu: As per [databind#3500], may need to check uniqueness
// of names
if (t.failOnRepeatedNames()) {
return findSubtypesCheckRepeatedNames(a.getName(), types);
} else {
ArrayList result = new ArrayList(types.length);
for (JsonSubTypes.Type type : types) {
result.add(new NamedType(type.value(), type.name()));
// [databind#2761]: alternative set of names to use
for (String name : type.names()) {
result.add(new NamedType(type.value(), name));
}
}
return result;
}
}
// @since 2.14
private List findSubtypesCheckRepeatedNames(String annotatedTypeName, JsonSubTypes.Type[] types)
{
ArrayList result = new ArrayList(types.length);
Set seenNames = new HashSet<>();
for (JsonSubTypes.Type type : types) {
final String typeName = type.name();
if (!typeName.isEmpty() && seenNames.contains(typeName)) {
throw new IllegalArgumentException("Annotated type [" + annotatedTypeName + "] got repeated subtype name [" + typeName + "]");
} else {
seenNames.add(typeName);
}
result.add(new NamedType(type.value(), typeName));
// [databind#2761]: alternative set of names to use
for (String altName : type.names()) {
if (!altName.isEmpty() && seenNames.contains(altName)) {
throw new IllegalArgumentException("Annotated type [" + annotatedTypeName + "] got repeated subtype name [" + altName + "]");
} else {
seenNames.add(altName);
}
result.add(new NamedType(type.value(), altName));
}
}
return result;
}
@Override
public String findTypeName(AnnotatedClass ac)
{
JsonTypeName tn = _findAnnotation(ac, JsonTypeName.class);
return (tn == null) ? null : tn.value();
}
@Override
public Boolean isTypeId(AnnotatedMember member) {
return _hasAnnotation(member, JsonTypeId.class);
}
/*
/**********************************************************
/* Annotations for Object Id handling
/**********************************************************
*/
@Override
public ObjectIdInfo findObjectIdInfo(Annotated ann) {
JsonIdentityInfo info = _findAnnotation(ann, JsonIdentityInfo.class);
if (info == null || info.generator() == ObjectIdGenerators.None.class) {
return null;
}
// In future may need to allow passing namespace?
PropertyName name = PropertyName.construct(info.property());
return new ObjectIdInfo(name, info.scope(), info.generator(), info.resolver());
}
@Override
public ObjectIdInfo findObjectReferenceInfo(Annotated ann, ObjectIdInfo objectIdInfo) {
JsonIdentityReference ref = _findAnnotation(ann, JsonIdentityReference.class);
if (ref == null) {
return objectIdInfo;
}
if (objectIdInfo == null) {
objectIdInfo = ObjectIdInfo.empty();
}
return objectIdInfo.withAlwaysAsId(ref.alwaysAsId());
}
/*
/**********************************************************
/* Serialization: general annotations
/**********************************************************
*/
@Override
public Object findSerializer(Annotated a)
{
JsonSerialize ann = _findAnnotation(a, JsonSerialize.class);
if (ann != null) {
@SuppressWarnings("rawtypes")
Class extends JsonSerializer> serClass = ann.using();
if (serClass != JsonSerializer.None.class) {
return serClass;
}
}
/* 18-Oct-2010, tatu: [JACKSON-351] @JsonRawValue handled just here, for now;
* if we need to get raw indicator from other sources need to add
* separate accessor within {@link AnnotationIntrospector} interface.
*/
JsonRawValue annRaw = _findAnnotation(a, JsonRawValue.class);
if ((annRaw != null) && annRaw.value()) {
// let's construct instance with nominal type:
Class> cls = a.getRawType();
return new RawSerializer