BasicBeanDescription.java
package com.fasterxml.jackson.databind.introspect;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.util.Annotations;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
/**
* Default {@link BeanDescription} implementation used by Jackson.
*<p>
* Although sub-classing is a theoretical possibility there are no known
* use cases for that, nor is such usage tested or supported.
* Separation from API is mostly to isolate some implementation details
* here and keep API simple.
*/
public class BasicBeanDescription extends BeanDescription
{
// since 2.9
private final static Class<?>[] NO_VIEWS = new Class<?>[0];
/*
/**********************************************************
/* General configuration
/**********************************************************
*/
/**
* We will hold a reference to the collector in cases where
* information is lazily accessed and constructed; properties
* are only accessed when they are actually needed.
*/
final protected POJOPropertiesCollector _propCollector;
final protected MapperConfig<?> _config;
final protected AnnotationIntrospector _annotationIntrospector;
/*
/**********************************************************
/* Information about type itself
/**********************************************************
*/
/**
* Information collected about the class introspected.
*/
final protected AnnotatedClass _classInfo;
/**
* @since 2.9
*/
protected Class<?>[] _defaultViews;
/**
* @since 2.9
*/
protected boolean _defaultViewsResolved;
/*
/**********************************************************
/* Member information
/**********************************************************
*/
/**
* Properties collected for the POJO; initialized as needed.
*/
protected List<BeanPropertyDefinition> _properties;
/**
* Details of Object Id to include, if any
*/
protected ObjectIdInfo _objectIdInfo;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
protected BasicBeanDescription(POJOPropertiesCollector coll,
JavaType type, AnnotatedClass classDef)
{
super(type);
_propCollector = coll;
_config = coll.getConfig();
// NOTE: null config only for some pre-constructed types
if (_config == null) {
_annotationIntrospector = null;
} else {
_annotationIntrospector = _config.getAnnotationIntrospector();
}
_classInfo = classDef;
}
/**
* Alternate constructor used in cases where property information is not needed,
* only class info.
*/
protected BasicBeanDescription(MapperConfig<?> config,
JavaType type, AnnotatedClass classDef, List<BeanPropertyDefinition> props)
{
super(type);
_propCollector = null;
_config = config;
// NOTE: null config only for some pre-constructed types
if (_config == null) {
_annotationIntrospector = null;
} else {
_annotationIntrospector = _config.getAnnotationIntrospector();
}
_classInfo = classDef;
_properties = props;
}
protected BasicBeanDescription(POJOPropertiesCollector coll)
{
this(coll, coll.getType(), coll.getClassDef());
_objectIdInfo = coll.getObjectIdInfo();
}
/**
* Factory method to use for constructing an instance to use for building
* deserializers.
*/
public static BasicBeanDescription forDeserialization(POJOPropertiesCollector coll) {
return new BasicBeanDescription(coll);
}
/**
* Factory method to use for constructing an instance to use for building
* serializers.
*/
public static BasicBeanDescription forSerialization(POJOPropertiesCollector coll) {
return new BasicBeanDescription(coll);
}
/**
* Factory method to use for constructing an instance to use for purposes
* other than building serializers or deserializers; will only have information
* on class, not on properties.
*/
public static BasicBeanDescription forOtherUse(MapperConfig<?> config,
JavaType type, AnnotatedClass ac)
{
return new BasicBeanDescription(config, type,
ac, Collections.<BeanPropertyDefinition>emptyList());
}
protected List<BeanPropertyDefinition> _properties() {
if (_properties == null) {
_properties = _propCollector.getProperties();
}
return _properties;
}
/*
/**********************************************************
/* Limited modifications by core databind functionality
/**********************************************************
*/
/**
* Method that can be used to prune unwanted properties, during
* construction of serializers and deserializers.
* Use with utmost care, if at all...
*
* @since 2.1
*/
public boolean removeProperty(String propName)
{
Iterator<BeanPropertyDefinition> it = _properties().iterator();
while (it.hasNext()) {
BeanPropertyDefinition prop = it.next();
if (prop.getName().equals(propName)) {
it.remove();
return true;
}
}
return false;
}
public boolean addProperty(BeanPropertyDefinition def)
{
// first: ensure we do not have such property
if (hasProperty(def.getFullName())) {
return false;
}
_properties().add(def);
return true;
}
/**
* @since 2.6
*/
public boolean hasProperty(PropertyName name) {
return findProperty(name) != null;
}
/**
* @since 2.6
*/
public BeanPropertyDefinition findProperty(PropertyName name)
{
for (BeanPropertyDefinition prop : _properties()) {
if (prop.hasName(name)) {
return prop;
}
}
return null;
}
/*
/**********************************************************
/* Simple accessors from BeanDescription
/**********************************************************
*/
@Override
public AnnotatedClass getClassInfo() { return _classInfo; }
@Override
public ObjectIdInfo getObjectIdInfo() { return _objectIdInfo; }
@Override
public List<BeanPropertyDefinition> findProperties() {
return _properties();
}
@Override // since 2.12
public AnnotatedMember findJsonKeyAccessor() {
return (_propCollector == null) ? null
: _propCollector.getJsonKeyAccessor();
}
@Override // since 2.9
public AnnotatedMember findJsonValueAccessor() {
return (_propCollector == null) ? null
: _propCollector.getJsonValueAccessor();
}
@Override
public Set<String> getIgnoredPropertyNames() {
Set<String> ign = (_propCollector == null) ? null
: _propCollector.getIgnoredPropertyNames();
if (ign == null) {
return Collections.emptySet();
}
return ign;
}
@Override
public boolean hasKnownClassAnnotations() {
return _classInfo.hasAnnotations();
}
@Override
public Annotations getClassAnnotations() {
return _classInfo.getAnnotations();
}
@Override
public AnnotatedConstructor findDefaultConstructor() {
return _classInfo.getDefaultConstructor();
}
@Override
public AnnotatedMember findAnySetterAccessor() throws IllegalArgumentException
{
if (_propCollector != null) {
AnnotatedMethod anyMethod = _propCollector.getAnySetterMethod();
if (anyMethod != null) {
// Also, let's be somewhat strict on how field name is to be
// passed; String, Object make sense, others not so much.
/* !!! 18-May-2009, tatu: how about enums? Can add support if
* requested; easy enough for devs to add support within method.
*/
Class<?> type = anyMethod.getRawParameterType(0);
if ((type != String.class) && (type != Object.class)) {
throw new IllegalArgumentException(String.format(
"Invalid 'any-setter' annotation on method '%s()': first argument not of type String or Object, but %s",
anyMethod.getName(), type.getName()));
}
return anyMethod;
}
AnnotatedMember anyField = _propCollector.getAnySetterField();
if (anyField != null) {
// For now let's require a Map; in future can add support for other
// types like perhaps Iterable<Map.Entry>?
Class<?> type = anyField.getRawType();
if (!Map.class.isAssignableFrom(type)
&& !JsonNode.class.isAssignableFrom(type)) {
throw new IllegalArgumentException(String.format(
"Invalid 'any-setter' annotation on field '%s': type is not instance of `java.util.Map` or `JsonNode`",
anyField.getName()));
}
return anyField;
}
}
return null;
}
@Override
public Map<Object, AnnotatedMember> findInjectables() {
if (_propCollector != null) {
return _propCollector.getInjectables();
}
return Collections.emptyMap();
}
@Override
public List<AnnotatedConstructor> getConstructors() {
return _classInfo.getConstructors();
}
@Override
public List<AnnotatedAndMetadata<AnnotatedConstructor, JsonCreator.Mode>> getConstructorsWithMode() {
List<AnnotatedConstructor> allCtors = _classInfo.getConstructors();
if (allCtors.isEmpty()) {
return Collections.emptyList();
}
List<AnnotatedAndMetadata<AnnotatedConstructor, JsonCreator.Mode>> result = new ArrayList<>();
for (AnnotatedConstructor ctor : allCtors) {
JsonCreator.Mode mode = _annotationIntrospector.findCreatorAnnotation(_config, ctor);
if (mode == JsonCreator.Mode.DISABLED) {
continue;
}
result.add(AnnotatedAndMetadata.of(ctor, mode));
}
return result;
}
@Override
public PotentialCreators getPotentialCreators() {
if (_propCollector == null) {
return new PotentialCreators();
}
return _propCollector.getPotentialCreators();
}
@Override
public Object instantiateBean(boolean fixAccess) {
AnnotatedConstructor ac = _classInfo.getDefaultConstructor();
if (ac == null) {
return null;
}
if (fixAccess) {
ac.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
try {
return ac.call();
} catch (Exception e) {
Throwable t = e;
while (t.getCause() != null) {
t = t.getCause();
}
ClassUtil.throwIfError(t);
ClassUtil.throwIfRTE(t);
throw new IllegalArgumentException("Failed to instantiate bean of type "
+_classInfo.getAnnotated().getName()+": ("+t.getClass().getName()+") "
+ClassUtil.exceptionMessage(t), t);
}
}
/*
/**********************************************************
/* Simple accessors, extended
/**********************************************************
*/
@Override
public AnnotatedMethod findMethod(String name, Class<?>[] paramTypes) {
return _classInfo.findMethod(name, paramTypes);
}
/*
/**********************************************************
/* General per-class annotation introspection
/**********************************************************
*/
@Override // since 2.17
public JsonFormat.Value findExpectedFormat()
{
if (_propCollector == null) {
return JsonFormat.Value.empty();
}
return _propCollector.getFormatOverrides();
}
@Override // since 2.9
public Class<?>[] findDefaultViews()
{
if (!_defaultViewsResolved) {
_defaultViewsResolved = true;
Class<?>[] def = (_annotationIntrospector == null) ? null
: _annotationIntrospector.findViews(_classInfo);
// one more twist: if default inclusion disabled, need to force empty set of views
if (def == null) {
if (!_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
def = NO_VIEWS;
}
}
_defaultViews = def;
}
return _defaultViews;
}
/*
/**********************************************************
/* Introspection for serialization
/**********************************************************
*/
@Override
public Converter<Object,Object> findSerializationConverter()
{
if (_annotationIntrospector == null) {
return null;
}
return _createConverter(_annotationIntrospector.findSerializationConverter(_classInfo));
}
/**
* Method for determining whether null properties should be written
* out for a Bean of introspected type. This is based on global
* feature (lowest priority, passed as argument)
* and per-class annotation (highest priority).
*/
@Override
public JsonInclude.Value findPropertyInclusion(JsonInclude.Value defValue) {
if (_annotationIntrospector != null) {
JsonInclude.Value incl = _annotationIntrospector.findPropertyInclusion(_classInfo);
if (incl != null) {
return (defValue == null) ? incl : defValue.withOverrides(incl);
}
}
return defValue;
}
/**
* Method used to locate the method of introspected class that
* implements {@link com.fasterxml.jackson.annotation.JsonAnyGetter}.
* If no such method exists null is returned.
* If more than one are found, an exception is thrown.
*/
@Override
public AnnotatedMember findAnyGetter() throws IllegalArgumentException
{
if (_propCollector != null) {
AnnotatedMember anyGetter = _propCollector.getAnyGetterMethod();
if (anyGetter != null) {
// For now let's require a Map; in future can add support for other
// types like perhaps Iterable<Map.Entry>?
Class<?> type = anyGetter.getRawType();
if (!Map.class.isAssignableFrom(type)) {
throw new IllegalArgumentException(String.format(
"Invalid 'any-getter' annotation on method %s(): return type is not instance of java.util.Map",
anyGetter.getName()));
}
return anyGetter;
}
AnnotatedMember anyField = _propCollector.getAnyGetterField();
if (anyField != null) {
// For now let's require a Map; in future can add support for other
// types like perhaps Iterable<Map.Entry>?
Class<?> type = anyField.getRawType();
if (!Map.class.isAssignableFrom(type)) {
throw new IllegalArgumentException(String.format(
"Invalid 'any-getter' annotation on field '%s': type is not instance of java.util.Map",
anyField.getName()));
}
return anyField;
}
}
return null;
}
@Override
public List<BeanPropertyDefinition> findBackReferences()
{
List<BeanPropertyDefinition> result = null;
HashSet<String> names = null;
for (BeanPropertyDefinition property : _properties()) {
AnnotationIntrospector.ReferenceProperty refDef = property.findReferenceType();
if ((refDef == null) || !refDef.isBackReference()) {
continue;
}
final String refName = refDef.getName();
if (result == null) {
result = new ArrayList<BeanPropertyDefinition>();
names = new HashSet<>();
names.add(refName);
} else {
if (!names.add(refName)) {
throw new IllegalArgumentException("Multiple back-reference properties with name "+ClassUtil.name(refName));
}
}
result.add(property);
}
return result;
}
/*
/**********************************************************
/* Introspection for deserialization, factories
/**********************************************************
*/
@Override
public List<AnnotatedMethod> getFactoryMethods()
{
// must filter out anything that clearly is not a factory method
List<AnnotatedMethod> candidates = _classInfo.getFactoryMethods();
if (candidates.isEmpty()) {
return candidates;
}
List<AnnotatedMethod> result = null;
for (AnnotatedMethod am : candidates) {
if (isFactoryMethod(am)) {
if (result == null) {
result = new ArrayList<AnnotatedMethod>();
}
result.add(am);
}
}
if (result == null) {
return Collections.emptyList();
}
return result;
}
@Override // since 2.13
public List<AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode>> getFactoryMethodsWithMode()
{
List<AnnotatedMethod> candidates = _classInfo.getFactoryMethods();
if (candidates.isEmpty()) {
return Collections.emptyList();
}
List<AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode>> result = null;
for (AnnotatedMethod am : candidates) {
AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode> match
= findFactoryMethodMetadata(am);
if (match != null) {
if (result == null) {
result = new ArrayList<>();
}
result.add(match);
}
}
if (result == null) {
return Collections.emptyList();
}
return result;
}
protected boolean isFactoryMethod(AnnotatedMethod am)
{
// First: return type must be compatible with the introspected class
// (i.e. allowed to be sub-class, although usually is the same class)
Class<?> rt = am.getRawReturnType();
if (!getBeanClass().isAssignableFrom(rt)) {
return false;
}
/* Also: must be a recognized factory method, meaning:
* (a) marked with @JsonCreator annotation, or
* (b) 1-argument "valueOf" (at this point, need not be public), or
* (c) 1-argument "fromString()" AND takes {@code String} as the argument
*/
JsonCreator.Mode mode = _annotationIntrospector.findCreatorAnnotation(_config, am);
if ((mode != null) && (mode != JsonCreator.Mode.DISABLED)) {
return true;
}
final String name = am.getName();
// 24-Oct-2016, tatu: As per [databind#1429] must ensure takes exactly one arg
if ("valueOf".equals(name)) {
if (am.getParameterCount() == 1) {
return true;
}
}
// [databind#208] Also accept "fromString()", if takes String or CharSequence
if ("fromString".equals(name)) {
if (am.getParameterCount() == 1) {
Class<?> cls = am.getRawParameterType(0);
if (cls == String.class || CharSequence.class.isAssignableFrom(cls)) {
return true;
}
}
}
return false;
}
// @since 2.13
protected AnnotatedAndMetadata<AnnotatedMethod, JsonCreator.Mode> findFactoryMethodMetadata(AnnotatedMethod am)
{
// First: return type must be compatible with the introspected class
// (i.e. allowed to be sub-class, although usually is the same class)
Class<?> rt = am.getRawReturnType();
if (!getBeanClass().isAssignableFrom(rt)) {
return null;
}
// Also: must be a recognized factory method, meaning:
// (a) marked with @JsonCreator annotation, or
// (b) 1-argument "valueOf" (at this point, need not be public), or
// (c) 1-argument "fromString()" AND takes {@code String} as the argument
JsonCreator.Mode mode = _annotationIntrospector.findCreatorAnnotation(_config, am);
if (mode != null) {
if (mode == JsonCreator.Mode.DISABLED) {
return null;
}
return AnnotatedAndMetadata.of(am, mode);
}
final String name = am.getName();
// 24-Oct-2016, tatu: As per [databind#1429] must ensure takes exactly one arg
if ("valueOf".equals(name)) {
if (am.getParameterCount() == 1) {
return AnnotatedAndMetadata.of(am, mode);
}
}
// [databind#208] Also accept "fromString()", if takes String or CharSequence
if ("fromString".equals(name)) {
if (am.getParameterCount() == 1) {
Class<?> cls = am.getRawParameterType(0);
if (cls == String.class || CharSequence.class.isAssignableFrom(cls)) {
return AnnotatedAndMetadata.of(am, mode);
}
}
}
return null;
}
/*
/**********************************************************
/* Introspection for deserialization, other
/**********************************************************
*/
@Override
public Class<?> findPOJOBuilder() {
return (_annotationIntrospector == null) ?
null : _annotationIntrospector.findPOJOBuilder(_classInfo);
}
@Override
public JsonPOJOBuilder.Value findPOJOBuilderConfig()
{
return (_annotationIntrospector == null) ?
null : _annotationIntrospector.findPOJOBuilderConfig(_classInfo);
}
@Override
public Converter<Object,Object> findDeserializationConverter()
{
if (_annotationIntrospector == null) {
return null;
}
return _createConverter(_annotationIntrospector.findDeserializationConverter(_classInfo));
}
@Override
public String findClassDescription() {
return (_annotationIntrospector == null) ?
null : _annotationIntrospector.findClassDescription(_classInfo);
}
/*
/**********************************************************
/* Helper methods, other
/**********************************************************
*/
@SuppressWarnings("unchecked")
protected Converter<Object,Object> _createConverter(Object converterDef)
{
if (converterDef == null) {
return null;
}
if (converterDef instanceof Converter<?,?>) {
return (Converter<Object,Object>) converterDef;
}
if (!(converterDef instanceof Class)) {
throw new IllegalStateException("AnnotationIntrospector returned Converter definition of type "
+converterDef.getClass().getName()+"; expected type Converter or Class<Converter> instead");
}
Class<?> converterClass = (Class<?>)converterDef;
// there are some known "no class" markers to consider too:
if (converterClass == Converter.None.class || ClassUtil.isBogusClass(converterClass)) {
return null;
}
if (!Converter.class.isAssignableFrom(converterClass)) {
throw new IllegalStateException("AnnotationIntrospector returned Class "
+converterClass.getName()+"; expected Class<Converter>");
}
HandlerInstantiator hi = _config.getHandlerInstantiator();
Converter<?,?> conv = (hi == null) ? null : hi.converterInstance(_config, _classInfo, converterClass);
if (conv == null) {
conv = (Converter<?,?>) ClassUtil.createInstance(converterClass,
_config.canOverrideAccessModifiers());
}
return (Converter<Object,Object>) conv;
}
}