package com.fasterxml.jackson.databind.deser.std; import java.io.IOException; import java.util.*; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.NullValueProvider; import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; import com.fasterxml.jackson.databind.deser.ValueInstantiator; import com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator; import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer; import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; import com.fasterxml.jackson.databind.util.ClassUtil; /** * Deserializer for {@link EnumMap} values. * <p> * Note: casting within this class is all messed up -- just could not figure out a way * to properly deal with recursive definition of "EnumMap<K extends Enum<K>, V> */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class EnumMapDeserializer extends ContainerDeserializerBase<EnumMap<?,?>> implements ContextualDeserializer, ResolvableDeserializer { private static final long serialVersionUID = 1; protected final Class<?> _enumClass; protected KeyDeserializer _keyDeserializer; protected JsonDeserializer<Object> _valueDeserializer; /** * If value instances have polymorphic type information, this * is the type deserializer that can handle it */ protected final TypeDeserializer _valueTypeDeserializer; // // Instance construction settings: /** * @since 2.9 */ protected final ValueInstantiator _valueInstantiator; /** * Deserializer that is used iff delegate-based creator is * to be used for deserializing from JSON Object. */ protected JsonDeserializer<Object> _delegateDeserializer; /** * If the Map is to be instantiated using non-default constructor * or factory method * that takes one or more named properties as argument(s), * this creator is used for instantiation. */ protected PropertyBasedCreator _propertyBasedCreator; /* /********************************************************** /* Life-cycle /********************************************************** */ /** * @since 2.9 */ public EnumMapDeserializer(JavaType mapType, ValueInstantiator valueInst, KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser, TypeDeserializer vtd, NullValueProvider nuller) { super(mapType, nuller, null); _enumClass = mapType.getKeyType().getRawClass(); _keyDeserializer = keyDeser; _valueDeserializer = (JsonDeserializer<Object>) valueDeser; _valueTypeDeserializer = vtd; _valueInstantiator = valueInst; } /** * @since 2.9 */ protected EnumMapDeserializer(EnumMapDeserializer base, KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser, TypeDeserializer vtd, NullValueProvider nuller) { super(base, nuller, base._unwrapSingle); _enumClass = base._enumClass; _keyDeserializer = keyDeser; _valueDeserializer = (JsonDeserializer<Object>) valueDeser; _valueTypeDeserializer = vtd; _valueInstantiator = base._valueInstantiator; _delegateDeserializer = base._delegateDeserializer; _propertyBasedCreator = base._propertyBasedCreator; } @Deprecated // since 2.9 public EnumMapDeserializer(JavaType mapType, KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser, TypeDeserializer vtd) { this(mapType, null, keyDeser, valueDeser, vtd, null); } public EnumMapDeserializer withResolved(KeyDeserializer keyDeserializer, JsonDeserializer<?> valueDeserializer, TypeDeserializer valueTypeDeser, NullValueProvider nuller) { if ((keyDeserializer == _keyDeserializer) && (nuller == _nullProvider) && (valueDeserializer == _valueDeserializer) && (valueTypeDeser == _valueTypeDeserializer)) { return this; } return new EnumMapDeserializer(this, keyDeserializer, valueDeserializer, valueTypeDeser, nuller); } /* /********************************************************** /* Validation, post-processing (ResolvableDeserializer) /********************************************************** */ @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException { // May need to resolve types for delegate- and/or property-based creators: if (_valueInstantiator != null) { if (_valueInstantiator.canCreateUsingDelegate()) { JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig()); if (delegateType == null) { ctxt.reportBadDefinition(_containerType, String.format( "Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingDelegate()', but null for 'getDelegateType()'", _containerType, _valueInstantiator.getClass().getName())); } /* Theoretically should be able to get CreatorProperty for delegate * parameter to pass; but things get tricky because DelegateCreator * may contain injectable values. So, for now, let's pass nothing. */ _delegateDeserializer = findDeserializer(ctxt, delegateType, null); } else if (_valueInstantiator.canCreateUsingArrayDelegate()) { JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig()); if (delegateType == null) { ctxt.reportBadDefinition(_containerType, String.format( "Invalid delegate-creator definition for %s: value instantiator (%s) returned true for 'canCreateUsingArrayDelegate()', but null for 'getArrayDelegateType()'", _containerType, _valueInstantiator.getClass().getName())); } _delegateDeserializer = findDeserializer(ctxt, delegateType, null); } else if (_valueInstantiator.canCreateFromObjectWith()) { SettableBeanProperty[] creatorProps = _valueInstantiator.getFromObjectArguments(ctxt.getConfig()); _propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator, creatorProps, ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)); } } } /** * Method called to finalize setup of this deserializer, * when it is known for which property deserializer is needed for. */ @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { // note: instead of finding key deserializer, with enums we actually // work with regular deserializers (less code duplication; but not // quite as clean as it ought to be) KeyDeserializer keyDeser = _keyDeserializer; if (keyDeser == null) { keyDeser = ctxt.findKeyDeserializer(_containerType.getKeyType(), property); } JsonDeserializer<?> valueDeser = _valueDeserializer; final JavaType vt = _containerType.getContentType(); if (valueDeser == null) { valueDeser = ctxt.findContextualValueDeserializer(vt, property); } else { // if directly assigned, probably not yet contextual, so: valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt); } TypeDeserializer vtd = _valueTypeDeserializer; if (vtd != null) { vtd = vtd.forProperty(property); } return withResolved(keyDeser, valueDeser, vtd, findContentNullProvider(ctxt, property, valueDeser)); } /** * Because of costs associated with constructing Enum resolvers, * let's cache instances by default. */ @Override public boolean isCachable() { // Important: do NOT cache if polymorphic values return (_valueDeserializer == null) && (_keyDeserializer == null) && (_valueTypeDeserializer == null); } /* /********************************************************** /* ContainerDeserializerBase API /********************************************************** */ @Override public JsonDeserializer<Object> getContentDeserializer() { return _valueDeserializer; } // Must override since we do not expose ValueInstantiator @Override // since 2.9 public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException { return constructMap(ctxt); } /* /********************************************************** /* Actual deserialization /********************************************************** */ @Override public EnumMap<?,?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { if (_propertyBasedCreator != null) { return _deserializeUsingProperties(p, ctxt); } if (_delegateDeserializer != null) { return (EnumMap<?,?>) _valueInstantiator.createUsingDelegate(ctxt, _delegateDeserializer.deserialize(p, ctxt)); } // Ok: must point to START_OBJECT JsonToken t = p.getCurrentToken(); if (t != JsonToken.START_OBJECT && t != JsonToken.FIELD_NAME && t != JsonToken.END_OBJECT) { // (empty) String may be ok however; or single-String-arg ctor if (t == JsonToken.VALUE_STRING) { return (EnumMap<?,?>) _valueInstantiator.createFromString(ctxt, p.getText()); } // slightly redundant (since String was passed above), but also handles empty array case: return _deserializeFromEmpty(p, ctxt); } EnumMap result = constructMap(ctxt); return deserialize(p, ctxt, result); } @Override public EnumMap<?,?> deserialize(JsonParser p, DeserializationContext ctxt, EnumMap result) throws IOException { // [databind#631]: Assign current value, to be accessible by custom deserializers p.setCurrentValue(result); final JsonDeserializer<Object> valueDes = _valueDeserializer; final TypeDeserializer typeDeser = _valueTypeDeserializer; String keyName; while ((keyName = p.nextFieldName()) != null) { // but we need to let key deserializer handle it separately, nonetheless Enum<?> key = (Enum<?>) _keyDeserializer.deserializeKey(keyName, ctxt); if (key == null) { if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { return (EnumMap<?,?>) ctxt.handleWeirdStringValue(_enumClass, keyName, "value not one of declared Enum instance names for %s", _containerType.getKeyType()); } // 24-Mar-2012, tatu: Null won't work as a key anyway, so let's // just skip the entry then. But we must skip the value as well, if so. p.nextToken(); p.skipChildren(); continue; } // And then the value... JsonToken t = p.nextToken(); // note: MUST check for nulls separately: deserializers will // not handle them (and maybe fail or return bogus data) Object value; try { if (t == JsonToken.VALUE_NULL) { if (_skipNullValues) { continue; } value = _nullProvider.getNullValue(ctxt); } else if (typeDeser == null) { value = valueDes.deserialize(p, ctxt); } else { value = valueDes.deserializeWithType(p, ctxt, typeDeser); } } catch (Exception e) { return wrapAndThrow(e, result, keyName); } result.put(key, value); } return result; } @Override public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { // In future could check current token... for now this should be enough: return typeDeserializer.deserializeTypedFromObject(p, ctxt); } protected EnumMap<?,?> constructMap(DeserializationContext ctxt) throws JsonMappingException { if (_valueInstantiator == null) { return new EnumMap(_enumClass); } try { if (!_valueInstantiator.canCreateUsingDefault()) { return (EnumMap<?,?>) ctxt.handleMissingInstantiator(handledType(), getValueInstantiator(), null, "no default constructor found"); } return (EnumMap<?,?>) _valueInstantiator.createUsingDefault(ctxt); } catch (IOException e) { return ClassUtil.throwAsMappingException(ctxt, e); } } public EnumMap<?,?> _deserializeUsingProperties(JsonParser p, DeserializationContext ctxt) throws IOException { final PropertyBasedCreator creator = _propertyBasedCreator; // null -> no ObjectIdReader for EnumMaps PropertyValueBuffer buffer = creator.startBuilding(p, ctxt, null); String keyName; if (p.isExpectedStartObjectToken()) { keyName = p.nextFieldName(); } else if (p.hasToken(JsonToken.FIELD_NAME)) { keyName = p.getCurrentName(); } else { keyName = null; } for (; keyName != null; keyName = p.nextFieldName()) { JsonToken t = p.nextToken(); // to get to value // creator property? SettableBeanProperty prop = creator.findCreatorProperty(keyName); if (prop != null) { // Last property to set? if (buffer.assignParameter(prop, prop.deserialize(p, ctxt))) { EnumMap<?,?> result; try { result = (EnumMap<?,?>)creator.build(ctxt, buffer); } catch (Exception e) { return wrapAndThrow(e, _containerType.getRawClass(), keyName); } return deserialize(p, ctxt, result); } continue; } // other property? needs buffering // but we need to let key deserializer handle it separately, nonetheless Enum<?> key = (Enum<?>) _keyDeserializer.deserializeKey(keyName, ctxt); if (key == null) { if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { return (EnumMap<?,?>) ctxt.handleWeirdStringValue(_enumClass, keyName, "value not one of declared Enum instance names for %s", _containerType.getKeyType()); } // 24-Mar-2012, tatu: Null won't work as a key anyway, so let's // just skip the entry then. But we must skip the value as well, if so. p.nextToken(); p.skipChildren(); continue; } Object value; try { if (t == JsonToken.VALUE_NULL) { if (_skipNullValues) { continue; } value = _nullProvider.getNullValue(ctxt); } else if (_valueTypeDeserializer == null) { value = _valueDeserializer.deserialize(p, ctxt); } else { value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); } } catch (Exception e) { wrapAndThrow(e, _containerType.getRawClass(), keyName); return null; } buffer.bufferMapProperty(key, value); } // end of JSON object? // if so, can just construct and leave... try { return (EnumMap<?,?>)creator.build(ctxt, buffer); } catch (Exception e) { wrapAndThrow(e, _containerType.getRawClass(), keyName); return null; } } }