| package com.fasterxml.jackson.databind.deser.std; |
| |
| import java.io.IOException; |
| |
| import com.fasterxml.jackson.core.*; |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; |
| import com.fasterxml.jackson.databind.deser.SettableBeanProperty; |
| import com.fasterxml.jackson.databind.deser.ValueInstantiator; |
| import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; |
| import com.fasterxml.jackson.databind.util.ClassUtil; |
| import com.fasterxml.jackson.databind.util.CompactStringObjectMap; |
| import com.fasterxml.jackson.databind.util.EnumResolver; |
| |
| /** |
| * Deserializer class that can deserialize instances of |
| * specified Enum class from Strings and Integers. |
| */ |
| @JacksonStdImpl // was missing until 2.6 |
| public class EnumDeserializer |
| extends StdScalarDeserializer<Object> |
| { |
| private static final long serialVersionUID = 1L; |
| |
| protected Object[] _enumsByIndex; |
| |
| /** |
| * @since 2.8 |
| */ |
| private final Enum<?> _enumDefaultValue; |
| |
| /** |
| * @since 2.7.3 |
| */ |
| protected final CompactStringObjectMap _lookupByName; |
| |
| /** |
| * Alternatively, we may need a different lookup object if "use toString" |
| * is defined. |
| * |
| * @since 2.7.3 |
| */ |
| protected CompactStringObjectMap _lookupByToString; |
| |
| public EnumDeserializer(EnumResolver byNameResolver) |
| { |
| super(byNameResolver.getEnumClass()); |
| _lookupByName = byNameResolver.constructLookup(); |
| _enumsByIndex = byNameResolver.getRawEnums(); |
| _enumDefaultValue = byNameResolver.getDefaultValue(); |
| } |
| |
| /** |
| * @deprecated Since 2.8 |
| */ |
| @Deprecated |
| public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config, |
| Class<?> enumClass, AnnotatedMethod factory) { |
| return deserializerForCreator(config, enumClass, factory, null, null); |
| } |
| |
| /** |
| * Factory method used when Enum instances are to be deserialized |
| * using a creator (static factory method) |
| * |
| * @return Deserializer based on given factory method |
| * |
| * @since 2.8 |
| */ |
| public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config, |
| Class<?> enumClass, AnnotatedMethod factory, |
| ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps) |
| { |
| if (config.canOverrideAccessModifiers()) { |
| ClassUtil.checkAndFixAccess(factory.getMember(), |
| config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); |
| } |
| return new FactoryBasedEnumDeserializer(enumClass, factory, |
| factory.getParameterType(0), |
| valueInstantiator, creatorProps); |
| } |
| |
| /** |
| * Factory method used when Enum instances are to be deserialized |
| * using a zero-/no-args factory method |
| * |
| * @return Deserializer based on given no-args factory method |
| * |
| * @since 2.8 |
| */ |
| public static JsonDeserializer<?> deserializerForNoArgsCreator(DeserializationConfig config, |
| Class<?> enumClass, AnnotatedMethod factory) |
| { |
| if (config.canOverrideAccessModifiers()) { |
| ClassUtil.checkAndFixAccess(factory.getMember(), |
| config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); |
| } |
| return new FactoryBasedEnumDeserializer(enumClass, factory); |
| } |
| |
| /* |
| /********************************************************** |
| /* Default JsonDeserializer implementation |
| /********************************************************** |
| */ |
| |
| /** |
| * Because of costs associated with constructing Enum resolvers, |
| * let's cache instances by default. |
| */ |
| @Override |
| public boolean isCachable() { return true; } |
| |
| @Override |
| public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException |
| { |
| JsonToken curr = p.getCurrentToken(); |
| |
| // Usually should just get string value: |
| if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) { |
| CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING) |
| ? _getToStringLookup(ctxt) : _lookupByName; |
| final String name = p.getText(); |
| Object result = lookup.find(name); |
| if (result == null) { |
| return _deserializeAltString(p, ctxt, lookup, name); |
| } |
| return result; |
| } |
| // But let's consider int acceptable as well (if within ordinal range) |
| if (curr == JsonToken.VALUE_NUMBER_INT) { |
| // ... unless told not to do that |
| int index = p.getIntValue(); |
| if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { |
| return ctxt.handleWeirdNumberValue(_enumClass(), index, |
| "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow" |
| ); |
| } |
| if (index >= 0 && index < _enumsByIndex.length) { |
| return _enumsByIndex[index]; |
| } |
| if ((_enumDefaultValue != null) |
| && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { |
| return _enumDefaultValue; |
| } |
| if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { |
| return ctxt.handleWeirdNumberValue(_enumClass(), index, |
| "index value outside legal index range [0..%s]", |
| _enumsByIndex.length-1); |
| } |
| return null; |
| } |
| return _deserializeOther(p, ctxt); |
| } |
| |
| /* |
| /********************************************************** |
| /* Internal helper methods |
| /********************************************************** |
| */ |
| |
| private final Object _deserializeAltString(JsonParser p, DeserializationContext ctxt, |
| CompactStringObjectMap lookup, String name) throws IOException |
| { |
| name = name.trim(); |
| if (name.length() == 0) { |
| if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) { |
| return getEmptyValue(ctxt); |
| } |
| } else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) { |
| // [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above) |
| char c = name.charAt(0); |
| if (c >= '0' && c <= '9') { |
| try { |
| int index = Integer.parseInt(name); |
| if (index >= 0 && index < _enumsByIndex.length) { |
| return _enumsByIndex[index]; |
| } |
| } catch (NumberFormatException e) { |
| // fine, ignore, was not an integer |
| } |
| } |
| } |
| if ((_enumDefaultValue != null) |
| && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) { |
| return _enumDefaultValue; |
| } |
| if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) { |
| return ctxt.handleWeirdStringValue(_enumClass(), name, |
| "value not one of declared Enum instance names: %s", lookup.keys()); |
| } |
| return null; |
| } |
| |
| protected Object _deserializeOther(JsonParser p, DeserializationContext ctxt) throws IOException |
| { |
| // [databind#381] |
| if (p.hasToken(JsonToken.START_ARRAY)) { |
| return _deserializeFromArray(p, ctxt); |
| } |
| return ctxt.handleUnexpectedToken(_enumClass(), p); |
| } |
| |
| protected Class<?> _enumClass() { |
| return handledType(); |
| } |
| |
| protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt) |
| { |
| CompactStringObjectMap lookup = _lookupByToString; |
| // note: exact locking not needed; all we care for here is to try to |
| // reduce contention for the initial resolution |
| if (lookup == null) { |
| synchronized (this) { |
| lookup = EnumResolver.constructUnsafeUsingToString(_enumClass(), |
| ctxt.getAnnotationIntrospector()) |
| .constructLookup(); |
| } |
| _lookupByToString = lookup; |
| } |
| return lookup; |
| } |
| } |