1 package com.fasterxml.jackson.databind.deser.std;
2 
3 import java.io.IOException;
4 import java.lang.reflect.Constructor;
5 import java.sql.Timestamp;
6 import java.text.*;
7 import java.util.*;
8 
9 import com.fasterxml.jackson.annotation.JsonFormat;
10 
11 import com.fasterxml.jackson.core.JsonParser;
12 import com.fasterxml.jackson.core.JsonToken;
13 
14 import com.fasterxml.jackson.databind.*;
15 import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
16 import com.fasterxml.jackson.databind.cfg.CoercionAction;
17 import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
18 import com.fasterxml.jackson.databind.type.LogicalType;
19 import com.fasterxml.jackson.databind.util.ClassUtil;
20 import com.fasterxml.jackson.databind.util.StdDateFormat;
21 
22 /**
23  * Container class for core JDK date/time type deserializers.
24  */
25 @SuppressWarnings("serial")
26 public class DateDeserializers
27 {
28     private final static HashSet<String> _classNames = new HashSet<String>();
29     static {
30         Class<?>[] numberTypes = new Class<?>[] {
31             Calendar.class,
32             GregorianCalendar.class,
33             java.sql.Date.class,
34             java.util.Date.class,
35             Timestamp.class,
36         };
37         for (Class<?> cls : numberTypes) {
cls.getName()38             _classNames.add(cls.getName());
39         }
40     }
41 
find(Class<?> rawType, String clsName)42     public static JsonDeserializer<?> find(Class<?> rawType, String clsName)
43     {
44         if (_classNames.contains(clsName)) {
45             // Start with the most common type
46             if (rawType == Calendar.class) {
47                 return new CalendarDeserializer();
48             }
49             if (rawType == java.util.Date.class) {
50                 return DateDeserializer.instance;
51             }
52             if (rawType == java.sql.Date.class) {
53                 return new SqlDateDeserializer();
54             }
55             if (rawType == Timestamp.class) {
56                 return new TimestampDeserializer();
57             }
58             if (rawType == GregorianCalendar.class) {
59                 return new CalendarDeserializer(GregorianCalendar.class);
60             }
61         }
62         return null;
63     }
64 
65     // @since 2.11
hasDeserializerFor(Class<?> rawType)66     public static boolean hasDeserializerFor(Class<?> rawType) {
67         return _classNames.contains(rawType.getName());
68     }
69 
70     /*
71     /**********************************************************
72     /* Intermediate class for Date-based ones
73     /**********************************************************
74      */
75 
76     protected abstract static class DateBasedDeserializer<T>
77         extends StdScalarDeserializer<T>
78         implements ContextualDeserializer
79     {
80         /**
81          * Specific format to use, if non-null; if null will
82          * just use default format.
83          */
84         protected final DateFormat _customFormat;
85 
86         /**
87          * Let's also keep format String for reference, to use for error messages
88          */
89         protected final String _formatString;
90 
DateBasedDeserializer(Class<?> clz)91         protected DateBasedDeserializer(Class<?> clz) {
92             super(clz);
93             _customFormat = null;
94             _formatString = null;
95         }
96 
DateBasedDeserializer(DateBasedDeserializer<T> base, DateFormat format, String formatStr)97         protected DateBasedDeserializer(DateBasedDeserializer<T> base,
98                 DateFormat format, String formatStr) {
99             super(base._valueClass);
100             _customFormat = format;
101             _formatString = formatStr;
102         }
103 
withDateFormat(DateFormat df, String formatStr)104         protected abstract DateBasedDeserializer<T> withDateFormat(DateFormat df, String formatStr);
105 
106         @Override // since 2.12
logicalType()107         public LogicalType logicalType() {
108             return LogicalType.DateTime;
109         }
110 
111         @Override
createContextual(DeserializationContext ctxt, BeanProperty property)112         public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
113                 BeanProperty property)
114            throws JsonMappingException
115         {
116             final JsonFormat.Value format = findFormatOverrides(ctxt, property,
117                     handledType());
118 
119             if (format != null) {
120                 TimeZone tz = format.getTimeZone();
121                 final Boolean lenient = format.getLenient();
122 
123                 // First: fully custom pattern?
124                 if (format.hasPattern()) {
125                     final String pattern = format.getPattern();
126                     final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
127                     SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
128                     if (tz == null) {
129                         tz = ctxt.getTimeZone();
130                     }
131                     df.setTimeZone(tz);
132                     if (lenient != null) {
133                         df.setLenient(lenient);
134                     }
135                     return withDateFormat(df, pattern);
136                 }
137                 // But if not, can still override timezone
138                 if (tz != null) {
139                     DateFormat df = ctxt.getConfig().getDateFormat();
140                     // one shortcut: with our custom format, can simplify handling a bit
141                     if (df.getClass() == StdDateFormat.class) {
142                         final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
143                         StdDateFormat std = (StdDateFormat) df;
144                         std = std.withTimeZone(tz);
145                         std = std.withLocale(loc);
146                         if (lenient != null) {
147                             std = std.withLenient(lenient);
148                         }
149                         df = std;
150                     } else {
151                         // otherwise need to clone, re-set timezone:
152                         df = (DateFormat) df.clone();
153                         df.setTimeZone(tz);
154                         if (lenient != null) {
155                             df.setLenient(lenient);
156                         }
157                     }
158                     return withDateFormat(df, _formatString);
159                 }
160                 // or maybe even just leniency?
161                 if (lenient != null) {
162                     DateFormat df = ctxt.getConfig().getDateFormat();
163                     String pattern = _formatString;
164                     // one shortcut: with our custom format, can simplify handling a bit
165                     if (df.getClass() == StdDateFormat.class) {
166                         StdDateFormat std = (StdDateFormat) df;
167                         std = std.withLenient(lenient);
168                         df = std;
169                         pattern = std.toPattern();
170                     } else {
171                         // otherwise need to clone,
172                         df = (DateFormat) df.clone();
173                         df.setLenient(lenient);
174                         if (df instanceof SimpleDateFormat) {
175                             ((SimpleDateFormat) df).toPattern();
176                         }
177                     }
178                     if (pattern == null) {
179                         pattern = "[unknown]";
180                     }
181                     return withDateFormat(df, pattern);
182                 }
183             }
184             return this;
185         }
186 
187         @Override
_parseDate(JsonParser p, DeserializationContext ctxt)188         protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
189             throws IOException
190         {
191             if (_customFormat != null) {
192                 if (p.hasToken(JsonToken.VALUE_STRING)) {
193                     String str = p.getText().trim();
194                     if (str.length() == 0) {
195                         final CoercionAction act = _checkFromStringCoercion(ctxt, str);
196                         switch (act) { // note: Fail handled above
197                         case AsEmpty:
198                             return new java.util.Date(0L);
199                         case AsNull:
200                         case TryConvert:
201                         default:
202                         }
203                         return null;
204                     }
205                     synchronized (_customFormat) {
206                         try {
207                             return _customFormat.parse(str);
208                         } catch (ParseException e) {
209                             return (java.util.Date) ctxt.handleWeirdStringValue(handledType(), str,
210                                     "expected format \"%s\"", _formatString);
211                         }
212                     }
213                 }
214             }
215             return super._parseDate(p, ctxt);
216         }
217     }
218 
219     /*
220     /**********************************************************
221     /* Deserializer implementations for Date types
222     /**********************************************************
223      */
224 
225     @JacksonStdImpl
226     public static class CalendarDeserializer extends DateBasedDeserializer<Calendar>
227     {
228         /**
229          * We may know actual expected type; if so, it will be
230          * used for instantiation.
231          *
232          * @since 2.9
233          */
234         protected final Constructor<Calendar> _defaultCtor;
235 
CalendarDeserializer()236         public CalendarDeserializer() {
237             super(Calendar.class);
238             _defaultCtor = null;
239         }
240 
241         @SuppressWarnings("unchecked")
CalendarDeserializer(Class<? extends Calendar> cc)242         public CalendarDeserializer(Class<? extends Calendar> cc) {
243             super(cc);
244             _defaultCtor = (Constructor<Calendar>) ClassUtil.findConstructor(cc, false);
245         }
246 
CalendarDeserializer(CalendarDeserializer src, DateFormat df, String formatString)247         public CalendarDeserializer(CalendarDeserializer src, DateFormat df, String formatString) {
248             super(src, df, formatString);
249             _defaultCtor = src._defaultCtor;
250         }
251 
252         @Override
withDateFormat(DateFormat df, String formatString)253         protected CalendarDeserializer withDateFormat(DateFormat df, String formatString) {
254             return new CalendarDeserializer(this, df, formatString);
255         }
256 
257         @Override // since 2.12
getEmptyValue(DeserializationContext ctxt)258         public Object getEmptyValue(DeserializationContext ctxt) {
259             GregorianCalendar cal = new GregorianCalendar();
260             cal.setTimeInMillis(0L);
261             return cal;
262         }
263 
264         @Override
deserialize(JsonParser p, DeserializationContext ctxt)265         public Calendar deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
266         {
267             Date d = _parseDate(p, ctxt);
268             if (d == null) {
269                 return null;
270             }
271             if (_defaultCtor == null) {
272                 return ctxt.constructCalendar(d);
273             }
274             try {
275                 Calendar c = _defaultCtor.newInstance();
276                 c.setTimeInMillis(d.getTime());
277                 TimeZone tz = ctxt.getTimeZone();
278                 if (tz != null) {
279                     c.setTimeZone(tz);
280                 }
281                 return c;
282             } catch (Exception e) {
283                 return (Calendar) ctxt.handleInstantiationProblem(handledType(), d, e);
284             }
285         }
286     }
287 
288     /**
289      * Simple deserializer for handling {@link java.util.Date} values.
290      *<p>
291      * One way to customize Date formats accepted is to override method
292      * {@link DeserializationContext#parseDate} that this basic
293      * deserializer calls.
294      */
295     @JacksonStdImpl
296     public static class DateDeserializer extends DateBasedDeserializer<Date>
297     {
298         public final static DateDeserializer instance = new DateDeserializer();
299 
DateDeserializer()300         public DateDeserializer() { super(Date.class); }
DateDeserializer(DateDeserializer base, DateFormat df, String formatString)301         public DateDeserializer(DateDeserializer base, DateFormat df, String formatString) {
302             super(base, df, formatString);
303         }
304 
305         @Override
withDateFormat(DateFormat df, String formatString)306         protected DateDeserializer withDateFormat(DateFormat df, String formatString) {
307             return new DateDeserializer(this, df, formatString);
308         }
309 
310         @Override // since 2.12
getEmptyValue(DeserializationContext ctxt)311         public Object getEmptyValue(DeserializationContext ctxt) {
312             return new Date(0L);
313         }
314 
315         @Override
deserialize(JsonParser p, DeserializationContext ctxt)316         public java.util.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
317             return _parseDate(p, ctxt);
318         }
319     }
320 
321     /**
322      * Compared to plain old {@link java.util.Date}, SQL version is easier
323      * to deal with: mostly because it is more limited.
324      */
325     public static class SqlDateDeserializer
326         extends DateBasedDeserializer<java.sql.Date>
327     {
SqlDateDeserializer()328         public SqlDateDeserializer() { super(java.sql.Date.class); }
SqlDateDeserializer(SqlDateDeserializer src, DateFormat df, String formatString)329         public SqlDateDeserializer(SqlDateDeserializer src, DateFormat df, String formatString) {
330             super(src, df, formatString);
331         }
332 
333         @Override
withDateFormat(DateFormat df, String formatString)334         protected SqlDateDeserializer withDateFormat(DateFormat df, String formatString) {
335             return new SqlDateDeserializer(this, df, formatString);
336         }
337 
338         @Override // since 2.12
getEmptyValue(DeserializationContext ctxt)339         public Object getEmptyValue(DeserializationContext ctxt) {
340             return new java.sql.Date(0L);
341         }
342 
343         @Override
deserialize(JsonParser p, DeserializationContext ctxt)344         public java.sql.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
345             Date d = _parseDate(p, ctxt);
346             return (d == null) ? null : new java.sql.Date(d.getTime());
347         }
348     }
349 
350     /**
351      * Simple deserializer for handling {@link java.sql.Timestamp} values.
352      *<p>
353      * One way to customize Timestamp formats accepted is to override method
354      * {@link DeserializationContext#parseDate} that this basic
355      * deserializer calls.
356      */
357     public static class TimestampDeserializer extends DateBasedDeserializer<Timestamp>
358     {
TimestampDeserializer()359         public TimestampDeserializer() { super(Timestamp.class); }
TimestampDeserializer(TimestampDeserializer src, DateFormat df, String formatString)360         public TimestampDeserializer(TimestampDeserializer src, DateFormat df, String formatString) {
361             super(src, df, formatString);
362         }
363 
364         @Override
withDateFormat(DateFormat df, String formatString)365         protected TimestampDeserializer withDateFormat(DateFormat df, String formatString) {
366             return new TimestampDeserializer(this, df, formatString);
367         }
368 
369         @Override // since 2.12
getEmptyValue(DeserializationContext ctxt)370         public Object getEmptyValue(DeserializationContext ctxt) {
371             return new Timestamp(0L);
372         }
373 
374         @Override
deserialize(JsonParser p, DeserializationContext ctxt)375         public java.sql.Timestamp deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
376         {
377             Date d = _parseDate(p, ctxt);
378             return (d == null) ? null : new Timestamp(d.getTime());
379         }
380     }
381 }
382