1 package com.fasterxml.jackson.databind.deser.std;
2 
3 import java.io.IOException;
4 
5 import com.fasterxml.jackson.annotation.JsonFormat;
6 
7 import com.fasterxml.jackson.core.*;
8 
9 import com.fasterxml.jackson.databind.*;
10 import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
11 import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
12 import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
13 import com.fasterxml.jackson.databind.deser.ValueInstantiator;
14 import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
15 import com.fasterxml.jackson.databind.type.LogicalType;
16 import com.fasterxml.jackson.databind.util.ClassUtil;
17 import com.fasterxml.jackson.databind.util.CompactStringObjectMap;
18 import com.fasterxml.jackson.databind.util.EnumResolver;
19 
20 /**
21  * Deserializer class that can deserialize instances of
22  * specified Enum class from Strings and Integers.
23  */
24 @JacksonStdImpl // was missing until 2.6
25 public class EnumDeserializer
26     extends StdScalarDeserializer<Object>
27     implements ContextualDeserializer
28 {
29     private static final long serialVersionUID = 1L;
30 
31     protected Object[] _enumsByIndex;
32 
33     /**
34      * @since 2.8
35      */
36     private final Enum<?> _enumDefaultValue;
37 
38     /**
39      * @since 2.7.3
40      */
41     protected final CompactStringObjectMap _lookupByName;
42 
43     /**
44      * Alternatively, we may need a different lookup object if "use toString"
45      * is defined.
46      *
47      * @since 2.7.3
48      */
49     protected CompactStringObjectMap _lookupByToString;
50 
51     protected final Boolean _caseInsensitive;
52 
53     /**
54      * @since 2.9
55      */
EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive)56     public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive)
57     {
58         super(byNameResolver.getEnumClass());
59         _lookupByName = byNameResolver.constructLookup();
60         _enumsByIndex = byNameResolver.getRawEnums();
61         _enumDefaultValue = byNameResolver.getDefaultValue();
62         _caseInsensitive = caseInsensitive;
63     }
64 
65     /**
66      * @since 2.9
67      */
EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)68     protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)
69     {
70         super(base);
71         _lookupByName = base._lookupByName;
72         _enumsByIndex = base._enumsByIndex;
73         _enumDefaultValue = base._enumDefaultValue;
74         _caseInsensitive = caseInsensitive;
75     }
76 
77     /**
78      * @deprecated Since 2.9
79      */
80     @Deprecated
EnumDeserializer(EnumResolver byNameResolver)81     public EnumDeserializer(EnumResolver byNameResolver) {
82         this(byNameResolver, null);
83     }
84 
85     /**
86      * @deprecated Since 2.8
87      */
88     @Deprecated
deserializerForCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory)89     public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config,
90             Class<?> enumClass, AnnotatedMethod factory) {
91         return deserializerForCreator(config, enumClass, factory, null, null);
92     }
93 
94     /**
95      * Factory method used when Enum instances are to be deserialized
96      * using a creator (static factory method)
97      *
98      * @return Deserializer based on given factory method
99      *
100      * @since 2.8
101      */
deserializerForCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory, ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps)102     public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config,
103             Class<?> enumClass, AnnotatedMethod factory,
104             ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps)
105     {
106         if (config.canOverrideAccessModifiers()) {
107             ClassUtil.checkAndFixAccess(factory.getMember(),
108                     config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
109         }
110         return new FactoryBasedEnumDeserializer(enumClass, factory,
111                 factory.getParameterType(0),
112                 valueInstantiator, creatorProps);
113     }
114 
115     /**
116      * Factory method used when Enum instances are to be deserialized
117      * using a zero-/no-args factory method
118      *
119      * @return Deserializer based on given no-args factory method
120      *
121      * @since 2.8
122      */
deserializerForNoArgsCreator(DeserializationConfig config, Class<?> enumClass, AnnotatedMethod factory)123     public static JsonDeserializer<?> deserializerForNoArgsCreator(DeserializationConfig config,
124             Class<?> enumClass, AnnotatedMethod factory)
125     {
126         if (config.canOverrideAccessModifiers()) {
127             ClassUtil.checkAndFixAccess(factory.getMember(),
128                     config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
129         }
130         return new FactoryBasedEnumDeserializer(enumClass, factory);
131     }
132 
133     /**
134      * @since 2.9
135      */
withResolved(Boolean caseInsensitive)136     public EnumDeserializer withResolved(Boolean caseInsensitive) {
137         if (_caseInsensitive == caseInsensitive) {
138             return this;
139         }
140         return new EnumDeserializer(this, caseInsensitive);
141     }
142 
143     @Override // since 2.9
createContextual(DeserializationContext ctxt, BeanProperty property)144     public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
145             BeanProperty property) throws JsonMappingException
146     {
147         Boolean caseInsensitive = findFormatFeature(ctxt, property, handledType(),
148                 JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
149         if (caseInsensitive == null) {
150             caseInsensitive = _caseInsensitive;
151         }
152         return withResolved(caseInsensitive);
153     }
154 
155     /*
156     /**********************************************************
157     /* Default JsonDeserializer implementation
158     /**********************************************************
159      */
160 
161     /**
162      * Because of costs associated with constructing Enum resolvers,
163      * let's cache instances by default.
164      */
165     @Override
isCachable()166     public boolean isCachable() { return true; }
167 
168     @Override // since 2.12
logicalType()169     public LogicalType logicalType() {
170         return LogicalType.Enum;
171     }
172 
173     @Override
deserialize(JsonParser p, DeserializationContext ctxt)174     public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
175     {
176         String text;
177         JsonToken curr = p.currentToken();
178 
179         // Usually should just get string value:
180         if (curr == JsonToken.VALUE_STRING || curr == JsonToken.FIELD_NAME) {
181             text = p.getText();
182 
183         // But let's consider int acceptable as well (if within ordinal range)
184         } else if (curr == JsonToken.VALUE_NUMBER_INT) {
185             // ... unless told not to do that
186             int index = p.getIntValue();
187             if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
188                 return ctxt.handleWeirdNumberValue(_enumClass(), index,
189                         "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
190                         );
191             }
192             if (index >= 0 && index < _enumsByIndex.length) {
193                 return _enumsByIndex[index];
194             }
195             if ((_enumDefaultValue != null)
196                     && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
197                 return _enumDefaultValue;
198             }
199             if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
200                 return ctxt.handleWeirdNumberValue(_enumClass(), index,
201                         "index value outside legal index range [0..%s]",
202                         _enumsByIndex.length-1);
203             }
204             return null;
205         } else if (curr == JsonToken.START_OBJECT) {
206             // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
207             text = ctxt.extractScalarFromObject(p, this, _valueClass);
208         } else {
209             return _deserializeOther(p, ctxt);
210         }
211 
212         CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
213                 ? _getToStringLookup(ctxt) : _lookupByName;
214         Object result = lookup.find(text);
215         if (result == null) {
216             String trimmed = text.trim();
217             if ((trimmed == text) || (result = lookup.find(trimmed)) == null) {
218                 return _deserializeAltString(p, ctxt, lookup, trimmed);
219             }
220         }
221         return result;
222     }
223 
224     /*
225     /**********************************************************
226     /* Internal helper methods
227     /**********************************************************
228      */
229 
_deserializeAltString(JsonParser p, DeserializationContext ctxt, CompactStringObjectMap lookup, String name)230     private final Object _deserializeAltString(JsonParser p, DeserializationContext ctxt,
231             CompactStringObjectMap lookup, String name) throws IOException
232     {
233         name = name.trim();
234         if (name.length() == 0) {
235             if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
236                 return getEmptyValue(ctxt);
237             }
238         } else {
239             // [databind#1313]: Case insensitive enum deserialization
240             if (Boolean.TRUE.equals(_caseInsensitive)) {
241                 Object match = lookup.findCaseInsensitive(name);
242                 if (match != null) {
243                     return match;
244                 }
245             } else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
246                 // [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
247                 char c = name.charAt(0);
248                 if (c >= '0' && c <= '9') {
249                     try {
250                         int index = Integer.parseInt(name);
251                         if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
252                             return ctxt.handleWeirdStringValue(_enumClass(), name,
253 "value looks like quoted Enum index, but `MapperFeature.ALLOW_COERCION_OF_SCALARS` prevents use"
254                                     );
255                         }
256                         if (index >= 0 && index < _enumsByIndex.length) {
257                             return _enumsByIndex[index];
258                         }
259                     } catch (NumberFormatException e) {
260                         // fine, ignore, was not an integer
261                     }
262                 }
263             }
264         }
265         if ((_enumDefaultValue != null)
266                 && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
267             return _enumDefaultValue;
268         }
269         if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
270             return ctxt.handleWeirdStringValue(_enumClass(), name,
271                     "not one of the values accepted for Enum class: %s",  lookup.keys());
272         }
273         return null;
274     }
275 
_deserializeOther(JsonParser p, DeserializationContext ctxt)276     protected Object _deserializeOther(JsonParser p, DeserializationContext ctxt) throws IOException
277     {
278         // [databind#381]
279         if (p.hasToken(JsonToken.START_ARRAY)) {
280             return _deserializeFromArray(p, ctxt);
281         }
282         return ctxt.handleUnexpectedToken(_enumClass(), p);
283     }
284 
_enumClass()285     protected Class<?> _enumClass() {
286         return handledType();
287     }
288 
_getToStringLookup(DeserializationContext ctxt)289     protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt)
290     {
291         CompactStringObjectMap lookup = _lookupByToString;
292         // note: exact locking not needed; all we care for here is to try to
293         // reduce contention for the initial resolution
294         if (lookup == null) {
295             synchronized (this) {
296                 lookup = EnumResolver.constructUnsafeUsingToString(_enumClass(),
297                         ctxt.getAnnotationIntrospector())
298                     .constructLookup();
299             }
300             _lookupByToString = lookup;
301         }
302         return lookup;
303     }
304 }
305