SettableBeanProperty.java
package tools.jackson.databind.deser;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import com.fasterxml.jackson.annotation.JacksonInject;
import tools.jackson.core.*;
import tools.jackson.core.util.InternCache;
import tools.jackson.databind.*;
import tools.jackson.databind.deser.bean.BeanDeserializer;
import tools.jackson.databind.deser.impl.FailingDeserializer;
import tools.jackson.databind.deser.impl.NullsConstantProvider;
import tools.jackson.databind.introspect.*;
import tools.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.util.Annotations;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.databind.util.NameTransformer;
import tools.jackson.databind.util.ViewMatcher;
/**
* Base class for deserializable properties of a bean: contains
* both type and name definitions, and reflection-based set functionality.
* Concrete sub-classes implement details, so that field- and
* setter-backed properties, as well as a few more esoteric variations,
* can be handled.
*/
public abstract class SettableBeanProperty
extends ConcreteBeanPropertyBase
{
/**
* To avoid nasty NPEs, let's use a placeholder for _valueDeserializer,
* if real deserializer is not (yet) available.
*/
protected static final ValueDeserializer<Object> MISSING_VALUE_DESERIALIZER = new FailingDeserializer(
"No _valueDeserializer assigned");
/**
* Logical name of the property (often but not always derived
* from the setter method name)
*/
protected final PropertyName _propName;
/**
* Base type for property; may be a supertype of actual value.
*/
protected final JavaType _type;
protected final PropertyName _wrapperName;
/**
* Class that contains this property (either class that declares
* the property or one of its subclasses), class that is
* deserialized using deserializer that contains this property.
*/
protected final transient Annotations _contextAnnotations;
/**
* Deserializer used for handling property value.
*/
protected final ValueDeserializer<Object> _valueDeserializer;
/**
* If value will contain type information (to support
* polymorphic handling), this is the type deserializer
* used to handle type resolution.
*/
protected final TypeDeserializer _valueTypeDeserializer;
/**
* Entity used for possible translation from `null` into non-null
* value of type of this property.
* Often same as <code>_valueDeserializer</code>, but not always.
*/
protected final NullValueProvider _nullProvider;
/*
/**********************************************************************
/* Configuration that is not yet immutable; generally assigned
/* during initialization process but cannot be passed to constructor.
/**********************************************************************
*/
/**
* If property represents a managed (forward) reference, we will need
* the name of reference for later linking.
*<p>
* TODO: should try to make immutable.
*/
protected String _managedReferenceName;
/**
* This is the information for object identity associated with the property.
* <p>
* TODO: should try to make immutable.
*/
protected ObjectIdInfo _objectIdInfo;
/**
* Helper object used for checking whether this property is to
* be included in the active view, if property is view-specific;
* null otherwise.
*<p>
* TODO: should try to make immutable.
*/
protected ViewMatcher _viewMatcher;
/**
* Index of property (within all property of a bean); assigned
* when all properties have been collected. Order of entries
* is arbitrary, but once indexes are assigned they are not
* changed.
*<p>
* TODO: should try to make immutable if at all possible
*/
protected int _propertyIndex = -1;
/*
/**********************************************************************
/* Life-cycle (construct & configure)
/**********************************************************************
*/
protected SettableBeanProperty(BeanPropertyDefinition propDef,
JavaType type, TypeDeserializer typeDeser, Annotations contextAnnotations)
{
this(propDef.getFullName(), type, propDef.getWrapperName(), typeDeser,
contextAnnotations, propDef.getMetadata());
}
protected SettableBeanProperty(PropertyName propName, JavaType type, PropertyName wrapper,
TypeDeserializer typeDeser, Annotations contextAnnotations,
PropertyMetadata metadata)
{
super(metadata);
// 09-Jan-2009, tatu: Intern()ing makes sense since Jackson parsed
// field names are (usually) interned too, hence lookups will be faster.
// 23-Oct-2009, tatu: should this be disabled wrt [JACKSON-180]?
// Probably need not, given that namespace of field/method names
// is not unbounded, unlike potential JSON names.
if (propName == null) {
_propName = PropertyName.NO_NAME;
} else {
_propName = propName.internSimpleName();
}
_type = type;
_wrapperName = wrapper;
_contextAnnotations = contextAnnotations;
_viewMatcher = null;
// 30-Jan-2012, tatu: Important: contextualize TypeDeserializer now...
if (typeDeser != null) {
typeDeser = typeDeser.forProperty(this);
}
_valueTypeDeserializer = typeDeser;
_valueDeserializer = MISSING_VALUE_DESERIALIZER;
_nullProvider = MISSING_VALUE_DESERIALIZER;
}
/**
* Constructor only used by {@link tools.jackson.databind.deser.impl.ObjectIdValueProperty}.
*/
protected SettableBeanProperty(PropertyName propName, JavaType type,
PropertyMetadata metadata, ValueDeserializer<Object> valueDeser)
{
super(metadata);
// as with above ctor, intern()ing probably fine
if (propName == null) {
_propName = PropertyName.NO_NAME;
} else {
_propName = propName.internSimpleName();
}
_type = type;
_wrapperName = null;
_contextAnnotations = null;
_viewMatcher = null;
_valueTypeDeserializer = null;
_valueDeserializer = valueDeser;
// 29-Jan-2017, tatu: Presumed to be irrelevant for ObjectId values...
_nullProvider = valueDeser;
}
/**
* Basic copy-constructor for sub-classes to use.
*/
protected SettableBeanProperty(SettableBeanProperty src)
{
super(src);
_propName = src._propName;
_type = src._type;
_wrapperName = src._wrapperName;
_contextAnnotations = src._contextAnnotations;
_valueDeserializer = src._valueDeserializer;
_valueTypeDeserializer = src._valueTypeDeserializer;
_managedReferenceName = src._managedReferenceName;
_propertyIndex = src._propertyIndex;
_viewMatcher = src._viewMatcher;
_objectIdInfo = src._objectIdInfo;
_nullProvider = src._nullProvider;
}
/**
* Copy-with-deserializer-change constructor for sub-classes to use.
*/
@SuppressWarnings("unchecked")
protected SettableBeanProperty(SettableBeanProperty src,
ValueDeserializer<?> deser, NullValueProvider nuller)
{
super(src);
_propName = src._propName;
_type = src._type;
_wrapperName = src._wrapperName;
_contextAnnotations = src._contextAnnotations;
_valueTypeDeserializer = src._valueTypeDeserializer;
_managedReferenceName = src._managedReferenceName;
_propertyIndex = src._propertyIndex;
if (deser == null) {
_valueDeserializer = MISSING_VALUE_DESERIALIZER;
} else {
_valueDeserializer = (ValueDeserializer<Object>) deser;
}
_viewMatcher = src._viewMatcher;
_objectIdInfo = src._objectIdInfo;
// 29-Jan-2017, tatu: Bit messy, but for now has to do...
if (nuller == MISSING_VALUE_DESERIALIZER) {
nuller = _valueDeserializer;
}
_nullProvider = nuller;
}
/**
* Copy-with-deserializer-change constructor for sub-classes to use.
*/
protected SettableBeanProperty(SettableBeanProperty src, PropertyName newName)
{
super(src);
_propName = newName;
_type = src._type;
_wrapperName = src._wrapperName;
_contextAnnotations = src._contextAnnotations;
_valueDeserializer = src._valueDeserializer;
_valueTypeDeserializer = src._valueTypeDeserializer;
_managedReferenceName = src._managedReferenceName;
_propertyIndex = src._propertyIndex;
_viewMatcher = src._viewMatcher;
_objectIdInfo = src._objectIdInfo;
_nullProvider = src._nullProvider;
}
/**
* Copy-with-type-deserializer-change constructor for sub-classes to use.
*/
protected SettableBeanProperty(SettableBeanProperty src, TypeDeserializer typeDeser)
{
super(src);
_propName = src._propName;
_type = src._type;
_wrapperName = src._wrapperName;
_contextAnnotations = src._contextAnnotations;
_valueDeserializer = src._valueDeserializer;
if (typeDeser != null) {
typeDeser = typeDeser.forProperty(this);
}
_valueTypeDeserializer = typeDeser;
_managedReferenceName = src._managedReferenceName;
_propertyIndex = src._propertyIndex;
_viewMatcher = src._viewMatcher;
_objectIdInfo = src._objectIdInfo;
_nullProvider = src._nullProvider;
}
/**
* Fluent factory method for constructing and returning a new instance
* with specified value deserializer.
* Note that this method should NOT change configuration of this instance.
*
* @param deser Deserializer to assign to the new property instance
*
* @return Newly constructed instance, if value deserializer differs from the
* one used for this instance; or 'this' if not.
*/
public abstract SettableBeanProperty withValueDeserializer(ValueDeserializer<?> deser);
/**
* Fluent factory method for constructing and returning a new instance
* with specified property name.
* Note that this method should NOT change configuration of this instance.
*
* @param newName Name to use for the new instance.
*
* @return Newly constructed instance, if property name differs from the
* one used for this instance; or 'this' if not.
*/
public abstract SettableBeanProperty withName(PropertyName newName);
public SettableBeanProperty withSimpleName(String simpleName) {
PropertyName n;
if (_propName == null) {
n = new PropertyName(simpleName);
} else {
n = _propName.withSimpleName(simpleName);
}
n = n.internSimpleName();
return (n == _propName) ? this : withName(n);
}
public abstract SettableBeanProperty withNullProvider(NullValueProvider nva);
public void setManagedReferenceName(String n) {
_managedReferenceName = n;
}
public void setObjectIdInfo(ObjectIdInfo objectIdInfo) {
_objectIdInfo = objectIdInfo;
}
public void setViews(Class<?>[] views) {
if (views == null) {
_viewMatcher = null;
} else {
_viewMatcher = ViewMatcher.construct(views);
}
}
/**
* Method used to assign index for property.
*/
public void assignIndex(int index) {
if (_propertyIndex != -1) {
if (_propertyIndex != index) {
throw new IllegalStateException("Property '"+getName()+"' already had index ("+_propertyIndex+"), trying to assign "+index);
}
}
_propertyIndex = index;
}
/**
* Method called to ensure that the mutator has proper access rights to
* be called, as per configuration. Overridden by implementations that
* have mutators that require access, fields and setters.
*/
public void fixAccess(DeserializationConfig config) {
;
}
public void markAsIgnorable() { }
public boolean isIgnorable() { return false; }
/**
* Whether this property requires merging of values (read-then-write)
*/
public boolean isMerging() {
// Most are not merging so default to this implementation
return false;
}
/*
/**********************************************************************
/* BeanProperty impl
/**********************************************************************
*/
@Override
public final String getName() {
return _propName.getSimpleName();
}
@Override
public PropertyName getFullName() {
return _propName;
}
@Override
public JavaType getType() { return _type; }
@Override
public PropertyName getWrapperName() {
return _wrapperName;
}
@Override
public abstract AnnotatedMember getMember();
@Override
public abstract <A extends Annotation> A getAnnotation(Class<A> acls);
@Override
public <A extends Annotation> A getContextAnnotation(Class<A> acls) {
return _contextAnnotations.get(acls);
}
@Override
public void depositSchemaProperty(JsonObjectFormatVisitor objectVisitor,
SerializationContext provider)
{
if (isRequired()) {
objectVisitor.property(this);
} else {
objectVisitor.optionalProperty(this);
}
}
/*
/**********************************************************************
/* Accessors
/**********************************************************************
*/
public Class<?> getDeclaringClass() {
return getMember().getDeclaringClass();
}
public String getManagedReferenceName() { return _managedReferenceName; }
public ObjectIdInfo getObjectIdInfo() { return _objectIdInfo; }
public boolean hasValueDeserializer() {
return (_valueDeserializer != null) && (_valueDeserializer != MISSING_VALUE_DESERIALIZER);
}
public boolean hasValueTypeDeserializer() { return (_valueTypeDeserializer != null); }
public ValueDeserializer<Object> getValueDeserializer() {
ValueDeserializer<Object> deser = _valueDeserializer;
if (deser == MISSING_VALUE_DESERIALIZER) {
return null;
}
return deser;
}
public TypeDeserializer getValueTypeDeserializer() { return _valueTypeDeserializer; }
public NullValueProvider getNullValueProvider() { return _nullProvider; }
public boolean visibleInView(Class<?> activeView) {
return (_viewMatcher == null) || _viewMatcher.isVisibleForView(activeView);
}
public boolean hasViews() { return _viewMatcher != null; }
/**
* Method for accessing unique index of this property; indexes are
* assigned once all properties of a {@link BeanDeserializer} have
* been collected.
*
* @return Index of this property
*/
public int getPropertyIndex() { return _propertyIndex; }
/**
* Method for accessing index of the creator property: for other
* types of properties will simply return -1.
*/
public int getCreatorIndex() {
// changed from 'return -1' in 2.7.9 / 2.8.7
throw new IllegalStateException(String.format(
"Internal error: no creator index for property '%s' (of type %s)",
this.getName(), getClass().getName()));
}
/**
* Accessor for id of injectable value, if this bean property supports
* value injection.
*/
public Object getInjectableValueId() { return null; }
/**
* Accessor for injection definition, if this bean property supports
* value injection.
*/
public JacksonInject.Value getInjectionDefinition() { return null; }
/**
* Accessor for checking whether this property is injectable, and if so,
* ONLY injectable (will not bind from input).
* Currently (2.11) can only return {@code true} for Creator-backed properties.
*
* @return True if (and only if) property has injector that is also defined NOT
* to bind from input.
*/
public boolean isInjectionOnly() { return false; } // overridden by CreatorProperty
/**
* Since 3.1
*/
public boolean isCreatorProperty() { return false; } // overridden by CreatorProperty
/*
/**********************************************************************
/* Public API
/**********************************************************************
*/
/**
* Method called to deserialize appropriate value, given parser (and
* context), and set it using appropriate mechanism.
* Pre-condition is that passed parser must point to the first token
* that should be consumed to produce the value (the only value for
* scalars, multiple for Objects and Arrays).
*/
public abstract void deserializeAndSet(JsonParser p,
DeserializationContext ctxt, Object instance) throws JacksonException;
/**
* Alternative to {@link #deserializeAndSet} that returns
* either return value of setter method called (if one is),
* or null to indicate that no return value is available.
* Mostly used to support Builder style deserialization.
*/
public abstract Object deserializeSetAndReturn(JsonParser p,
DeserializationContext ctxt, Object instance) throws JacksonException;
/**
* Method called to assign given value to this property, on
* specified Object.
*<p>
* Note: this is an optional operation, not supported by all
* implementations, creator-backed properties for example do not
* support this method.
*/
public abstract void set(DeserializationContext ctxt,
Object instance, Object value);
/**
* Method called to assign given value to this property, on
* specified Object, and return whatever delegating accessor
* returned (if anything)
*<p>
* Note: this is an optional operation, not supported by all
* implementations, creator-backed properties for example do not
* support this method.
*/
public abstract Object setAndReturn(DeserializationContext ctxt,
Object instance, Object value);
/**
* This method is needed by some specialized bean deserializers,
* and also called by some {@link #deserializeAndSet} implementations.
*<p>
* Pre-condition is that passed parser must point to the first token
* that should be consumed to produce the value (the only value for
* scalars, multiple for Objects and Arrays).
*<p>
* Note that this method is final for performance reasons: to override
* functionality you must override other methods that call this method;
* this method should also not be called directly unless you really know
* what you are doing (and probably not even then).
*/
public final Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException
{
if (p.hasToken(JsonToken.VALUE_NULL)) {
return _nullProvider.getNullValue(ctxt);
}
if (_valueTypeDeserializer != null) {
return _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
}
// 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
Object value = _valueDeserializer.deserialize(p, ctxt);
if (value == null) {
value = _nullProvider.getNullValue(ctxt);
}
return value;
}
public final Object deserializeWith(JsonParser p, DeserializationContext ctxt,
Object toUpdate) throws JacksonException
{
// 20-Oct-2016, tatu: Not 100% sure what to do; probably best to simply return
// null value and let caller decide what to do.
if (p.hasToken(JsonToken.VALUE_NULL)) {
// ... except for "skip nulls" case which should just do that:
if (NullsConstantProvider.isSkipper(_nullProvider)) {
return toUpdate;
}
return _nullProvider.getNullValue(ctxt);
}
if (_valueTypeDeserializer != null) {
// 25-Oct-2021 Added by James to support merging polymorphic property
// https://github.com/FasterXML/jackson-databind/issues/2541
// Please note we only support merging same type polymorphic property for now,
// merging different type is hard and usually doesn't make sense.
// Please note you need to configure {@link DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} as false to
// enable this feature otherwise the unknown property exception will be thrown.
JavaType subType = ctxt.getTypeFactory().constructType(toUpdate.getClass());
ValueDeserializer<Object> subTypeValueDeserializer = ctxt.findContextualValueDeserializer(subType, this);
return subTypeValueDeserializer.deserialize(p, ctxt, toUpdate);
}
// 04-May-2018, tatu: [databind#2023] Coercion from String (mostly) can give null
Object value = _valueDeserializer.deserialize(p, ctxt, toUpdate);
if (value == null) {
if (NullsConstantProvider.isSkipper(_nullProvider)) {
return toUpdate;
}
value = _nullProvider.getNullValue(ctxt);
}
return value;
}
/**
* Returns a copy of this property, unwrapped using given {@link NameTransformer}.
*
* @since 2.19
*/
public SettableBeanProperty unwrapped(DeserializationContext ctxt, NameTransformer xf)
{
String newName = xf.transform(getName());
newName = InternCache.instance.intern(newName);
SettableBeanProperty renamed = withSimpleName(newName);
ValueDeserializer<?> deser = renamed.getValueDeserializer();
if (deser != null) {
@SuppressWarnings("unchecked")
ValueDeserializer<Object> newDeser = (ValueDeserializer<Object>)
deser.unwrappingDeserializer(ctxt, xf);
if (newDeser != deser) {
renamed = renamed.withValueDeserializer(newDeser);
}
}
return renamed;
}
/*
/**********************************************************************
/* Helper methods
/**********************************************************************
*/
protected void _throwAsJacksonE(JsonParser p, Throwable e, Object value)
throws JacksonException
{
if (e instanceof IllegalArgumentException) {
String actType = ClassUtil.classNameOf(value);
StringBuilder msg = new StringBuilder("Problem deserializing property '")
.append(getName())
.append("' (expected type: ")
.append(getType())
.append("; actual type: ")
.append(actType).append(")");
String origMsg = ClassUtil.exceptionMessage(e);
if (origMsg != null) {
msg.append(", problem: ")
.append(origMsg);
} else {
msg.append(" (no error message provided)");
}
throw DatabindException.from(p, msg.toString(), e);
}
_throwAsJacksonE(p, e);
}
protected void _throwAsJacksonE(JsonParser p, Throwable e) throws JacksonException
{
ClassUtil.throwIfError(e);
ClassUtil.throwIfRTE(e);
ClassUtil.throwIfJacksonE(e);
// 10-Apr-2025: [databind#4603] no more unwrapping, retain exception
// Throwable th = ClassUtil.getRootCause(e);
// ... except for InvocationTargetException which we still unwrap as it
// adds no value
if (e instanceof InvocationTargetException ite) {
Throwable t = ite.getTargetException();
ClassUtil.throwIfRTE(t);
ClassUtil.throwIfJacksonE(t);
throw DatabindException.from(p, ClassUtil.exceptionMessage(t), t);
}
throw DatabindException.from(p, ClassUtil.exceptionMessage(e), e);
}
@Override public String toString() { return "[property '"+getName()+"']"; }
/*
/**********************************************************************
/* Helper classes
/**********************************************************************
*/
/**
* Helper class that is designed to both make it easier to sub-class
* delegating subtypes and to reduce likelihood of breakage when
* new methods are added.
*<p>
* Class was specifically added to help with {@code Afterburner}
* module, but its use is not limited to only support it.
*/
public static abstract class Delegating
extends SettableBeanProperty
{
protected final SettableBeanProperty delegate;
protected Delegating(SettableBeanProperty d) {
super(d);
delegate = d;
}
/**
* Method sub-classes must implement, to construct a new instance
* with given delegate.
*/
protected abstract SettableBeanProperty withDelegate(SettableBeanProperty d);
protected SettableBeanProperty _with(SettableBeanProperty newDelegate) {
if (newDelegate == delegate) {
return this;
}
return withDelegate(newDelegate);
}
@Override
public SettableBeanProperty withValueDeserializer(ValueDeserializer<?> deser) {
return _with(delegate.withValueDeserializer(deser));
}
@Override
public SettableBeanProperty withName(PropertyName newName) {
return _with(delegate.withName(newName));
}
@Override
public SettableBeanProperty withNullProvider(NullValueProvider nva) {
return _with(delegate.withNullProvider(nva));
}
@Override
public void assignIndex(int index) {
delegate.assignIndex(index);
}
@Override
public void fixAccess(DeserializationConfig config) {
delegate.fixAccess(config);
}
/*
/******************************************************************
/* Accessors
/******************************************************************
*/
@Override
public Class<?> getDeclaringClass() { return delegate.getDeclaringClass(); }
@Override
public String getManagedReferenceName() { return delegate.getManagedReferenceName(); }
@Override
public ObjectIdInfo getObjectIdInfo() { return delegate.getObjectIdInfo(); }
@Override
public boolean hasValueDeserializer() { return delegate.hasValueDeserializer(); }
@Override
public boolean hasValueTypeDeserializer() { return delegate.hasValueTypeDeserializer(); }
@Override
public ValueDeserializer<Object> getValueDeserializer() { return delegate.getValueDeserializer(); }
@Override
public TypeDeserializer getValueTypeDeserializer() { return delegate.getValueTypeDeserializer(); }
@Override
public boolean visibleInView(Class<?> activeView) { return delegate.visibleInView(activeView); }
@Override
public boolean hasViews() { return delegate.hasViews(); }
@Override
public int getPropertyIndex() { return delegate.getPropertyIndex(); }
@Override
public int getCreatorIndex() { return delegate.getCreatorIndex(); }
@Override
public Object getInjectableValueId() { return delegate.getInjectableValueId(); }
@Override
public boolean isInjectionOnly() { return delegate.isInjectionOnly(); }
@Override
public boolean isCreatorProperty() { return delegate.isCreatorProperty(); }
@Override
public AnnotatedMember getMember() {
return delegate.getMember();
}
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
return delegate.getAnnotation(acls);
}
/*
/******************************************************************
/* Extended API
/******************************************************************
*/
public SettableBeanProperty getDelegate() {
return delegate;
}
/*
/******************************************************************
/* Actual mutators
/******************************************************************
*/
@Override
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object instance)
throws JacksonException
{
delegate.deserializeAndSet(p, ctxt, instance);
}
@Override
public Object deserializeSetAndReturn(JsonParser p,
DeserializationContext ctxt, Object instance)
throws JacksonException
{
return delegate.deserializeSetAndReturn(p, ctxt, instance);
}
@Override
public void set(DeserializationContext ctxt, Object instance, Object value) {
delegate.set(ctxt, instance, value);
}
@Override
public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value) {
return delegate.setAndReturn(ctxt, instance, value);
}
}
}