package com.fasterxml.jackson.databind.deser; import java.io.IOException; import java.lang.annotation.Annotation; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.util.InternCache; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.impl.NullProvider; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.Annotations; import com.fasterxml.jackson.databind.util.ViewMatcher; /** * Base class for deserilizable 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 implements BeanProperty, java.io.Serializable { private static final long serialVersionUID = -1026580169193933453L; /** * 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 transient 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). */ 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; /** * Helper object used for checking whether this property is to * be included in the active view, if property is view-specific; * null otherwise. */ 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. */ protected int _propertyIndex = -1; /* /********************************************************** /* Life-cycle (construct & configure) /********************************************************** */ protected SettableBeanProperty(BeanPropertyDefinition propDef, JavaType type, TypeDeserializer typeDeser, Annotations contextAnnotations) { this(propDef.getName(), type, typeDeser, contextAnnotations); } 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]? /* Probably need not, given that namespace of field/method names * is not unbounded, unlike potential JSON names. */ if (propName == null || propName.length() == 0) { _propName = ""; } else { _propName = InternCache.instance.intern(propName); } _type = type; _contextAnnotations = contextAnnotations; _viewMatcher = null; // 30-Jan-2012, tatu: Important: contextualize TypeDeserializer now... if (typeDeser != null) { typeDeser = typeDeser.forProperty(this); } _valueTypeDeserializer = typeDeser; } /** * Basic copy-constructor for sub-classes to use. */ 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; _viewMatcher = src._viewMatcher; } /** * Copy-with-deserializer-change constructor for sub-classes to use. */ @SuppressWarnings("unchecked") protected SettableBeanProperty(SettableBeanProperty src, JsonDeserializer<?> deser) { _propName = src._propName; _type = src._type; _contextAnnotations = src._contextAnnotations; _valueTypeDeserializer = src._valueTypeDeserializer; _managedReferenceName = src._managedReferenceName; _propertyIndex = src._propertyIndex; _valueDeserializer = (JsonDeserializer<Object>) deser; if (deser == null) { _nullProvider = null; } else { Object nvl = deser.getNullValue(); _nullProvider = (nvl == null) ? null : new NullProvider(_type, nvl); } _viewMatcher = src._viewMatcher; } /** * Copy-with-deserializer-change constructor for sub-classes to use. */ protected SettableBeanProperty(SettableBeanProperty src, String newName) { _propName = newName; _type = src._type; _contextAnnotations = src._contextAnnotations; _valueDeserializer = src._valueDeserializer; _valueTypeDeserializer = src._valueTypeDeserializer; _nullProvider = src._nullProvider; _managedReferenceName = src._managedReferenceName; _propertyIndex = src._propertyIndex; _viewMatcher = src._viewMatcher; } /** * 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(JsonDeserializer<?> deser); /** * Fluent factory method for constructing and returning a new instance * with specified propert 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(String newName); public void setManagedReferenceName(String n) { _managedReferenceName = n; } 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) { 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; } public abstract <A extends Annotation> A getAnnotation(Class<A> acls); // @Override public abstract AnnotatedMember getMember(); public <A extends Annotation> A getContextAnnotation(Class<A> acls) { return _contextAnnotations.get(acls); } /* /********************************************************** /* Accessors /********************************************************** */ protected final Class<?> getDeclaringClass() { return getMember().getDeclaringClass(); } public String getManagedReferenceName() { return _managedReferenceName; } public boolean hasValueDeserializer() { return (_valueDeserializer != null); } public boolean hasValueTypeDeserializer() { return (_valueTypeDeserializer != null); } public JsonDeserializer<Object> getValueDeserializer() { return _valueDeserializer; } public TypeDeserializer getValueTypeDeserializer() { return _valueTypeDeserializer; } 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. * * @since 2.1 */ public int getCreatorIndex() { return -1; } /** * Accessor for id of injectable value, if this bean property supports * value injection. */ 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; /** * 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. * * @since 2.0 */ public abstract Object deserializeSetAndReturn(JsonParser jp, DeserializationContext ctxt, Object instance) throws IOException, JsonProcessingException; /** * 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(Object instance, Object value) throws IOException; /** * 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. * * @since 2.0 */ public abstract Object setAndReturn(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). *<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 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(getName()); 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()+"']"; } }