com.fitbur.jackson.databind.introspect.JacksonAnnotationIntrospector Maven / Gradle / Ivy
package com.fitbur.jackson.databind.introspect;
import java.beans.ConstructorProperties;
import java.beans.Transient;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import com.fitbur.jackson.annotation.*;
import com.fitbur.jackson.core.Version;
import com.fitbur.jackson.databind.*;
import com.fitbur.jackson.databind.annotation.*;
import com.fitbur.jackson.databind.cfg.HandlerInstantiator;
import com.fitbur.jackson.databind.cfg.MapperConfig;
import com.fitbur.jackson.databind.jsontype.NamedType;
import com.fitbur.jackson.databind.jsontype.TypeIdResolver;
import com.fitbur.jackson.databind.jsontype.TypeResolverBuilder;
import com.fitbur.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fitbur.jackson.databind.ser.BeanPropertyWriter;
import com.fitbur.jackson.databind.ser.VirtualBeanPropertyWriter;
import com.fitbur.jackson.databind.ser.impl.AttributePropertyWriter;
import com.fitbur.jackson.databind.ser.std.RawSerializer;
import com.fitbur.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
};
private static final Java7Support _jdk7Helper;
static {
Java7Support x = null;
try {
x = Java7Support.class.newInstance();
} catch (Throwable t) {
// 24-Nov-2015, tatu: Should we log or not?
java.util.logging.Logger.getLogger(JacksonAnnotationIntrospector.class.getName())
.warning("Unable to load JDK7 annotation types; will have to skip");
}
_jdk7Helper = 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);
public JacksonAnnotationIntrospector() { }
@Override
public Version version() {
return com.fitbur.jackson.databind.cfg.PackageVersion.VERSION;
}
protected Object readResolve() {
if (_annotationsInside == null) {
_annotationsInside = new LRUMap,Boolean>(48, 48);
}
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
public String findEnumValue(Enum> value)
{
// 11-Jun-2015, tatu: As per [databind#677], need to allow explicit naming.
// Unfortunately can not 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.getClass().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 : ClassUtil.getDeclaredFields(enumType)) {
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();
names[i] = expl.get(defName);
}
}
return names;
}
/*
/**********************************************************
/* 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.length() == 0) {
ns = null;
}
return PropertyName.construct(ann.value(), ns);
}
@Override
@Deprecated // since 2.6, remove from 2.7 or later
public String[] findPropertiesToIgnore(Annotated ac) {
JsonIgnoreProperties ignore = _findAnnotation(ac, JsonIgnoreProperties.class);
return (ignore == null) ? null : ignore.value();
}
@Override // since 2.6
public String[] findPropertiesToIgnore(Annotated ac, boolean forSerialization) {
JsonIgnoreProperties ignore = _findAnnotation(ac, JsonIgnoreProperties.class);
if (ignore == null) {
return null;
}
// 13-May-2015, tatu: As per [databind#95], allow read-only/write-only props
if (forSerialization) {
if (ignore.allowGetters()) {
return null;
}
} else {
if (ignore.allowSetters()) {
return null;
}
}
return ignore.value();
}
@Override
public Boolean findIgnoreUnknownProperties(AnnotatedClass ac) {
JsonIgnoreProperties ignore = _findAnnotation(ac, JsonIgnoreProperties.class);
return (ignore == null) ? null : ignore.ignoreUnknown();
}
@Override
public Boolean isIgnorableType(AnnotatedClass ac) {
JsonIgnoreType ignore = _findAnnotation(ac, JsonIgnoreType.class);
return (ignore == null) ? null : ignore.value();
}
@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.length() > 0) {
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 param) {
// not known by default (until JDK8) for creators; default
//
return null;
}
@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);
return (f == null) ? null : new JsonFormat.Value(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
public Object findInjectableValueId(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.
*/
String id = ann.value();
if (id.length() == 0) {
// slight complication; for setters, type
if (!(m instanceof AnnotatedMethod)) {
return m.getRawType().getName();
}
AnnotatedMethod am = (AnnotatedMethod) m;
if (am.getParameterCount() == 0) {
return m.getRawType().getName();
}
return am.getRawParameterType(0).getName();
}
return id;
}
@Override
public Class>[] findViews(Annotated a)
{
JsonView ann = _findAnnotation(a, JsonView.class);
return (ann == null) ? null : ann.value();
}
@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;
}
} 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;
}
/*
/**********************************************************
/* 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:
*/
if (baseType.isContainerType()) 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();
ArrayList result = new ArrayList(types.length);
for (JsonSubTypes.Type type : types) {
result.add(new NamedType(type.value(), type.name()));
}
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) {
objectIdInfo = objectIdInfo.withAlwaysAsId(ref.alwaysAsId());
}
return objectIdInfo;
}
/*
/**********************************************************
/* 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