blob: a92cbb66db3c90daf639bcc86f78fda74a83fa03 [file] [log] [blame]
package com.fasterxml.jackson.databind.deser;
import java.io.IOException;
import java.util.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.impl.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.util.NameTransformer;
import com.fasterxml.jackson.databind.util.TokenBuffer;
/**
* Class that handles deserialization using a separate
* Builder class, which is used for data binding and
* produces actual deserialized value at the end
* of data binding.
*<p>
* Note on implementation: much of code has been copied from
* {@link BeanDeserializer}; there may be opportunities to
* refactor this in future.
*/
public class BuilderBasedDeserializer
extends BeanDeserializerBase
{
private static final long serialVersionUID = 1L;
protected final AnnotatedMethod _buildMethod;
/**
* Type that the builder will produce, target type; as opposed to
* `handledType()` which refers to Builder class.
*
* @since 2.9
*/
protected final JavaType _targetType;
/*
/**********************************************************
/* Life-cycle, construction, initialization
/**********************************************************
*/
/**
* Constructor used by {@link BeanDeserializerBuilder}.
*/
public BuilderBasedDeserializer(BeanDeserializerBuilder builder,
BeanDescription beanDesc, JavaType targetType,
BeanPropertyMap properties, Map<String, SettableBeanProperty> backRefs,
Set<String> ignorableProps, boolean ignoreAllUnknown,
boolean hasViews)
{
super(builder, beanDesc, properties, backRefs,
ignorableProps, ignoreAllUnknown, hasViews);
_targetType = targetType;
_buildMethod = builder.getBuildMethod();
// 05-Mar-2012, tatu: Cannot really make Object Ids work with builders, not yet anyway
if (_objectIdReader != null) {
throw new IllegalArgumentException("Cannot use Object Id with Builder-based deserialization (type "
+beanDesc.getType()+")");
}
}
/**
* @deprecated Since 2.9
*/
@Deprecated
public BuilderBasedDeserializer(BeanDeserializerBuilder builder,
BeanDescription beanDesc,
BeanPropertyMap properties, Map<String, SettableBeanProperty> backRefs,
Set<String> ignorableProps, boolean ignoreAllUnknown,
boolean hasViews)
{
this(builder, beanDesc,
beanDesc.getType(), // Wrong! But got no access via `BeanDeserializerBuilder`
properties, backRefs, ignorableProps, ignoreAllUnknown, hasViews);
}
/**
* Copy-constructor that can be used by sub-classes to allow
* copy-on-write styling copying of settings of an existing instance.
*/
protected BuilderBasedDeserializer(BuilderBasedDeserializer src)
{
this(src, src._ignoreAllUnknown);
}
protected BuilderBasedDeserializer(BuilderBasedDeserializer src, boolean ignoreAllUnknown)
{
super(src, ignoreAllUnknown);
_buildMethod = src._buildMethod;
_targetType = src._targetType;
}
protected BuilderBasedDeserializer(BuilderBasedDeserializer src, NameTransformer unwrapper) {
super(src, unwrapper);
_buildMethod = src._buildMethod;
_targetType = src._targetType;
}
public BuilderBasedDeserializer(BuilderBasedDeserializer src, ObjectIdReader oir) {
super(src, oir);
_buildMethod = src._buildMethod;
_targetType = src._targetType;
}
public BuilderBasedDeserializer(BuilderBasedDeserializer src, Set<String> ignorableProps) {
super(src, ignorableProps);
_buildMethod = src._buildMethod;
_targetType = src._targetType;
}
public BuilderBasedDeserializer(BuilderBasedDeserializer src, BeanPropertyMap props) {
super(src, props);
_buildMethod = src._buildMethod;
_targetType = src._targetType;
}
@Override
public JsonDeserializer<Object> unwrappingDeserializer(NameTransformer unwrapper)
{
/* main thing really is to just enforce ignoring of unknown
* properties; since there may be multiple unwrapped values
* and properties for all may be interleaved...
*/
return new BuilderBasedDeserializer(this, unwrapper);
}
@Override
public BeanDeserializerBase withObjectIdReader(ObjectIdReader oir) {
return new BuilderBasedDeserializer(this, oir);
}
@Override
public BeanDeserializerBase withIgnorableProperties(Set<String> ignorableProps) {
return new BuilderBasedDeserializer(this, ignorableProps);
}
@Override
public BeanDeserializerBase withBeanProperties(BeanPropertyMap props) {
return new BuilderBasedDeserializer(this, props);
}
@Override
protected BeanDeserializerBase asArrayDeserializer() {
SettableBeanProperty[] props = _beanProperties.getPropertiesInInsertionOrder();
return new BeanAsArrayBuilderDeserializer(this, _targetType, props, _buildMethod);
}
/*
/**********************************************************
/* JsonDeserializer implementation
/**********************************************************
*/
@Override // since 2.9
public Boolean supportsUpdate(DeserializationConfig config) {
// 26-Oct-2016, tatu: No, we can't merge Builder-based POJOs as of now
return Boolean.FALSE;
}
/*
/**********************************************************
/* JsonDeserializer implementation
/**********************************************************
*/
protected Object finishBuild(DeserializationContext ctxt, Object builder)
throws IOException
{
// As per [databind#777], allow returning builder itself
if (null == _buildMethod) {
return builder;
}
try {
return _buildMethod.getMember().invoke(builder, (Object[]) null);
} catch (Exception e) {
return wrapInstantiationProblem(e, ctxt);
}
}
/**
* Main deserialization method for bean-based objects (POJOs).
*/
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
// common case first:
if (p.isExpectedStartObjectToken()) {
JsonToken t = p.nextToken();
if (_vanillaProcessing) {
return finishBuild(ctxt, vanillaDeserialize(p, ctxt, t));
}
Object builder = deserializeFromObject(p, ctxt);
return finishBuild(ctxt, builder);
}
// and then others, generally requiring use of @JsonCreator
switch (p.getCurrentTokenId()) {
case JsonTokenId.ID_STRING:
return finishBuild(ctxt, deserializeFromString(p, ctxt));
case JsonTokenId.ID_NUMBER_INT:
return finishBuild(ctxt, deserializeFromNumber(p, ctxt));
case JsonTokenId.ID_NUMBER_FLOAT:
return finishBuild(ctxt, deserializeFromDouble(p, ctxt));
case JsonTokenId.ID_EMBEDDED_OBJECT:
return p.getEmbeddedObject();
case JsonTokenId.ID_TRUE:
case JsonTokenId.ID_FALSE:
return finishBuild(ctxt, deserializeFromBoolean(p, ctxt));
case JsonTokenId.ID_START_ARRAY:
// these only work if there's a (delegating) creator...
return finishBuild(ctxt, deserializeFromArray(p, ctxt));
case JsonTokenId.ID_FIELD_NAME:
case JsonTokenId.ID_END_OBJECT:
return finishBuild(ctxt, deserializeFromObject(p, ctxt));
default:
}
return ctxt.handleUnexpectedToken(getValueType(ctxt), p);
}
/**
* Secondary deserialization method, called in cases where POJO
* instance is created as part of deserialization, potentially
* after collecting some or all of the properties to set.
*/
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt,
Object value) throws IOException
{
// 26-Oct-2016, tatu: I cannot see any of making this actually
// work correctly, so let's indicate problem right away
JavaType valueType = _targetType;
// Did they try to give us builder?
Class<?> builderRawType = handledType();
Class<?> instRawType = value.getClass();
if (builderRawType.isAssignableFrom(instRawType)) {
return ctxt.reportBadDefinition(valueType, String.format(
"Deserialization of %s by passing existing Builder (%s) instance not supported",
valueType, builderRawType.getName()));
}
return ctxt.reportBadDefinition(valueType, String.format(
"Deserialization of %s by passing existing instance (of %s) not supported",
valueType, instRawType.getName()));
}
/*
/**********************************************************
/* Concrete deserialization methods
/**********************************************************
*/
/**
* Streamlined version that is only used when no "special"
* features are enabled.
*/
private final Object vanillaDeserialize(JsonParser p,
DeserializationContext ctxt, JsonToken t)
throws IOException
{
Object bean = _valueInstantiator.createUsingDefault(ctxt);
for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
bean = prop.deserializeSetAndReturn(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
} else {
handleUnknownVanilla(p, ctxt, bean, propName);
}
}
return bean;
}
/**
* General version used when handling needs more advanced
* features.
*/
@Override
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt)
throws IOException
{
if (_nonStandardCreation) {
if (_unwrappedPropertyHandler != null) {
return deserializeWithUnwrapped(p, ctxt);
}
if (_externalTypeIdHandler != null) {
return deserializeWithExternalTypeId(p, ctxt);
}
return deserializeFromObjectUsingNonDefault(p, ctxt);
}
Object bean = _valueInstantiator.createUsingDefault(ctxt);
if (_injectables != null) {
injectValues(ctxt, bean);
}
if (_needViewProcesing) {
Class<?> view = ctxt.getActiveView();
if (view != null) {
return deserializeWithView(p, ctxt, bean, view);
}
}
for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
bean = prop.deserializeSetAndReturn(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
}
return bean;
}
/**
* Method called to deserialize bean using "property-based creator":
* this means that a non-default constructor or factory method is
* called, and then possibly other setters. The trick is that
* values for creator method need to be buffered, first; and
* due to non-guaranteed ordering possibly some other properties
* as well.
*
* @return Builder instance constructed
*/
@Override
@SuppressWarnings("resource")
protected Object _deserializeUsingPropertyBased(final JsonParser p,
final DeserializationContext ctxt)
throws IOException
{
final PropertyBasedCreator creator = _propertyBasedCreator;
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader);
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
// 04-Jan-2010, tatu: May need to collect unknown properties for polymorphic cases
TokenBuffer unknown = null;
JsonToken t = p.getCurrentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken(); // to point to value
// creator property?
SettableBeanProperty creatorProp = creator.findCreatorProperty(propName);
if (creatorProp != null) {
if ((activeView != null) && !creatorProp.visibleInView(activeView)) {
p.skipChildren();
continue;
}
// Last creator property to set?
if (buffer.assignParameter(creatorProp, creatorProp.deserialize(p, ctxt))) {
p.nextToken(); // to move to following FIELD_NAME/END_OBJECT
Object builder;
try {
builder = creator.build(ctxt, buffer);
} catch (Exception e) {
wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt);
continue; // never gets here
}
// polymorphic?
if (builder.getClass() != _beanType.getRawClass()) {
return handlePolymorphic(p, ctxt, builder, unknown);
}
if (unknown != null) { // nope, just extra unknown stuff...
builder = handleUnknownProperties(ctxt, builder, unknown);
}
// or just clean?
return _deserialize(p, ctxt, builder);
}
continue;
}
// Object Id property?
if (buffer.readIdProperty(propName)) {
continue;
}
// regular property? needs buffering
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) {
buffer.bufferProperty(prop, prop.deserialize(p, ctxt));
continue;
}
// As per [JACKSON-313], things marked as ignorable should not be
// passed to any setter
if (_ignorableProps != null && _ignorableProps.contains(propName)) {
handleIgnoredProperty(p, ctxt, handledType(), propName);
continue;
}
// "any property"?
if (_anySetter != null) {
buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt));
continue;
}
// Ok then, let's collect the whole field; name and value
if (unknown == null) {
unknown = new TokenBuffer(p, ctxt);
}
unknown.writeFieldName(propName);
unknown.copyCurrentStructure(p);
}
// We hit END_OBJECT, so:
Object builder;
try {
builder = creator.build(ctxt, buffer);
} catch (Exception e) {
builder = wrapInstantiationProblem(e, ctxt);
}
if (unknown != null) {
// polymorphic?
if (builder.getClass() != _beanType.getRawClass()) {
return handlePolymorphic(null, ctxt, builder, unknown);
}
// no, just some extra unknown properties
return handleUnknownProperties(ctxt, builder, unknown);
}
return builder;
}
@SuppressWarnings("resource")
protected final Object _deserialize(JsonParser p,
DeserializationContext ctxt, Object builder) throws IOException
{
if (_injectables != null) {
injectValues(ctxt, builder);
}
if (_unwrappedPropertyHandler != null) {
if (p.hasToken(JsonToken.START_OBJECT)) {
p.nextToken();
}
TokenBuffer tokens = new TokenBuffer(p, ctxt);
tokens.writeStartObject();
return deserializeWithUnwrapped(p, ctxt, builder, tokens);
}
if (_externalTypeIdHandler != null) {
return deserializeWithExternalTypeId(p, ctxt, builder);
}
if (_needViewProcesing) {
Class<?> view = ctxt.getActiveView();
if (view != null) {
return deserializeWithView(p, ctxt, builder, view);
}
}
JsonToken t = p.getCurrentToken();
// 23-Mar-2010, tatu: In some cases, we start with full JSON object too...
if (t == JsonToken.START_OBJECT) {
t = p.nextToken();
}
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
builder = prop.deserializeSetAndReturn(p, ctxt, builder);
} catch (Exception e) {
wrapAndThrow(e, builder, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, builder, propName);
}
return builder;
}
/*
/**********************************************************
/* Deserializing when we have to consider an active View
/**********************************************************
*/
protected final Object deserializeWithView(JsonParser p, DeserializationContext ctxt,
Object bean, Class<?> activeView)
throws IOException
{
JsonToken t = p.getCurrentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
// Skip field name:
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) {
if (!prop.visibleInView(activeView)) {
p.skipChildren();
continue;
}
try {
bean = prop.deserializeSetAndReturn(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
}
return bean;
}
/*
/**********************************************************
/* Handling for cases where we have "unwrapped" values
/**********************************************************
*/
/**
* Method called when there are declared "unwrapped" properties
* which need special handling
*/
@SuppressWarnings("resource")
protected Object deserializeWithUnwrapped(JsonParser p, DeserializationContext ctxt)
throws IOException
{
if (_delegateDeserializer != null) {
return _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(p, ctxt));
}
if (_propertyBasedCreator != null) {
return deserializeUsingPropertyBasedWithUnwrapped(p, ctxt);
}
TokenBuffer tokens = new TokenBuffer(p, ctxt);
tokens.writeStartObject();
Object bean = _valueInstantiator.createUsingDefault(ctxt);
if (_injectables != null) {
injectValues(ctxt, bean);
}
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
if (activeView != null && !prop.visibleInView(activeView)) {
p.skipChildren();
continue;
}
try {
bean = prop.deserializeSetAndReturn(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
// ignorable things should be ignored
if (_ignorableProps != null && _ignorableProps.contains(propName)) {
handleIgnoredProperty(p, ctxt, bean, propName);
continue;
}
// but... others should be passed to unwrapped property deserializers
tokens.writeFieldName(propName);
tokens.copyCurrentStructure(p);
// how about any setter? We'll get copies but...
if (_anySetter != null) {
try {
_anySetter.deserializeAndSet(p, ctxt, bean, propName);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
}
tokens.writeEndObject();
return _unwrappedPropertyHandler.processUnwrapped(p, ctxt, bean, tokens);
}
@SuppressWarnings("resource")
protected Object deserializeUsingPropertyBasedWithUnwrapped(JsonParser p,
DeserializationContext ctxt)
throws IOException
{
final PropertyBasedCreator creator = _propertyBasedCreator;
PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, _objectIdReader);
TokenBuffer tokens = new TokenBuffer(p, ctxt);
tokens.writeStartObject();
Object builder = null;
JsonToken t = p.getCurrentToken();
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
p.nextToken(); // to point to value
// creator property?
SettableBeanProperty creatorProp = creator.findCreatorProperty(propName);
if (creatorProp != null) {
// Last creator property to set?
if (buffer.assignParameter(creatorProp, creatorProp.deserialize(p, ctxt))) {
t = p.nextToken(); // to move to following FIELD_NAME/END_OBJECT
try {
builder = creator.build(ctxt, buffer);
} catch (Exception e) {
wrapAndThrow(e, _beanType.getRawClass(), propName, ctxt);
continue; // never gets here
}
if (builder.getClass() != _beanType.getRawClass()) {
return handlePolymorphic(p, ctxt, builder, tokens);
}
return deserializeWithUnwrapped(p, ctxt, builder, tokens);
}
continue;
}
// Object Id property?
if (buffer.readIdProperty(propName)) {
continue;
}
// regular property? needs buffering
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) {
buffer.bufferProperty(prop, prop.deserialize(p, ctxt));
continue;
}
if (_ignorableProps != null && _ignorableProps.contains(propName)) {
handleIgnoredProperty(p, ctxt, handledType(), propName);
continue;
}
tokens.writeFieldName(propName);
tokens.copyCurrentStructure(p);
// "any property"?
if (_anySetter != null) {
buffer.bufferAnyProperty(_anySetter, propName, _anySetter.deserialize(p, ctxt));
}
}
tokens.writeEndObject();
// We hit END_OBJECT, so:
if (builder == null) {
try {
builder = creator.build(ctxt, buffer);
} catch (Exception e) {
return wrapInstantiationProblem(e, ctxt);
}
}
return _unwrappedPropertyHandler.processUnwrapped(p, ctxt, builder, tokens);
}
protected Object deserializeWithUnwrapped(JsonParser p,
DeserializationContext ctxt, Object builder, TokenBuffer tokens)
throws IOException
{
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
for (JsonToken t = p.getCurrentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
SettableBeanProperty prop = _beanProperties.find(propName);
p.nextToken();
if (prop != null) { // normal case
if (activeView != null && !prop.visibleInView(activeView)) {
p.skipChildren();
continue;
}
try {
builder = prop.deserializeSetAndReturn(p, ctxt, builder);
} catch (Exception e) {
wrapAndThrow(e, builder, propName, ctxt);
}
continue;
}
if (_ignorableProps != null && _ignorableProps.contains(propName)) {
handleIgnoredProperty(p, ctxt, builder, propName);
continue;
}
// but... others should be passed to unwrapped property deserializers
tokens.writeFieldName(propName);
tokens.copyCurrentStructure(p);
// how about any setter? We'll get copies but...
if (_anySetter != null) {
_anySetter.deserializeAndSet(p, ctxt, builder, propName);
}
}
tokens.writeEndObject();
return _unwrappedPropertyHandler.processUnwrapped(p, ctxt, builder, tokens);
}
/*
/**********************************************************
/* Handling for cases where we have property/-ies with
/* external type id
/**********************************************************
*/
protected Object deserializeWithExternalTypeId(JsonParser p, DeserializationContext ctxt)
throws IOException
{
if (_propertyBasedCreator != null) {
return deserializeUsingPropertyBasedWithExternalTypeId(p, ctxt);
}
return deserializeWithExternalTypeId(p, ctxt, _valueInstantiator.createUsingDefault(ctxt));
}
protected Object deserializeWithExternalTypeId(JsonParser p,
DeserializationContext ctxt, Object bean)
throws IOException
{
final Class<?> activeView = _needViewProcesing ? ctxt.getActiveView() : null;
final ExternalTypeHandler ext = _externalTypeIdHandler.start();
for (JsonToken t = p.getCurrentToken(); t == JsonToken.FIELD_NAME; t = p.nextToken()) {
String propName = p.getCurrentName();
t = p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
// May have property AND be used as external type id:
if (t.isScalarValue()) {
ext.handleTypePropertyValue(p, ctxt, propName, bean);
}
if (activeView != null && !prop.visibleInView(activeView)) {
p.skipChildren();
continue;
}
try {
bean = prop.deserializeSetAndReturn(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
// ignorable things should be ignored
if (_ignorableProps != null && _ignorableProps.contains(propName)) {
handleIgnoredProperty(p, ctxt, bean, propName);
continue;
}
// but others are likely to be part of external type id thingy...
if (ext.handlePropertyValue(p, ctxt, propName, bean)) {
continue;
}
// if not, the usual fallback handling:
if (_anySetter != null) {
try {
_anySetter.deserializeAndSet(p, ctxt, bean, propName);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
} else {
// Unknown: let's call handler method
handleUnknownProperty(p, ctxt, bean, propName);
}
}
// and when we get this far, let's try finalizing the deal:
return ext.complete(p, ctxt, bean);
}
protected Object deserializeUsingPropertyBasedWithExternalTypeId(JsonParser p,
DeserializationContext ctxt)
throws IOException
{
// !!! 04-Mar-2012, TODO: Need to fix -- will not work as is...
JavaType t = _targetType;
return ctxt.reportBadDefinition(t, String.format(
"Deserialization (of %s) with Builder, External type id, @JsonCreator not yet implemented",
t));
}
}