| package com.fasterxml.jackson.databind; |
| |
| import java.io.IOException; |
| import java.text.DateFormat; |
| import java.text.ParseException; |
| import java.util.*; |
| |
| import com.fasterxml.jackson.annotation.ObjectIdGenerator; |
| import com.fasterxml.jackson.core.*; |
| |
| import com.fasterxml.jackson.databind.deser.*; |
| import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId; |
| import com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer; |
| import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; |
| import com.fasterxml.jackson.databind.introspect.Annotated; |
| import com.fasterxml.jackson.databind.introspect.ObjectIdInfo; |
| import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; |
| import com.fasterxml.jackson.databind.node.JsonNodeFactory; |
| import com.fasterxml.jackson.databind.type.TypeFactory; |
| import com.fasterxml.jackson.databind.util.ArrayBuilders; |
| import com.fasterxml.jackson.databind.util.ClassUtil; |
| import com.fasterxml.jackson.databind.util.LinkedNode; |
| import com.fasterxml.jackson.databind.util.ObjectBuffer; |
| |
| /** |
| * Context for the process of deserialization a single root-level value. |
| * Used to allow passing in configuration settings and reusable temporary |
| * objects (scrap arrays, containers). |
| *<p> |
| * Instance life-cycle is such that an partially configured "blueprint" object |
| * is registered with {@link ObjectMapper} (and {@link ObjectReader}, |
| * and when an actual instance is needed for deserialization, |
| * a fully configured instance will |
| * be created using a method in excented API of sub-class |
| * ({@link com.fasterxml.jackson.databind.deser.DefaultDeserializationContext#createInstance}). |
| * Each instance is guaranteed to only be used from single-threaded context; |
| * instances may be reused iff no configuration has changed. |
| *<p> |
| * Defined as abstract class so that implementations must define methods |
| * for reconfiguring blueprints and creating instances. |
| */ |
| public abstract class DeserializationContext |
| { |
| /** |
| * Let's limit length of error messages, for cases where underlying data |
| * may be very large -- no point in spamming logs with megs of meaningless |
| * data. |
| */ |
| private final static int MAX_ERROR_STR_LEN = 500; |
| |
| /* |
| /********************************************************** |
| /* Configuration, immutable |
| /********************************************************** |
| */ |
| |
| /** |
| * Object that handle details of {@link JsonDeserializer} caching. |
| */ |
| protected final DeserializerCache _cache; |
| |
| /* |
| /********************************************************** |
| /* Configuration, changeable via fluent factories |
| /********************************************************** |
| */ |
| |
| /** |
| * Read-only factory instance; exposed to let |
| * owners (<code>ObjectMapper</code>, <code>ObjectReader</code>) |
| * access it. |
| */ |
| public final DeserializerFactory _factory; |
| |
| /* |
| /********************************************************** |
| /* Configuration that gets set for instances (not blueprints) |
| /* (partly denormalized for performance) |
| /********************************************************** |
| */ |
| |
| /** |
| * Generic deserialization processing configuration |
| */ |
| protected final DeserializationConfig _config; |
| |
| /** |
| * Bitmap of {@link DeserializationFeature}s that are enabled |
| */ |
| protected final int _featureFlags; |
| |
| /** |
| * Currently active view, if any. |
| */ |
| protected final Class<?> _view; |
| |
| /** |
| * Currently active parser used for deserialization. |
| * May be different from the outermost parser |
| * when content is buffered. |
| */ |
| protected JsonParser _parser; |
| |
| /** |
| * Object used for resolving references to injectable |
| * values. |
| */ |
| protected final InjectableValues _injectableValues; |
| |
| /* |
| /********************************************************** |
| /* Per-operation reusable helper objects (not for blueprints) |
| /********************************************************** |
| */ |
| |
| protected ArrayBuilders _arrayBuilders; |
| |
| protected ObjectBuffer _objectBuffer; |
| |
| protected DateFormat _dateFormat; |
| |
| /* |
| /********************************************************** |
| /* Life-cycle |
| /********************************************************** |
| */ |
| |
| protected DeserializationContext(DeserializerFactory df) { |
| this(df, null); |
| } |
| |
| protected DeserializationContext(DeserializerFactory df, |
| DeserializerCache cache) |
| { |
| if (df == null) { |
| throw new IllegalArgumentException("Can not pass null DeserializerFactory"); |
| } |
| _factory = df; |
| _cache = (cache == null) ? new DeserializerCache() : cache; |
| |
| _featureFlags = 0; |
| _config = null; |
| _injectableValues = null; |
| _view = null; |
| } |
| |
| protected DeserializationContext(DeserializationContext src, |
| DeserializerFactory factory) |
| { |
| _cache = src._cache; |
| _factory = factory; |
| |
| _config = src._config; |
| _featureFlags = src._featureFlags; |
| _view = src._view; |
| _parser = src._parser; |
| _injectableValues = src._injectableValues; |
| } |
| |
| protected DeserializationContext(DeserializationContext src, |
| DeserializationConfig config, JsonParser jp, |
| InjectableValues injectableValues) |
| { |
| _cache = src._cache; |
| _factory = src._factory; |
| |
| _config = config; |
| _featureFlags = config.getDeserializationFeatures(); |
| _view = config.getActiveView(); |
| _parser = jp; |
| _injectableValues = injectableValues; |
| } |
| |
| /* |
| /********************************************************** |
| /* Public API, accessors |
| /********************************************************** |
| */ |
| |
| /** |
| * Method for accessing configuration setting object for |
| * currently active deserialization. |
| */ |
| public DeserializationConfig getConfig() { return _config; } |
| |
| /** |
| * Convenience method for checking whether specified on/off |
| * feature is enabled |
| */ |
| public final boolean isEnabled(DeserializationFeature feat) { |
| /* 03-Dec-2010, tatu: minor shortcut; since this is called quite often, |
| * let's use a local copy of feature settings: |
| */ |
| return (_featureFlags & feat.getMask()) != 0; |
| } |
| |
| public final boolean isEnabled(MapperFeature feat) { |
| return _config.isEnabled(feat); |
| } |
| |
| public final AnnotationIntrospector getAnnotationIntrospector() { |
| return _config.getAnnotationIntrospector(); |
| } |
| |
| /** |
| * Method for accessing the currently active parser. |
| * May be different from the outermost parser |
| * when content is buffered. |
| *<p> |
| * Use of this method is discouraged: if code has direct access |
| * to the active parser, that should be used instead. |
| */ |
| public final JsonParser getParser() { return _parser; } |
| |
| public final Object findInjectableValue(Object valueId, |
| BeanProperty forProperty, Object beanInstance) |
| { |
| if (_injectableValues == null) { |
| throw new IllegalStateException("No 'injectableValues' configured, can not inject value with id ["+valueId+"]"); |
| } |
| return _injectableValues.findInjectableValue(valueId, this, forProperty, beanInstance); |
| } |
| |
| /** |
| * Accessor for locating currently active view, if any; |
| * returns null if no view has been set. |
| */ |
| public final Class<?> getActiveView() { |
| return _view; |
| } |
| |
| /** |
| * Convenience method, functionally equivalent to: |
| *<pre> |
| * getConfig().canOverrideAccessModifiers(); |
| * </pre> |
| */ |
| public final boolean canOverrideAccessModifiers() { |
| return _config.canOverrideAccessModifiers(); |
| } |
| |
| /** |
| * Convenience method for accessing the default Base64 encoding |
| * used for decoding base64 encoded binary content. |
| * Same as calling: |
| *<pre> |
| * getConfig().getBase64Variant(); |
| *</pre> |
| */ |
| public final Base64Variant getBase64Variant() { |
| return _config.getBase64Variant(); |
| } |
| |
| /** |
| * Convenience method, functionally equivalent to: |
| *<pre> |
| * getConfig().getNodeFactory(); |
| * </pre> |
| */ |
| public final JsonNodeFactory getNodeFactory() { |
| return _config.getNodeFactory(); |
| } |
| |
| /** |
| * Convenience method, functionally equivalent to: |
| *<pre> |
| * getConfig().constructType(cls); |
| * </pre> |
| */ |
| public final JavaType constructType(Class<?> cls) { |
| return _config.constructType(cls); |
| } |
| |
| /** |
| * Convenience method, functionally equivalent to: |
| *<pre> |
| * getConfig().getTypeFactory(); |
| * </pre> |
| */ |
| public final TypeFactory getTypeFactory() { |
| return _config.getTypeFactory(); |
| } |
| |
| /* |
| /********************************************************** |
| /* Public API, pass-through to DeserializerCache |
| /********************************************************** |
| */ |
| |
| /** |
| * Method for checking whether we could find a deserializer |
| * for given type. |
| */ |
| public boolean hasValueDeserializerFor(JavaType type) { |
| return _cache.hasValueDeserializerFor(this, _factory, type); |
| } |
| |
| |
| /** |
| * Method for finding a value deserializer, and creating a contextual |
| * version if necessary, for value reached via specified property. |
| */ |
| @SuppressWarnings("unchecked") |
| public final JsonDeserializer<Object> findContextualValueDeserializer(JavaType type, |
| BeanProperty property) throws JsonMappingException |
| { |
| JsonDeserializer<Object> deser = _cache.findValueDeserializer(this, |
| _factory, type); |
| if (deser != null) { |
| if (deser instanceof ContextualDeserializer) { |
| deser = (JsonDeserializer<Object>)((ContextualDeserializer) deser).createContextual(this, property); |
| } |
| } |
| return deser; |
| } |
| |
| /** |
| * Method for finding a deserializer for root-level value. |
| */ |
| @SuppressWarnings("unchecked") |
| public final JsonDeserializer<Object> findRootValueDeserializer(JavaType type) |
| throws JsonMappingException |
| { |
| JsonDeserializer<Object> deser = _cache.findValueDeserializer(this, |
| _factory, type); |
| if (deser == null) { // can this occur? |
| return null; |
| } |
| if (deser instanceof ContextualDeserializer) { |
| deser = (JsonDeserializer<Object>)((ContextualDeserializer) deser).createContextual(this, null); |
| } |
| TypeDeserializer typeDeser = _factory.findTypeDeserializer(_config, type); |
| if (typeDeser != null) { |
| // important: contextualize to indicate this is for root value |
| typeDeser = typeDeser.forProperty(null); |
| return new TypeWrappedDeserializer(typeDeser, deser); |
| } |
| return deser; |
| } |
| |
| /** |
| * Convenience method, functionally same as: |
| *<pre> |
| * getDeserializerProvider().findKeyDeserializer(getConfig(), propertyType, property); |
| *</pre> |
| */ |
| public final KeyDeserializer findKeyDeserializer(JavaType keyType, |
| BeanProperty property) throws JsonMappingException { |
| KeyDeserializer kd = _cache.findKeyDeserializer(this, |
| _factory, keyType); |
| // Second: contextualize? |
| if (kd instanceof ContextualKeyDeserializer) { |
| kd = ((ContextualKeyDeserializer) kd).createContextual(this, property); |
| } |
| return kd; |
| } |
| |
| /* |
| /********************************************************** |
| /* Public API, ObjectId handling |
| /********************************************************** |
| */ |
| |
| /** |
| * Method called to find and return entry corresponding to given |
| * Object Id: will add an entry if necessary, and never returns null |
| */ |
| public abstract ReadableObjectId findObjectId(Object id, |
| ObjectIdGenerator<?> generator); |
| |
| /* |
| /********************************************************** |
| /* Extended API: handler instantiation |
| /********************************************************** |
| */ |
| |
| public abstract JsonDeserializer<Object> deserializerInstance(Annotated annotated, |
| Object deserDef) |
| throws JsonMappingException; |
| |
| public abstract KeyDeserializer keyDeserializerInstance(Annotated annotated, |
| Object deserDef) |
| throws JsonMappingException; |
| |
| public abstract ObjectIdGenerator<?> objectIdGeneratorInstance(Annotated annotated, |
| ObjectIdInfo objectIdInfo) |
| throws JsonMappingException; |
| |
| /* |
| /********************************************************** |
| /* Public API, helper object recycling |
| /********************************************************** |
| */ |
| |
| /** |
| * Method that can be used to get access to a reusable ObjectBuffer, |
| * useful for efficiently constructing Object arrays and Lists. |
| * Note that leased buffers should be returned once deserializer |
| * is done, to allow for reuse during same round of deserialization. |
| */ |
| public final ObjectBuffer leaseObjectBuffer() |
| { |
| ObjectBuffer buf = _objectBuffer; |
| if (buf == null) { |
| buf = new ObjectBuffer(); |
| } else { |
| _objectBuffer = null; |
| } |
| return buf; |
| } |
| |
| /** |
| * Method to call to return object buffer previously leased with |
| * {@link #leaseObjectBuffer}. |
| * |
| * @param buf Returned object buffer |
| */ |
| public final void returnObjectBuffer(ObjectBuffer buf) |
| { |
| /* Already have a reusable buffer? Let's retain bigger one |
| * (or if equal, favor newer one, shorter life-cycle) |
| */ |
| if (_objectBuffer == null |
| || buf.initialCapacity() >= _objectBuffer.initialCapacity()) { |
| _objectBuffer = buf; |
| } |
| } |
| |
| /** |
| * Method for accessing object useful for building arrays of |
| * primitive types (such as int[]). |
| */ |
| public final ArrayBuilders getArrayBuilders() |
| { |
| if (_arrayBuilders == null) { |
| _arrayBuilders = new ArrayBuilders(); |
| } |
| return _arrayBuilders; |
| } |
| |
| /* |
| /********************************************************** |
| /* Parsing methods that may use reusable/-cyclable objects |
| /********************************************************** |
| */ |
| |
| /** |
| * Convenience method for parsing a Date from given String, using |
| * currently configured date format (accessed using |
| * {@link DeserializationConfig#getDateFormat()}). |
| *<p> |
| * Implementation will handle thread-safety issues related to |
| * date formats such that first time this method is called, |
| * date format is cloned, and cloned instance will be retained |
| * for use during this deserialization round. |
| */ |
| public Date parseDate(String dateStr) |
| throws IllegalArgumentException |
| { |
| try { |
| return getDateFormat().parse(dateStr); |
| } catch (ParseException pex) { |
| throw new IllegalArgumentException(pex.getMessage()); |
| } |
| } |
| |
| /** |
| * Convenience method for constructing Calendar instance set |
| * to specified time, to be modified and used by caller. |
| */ |
| public Calendar constructCalendar(Date d) |
| { |
| /* 08-Jan-2008, tatu: not optimal, but should work for the |
| * most part; let's revise as needed. |
| */ |
| Calendar c = Calendar.getInstance(); |
| c.setTime(d); |
| return c; |
| } |
| |
| /* |
| /********************************************************** |
| /* Methods for problem handling, reporting |
| /********************************************************** |
| */ |
| |
| /** |
| * Method deserializers can call to inform configured {@link DeserializationProblemHandler}s |
| * of an unrecognized property. |
| * |
| * @return True if there was a configured problem handler that was able to handle the |
| * problem |
| */ |
| /** |
| * Method deserializers can call to inform configured {@link DeserializationProblemHandler}s |
| * of an unrecognized property. |
| */ |
| public boolean handleUnknownProperty(JsonParser jp, JsonDeserializer<?> deser, |
| Object instanceOrClass, String propName) |
| throws IOException, JsonProcessingException |
| { |
| LinkedNode<DeserializationProblemHandler> h = _config.getProblemHandlers(); |
| if (h != null) { |
| while (h != null) { |
| // Can bail out if it's handled |
| if (h.value().handleUnknownProperty(this, jp, deser, instanceOrClass, propName)) { |
| return true; |
| } |
| h = h.next(); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Helper method for reporting a problem with unhandled unknown exception |
| * |
| * @param instanceOrClass Either value being populated (if one has been |
| * instantiated), or Class that indicates type that would be (or |
| * have been) instantiated |
| */ |
| public void reportUnknownProperty(Object instanceOrClass, String fieldName) |
| throws JsonMappingException |
| { |
| if (isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) { |
| throw UnrecognizedPropertyException.from(_parser, instanceOrClass, fieldName); |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Methods for constructing exceptions |
| /********************************************************** |
| */ |
| |
| /** |
| * Helper method for constructing generic mapping exception for specified type |
| */ |
| public JsonMappingException mappingException(Class<?> targetClass) { |
| return mappingException(targetClass, _parser.getCurrentToken()); |
| } |
| |
| public JsonMappingException mappingException(Class<?> targetClass, JsonToken token) |
| { |
| String clsName = _calcName(targetClass); |
| return JsonMappingException.from(_parser, |
| "Can not deserialize instance of "+clsName+" out of "+token+" token"); |
| } |
| |
| /** |
| * Helper method for constructing generic mapping exception with specified |
| * message and current location information |
| */ |
| public JsonMappingException mappingException(String message) { |
| return JsonMappingException.from(getParser(), message); |
| } |
| |
| /** |
| * Helper method for constructing instantiation exception for specified type, |
| * to indicate problem with physically constructing instance of |
| * specified class (missing constructor, exception from constructor) |
| */ |
| public JsonMappingException instantiationException(Class<?> instClass, Throwable t) |
| { |
| return JsonMappingException.from(_parser, |
| "Can not construct instance of "+instClass.getName()+", problem: "+t.getMessage(), |
| t); |
| } |
| |
| public JsonMappingException instantiationException(Class<?> instClass, String msg) { |
| return JsonMappingException.from(_parser, "Can not construct instance of "+instClass.getName()+", problem: "+msg); |
| } |
| |
| /** |
| * Method that will construct an exception suitable for throwing when |
| * some String values are acceptable, but the one encountered is not. |
| */ |
| public JsonMappingException weirdStringException(Class<?> instClass, String msg) { |
| return JsonMappingException.from(_parser, "Can not construct instance of "+instClass.getName()+" from String value '"+_valueDesc()+"': "+msg); |
| } |
| |
| /** |
| * Helper method for constructing exception to indicate that input JSON |
| * Number was not suitable for deserializing into given type. |
| */ |
| public JsonMappingException weirdNumberException(Class<?> instClass, String msg) { |
| return JsonMappingException.from(_parser, "Can not construct instance of "+instClass.getName()+" from number value ("+_valueDesc()+"): "+msg); |
| } |
| |
| /** |
| * Helper method for constructing exception to indicate that given JSON |
| * Object field name was not in format to be able to deserialize specified |
| * key type. |
| */ |
| public JsonMappingException weirdKeyException(Class<?> keyClass, String keyValue, String msg) |
| { |
| return JsonMappingException.from(_parser, "Can not construct Map key of type "+keyClass.getName()+" from String \""+_desc(keyValue)+"\": "+msg); |
| } |
| |
| /** |
| * Helper method for indicating that the current token was expected to be another |
| * token. |
| */ |
| public JsonMappingException wrongTokenException(JsonParser jp, JsonToken expToken, String msg) |
| { |
| return JsonMappingException.from(jp, "Unexpected token ("+jp.getCurrentToken()+"), expected "+expToken+": "+msg); |
| } |
| |
| /** |
| * Helper method for constructing exception to indicate that given |
| * type id (parsed from JSON) could not be converted to a Java type. |
| */ |
| public JsonMappingException unknownTypeException(JavaType type, String id) |
| { |
| return JsonMappingException.from(_parser, "Could not resolve type id '"+id+"' into a subtype of "+type); |
| } |
| |
| /* |
| /********************************************************** |
| /* Overridable internal methods |
| /********************************************************** |
| */ |
| |
| protected DateFormat getDateFormat() |
| { |
| if (_dateFormat == null) { |
| // must create a clone since Formats are not thread-safe: |
| _dateFormat = (DateFormat)_config.getDateFormat().clone(); |
| } |
| return _dateFormat; |
| } |
| |
| protected String determineClassName(Object instance) |
| { |
| return ClassUtil.getClassDescription(instance); |
| } |
| |
| /* |
| /********************************************************** |
| /* Other internal methods |
| /********************************************************** |
| */ |
| |
| protected String _calcName(Class<?> cls) |
| { |
| if (cls.isArray()) { |
| return _calcName(cls.getComponentType())+"[]"; |
| } |
| return cls.getName(); |
| } |
| |
| protected String _valueDesc() |
| { |
| try { |
| return _desc(_parser.getText()); |
| } catch (Exception e) { |
| return "[N/A]"; |
| } |
| } |
| protected String _desc(String desc) |
| { |
| // !!! should we quote it? (in case there are control chars, linefeeds) |
| if (desc.length() > MAX_ERROR_STR_LEN) { |
| desc = desc.substring(0, MAX_ERROR_STR_LEN) + "]...[" + desc.substring(desc.length() - MAX_ERROR_STR_LEN); |
| } |
| return desc; |
| } |
| } |