package com.fasterxml.jackson.databind.deser;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap;
import com.fasterxml.jackson.databind.deser.impl.ObjectIdValueProperty;
import com.fasterxml.jackson.databind.deser.impl.ObjectIdReader;
import com.fasterxml.jackson.databind.deser.impl.ValueInjector;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.util.Annotations;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.IgnorePropertiesUtil;
Builder class used for aggregating deserialization information about a POJO, in order to build a JsonDeserializer
for deserializing instances. /**
* Builder class used for aggregating deserialization information about
* a POJO, in order to build a {@link JsonDeserializer} for deserializing
* instances.
*/
public class BeanDeserializerBuilder
{
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
final protected DeserializationConfig _config;
Since: 2.9
/**
* @since 2.9
*/
final protected DeserializationContext _context;
/*
/**********************************************************
/* General information about POJO
/**********************************************************
*/
Introspected information about POJO for deserializer to handle
/**
* Introspected information about POJO for deserializer to handle
*/
final protected BeanDescription _beanDesc;
/*
/**********************************************************
/* Accumulated information about properties
/**********************************************************
*/
Properties to deserialize collected so far.
/**
* Properties to deserialize collected so far.
*/
final protected Map<String, SettableBeanProperty> _properties
= new LinkedHashMap<String, SettableBeanProperty>();
Value injectors for deserialization
/**
* Value injectors for deserialization
*/
protected List<ValueInjector> _injectables;
Back-reference properties this bean contains (if any)
/**
* Back-reference properties this bean contains (if any)
*/
protected HashMap<String, SettableBeanProperty> _backRefProperties;
Set of names of properties that are recognized but are to be ignored for deserialization
purposes (meaning no exception is thrown, value is just skipped).
/**
* Set of names of properties that are recognized but are to be ignored for deserialization
* purposes (meaning no exception is thrown, value is just skipped).
*/
protected HashSet<String> _ignorableProps;
Set of names of properties that are recognized and are set to be included for deserialization
purposes (null deactivate this, empty includes nothing).
/**
* Set of names of properties that are recognized and are set to be included for deserialization
* purposes (null deactivate this, empty includes nothing).
*/
protected HashSet<String> _includableProps;
Object that will handle value instantiation for the bean type.
/**
* Object that will handle value instantiation for the bean type.
*/
protected ValueInstantiator _valueInstantiator;
Handler for Object Id values, if Object Ids are enabled for the
bean type.
/**
* Handler for Object Id values, if Object Ids are enabled for the
* bean type.
*/
protected ObjectIdReader _objectIdReader;
Fallback setter used for handling any properties that are not
mapped to regular setters. If setter is not null, it will be
called once for each such property.
/**
* Fallback setter used for handling any properties that are not
* mapped to regular setters. If setter is not null, it will be
* called once for each such property.
*/
protected SettableAnyProperty _anySetter;
Flag that can be set to ignore and skip unknown properties.
If set, will not throw an exception for unknown properties.
/**
* Flag that can be set to ignore and skip unknown properties.
* If set, will not throw an exception for unknown properties.
*/
protected boolean _ignoreAllUnknown;
When creating Builder-based deserializers, this indicates
method to call on builder to finalize value.
/**
* When creating Builder-based deserializers, this indicates
* method to call on builder to finalize value.
*/
protected AnnotatedMethod _buildMethod;
In addition, Builder may have additional configuration
/**
* In addition, Builder may have additional configuration
*/
protected JsonPOJOBuilder.Value _builderConfig;
/*
/**********************************************************
/* Life-cycle: construction
/**********************************************************
*/
public BeanDeserializerBuilder(BeanDescription beanDesc,
DeserializationContext ctxt)
{
_beanDesc = beanDesc;
_context = ctxt;
_config = ctxt.getConfig();
}
Copy constructor for sub-classes to use, when constructing
custom builder instances
/**
* Copy constructor for sub-classes to use, when constructing
* custom builder instances
*/
protected BeanDeserializerBuilder(BeanDeserializerBuilder src)
{
_beanDesc = src._beanDesc;
_context = src._context;
_config = src._config;
// let's make copy of properties
_properties.putAll(src._properties);
_injectables = _copy(src._injectables);
_backRefProperties = _copy(src._backRefProperties);
// Hmmh. Should we create defensive copies here? For now, not yet
_ignorableProps = src._ignorableProps;
_includableProps = src._includableProps;
_valueInstantiator = src._valueInstantiator;
_objectIdReader = src._objectIdReader;
_anySetter = src._anySetter;
_ignoreAllUnknown = src._ignoreAllUnknown;
_buildMethod = src._buildMethod;
_builderConfig = src._builderConfig;
}
private static HashMap<String, SettableBeanProperty> _copy(HashMap<String, SettableBeanProperty> src) {
return (src == null) ? null
: new HashMap<String, SettableBeanProperty>(src);
}
private static <T> List<T> _copy(List<T> src) {
return (src == null) ? null : new ArrayList<T>(src);
}
/*
/**********************************************************
/* Life-cycle: state modification (adders, setters)
/**********************************************************
*/
Method for adding a new property or replacing a property.
/**
* Method for adding a new property or replacing a property.
*/
public void addOrReplaceProperty(SettableBeanProperty prop, boolean allowOverride) {
_properties.put(prop.getName(), prop);
}
Method to add a property setter. Will ensure that there is no unexpected override; if one is found will throw a IllegalArgumentException
. /**
* Method to add a property setter. Will ensure that there is no
* unexpected override; if one is found will throw a
* {@link IllegalArgumentException}.
*/
public void addProperty(SettableBeanProperty prop)
{
SettableBeanProperty old = _properties.put(prop.getName(), prop);
if (old != null && old != prop) { // should never occur...
throw new IllegalArgumentException("Duplicate property '"+prop.getName()+"' for "+_beanDesc.getType());
}
}
Method called to add a property that represents so-called back reference;
reference that "points back" to object that has forward reference to
currently built bean.
/**
* Method called to add a property that represents so-called back reference;
* reference that "points back" to object that has forward reference to
* currently built bean.
*/
public void addBackReferenceProperty(String referenceName, SettableBeanProperty prop)
{
if (_backRefProperties == null) {
_backRefProperties = new HashMap<String, SettableBeanProperty>(4);
}
// 15-Sep-2016, tatu: For some reason fixing access at point of `build()` does
// NOT work (2 failing unit tests). Not 100% clear why, but for now force
// access set early; unfortunate, but since it works....
if (_config.canOverrideAccessModifiers()) {
prop.fixAccess(_config);
}
_backRefProperties.put(referenceName, prop);
// 16-Jan-2018, tatu: As per [databind#1878] we may want to leave it as is, to allow
// population for cases of "wrong direction", traversing parent first
// If this causes problems should probably instead include in "ignored properties" list
// Alternatively could also extend annotation to allow/disallow explicit value from input
/*
if (_properties != null) {
_properties.remove(prop.getName());
}
*/
}
public void addInjectable(PropertyName propName, JavaType propType,
Annotations contextAnnotations, AnnotatedMember member,
Object valueId)
{
if (_injectables == null) {
_injectables = new ArrayList<ValueInjector>();
}
if ( _config.canOverrideAccessModifiers()) {
member.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
_injectables.add(new ValueInjector(propName, propType, member, valueId));
}
Method that will add property name as one of properties that can
be ignored if not recognized.
/**
* Method that will add property name as one of properties that can
* be ignored if not recognized.
*/
public void addIgnorable(String propName)
{
if (_ignorableProps == null) {
_ignorableProps = new HashSet<String>();
}
_ignorableProps.add(propName);
}
Method that will add property name as one of the properties that will be included.
Since: 2.12
/**
* Method that will add property name as one of the properties that will be included.
*
* @since 2.12
*/
public void addIncludable(String propName)
{
if (_includableProps == null) {
_includableProps = new HashSet<>();
}
_includableProps.add(propName);
}
Method called by deserializer factory, when a "creator property"
(something that is passed via constructor- or factory method argument;
instead of setter or field).
Default implementation does not do anything; we may need to revisit this
decision if these properties need to be available through accessors.
For now, however, we just have to ensure that we don't try to resolve
types that masked setter/field has (see [JACKSON-700] for details).
/**
* Method called by deserializer factory, when a "creator property"
* (something that is passed via constructor- or factory method argument;
* instead of setter or field).
*<p>
* Default implementation does not do anything; we may need to revisit this
* decision if these properties need to be available through accessors.
* For now, however, we just have to ensure that we don't try to resolve
* types that masked setter/field has (see [JACKSON-700] for details).
*/
public void addCreatorProperty(SettableBeanProperty prop)
{
addProperty(prop);
}
public void setAnySetter(SettableAnyProperty s)
{
if (_anySetter != null && s != null) {
throw new IllegalStateException("_anySetter already set to non-null");
}
_anySetter = s;
}
public void setIgnoreUnknownProperties(boolean ignore) {
_ignoreAllUnknown = ignore;
}
public void setValueInstantiator(ValueInstantiator inst) {
_valueInstantiator = inst;
}
public void setObjectIdReader(ObjectIdReader r) {
_objectIdReader = r;
}
public void setPOJOBuilder(AnnotatedMethod buildMethod, JsonPOJOBuilder.Value config) {
_buildMethod = buildMethod;
_builderConfig = config;
}
/*
/**********************************************************
/* Public accessors
/**********************************************************
*/
Method that allows accessing all properties that this
builder currently contains.
Note that properties are returned in order that properties
are ordered (explictly, or by rule), which is the serialization
order.
/**
* Method that allows accessing all properties that this
* builder currently contains.
*<p>
* Note that properties are returned in order that properties
* are ordered (explictly, or by rule), which is the serialization
* order.
*/
public Iterator<SettableBeanProperty> getProperties() {
return _properties.values().iterator();
}
public SettableBeanProperty findProperty(PropertyName propertyName) {
return _properties.get(propertyName.getSimpleName());
}
public boolean hasProperty(PropertyName propertyName) {
return findProperty(propertyName) != null;
}
public SettableBeanProperty removeProperty(PropertyName name) {
return _properties.remove(name.getSimpleName());
}
public SettableAnyProperty getAnySetter() {
return _anySetter;
}
public ValueInstantiator getValueInstantiator() {
return _valueInstantiator;
}
public List<ValueInjector> getInjectables() {
return _injectables;
}
public ObjectIdReader getObjectIdReader() {
return _objectIdReader;
}
public AnnotatedMethod getBuildMethod() {
return _buildMethod;
}
public JsonPOJOBuilder.Value getBuilderConfig() {
return _builderConfig;
}
Since: 2.9.4
/**
* @since 2.9.4
*/
public boolean hasIgnorable(String name) {
return IgnorePropertiesUtil.shouldIgnore(name, _ignorableProps, _includableProps);
}
/*
/**********************************************************
/* Build method(s)
/**********************************************************
*/
Method for constructing a BeanDeserializer
, given all information collected. /**
* Method for constructing a {@link BeanDeserializer}, given all
* information collected.
*/
public JsonDeserializer<?> build()
{
Collection<SettableBeanProperty> props = _properties.values();
_fixAccess(props);
BeanPropertyMap propertyMap = BeanPropertyMap.construct(_config, props,
_collectAliases(props),
_findCaseInsensitivity());
propertyMap.assignIndexes();
// view processing must be enabled if:
// (a) fields are not included by default (when deserializing with view), OR
// (b) one of properties has view(s) to included in defined
boolean anyViews = !_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION);
if (!anyViews) {
for (SettableBeanProperty prop : props) {
if (prop.hasViews()) {
anyViews = true;
break;
}
}
}
// one more thing: may need to create virtual ObjectId property:
if (_objectIdReader != null) {
/* 18-Nov-2012, tatu: May or may not have annotations for id property;
* but no easy access. But hard to see id property being optional,
* so let's consider required at this point.
*/
ObjectIdValueProperty prop = new ObjectIdValueProperty(_objectIdReader, PropertyMetadata.STD_REQUIRED);
propertyMap = propertyMap.withProperty(prop);
}
return new BeanDeserializer(this,
_beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown, _includableProps,
anyViews);
}
Alternate build method used when we must be using some form of
abstract resolution, usually by using addition Type Id
("polymorphic deserialization")
Since: 2.0
/**
* Alternate build method used when we must be using some form of
* abstract resolution, usually by using addition Type Id
* ("polymorphic deserialization")
*
* @since 2.0
*/
public AbstractDeserializer buildAbstract() {
return new AbstractDeserializer(this, _beanDesc, _backRefProperties, _properties);
}
Method for constructing a specialized deserializer that uses
additional external Builder object during data binding.
/**
* Method for constructing a specialized deserializer that uses
* additional external Builder object during data binding.
*/
public JsonDeserializer<?> buildBuilderBased(JavaType valueType, String expBuildMethodName)
throws JsonMappingException
{
// First: validation; must have build method that returns compatible type
if (_buildMethod == null) {
// as per [databind#777], allow empty name
if (!expBuildMethodName.isEmpty()) {
_context.reportBadDefinition(_beanDesc.getType(),
String.format("Builder class %s does not have build method (name: '%s')",
ClassUtil.getTypeDescription(_beanDesc.getType()),
expBuildMethodName));
}
} else {
// also: type of the method must be compatible
Class<?> rawBuildType = _buildMethod.getRawReturnType();
Class<?> rawValueType = valueType.getRawClass();
if ((rawBuildType != rawValueType)
&& !rawBuildType.isAssignableFrom(rawValueType)
&& !rawValueType.isAssignableFrom(rawBuildType)) {
_context.reportBadDefinition(_beanDesc.getType(),
String.format("Build method `%s` has wrong return type (%s), not compatible with POJO type (%s)",
_buildMethod.getFullName(),
ClassUtil.getClassDescription(rawBuildType),
ClassUtil.getTypeDescription(valueType)));
}
}
// And if so, we can try building the deserializer
Collection<SettableBeanProperty> props = _properties.values();
_fixAccess(props);
BeanPropertyMap propertyMap = BeanPropertyMap.construct(_config, props,
_collectAliases(props),
_findCaseInsensitivity());
propertyMap.assignIndexes();
boolean anyViews = !_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION);
if (!anyViews) {
for (SettableBeanProperty prop : props) {
if (prop.hasViews()) {
anyViews = true;
break;
}
}
}
if (_objectIdReader != null) {
// May or may not have annotations for id property; but no easy access.
// But hard to see id property being optional, so let's consider required at this point.
ObjectIdValueProperty prop = new ObjectIdValueProperty(_objectIdReader,
PropertyMetadata.STD_REQUIRED);
propertyMap = propertyMap.withProperty(prop);
}
return createBuilderBasedDeserializer(valueType, propertyMap, anyViews);
}
Extension point for overriding the actual creation of the builder deserializer.
Since: 2.11
/**
* Extension point for overriding the actual creation of the builder deserializer.
*
* @since 2.11
*/
protected JsonDeserializer<?> createBuilderBasedDeserializer(JavaType valueType,
BeanPropertyMap propertyMap, boolean anyViews) {
return new BuilderBasedDeserializer(this,
_beanDesc, valueType, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown,
_includableProps, anyViews);
}
/*
/**********************************************************
/* Internal helper method(s)
/**********************************************************
*/
protected void _fixAccess(Collection<SettableBeanProperty> mainProps)
{
/* 07-Sep-2016, tatu: Ideally we should be able to avoid forcing
* access to properties that are likely ignored, but due to
* renaming it seems this is not a safe thing to do (there was
* at least one failing test). May need to dig deeper in future;
* for now let's just play it safe.
*/
/*
Set<String> ignorable = _ignorableProps;
if (ignorable == null) {
ignorable = Collections.emptySet();
}
*/
// 17-Jun-2020, tatu: [databind#2760] means we should not force access
// if we are not configured to... at least not "regular" properties
if (_config.canOverrideAccessModifiers()) {
for (SettableBeanProperty prop : mainProps) {
/*
// first: no point forcing access on to-be-ignored properties
if (ignorable.contains(prop.getName())) {
continue;
}
*/
prop.fixAccess(_config);
}
}
// 15-Sep-2016, tatu: Access via back-ref properties has been done earlier
// as it has to, for some reason, so not repeated here.
/*
if (_backRefProperties != null) {
for (SettableBeanProperty prop : _backRefProperties.values()) {
prop.fixAccess(_config);
}
}
*/
// 17-Jun-2020, tatu: Despite [databind#2760], it seems that methods that
// are explicitly defined (any setter via annotation, builder too) can not
// be left as-is? May reconsider based on feedback
if (_anySetter != null) {
_anySetter.fixAccess(_config);
}
if (_buildMethod != null) {
_buildMethod.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
}
protected Map<String,List<PropertyName>> _collectAliases(Collection<SettableBeanProperty> props)
{
Map<String,List<PropertyName>> mapping = null;
AnnotationIntrospector intr = _config.getAnnotationIntrospector();
if (intr != null) {
for (SettableBeanProperty prop : props) {
List<PropertyName> aliases = intr.findPropertyAliases(prop.getMember());
if ((aliases == null) || aliases.isEmpty()) {
continue;
}
if (mapping == null) {
mapping = new HashMap<>();
}
mapping.put(prop.getName(), aliases);
}
}
if (mapping == null) {
return Collections.emptyMap();
}
return mapping;
}
// @since 2.12
protected boolean _findCaseInsensitivity() {
// 07-May-2020, tatu: First find combination of per-type config overrides (higher
// precedence) and per-type annotations (lower):
JsonFormat.Value format = _beanDesc.findExpectedFormat(null);
// and see if any of those has explicit definition; if not, use global baseline default
Boolean B = format.getFeature(JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
return (B == null) ? _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
: B.booleanValue();
}
}