1 package com.fasterxml.jackson.databind.deser.std;
2
3 import java.io.IOException;
4
5 import com.fasterxml.jackson.annotation.JsonFormat;
6 import com.fasterxml.jackson.core.*;
7 import com.fasterxml.jackson.databind.*;
8 import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
9 import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
10 import com.fasterxml.jackson.databind.deser.NullValueProvider;
11 import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
12 import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
13 import com.fasterxml.jackson.databind.util.AccessPattern;
14 import com.fasterxml.jackson.databind.util.ObjectBuffer;
15
16 /**
17  * Separate implementation for serializing String arrays (instead of
18  * using {@link ObjectArrayDeserializer}.
19  * Used if (and only if) no custom value deserializers are used.
20  */

21 @JacksonStdImpl
22 public final class StringArrayDeserializer
23 // 28-Oct-2016, tatu: Should do this:
24 //    extends ContainerDeserializerBase<String[]>
25 // but for now won't:
26     extends StdDeserializer<String[]>
27     implements ContextualDeserializer
28 {
29     private static final long serialVersionUID = 2L;
30
31     private final static String[] NO_STRINGS = new String[0];
32
33     public final static StringArrayDeserializer instance = new StringArrayDeserializer();
34
35     /**
36      * Value serializer to use, if not the standard one (which is inlined)
37      */

38     protected JsonDeserializer<String> _elementDeserializer;
39
40     /**
41      * Handler we need for dealing with nulls.
42      *
43      * @since 2.9
44      */

45     protected final NullValueProvider _nullProvider;
46
47     /**
48      * Specific override for this instance (from proper, or global per-type overrides)
49      * to indicate whether single value may be taken to mean an unwrapped one-element array
50      * or not. If null, left to global defaults.
51      *
52      * @since 2.7
53      */

54     protected final Boolean _unwrapSingle;
55
56     /**
57      * Marker flag set if the <code>_nullProvider</code> indicates that all null
58      * content values should be skipped (instead of being possibly converted).
59      *
60      * @since 2.9
61      */

62     protected final boolean _skipNullValues;
63     
64     public StringArrayDeserializer() {
65         this(nullnullnull);
66     }
67
68     @SuppressWarnings("unchecked")
69     protected StringArrayDeserializer(JsonDeserializer<?> deser,
70             NullValueProvider nuller, Boolean unwrapSingle) {
71         super(String[].class);
72         _elementDeserializer = (JsonDeserializer<String>) deser;
73         _nullProvider = nuller;
74         _unwrapSingle = unwrapSingle;
75         _skipNullValues = NullsConstantProvider.isSkipper(nuller);
76     }
77
78     @Override // since 2.9
79     public Boolean supportsUpdate(DeserializationConfig config) {
80         return Boolean.TRUE;
81     }
82
83     @Override // since 2.9
84     public AccessPattern getEmptyAccessPattern() {
85         // immutable, shareable so:
86         return AccessPattern.CONSTANT;
87     }
88
89     @Override // since 2.9
90     public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException {
91         return NO_STRINGS;
92     }
93
94     /**
95      * Contextualization is needed to see whether we can "inline" deserialization
96      * of String values, or if we have to use separate value deserializer.
97      */

98     @Override
99     public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
100             throws JsonMappingException
101     {
102         JsonDeserializer<?> deser = _elementDeserializer;
103         // May have a content converter
104         deser = findConvertingContentDeserializer(ctxt, property, deser);
105         JavaType type = ctxt.constructType(String.class);
106         if (deser == null) {
107             deser = ctxt.findContextualValueDeserializer(type, property);
108         } else { // if directly assigned, probably not yet contextual, so:
109             deser = ctxt.handleSecondaryContextualization(deser, property, type);
110         }
111         // One more thing: allow unwrapping?
112         Boolean unwrapSingle = findFormatFeature(ctxt, property, String[].class,
113                 JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
114         NullValueProvider nuller = findContentNullProvider(ctxt, property, deser);
115         // Ok ok: if all we got is the default String deserializer, can just forget about it
116         if ((deser != null) && isDefaultDeserializer(deser)) {
117             deser = null;
118         }
119         if ((_elementDeserializer == deser)
120                 && (_unwrapSingle == unwrapSingle)
121                 && (_nullProvider == nuller)) {
122             return this;
123         }
124         return new StringArrayDeserializer(deser, nuller, unwrapSingle);
125     }
126
127     @Override
128     public String[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
129     {
130         // Ok: must point to START_ARRAY (or equivalent)
131         if (!p.isExpectedStartArrayToken()) {
132             return handleNonArray(p, ctxt);
133         }
134         if (_elementDeserializer != null) {
135             return _deserializeCustom(p, ctxt, null);
136         }
137
138         final ObjectBuffer buffer = ctxt.leaseObjectBuffer();
139         Object[] chunk = buffer.resetAndStart();
140
141         int ix = 0;
142
143         try {
144             while (true) {
145                 String value = p.nextTextValue();
146                 if (value == null) {
147                     JsonToken t = p.getCurrentToken();
148                     if (t == JsonToken.END_ARRAY) {
149                         break;
150                     }
151                     if (t == JsonToken.VALUE_NULL) {
152                         if (_skipNullValues) {
153                             continue;
154                         }
155                         value = (String) _nullProvider.getNullValue(ctxt);
156                     } else {
157                         value = _parseString(p, ctxt);
158                     }
159                 }
160                 if (ix >= chunk.length) {
161                     chunk = buffer.appendCompletedChunk(chunk);
162                     ix = 0;
163                 }
164                 chunk[ix++] = value;
165             }
166         } catch (Exception e) {
167             throw JsonMappingException.wrapWithPath(e, chunk, buffer.bufferedSize() + ix);
168         }
169         String[] result = buffer.completeAndClearBuffer(chunk, ix, String.class);
170         ctxt.returnObjectBuffer(buffer);
171         return result;
172     }
173
174     /**
175      * Offlined version used when we do not use the default deserialization method.
176      */

177     protected final String[] _deserializeCustom(JsonParser p, DeserializationContext ctxt,
178             String[] old) throws IOException
179     {
180         final ObjectBuffer buffer = ctxt.leaseObjectBuffer();
181         int ix;
182         Object[] chunk;
183
184         if (old == null) {
185             ix = 0;
186             chunk = buffer.resetAndStart();
187         } else {
188             ix = old.length;
189             chunk = buffer.resetAndStart(old, ix);
190         }
191         
192         final JsonDeserializer<String> deser = _elementDeserializer;
193
194         try {
195             while (true) {
196                 /* 30-Dec-2014, tatu: This may look odd, but let's actually call method
197                  *   that suggest we are expecting a String; this helps with some formats,
198                  *   notably XML. Note, however, that while we can get String, we can't
199                  *   assume that's what we use due to custom deserializer
200                  */

201                 String value;
202                 if (p.nextTextValue() == null) {
203                     JsonToken t = p.getCurrentToken();
204                     if (t == JsonToken.END_ARRAY) {
205                         break;
206                     }
207                     // Ok: no need to convert Strings, but must recognize nulls
208                     if (t == JsonToken.VALUE_NULL) {
209                         if (_skipNullValues) {
210                             continue;
211                         }
212                         value = (String) _nullProvider.getNullValue(ctxt);
213                     } else {
214                         value = deser.deserialize(p, ctxt);
215                     }
216                 } else {
217                     value = deser.deserialize(p, ctxt);
218                 }
219                 if (ix >= chunk.length) {
220                     chunk = buffer.appendCompletedChunk(chunk);
221                     ix = 0;
222                 }
223                 chunk[ix++] = value;
224             }
225         } catch (Exception e) {
226             // note: pass String.class, not String[].class, as we need element type for error info
227             throw JsonMappingException.wrapWithPath(e, String.class, ix);
228         }
229         String[] result = buffer.completeAndClearBuffer(chunk, ix, String.class);
230         ctxt.returnObjectBuffer(buffer);
231         return result;
232     }
233
234     @Override
235     public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException {
236         return typeDeserializer.deserializeTypedFromArray(p, ctxt);
237     }
238
239     @Override
240     public String[] deserialize(JsonParser p, DeserializationContext ctxt,
241             String[] intoValue) throws IOException
242     {
243         // Ok: must point to START_ARRAY (or equivalent)
244         if (!p.isExpectedStartArrayToken()) {
245             String[] arr = handleNonArray(p, ctxt);
246             if (arr == null) {
247                 return intoValue;
248             }
249             final int offset = intoValue.length;
250             String[] result = new String[offset + arr.length];
251             System.arraycopy(intoValue, 0, result, 0, offset);
252             System.arraycopy(arr, 0, result, offset, arr.length);
253             return result;
254         }
255
256         if (_elementDeserializer != null) {
257             return _deserializeCustom(p, ctxt, intoValue);
258         }
259         final ObjectBuffer buffer = ctxt.leaseObjectBuffer();
260         int ix = intoValue.length;
261         Object[] chunk = buffer.resetAndStart(intoValue, ix);
262
263         try {
264             while (true) {
265                 String value = p.nextTextValue();
266                 if (value == null) {
267                     JsonToken t = p.getCurrentToken();
268                     if (t == JsonToken.END_ARRAY) {
269                         break;
270                     }
271                     if (t == JsonToken.VALUE_NULL) {
272                         // 03-Feb-2017, tatu: Should we skip null here or not?
273                         if (_skipNullValues) {
274                             return NO_STRINGS;
275                         }
276                         value = (String) _nullProvider.getNullValue(ctxt);
277                     } else {
278                         value = _parseString(p, ctxt);
279                     }
280                 }
281                 if (ix >= chunk.length) {
282                     chunk = buffer.appendCompletedChunk(chunk);
283                     ix = 0;
284                 }
285                 chunk[ix++] = value;
286             }
287         } catch (Exception e) {
288             throw JsonMappingException.wrapWithPath(e, chunk, buffer.bufferedSize() + ix);
289         }
290         String[] result = buffer.completeAndClearBuffer(chunk, ix, String.class);
291         ctxt.returnObjectBuffer(buffer);
292         return result;
293     }
294
295     private final String[] handleNonArray(JsonParser p, DeserializationContext ctxt) throws IOException
296     {
297         // implicit arrays from single values?
298         boolean canWrap = (_unwrapSingle == Boolean.TRUE) ||
299                 ((_unwrapSingle == null) &&
300                         ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
301         if (canWrap) {
302             String value = p.hasToken(JsonToken.VALUE_NULL)
303                     ? (String) _nullProvider.getNullValue(ctxt)
304                     : _parseString(p, ctxt);
305             return new String[] { value };
306         }
307         if (p.hasToken(JsonToken.VALUE_STRING)
308                     && ctxt.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
309             String str = p.getText();
310             if (str.length() == 0) {
311                 return null;
312             }
313         }
314         return (String[]) ctxt.handleUnexpectedToken(_valueClass, p);
315     }
316 }
317