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.util.ClassUtil;
16 import com.fasterxml.jackson.databind.util.CompactStringObjectMap;
17 import com.fasterxml.jackson.databind.util.EnumResolver;
18
19 /**
20  * Deserializer class that can deserialize instances of
21  * specified Enum class from Strings and Integers.
22  */

23 @JacksonStdImpl // was missing until 2.6
24 public class EnumDeserializer
25     extends StdScalarDeserializer<Object>
26     implements ContextualDeserializer
27 {
28     private static final long serialVersionUID = 1L;
29
30     protected Object[] _enumsByIndex;
31     
32     /**
33      * @since 2.8
34      */

35     private final Enum<?> _enumDefaultValue;
36
37     /**
38      * @since 2.7.3
39      */

40     protected final CompactStringObjectMap _lookupByName;
41
42     /**
43      * Alternatively, we may need a different lookup object if "use toString"
44      * is defined.
45      *
46      * @since 2.7.3
47      */

48     protected CompactStringObjectMap _lookupByToString;
49
50     protected final Boolean _caseInsensitive;
51
52     /**
53      * @since 2.9
54      */

55     public EnumDeserializer(EnumResolver byNameResolver, Boolean caseInsensitive)
56     {
57         super(byNameResolver.getEnumClass());
58         _lookupByName = byNameResolver.constructLookup();
59         _enumsByIndex = byNameResolver.getRawEnums();
60         _enumDefaultValue = byNameResolver.getDefaultValue();
61         _caseInsensitive = caseInsensitive;
62     }
63
64     /**
65      * @since 2.9
66      */

67     protected EnumDeserializer(EnumDeserializer base, Boolean caseInsensitive)
68     {
69         super(base);
70         _lookupByName = base._lookupByName;
71         _enumsByIndex = base._enumsByIndex;
72         _enumDefaultValue = base._enumDefaultValue;
73         _caseInsensitive = caseInsensitive;
74     }
75
76     /**
77      * @deprecated Since 2.9
78      */

79     @Deprecated
80     public EnumDeserializer(EnumResolver byNameResolver) {
81         this(byNameResolver, null);
82     }
83     
84     /**
85      * @deprecated Since 2.8
86      */

87     @Deprecated
88     public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config,
89             Class<?> enumClass, AnnotatedMethod factory) {
90         return deserializerForCreator(config, enumClass, factory, nullnull);
91     }
92
93     /**
94      * Factory method used when Enum instances are to be deserialized
95      * using a creator (static factory method)
96      * 
97      * @return Deserializer based on given factory method
98      *
99      * @since 2.8
100      */

101     public static JsonDeserializer<?> deserializerForCreator(DeserializationConfig config,
102             Class<?> enumClass, AnnotatedMethod factory,
103             ValueInstantiator valueInstantiator, SettableBeanProperty[] creatorProps)
104     {
105         if (config.canOverrideAccessModifiers()) {
106             ClassUtil.checkAndFixAccess(factory.getMember(),
107                     config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
108         }
109         return new FactoryBasedEnumDeserializer(enumClass, factory,
110                 factory.getParameterType(0),
111                 valueInstantiator, creatorProps);
112     }
113
114     /**
115      * Factory method used when Enum instances are to be deserialized
116      * using a zero-/no-args factory method
117      * 
118      * @return Deserializer based on given no-args factory method
119      *
120      * @since 2.8
121      */

122     public static JsonDeserializer<?> deserializerForNoArgsCreator(DeserializationConfig config,
123             Class<?> enumClass, AnnotatedMethod factory)
124     {
125         if (config.canOverrideAccessModifiers()) {
126             ClassUtil.checkAndFixAccess(factory.getMember(),
127                     config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
128         }
129         return new FactoryBasedEnumDeserializer(enumClass, factory);
130     }
131
132     /**
133      * @since 2.9
134      */

135     public EnumDeserializer withResolved(Boolean caseInsensitive) {
136         if (_caseInsensitive == caseInsensitive) {
137             return this;
138         }
139         return new EnumDeserializer(this, caseInsensitive);
140     }
141     
142     @Override // since 2.9
143     public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
144             BeanProperty property) throws JsonMappingException
145     {
146         Boolean caseInsensitive = findFormatFeature(ctxt, property, handledType(),
147                 JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
148         if (caseInsensitive == null) {
149             caseInsensitive = _caseInsensitive;
150         }
151         return withResolved(caseInsensitive);
152     }
153
154     /*
155     /**********************************************************
156     /* Default JsonDeserializer implementation
157     /**********************************************************
158      */

159
160     /**
161      * Because of costs associated with constructing Enum resolvers,
162      * let's cache instances by default.
163      */

164     @Override
165     public boolean isCachable() { return true; }
166
167     @Override
168     public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
169     {
170         JsonToken curr = p.currentToken();
171
172         // Usually should just get string value:
173         // 04-Sep-2020, tatu: for 2.11.3 / 2.12.0, removed "FIELD_NAME" as allowed;
174         //   did not work and gave odd error message.
175         if (curr == JsonToken.VALUE_STRING) {
176             CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
177                     ? _getToStringLookup(ctxt) : _lookupByName;
178             final String name = p.getText();
179             Object result = lookup.find(name);
180             if (result == null) {
181                 return _deserializeAltString(p, ctxt, lookup, name);
182             }
183             return result;
184         }
185         // But let's consider int acceptable as well (if within ordinal range)
186         if (curr == JsonToken.VALUE_NUMBER_INT) {
187             // ... unless told not to do that
188             int index = p.getIntValue();
189             if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
190                 return ctxt.handleWeirdNumberValue(_enumClass(), index,
191                         "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
192                         );
193             }
194             if (index >= 0 && index < _enumsByIndex.length) {
195                 return _enumsByIndex[index];
196             }
197             if ((_enumDefaultValue != null)
198                     && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
199                 return _enumDefaultValue;
200             }
201             if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
202                 return ctxt.handleWeirdNumberValue(_enumClass(), index,
203                         "index value outside legal index range [0..%s]",
204                         _enumsByIndex.length-1);
205             }
206             return null;
207         }
208         return _deserializeOther(p, ctxt);
209     }
210
211     /*
212     /**********************************************************
213     /* Internal helper methods
214     /**********************************************************
215      */

216     
217     private final Object _deserializeAltString(JsonParser p, DeserializationContext ctxt,
218             CompactStringObjectMap lookup, String name) throws IOException
219     {
220         name = name.trim();
221         if (name.length() == 0) {
222             if (ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
223                 return getEmptyValue(ctxt);
224             }
225         } else {
226             // [databind#1313]: Case insensitive enum deserialization
227             if (Boolean.TRUE.equals(_caseInsensitive)) {
228                 Object match = lookup.findCaseInsensitive(name);
229                 if (match != null) {
230                     return match;
231                 }
232             } else if (!ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
233                 // [databind#149]: Allow use of 'String' indexes as well -- unless prohibited (as per above)
234                 char c = name.charAt(0);
235                 if (c >= '0' && c <= '9') {
236                     try {
237                         int index = Integer.parseInt(name);
238                         if (!ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
239                             return ctxt.handleWeirdStringValue(_enumClass(), name,
240 "value looks like quoted Enum index, but `MapperFeature.ALLOW_COERCION_OF_SCALARS` prevents use"
241                                     );
242                         }
243                         if (index >= 0 && index < _enumsByIndex.length) {
244                             return _enumsByIndex[index];
245                         }
246                     } catch (NumberFormatException e) {
247                         // fine, ignore, was not an integer
248                     }
249                 }
250             }
251         }
252         if ((_enumDefaultValue != null)
253                 && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
254             return _enumDefaultValue;
255         }
256         if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
257             return ctxt.handleWeirdStringValue(_enumClass(), name,
258                     "not one of the values accepted for Enum class: %s",  lookup.keys());
259         }
260         return null;
261     }
262
263     protected Object _deserializeOther(JsonParser p, DeserializationContext ctxt) throws IOException
264     {
265         // [databind#381]
266         if (p.hasToken(JsonToken.START_ARRAY)) {
267             return _deserializeFromArray(p, ctxt);
268         }
269         return ctxt.handleUnexpectedToken(_enumClass(), p);
270     }
271
272     protected Class<?> _enumClass() {
273         return handledType();
274     }
275
276     protected CompactStringObjectMap _getToStringLookup(DeserializationContext ctxt)
277     {
278         CompactStringObjectMap lookup = _lookupByToString;
279         // note: exact locking not needed; all we care for here is to try to
280         // reduce contention for the initial resolution
281         if (lookup == null) {
282             synchronized (this) {
283                 lookup = EnumResolver.constructUnsafeUsingToString(_enumClass(),
284                         ctxt.getAnnotationIntrospector())
285                     .constructLookup();
286             }
287             _lookupByToString = lookup;
288         }
289         return lookup;
290     }
291 }
292