blob: e477b5c46fbf5be57d0adf510616961f9876ddc5 [file] [log] [blame]
package org.codehaus.jackson.map.deser;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import org.codehaus.jackson.*;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.introspect.AnnotatedField;
import org.codehaus.jackson.map.introspect.AnnotatedMember;
import org.codehaus.jackson.map.introspect.AnnotatedMethod;
import org.codehaus.jackson.map.util.Annotations;
import org.codehaus.jackson.map.util.ClassUtil;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.util.InternCache;
/**
* Base class for settable properties of a bean: contains
* both type and name definitions, and reflection-based set functionality.
* Concrete sub-classes implement details, so that both field- and
* setter-backed properties can be handled
*/
public abstract class SettableBeanProperty
implements BeanProperty // since 1.7
{
/**
* Logical name of the property (often but not always derived
* from the setter method name)
*/
protected final String _propName;
/**
* Base type for property; may be a supertype of actual value.
*/
protected final JavaType _type;
/**
* 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 Annotations _contextAnnotations;
/**
* Deserializer used for handling property value.
*/
protected JsonDeserializer<Object> _valueDeserializer;
/**
* If value will contain type information (to support
* polymorphic handling), this is the type deserializer
* used to handle type resolution.
*/
protected TypeDeserializer _valueTypeDeserializer;
/**
* Object used to figure out value to be used when 'null' literal is encountered in JSON.
* For most types simply Java null, but for primitive types must
* be a non-null value (like Integer.valueOf(0) for int).
*
* @since 1.7
*/
protected NullProvider _nullProvider;
/**
* If property represents a managed (forward) reference
* (see [JACKSON-235]), we will need name of reference for
* later linking.
*/
protected String _managedReferenceName;
/**
* 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.
*
* @since 1.7
*/
protected int _propertyIndex = -1;
/*
/**********************************************************
/* Life-cycle (construct & configure)
/**********************************************************
*/
protected SettableBeanProperty(String propName, JavaType type, TypeDeserializer typeDeser,
Annotations contextAnnotations)
{
/* 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]?
if (propName == null || propName.length() == 0) {
_propName = "";
} else {
_propName = InternCache.instance.intern(propName);
}
_type = type;
_contextAnnotations = contextAnnotations;
_valueTypeDeserializer = typeDeser;
}
/**
* Basic copy-constructor for sub-classes to use.
*
* @since 1.9
*/
protected SettableBeanProperty(SettableBeanProperty src)
{
_propName = src._propName;
_type = src._type;
_contextAnnotations = src._contextAnnotations;
_valueDeserializer = src._valueDeserializer;
_valueTypeDeserializer = src._valueTypeDeserializer;
_nullProvider = src._nullProvider;
_managedReferenceName = src._managedReferenceName;
_propertyIndex = src._propertyIndex;
}
/**
* Copy-with-deserializer-change constructor for sub-classes to use.
*
* @since 1.9
*/
protected SettableBeanProperty(SettableBeanProperty src, JsonDeserializer<Object> deser)
{
_propName = src._propName;
_type = src._type;
_contextAnnotations = src._contextAnnotations;
_valueTypeDeserializer = src._valueTypeDeserializer;
_managedReferenceName = src._managedReferenceName;
_propertyIndex = src._propertyIndex;
_valueDeserializer = deser;
if (deser == null) {
_nullProvider = null;
} else {
Object nvl = deser.getNullValue();
_nullProvider = (nvl == null) ? null : new NullProvider(_type, nvl);
}
}
@Deprecated
public void setValueDeserializer(JsonDeserializer<Object> deser)
{
if (_valueDeserializer != null) { // sanity check
throw new IllegalStateException("Already had assigned deserializer for property '"+getName()+"' (class "+getDeclaringClass().getName()+")");
}
_valueDeserializer = deser;
Object nvl = _valueDeserializer.getNullValue();
_nullProvider = (nvl == null) ? null : new NullProvider(_type, nvl);
}
/**
* @since 1.9
*/
public abstract SettableBeanProperty withValueDeserializer(JsonDeserializer<Object> deser);
public void setManagedReferenceName(String n) {
_managedReferenceName = n;
}
/**
* Method used to assign index for property.
*
* @since 1.7
*/
public void assignIndex(int index) {
if (_propertyIndex != -1) {
throw new IllegalStateException("Property '"+getName()+"' already had index ("+_propertyIndex+"), trying to assign "+index);
}
_propertyIndex = index;
}
/*
/**********************************************************
/* BeanProperty impl
/**********************************************************
*/
@Override
public final String getName() { return _propName; }
@Override
public JavaType getType() { return _type; }
@Override
public abstract <A extends Annotation> A getAnnotation(Class<A> acls);
@Override
public abstract AnnotatedMember getMember();
@Override
public <A extends Annotation> A getContextAnnotation(Class<A> acls) {
return _contextAnnotations.get(acls);
}
/*
/**********************************************************
/* Accessors
/**********************************************************
*/
protected final Class<?> getDeclaringClass() {
return getMember().getDeclaringClass();
}
/**
* @deprecated Since 1.7, use {@link #getName} instead.
*/
@Deprecated
public String getPropertyName() { return _propName; }
public String getManagedReferenceName() { return _managedReferenceName; }
public boolean hasValueDeserializer() { return (_valueDeserializer != null); }
/**
* @since 1.9
*/
public boolean hasValueTypeDeserializer() { return (_valueTypeDeserializer != null); }
/**
* @since 1.9
*/
public JsonDeserializer<Object> getValueDeserializer() { return _valueDeserializer; }
/**
* @since 1.9
*/
public TypeDeserializer getValueTypeDeserializer() { return _valueTypeDeserializer; }
/**
* 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
*
* @since 1.7 Although note that it was misspelled as "getProperytIndex"; fixed in 1.9
*/
public int getPropertyIndex() { return _propertyIndex; }
/**
* @deprecated Since 1.9: use {@link #getPropertyIndex} instead
*/
@Deprecated
public int getProperytIndex() { return getPropertyIndex(); }
/**
* Accessor for id of injectable value, if this bean property supports
* value injection.
*
* @since 1.9
*/
public Object getInjectableValueId() { return null; }
/*
/**********************************************************
/* 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 jp, DeserializationContext ctxt,
Object instance)
throws IOException, JsonProcessingException;
public abstract void set(Object instance, Object value)
throws IOException;
/**
* 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).
*/
public final Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NULL) {
return (_nullProvider == null) ? null : _nullProvider.nullValue(ctxt);
}
if (_valueTypeDeserializer != null) {
return _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
}
return _valueDeserializer.deserialize(jp, ctxt);
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
/**
* Method that takes in exception of any type, and casts or wraps it
* to an IOException or its subclass.
*/
protected void _throwAsIOE(Exception e, Object value)
throws IOException
{
if (e instanceof IllegalArgumentException) {
String actType = (value == null) ? "[NULL]" : value.getClass().getName();
StringBuilder msg = new StringBuilder("Problem deserializing property '").append(getPropertyName());
msg.append("' (expected type: ").append(getType());
msg.append("; actual type: ").append(actType).append(")");
String origMsg = e.getMessage();
if (origMsg != null) {
msg.append(", problem: ").append(origMsg);
} else {
msg.append(" (no error message provided)");
}
throw new JsonMappingException(msg.toString(), null, e);
}
_throwAsIOE(e);
}
protected IOException _throwAsIOE(Exception e)
throws IOException
{
if (e instanceof IOException) {
throw (IOException) e;
}
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
// let's wrap the innermost problem
Throwable th = e;
while (th.getCause() != null) {
th = th.getCause();
}
throw new JsonMappingException(th.getMessage(), null, th);
}
@Override public String toString() { return "[property '"+getName()+"']"; }
/*
/**********************************************************
/* Implementation classes
/**********************************************************
*/
/**
* This concrete sub-class implements property that is set
* using regular "setter" method.
*/
public final static class MethodProperty
extends SettableBeanProperty
{
protected final AnnotatedMethod _annotated;
/**
* Setter method for modifying property value; used for
* "regular" method-accessible properties.
*/
protected final Method _setter;
public MethodProperty(String name, JavaType type, TypeDeserializer typeDeser,
Annotations contextAnnotations, AnnotatedMethod method)
{
super(name, type, typeDeser, contextAnnotations);
_annotated = method;
_setter = method.getAnnotated();
}
protected MethodProperty(MethodProperty src, JsonDeserializer<Object> deser) {
super(src, deser);
_annotated = src._annotated;
_setter = src._setter;
}
@Override
public MethodProperty withValueDeserializer(JsonDeserializer<Object> deser) {
return new MethodProperty(this, deser);
}
/*
/**********************************************************
/* BeanProperty impl
/**********************************************************
*/
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
return _annotated.getAnnotation(acls);
}
@Override public AnnotatedMember getMember() { return _annotated; }
/*
/**********************************************************
/* Overridden methods
/**********************************************************
*/
@Override
public void deserializeAndSet(JsonParser jp, DeserializationContext ctxt,
Object instance)
throws IOException, JsonProcessingException
{
set(instance, deserialize(jp, ctxt));
}
@Override
public final void set(Object instance, Object value)
throws IOException
{
try {
_setter.invoke(instance, value);
} catch (Exception e) {
_throwAsIOE(e, value);
}
}
}
/**
* This concrete sub-class implements Collection or Map property that is
* indirectly by getting the property value and directly modifying it.
*/
public final static class SetterlessProperty
extends SettableBeanProperty
{
protected final AnnotatedMethod _annotated;
/**
* Get method for accessing property value used to access property
* (of Collection or Map type) to modify.
*/
protected final Method _getter;
public SetterlessProperty(String name, JavaType type, TypeDeserializer typeDeser,
Annotations contextAnnotations, AnnotatedMethod method)
{
super(name, type, typeDeser, contextAnnotations);
_annotated = method;
_getter = method.getAnnotated();
}
protected SetterlessProperty(SetterlessProperty src, JsonDeserializer<Object> deser) {
super(src, deser);
_annotated = src._annotated;
_getter = src._getter;
}
@Override
public SetterlessProperty withValueDeserializer(JsonDeserializer<Object> deser) {
return new SetterlessProperty(this, deser);
}
/*
/**********************************************************
/* BeanProperty impl
/**********************************************************
*/
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
return _annotated.getAnnotation(acls);
}
@Override public AnnotatedMember getMember() { return _annotated; }
/*
/**********************************************************
/* Overridden methods
/**********************************************************
*/
@Override
public final void deserializeAndSet(JsonParser jp, DeserializationContext ctxt,
Object instance)
throws IOException, JsonProcessingException
{
JsonToken t = jp.getCurrentToken();
if (t == JsonToken.VALUE_NULL) {
/* Hmmh. Is this a problem? We won't be setting anything, so it's
* equivalent of empty Collection/Map in this case
*/
return;
}
// Ok: then, need to fetch Collection/Map to modify:
Object toModify;
try {
toModify = _getter.invoke(instance);
} catch (Exception e) {
_throwAsIOE(e);
return; // never gets here
}
/* Note: null won't work, since we can't then inject anything
* in. At least that's not good in common case. However,
* theoretically the case where we get JSON null might
* be compatible. If so, implementation could be changed.
*/
if (toModify == null) {
throw new JsonMappingException("Problem deserializing 'setterless' property '"+getName()+"': get method returned null");
}
_valueDeserializer.deserialize(jp, ctxt, toModify);
}
@Override
public final void set(Object instance, Object value)
throws IOException
{
throw new UnsupportedOperationException("Should never call 'set' on setterless property");
}
}
/**
* This concrete sub-class implements property that is set
* directly assigning to a Field.
*/
public final static class FieldProperty
extends SettableBeanProperty
{
protected final AnnotatedField _annotated;
/**
* Actual field to set when deserializing this property.
*/
protected final Field _field;
public FieldProperty(String name, JavaType type, TypeDeserializer typeDeser,
Annotations contextAnnotations, AnnotatedField field)
{
super(name, type, typeDeser, contextAnnotations);
_annotated = field;
_field = field.getAnnotated();
}
protected FieldProperty(FieldProperty src, JsonDeserializer<Object> deser) {
super(src, deser);
_annotated = src._annotated;
_field = src._field;
}
@Override
public FieldProperty withValueDeserializer(JsonDeserializer<Object> deser) {
return new FieldProperty(this, deser);
}
/*
/**********************************************************
/* BeanProperty impl
/**********************************************************
*/
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
return _annotated.getAnnotation(acls);
}
@Override public AnnotatedMember getMember() { return _annotated; }
/*
/**********************************************************
/* Overridden methods
/**********************************************************
*/
@Override
public void deserializeAndSet(JsonParser jp, DeserializationContext ctxt,
Object instance)
throws IOException, JsonProcessingException
{
set(instance, deserialize(jp, ctxt));
}
@Override
public final void set(Object instance, Object value)
throws IOException
{
try {
_field.set(instance, value);
} catch (Exception e) {
_throwAsIOE(e, value);
}
}
}
/**
* Wrapper property that is used to handle managed (forward) properties
* (see [JACKSON-235] for more information). Basically just need to
* delegate first to actual forward property, and
*
* @author tatu
*/
public final static class ManagedReferenceProperty
extends SettableBeanProperty
{
protected final String _referenceName;
/**
* Flag that indicates whether property to handle is a container type
* (array, Collection, Map) or not.
*/
protected final boolean _isContainer;
protected final SettableBeanProperty _managedProperty;
protected final SettableBeanProperty _backProperty;
public ManagedReferenceProperty(String refName,
SettableBeanProperty forward, SettableBeanProperty backward,
Annotations contextAnnotations,
boolean isContainer)
{
super(forward.getName(), forward.getType(), forward._valueTypeDeserializer,
contextAnnotations);
_referenceName = refName;
_managedProperty = forward;
_backProperty = backward;
_isContainer = isContainer;
}
protected ManagedReferenceProperty(ManagedReferenceProperty src, JsonDeserializer<Object> deser)
{
super(src, deser);
_referenceName = src._referenceName;
_isContainer = src._isContainer;
_managedProperty = src._managedProperty;
_backProperty = src._backProperty;
}
@Override
public ManagedReferenceProperty withValueDeserializer(JsonDeserializer<Object> deser) {
return new ManagedReferenceProperty(this, deser);
}
/*
/**********************************************************
/* BeanProperty impl
/**********************************************************
*/
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
return _managedProperty.getAnnotation(acls);
}
@Override public AnnotatedMember getMember() { return _managedProperty.getMember(); }
/*
/**********************************************************
/* Overridden methods
/**********************************************************
*/
@Override
public void deserializeAndSet(JsonParser jp, DeserializationContext ctxt,
Object instance)
throws IOException, JsonProcessingException
{
set(instance, _managedProperty.deserialize(jp, ctxt));
}
@Override
public final void set(Object instance, Object value)
throws IOException
{
_managedProperty.set(instance, value);
/* And then back reference, if (and only if!) we actually have a non-null
* reference
*/
if (value != null) {
if (_isContainer) { // ok, this gets ugly... but has to do for now
if (value instanceof Object[]) {
for (Object ob : (Object[]) value) {
if (ob != null) {
_backProperty.set(ob, instance);
}
}
} else if (value instanceof Collection<?>) {
for (Object ob : (Collection<?>) value) {
if (ob != null) {
_backProperty.set(ob, instance);
}
}
} else if (value instanceof Map<?,?>) {
for (Object ob : ((Map<?,?>) value).values()) {
if (ob != null) {
_backProperty.set(ob, instance);
}
}
} else {
throw new IllegalStateException("Unsupported container type ("+value.getClass().getName()
+") when resolving reference '"+_referenceName+"'");
}
} else {
_backProperty.set(value, instance);
}
}
}
}
/**
* This sub-class is used to handle special case of value being a
* non-static inner class. If so, we will have to use a special
* alternative for default constructor; but otherwise can delegate
* to regular implementation.
*
* @since 1.9
*/
public final static class InnerClassProperty
extends SettableBeanProperty
{
/**
* Actual property that we use after value construction.
*/
protected final SettableBeanProperty _delegate;
/**
* Single-arg constructor we use for value instantiation.
*/
protected final Constructor<?> _creator;
public InnerClassProperty(SettableBeanProperty delegate,
Constructor<?> ctor)
{
super(delegate);
_delegate = delegate;
_creator = ctor;
}
protected InnerClassProperty(InnerClassProperty src, JsonDeserializer<Object> deser)
{
super(src, deser);
_delegate = src._delegate.withValueDeserializer(deser);
_creator = src._creator;
}
@Override
public InnerClassProperty withValueDeserializer(JsonDeserializer<Object> deser) {
return new InnerClassProperty(this, deser);
}
// // // BeanProperty impl
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
return _delegate.getAnnotation(acls);
}
@Override public AnnotatedMember getMember() { return _delegate.getMember(); }
// // // Overridden methods
@Override
public void deserializeAndSet(JsonParser jp, DeserializationContext ctxt,
Object bean)
throws IOException, JsonProcessingException
{
// mostly copied from super class impl:
JsonToken t = jp.getCurrentToken();
Object value;
if (t == JsonToken.VALUE_NULL) {
value = (_nullProvider == null) ? null : _nullProvider.nullValue(ctxt);
} else if (_valueTypeDeserializer != null) {
value = _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
} else { // the usual case
try {
value = _creator.newInstance(bean);
} catch (Exception e) {
ClassUtil.unwrapAndThrowAsIAE(e, "Failed to instantiate class "+_creator.getDeclaringClass().getName()+", problem: "+e.getMessage());
value = null;
}
_valueDeserializer.deserialize(jp, ctxt, value);
}
set(bean, value);
}
@Override
public final void set(Object instance, Object value) throws IOException
{
_delegate.set(instance, value);
}
}
/*
/**********************************************************
/* Other helper classes
/**********************************************************
*/
/**
* To support [JACKSON-420] we need bit more indirection; this is used to produce
* artificial failure for primitives that don't accept JSON null as value.
*/
protected final static class NullProvider
{
private final Object _nullValue;
private final boolean _isPrimitive;
private final Class<?> _rawType;
protected NullProvider(JavaType type, Object nullValue)
{
_nullValue = nullValue;
// [JACKSON-420]
_isPrimitive = type.isPrimitive();
_rawType = type.getRawClass();
}
public Object nullValue(DeserializationContext ctxt) throws JsonProcessingException
{
if (_isPrimitive && ctxt.isEnabled(DeserializationConfig.Feature.FAIL_ON_NULL_FOR_PRIMITIVES)) {
throw ctxt.mappingException("Can not map JSON null into type "+_rawType.getName()
+" (set DeserializationConfig.Feature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)");
}
return _nullValue;
}
}
}