1 package com.fasterxml.jackson.databind.deser.std;
2
3 import java.io.IOException;
4 import java.util.Collection;
5
6 import com.fasterxml.jackson.annotation.JsonFormat;
7 import com.fasterxml.jackson.core.*;
8 import com.fasterxml.jackson.databind.*;
9 import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
10 import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
11 import com.fasterxml.jackson.databind.deser.NullValueProvider;
12 import com.fasterxml.jackson.databind.deser.ValueInstantiator;
13 import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams;
14 import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
15
16 /**
17  * Specifically optimized version for {@link java.util.Collection}s
18  * that contain String values; reason is that this is a very common
19  * type and we can make use of the fact that Strings are final.
20  */

21 @JacksonStdImpl
22 public final class StringCollectionDeserializer
23     extends ContainerDeserializerBase<Collection<String>>
24     implements ContextualDeserializer
25 {
26     private static final long serialVersionUID = 1L;
27
28     // // Configuration
29
30     /**
31      * Value deserializer to use, if NOT the standard one
32      * (if it is, will be null).
33      */

34     protected final JsonDeserializer<String> _valueDeserializer;
35
36     // // Instance construction settings:
37     
38     /**
39      * Instantiator used in case custom handling is needed for creation.
40      */

41     protected final ValueInstantiator _valueInstantiator;
42
43     /**
44      * Deserializer that is used iff delegate-based creator is
45      * to be used for deserializing from JSON Object.
46      */

47     protected final JsonDeserializer<Object> _delegateDeserializer;
48
49     // NOTE: no PropertyBasedCreator, as JSON Arrays have no properties
50
51     /*
52     /**********************************************************
53     /* Life-cycle
54     /**********************************************************
55      */

56     
57     public StringCollectionDeserializer(JavaType collectionType,
58             JsonDeserializer<?> valueDeser, ValueInstantiator valueInstantiator)
59     {
60         this(collectionType, valueInstantiator, null, valueDeser, valueDeser, null);
61     }
62
63     @SuppressWarnings("unchecked")
64     protected StringCollectionDeserializer(JavaType collectionType,
65             ValueInstantiator valueInstantiator, JsonDeserializer<?> delegateDeser,
66             JsonDeserializer<?> valueDeser,
67             NullValueProvider nuller, Boolean unwrapSingle)
68     {
69         super(collectionType, nuller, unwrapSingle);
70         _valueDeserializer = (JsonDeserializer<String>) valueDeser;
71         _valueInstantiator = valueInstantiator;
72         _delegateDeserializer = (JsonDeserializer<Object>) delegateDeser;
73     }
74
75     protected StringCollectionDeserializer withResolved(JsonDeserializer<?> delegateDeser,
76             JsonDeserializer<?> valueDeser,
77             NullValueProvider nuller, Boolean unwrapSingle)
78     {
79         if ((_unwrapSingle == unwrapSingle) && (_nullProvider == nuller)
80                 && (_valueDeserializer == valueDeser) && (_delegateDeserializer == delegateDeser)) {
81             return this;
82         }
83         return new StringCollectionDeserializer(_containerType, _valueInstantiator,
84                 delegateDeser, valueDeser, nuller, unwrapSingle);
85     }
86
87     @Override // since 2.5
88     public boolean isCachable() {
89         // 26-Mar-2015, tatu: Important: prevent caching if custom deserializers via annotations
90         //    are involved
91         return (_valueDeserializer == null) && (_delegateDeserializer == null);
92     }
93     
94     /*
95     /**********************************************************
96     /* Validation, post-processing
97     /**********************************************************
98      */

99     @Override
100     public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
101             BeanProperty property) throws JsonMappingException
102     {
103         // May need to resolve types for delegate-based creators:
104         JsonDeserializer<Object> delegate = null;
105         if (_valueInstantiator != null) {
106             // [databind#2324]: check both array-delegating and delegating
107             AnnotatedWithParams delegateCreator = _valueInstantiator.getArrayDelegateCreator();
108             if (delegateCreator != null) {
109                 JavaType delegateType = _valueInstantiator.getArrayDelegateType(ctxt.getConfig());
110                 delegate = findDeserializer(ctxt, delegateType, property);
111             } else if ((delegateCreator = _valueInstantiator.getDelegateCreator()) != null) {
112                 JavaType delegateType = _valueInstantiator.getDelegateType(ctxt.getConfig());
113                 delegate = findDeserializer(ctxt, delegateType, property);
114             }
115         }
116         JsonDeserializer<?> valueDeser = _valueDeserializer;
117         final JavaType valueType = _containerType.getContentType();
118         if (valueDeser == null) {
119             // [databind#125]: May have a content converter
120             valueDeser = findConvertingContentDeserializer(ctxt, property, valueDeser);
121             if (valueDeser == null) {
122             // And we may also need to get deserializer for String
123                 valueDeser = ctxt.findContextualValueDeserializer(valueType, property);
124             }
125         } else { // if directly assigned, probably not yet contextual, so:
126             valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, valueType);
127         }
128         // 11-Dec-2015, tatu: Should we pass basic `Collection.class`, or more refined? Mostly
129         //   comes down to "List vs Collection" I suppose... for now, pass Collection
130         Boolean unwrapSingle = findFormatFeature(ctxt, property, Collection.class,
131                 JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
132         NullValueProvider nuller = findContentNullProvider(ctxt, property, valueDeser);
133         if (isDefaultDeserializer(valueDeser)) {
134             valueDeser = null;
135         }
136         return withResolved(delegate, valueDeser, nuller, unwrapSingle);
137     }
138     
139     /*
140     /**********************************************************
141     /* ContainerDeserializerBase API
142     /**********************************************************
143      */

144
145     @SuppressWarnings("unchecked")
146     @Override
147     public JsonDeserializer<Object> getContentDeserializer() {
148         JsonDeserializer<?> deser = _valueDeserializer;
149         return (JsonDeserializer<Object>) deser;
150     }
151
152     @Override
153     public ValueInstantiator getValueInstantiator() {
154         return _valueInstantiator;
155     }
156
157     /*
158     /**********************************************************
159     /* JsonDeserializer API
160     /**********************************************************
161      */

162     
163     @SuppressWarnings("unchecked")
164     @Override
165     public Collection<String> deserialize(JsonParser p, DeserializationContext ctxt)
166         throws IOException
167     {
168         if (_delegateDeserializer != null) {
169             return (Collection<String>) _valueInstantiator.createUsingDelegate(ctxt,
170                     _delegateDeserializer.deserialize(p, ctxt));
171         }
172         final Collection<String> result = (Collection<String>) _valueInstantiator.createUsingDefault(ctxt);
173         return deserialize(p, ctxt, result);
174     }
175
176     @Override
177     public Collection<String> deserialize(JsonParser p, DeserializationContext ctxt,
178             Collection<String> result)
179         throws IOException
180     {
181         // Ok: must point to START_ARRAY
182         if (!p.isExpectedStartArrayToken()) {
183             return handleNonArray(p, ctxt, result);
184         }
185
186         if (_valueDeserializer != null) {
187             return deserializeUsingCustom(p, ctxt, result, _valueDeserializer);
188         }
189         try {
190             while (true) {
191                 // First the common case:
192                 String value = p.nextTextValue();
193                 if (value != null) {
194                     result.add(value);
195                     continue;
196                 }
197                 JsonToken t = p.getCurrentToken();
198                 if (t == JsonToken.END_ARRAY) {
199                     break;
200                 }
201                 if (t == JsonToken.VALUE_NULL) {
202                     if (_skipNullValues) {
203                         continue;
204                     }
205                     value = (String) _nullProvider.getNullValue(ctxt);
206                 } else {
207                     value = _parseString(p, ctxt);
208                 }
209                 result.add(value);
210             }
211         } catch (Exception e) {
212             throw JsonMappingException.wrapWithPath(e, result, result.size());
213         }
214         return result;
215     }
216     
217     private Collection<String> deserializeUsingCustom(JsonParser p, DeserializationContext ctxt,
218             Collection<String> result, final JsonDeserializer<String> deser) throws IOException
219     {
220         try {
221             while (true) {
222                 /* 30-Dec-2014, tatu: This may look odd, but let's actually call method
223                  *   that suggest we are expecting a String; this helps with some formats,
224                  *   notably XML. Note, however, that while we can get String, we can't
225                  *   assume that's what we use due to custom deserializer
226                  */

227                 String value;
228                 if (p.nextTextValue() == null) {
229                     JsonToken t = p.getCurrentToken();
230                     if (t == JsonToken.END_ARRAY) {
231                         break;
232                     }
233                     // Ok: no need to convert Strings, but must recognize nulls
234                     if (t == JsonToken.VALUE_NULL) {
235                         if (_skipNullValues) {
236                             continue;
237                         }
238                         value = (String) _nullProvider.getNullValue(ctxt);
239                     } else {
240                         value = deser.deserialize(p, ctxt);
241                     }
242                 } else {
243                     value = deser.deserialize(p, ctxt);
244                 }
245                 result.add(value);
246             }
247         } catch (Exception e) {
248             throw JsonMappingException.wrapWithPath(e, result, result.size());
249         }
250         return result;
251     }
252
253     @Override
254     public Object deserializeWithType(JsonParser p, DeserializationContext ctxt,
255             TypeDeserializer typeDeserializer) throws IOException {
256         // In future could check current token... for now this should be enough:
257         return typeDeserializer.deserializeTypedFromArray(p, ctxt);
258     }
259
260     /**
261      * Helper method called when current token is not START_ARRAY. Will either
262      * throw an exception, or try to handle value as if member of implicit
263      * array, depending on configuration.
264      */

265     @SuppressWarnings("unchecked")
266     private final Collection<String> handleNonArray(JsonParser p, DeserializationContext ctxt,
267             Collection<String> result) throws IOException
268     {
269         // implicit arrays from single values?
270         boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
271                 ((_unwrapSingle == null) &&
272                         ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
273         if (!canWrap) {
274             return (Collection<String>) ctxt.handleUnexpectedToken(_containerType.getRawClass(), p);
275         }
276         // Strings are one of "native" (intrinsic) types, so there's never type deserializer involved
277         JsonDeserializer<String> valueDes = _valueDeserializer;
278         JsonToken t = p.getCurrentToken();
279
280         String value;
281         
282         if (t == JsonToken.VALUE_NULL) {
283             // 03-Feb-2017, tatu: Does this work?
284             if (_skipNullValues) {
285                 return result;
286             }
287             value = (String) _nullProvider.getNullValue(ctxt);
288         } else {
289             try {
290                 value = (valueDes == null) ? _parseString(p, ctxt) : valueDes.deserialize(p, ctxt);
291             } catch (Exception e) {
292                 throw JsonMappingException.wrapWithPath(e, result, result.size());
293             }
294         }
295         result.add(value);
296         return result;
297     }
298 }
299