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.deser.ContextualDeserializer;
17 import com.fasterxml.jackson.databind.util.ClassUtil;
18 import com.fasterxml.jackson.databind.util.StdDateFormat;
19
20 /**
21  * Container class for core JDK date/time type deserializers.
22  */

23 @SuppressWarnings("serial")
24 public class DateDeserializers
25 {
26     private final static HashSet<String> _classNames = new HashSet<String>();
27     static {
28         Class<?>[] numberTypes = new Class<?>[] {
29             Calendar.class,
30             GregorianCalendar.class,
31             java.sql.Date.class,
32             java.util.Date.class,
33             Timestamp.class,
34         };
35         for (Class<?> cls : numberTypes) {
36             _classNames.add(cls.getName());
37         }
38     }
39
40     public static JsonDeserializer<?> find(Class<?> rawType, String clsName)
41     {
42         if (_classNames.contains(clsName)) {
43             // Start with the most common type
44             if (rawType == Calendar.class) {
45                 return new CalendarDeserializer();
46             }
47             if (rawType == java.util.Date.class) {
48                 return DateDeserializer.instance;
49             }
50             if (rawType == java.sql.Date.class) {
51                 return new SqlDateDeserializer();
52             }
53             if (rawType == Timestamp.class) {
54                 return new TimestampDeserializer();
55             }
56             if (rawType == GregorianCalendar.class) {
57                 return new CalendarDeserializer(GregorianCalendar.class);
58             }
59         }
60         return null;
61     }
62
63     // @since 2.11
64     public static boolean hasDeserializerFor(Class<?> rawType) {
65         return _classNames.contains(rawType.getName());
66     }
67
68     /*
69     /**********************************************************
70     /* Intermediate class for Date-based ones
71     /**********************************************************
72      */

73
74     protected abstract static class DateBasedDeserializer<T>
75         extends StdScalarDeserializer<T>
76         implements ContextualDeserializer
77     {
78         /**
79          * Specific format to use, if non-nullif null will
80          * just use default format.
81          */

82         protected final DateFormat _customFormat;
83
84         /**
85          * Let's also keep format String for reference, to use for error messages
86          */

87         protected final String _formatString;
88
89         protected DateBasedDeserializer(Class<?> clz) {
90             super(clz);
91             _customFormat = null;
92             _formatString = null;
93         }
94
95         protected DateBasedDeserializer(DateBasedDeserializer<T> base,
96                 DateFormat format, String formatStr) {
97             super(base._valueClass);
98             _customFormat = format;
99             _formatString = formatStr;
100         }
101
102         protected abstract DateBasedDeserializer<T> withDateFormat(DateFormat df, String formatStr);
103
104         @Override
105         public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
106                 BeanProperty property)
107            throws JsonMappingException
108         {
109             final JsonFormat.Value format = findFormatOverrides(ctxt, property,
110                     handledType());
111
112             if (format != null) {
113                 TimeZone tz = format.getTimeZone();
114                 final Boolean lenient = format.getLenient();
115
116                 // First: fully custom pattern?
117                 if (format.hasPattern()) {
118                     final String pattern = format.getPattern();
119                     final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
120                     SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
121                     if (tz == null) {
122                         tz = ctxt.getTimeZone();
123                     }
124                     df.setTimeZone(tz);
125                     if (lenient != null) {
126                         df.setLenient(lenient);
127                     }
128                     return withDateFormat(df, pattern);
129                 }
130                 // But if not, can still override timezone
131                 if (tz != null) {
132                     DateFormat df = ctxt.getConfig().getDateFormat();
133                     // one shortcut: with our custom format, can simplify handling a bit
134                     if (df.getClass() == StdDateFormat.class) {
135                         final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
136                         StdDateFormat std = (StdDateFormat) df;
137                         std = std.withTimeZone(tz);
138                         std = std.withLocale(loc);
139                         if (lenient != null) {
140                             std = std.withLenient(lenient);
141                         }
142                         df = std;
143                     } else {
144                         // otherwise need to clone, re-set timezone:
145                         df = (DateFormat) df.clone();
146                         df.setTimeZone(tz);
147                         if (lenient != null) {
148                             df.setLenient(lenient);
149                         }
150                     }
151                     return withDateFormat(df, _formatString);
152                 }
153                 // or maybe even just leniency?
154                 if (lenient != null) {
155                     DateFormat df = ctxt.getConfig().getDateFormat();
156                     String pattern = _formatString;
157                     // one shortcut: with our custom format, can simplify handling a bit
158                     if (df.getClass() == StdDateFormat.class) {
159                         StdDateFormat std = (StdDateFormat) df;
160                         std = std.withLenient(lenient);
161                         df = std;
162                         pattern = std.toPattern();
163                     } else {
164                         // otherwise need to clone,
165                         df = (DateFormat) df.clone();
166                         df.setLenient(lenient);
167                         if (df instanceof SimpleDateFormat) {
168                             ((SimpleDateFormat) df).toPattern();
169                         }
170                     }
171                     if (pattern == null) {
172                         pattern = "[unknown]";
173                     }
174                     return withDateFormat(df, pattern);
175                 }
176             }
177             return this;
178         }
179
180         @Override
181         protected java.util.Date _parseDate(JsonParser p, DeserializationContext ctxt)
182             throws IOException
183         {
184             if (_customFormat != null) {
185                 if (p.hasToken(JsonToken.VALUE_STRING)) {
186                     String str = p.getText().trim();
187                     if (str.length() == 0) {
188                         return (Date) getEmptyValue(ctxt);
189                     }
190                     synchronized (_customFormat) {
191                         try {
192                             return _customFormat.parse(str);
193                         } catch (ParseException e) {
194                             return (java.util.Date) ctxt.handleWeirdStringValue(handledType(), str,
195                                     "expected format \"%s\"", _formatString);
196                         }
197                     }
198                 }
199             }
200             return super._parseDate(p, ctxt);
201         }
202     }
203
204     /*
205     /**********************************************************
206     /* Deserializer implementations for Date types
207     /**********************************************************
208      */

209
210     @JacksonStdImpl
211     public static class CalendarDeserializer extends DateBasedDeserializer<Calendar>
212     {
213         /**
214          * We may know actual expected type; if so, it will be
215          * used for instantiation.
216          *
217          * @since 2.9
218          */

219         protected final Constructor<Calendar> _defaultCtor;
220
221         public CalendarDeserializer() {
222             super(Calendar.class);
223             _defaultCtor = null;
224         }
225
226         @SuppressWarnings("unchecked")
227         public CalendarDeserializer(Class<? extends Calendar> cc) {
228             super(cc);
229             _defaultCtor = (Constructor<Calendar>) ClassUtil.findConstructor(cc, false);
230         }
231
232         public CalendarDeserializer(CalendarDeserializer src, DateFormat df, String formatString) {
233             super(src, df, formatString);
234             _defaultCtor = src._defaultCtor;
235         }
236
237         @Override
238         protected CalendarDeserializer withDateFormat(DateFormat df, String formatString) {
239             return new CalendarDeserializer(this, df, formatString);
240         }
241
242         @Override
243         public Calendar deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
244         {
245             Date d = _parseDate(p, ctxt);
246             if (d == null) {
247                 return null;
248             }
249             if (_defaultCtor == null) {
250                 return ctxt.constructCalendar(d);
251             }
252             try {
253                 Calendar c = _defaultCtor.newInstance();            
254                 c.setTimeInMillis(d.getTime());
255                 TimeZone tz = ctxt.getTimeZone();
256                 if (tz != null) {
257                     c.setTimeZone(tz);
258                 }
259                 return c;
260             } catch (Exception e) {
261                 return (Calendar) ctxt.handleInstantiationProblem(handledType(), d, e);
262             }
263         }
264     }
265
266     /**
267      * Simple deserializer for handling {@link java.util.Date} values.
268      *<p>
269      * One way to customize Date formats accepted is to override method
270      * {@link DeserializationContext#parseDate} that this basic
271      * deserializer calls.
272      */

273     @JacksonStdImpl
274     public static class DateDeserializer extends DateBasedDeserializer<Date>
275     {
276         public final static DateDeserializer instance = new DateDeserializer();
277
278         public DateDeserializer() { super(Date.class); }
279         public DateDeserializer(DateDeserializer base, DateFormat df, String formatString) {
280             super(base, df, formatString);
281         }
282
283         @Override
284         protected DateDeserializer withDateFormat(DateFormat df, String formatString) {
285             return new DateDeserializer(this, df, formatString);
286         }
287         
288         @Override
289         public java.util.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
290             return _parseDate(p, ctxt);
291         }
292     }
293
294     /**
295      * Compared to plain old {@link java.util.Date}, SQL version is easier
296      * to deal with: mostly because it is more limited.
297      */

298     public static class SqlDateDeserializer
299         extends DateBasedDeserializer<java.sql.Date>
300     {
301         public SqlDateDeserializer() { super(java.sql.Date.class); }
302         public SqlDateDeserializer(SqlDateDeserializer src, DateFormat df, String formatString) {
303             super(src, df, formatString);
304         }
305
306         @Override
307         protected SqlDateDeserializer withDateFormat(DateFormat df, String formatString) {
308             return new SqlDateDeserializer(this, df, formatString);
309         }
310         
311         @Override
312         public java.sql.Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
313             Date d = _parseDate(p, ctxt);
314             return (d == null) ? null : new java.sql.Date(d.getTime());
315         }
316     }
317
318     /**
319      * Simple deserializer for handling {@link java.sql.Timestamp} values.
320      *<p>
321      * One way to customize Timestamp formats accepted is to override method
322      * {@link DeserializationContext#parseDate} that this basic
323      * deserializer calls.
324      */

325     public static class TimestampDeserializer extends DateBasedDeserializer<Timestamp>
326     {
327         public TimestampDeserializer() { super(Timestamp.class); }
328         public TimestampDeserializer(TimestampDeserializer src, DateFormat df, String formatString) {
329             super(src, df, formatString);
330         }
331
332         @Override
333         protected TimestampDeserializer withDateFormat(DateFormat df, String formatString) {
334             return new TimestampDeserializer(this, df, formatString);
335         }
336         
337         @Override
338         public java.sql.Timestamp deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
339         {
340             Date d = _parseDate(p, ctxt);
341             return (d == null) ? null : new Timestamp(d.getTime());
342         }
343     }
344 }
345