1 /*
2  * Copyright 2013 FasterXML.com
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may
5  * not use this file except in compliance with the License. You may obtain
6  * a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the license for the specific language governing permissions and
14  * limitations under the license.
15  */

16
17 package com.fasterxml.jackson.datatype.jsr310.deser;
18
19 import java.io.IOException;
20 import java.time.DateTimeException;
21 import java.time.Instant;
22 import java.time.LocalDate;
23 import java.time.LocalDateTime;
24 import java.time.ZoneOffset;
25 import java.time.format.DateTimeFormatter;
26
27 import com.fasterxml.jackson.annotation.JsonFormat;
28 import com.fasterxml.jackson.core.*;
29 import com.fasterxml.jackson.databind.DeserializationContext;
30 import com.fasterxml.jackson.databind.DeserializationFeature;
31
32 /**
33  * Deserializer for Java 8 temporal {@link LocalDate}s.
34  *
35  * @author Nick Williams
36  */

37 public class LocalDateDeserializer extends JSR310DateTimeDeserializerBase<LocalDate>
38 {
39     private static final long serialVersionUID = 1L;
40
41     private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
42     
43     public static final LocalDateDeserializer INSTANCE = new LocalDateDeserializer();
44
45     protected LocalDateDeserializer() {
46         this(DEFAULT_FORMATTER);
47     }
48
49     public LocalDateDeserializer(DateTimeFormatter dtf) {
50         super(LocalDate.class, dtf);
51     }
52
53     /**
54      * Since 2.10
55      */

56     public LocalDateDeserializer(LocalDateDeserializer base, DateTimeFormatter dtf) {
57         super(base, dtf);
58     }
59
60     /**
61      * Since 2.10
62      */

63     protected LocalDateDeserializer(LocalDateDeserializer base, Boolean leniency) {
64         super(base, leniency);
65     }
66
67     /**
68      * Since 2.11
69      */

70     protected LocalDateDeserializer(LocalDateDeserializer base, JsonFormat.Shape shape) {
71         super(base, shape);
72     }
73
74     @Override
75     protected LocalDateDeserializer withDateFormat(DateTimeFormatter dtf) {
76         return new LocalDateDeserializer(this, dtf);
77     }
78
79     @Override
80     protected LocalDateDeserializer withLeniency(Boolean leniency) {
81         return new LocalDateDeserializer(this, leniency);
82     }
83
84     @Override
85     protected LocalDateDeserializer withShape(JsonFormat.Shape shape) { return new LocalDateDeserializer(this, shape); }
86
87     @Override
88     public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException
89     {
90         if (parser.hasToken(JsonToken.VALUE_STRING)) {
91             String string = parser.getText().trim();
92             if (string.length() == 0) {
93                 if (!isLenient()) {
94                     return _failForNotLenient(parser, context, JsonToken.VALUE_STRING);
95                 }
96                 return null;
97             }
98             // as per [datatype-jsr310#37], only check for optional (and, incorrect...) time marker 'T'
99             // if we are using default formatter
100             DateTimeFormatter format = _formatter;
101             try {
102                 if (format == DEFAULT_FORMATTER) {
103                     // JavaScript by default includes time in JSON serialized Dates (UTC/ISO instant format).
104                     if (string.length() > 10 && string.charAt(10) == 'T') {
105                        if (string.endsWith("Z")) {
106                            return LocalDateTime.ofInstant(Instant.parse(string), ZoneOffset.UTC).toLocalDate();
107                        } else {
108                            return LocalDate.parse(string, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
109                        }
110                     }
111                 }
112                 return LocalDate.parse(string, format);
113             } catch (DateTimeException e) {
114                 return _handleDateTimeException(context, e, string);
115             }
116         }
117         if (parser.isExpectedStartArrayToken()) {
118             JsonToken t = parser.nextToken();
119             if (t == JsonToken.END_ARRAY) {
120                 return null;
121             }
122             if (context.isEnabled(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS)
123                     && (t == JsonToken.VALUE_STRING || t==JsonToken.VALUE_EMBEDDED_OBJECT)) {
124                 final LocalDate parsed = deserialize(parser, context);
125                 if (parser.nextToken() != JsonToken.END_ARRAY) {
126                     handleMissingEndArrayForSingle(parser, context);
127                 }
128                 return parsed;            
129             }
130             if (t == JsonToken.VALUE_NUMBER_INT) {
131                 int year = parser.getIntValue();
132                 int month = parser.nextIntValue(-1);
133                 int day = parser.nextIntValue(-1);
134                 
135                 if (parser.nextToken() != JsonToken.END_ARRAY) {
136                     throw context.wrongTokenException(parser, handledType(), JsonToken.END_ARRAY,
137                             "Expected array to end");
138                 }
139                 return LocalDate.of(year, month, day);
140             }
141             context.reportInputMismatch(handledType(),
142                     "Unexpected token (%s) within Array, expected VALUE_NUMBER_INT",
143                     t);
144         }
145         if (parser.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
146             return (LocalDate) parser.getEmbeddedObject();
147         }
148         // 06-Jan-2018, tatu: Is this actually safe? Do users expect such coercion?
149         if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
150             // issue 58 - also check for NUMBER_INT, which needs to be specified when serializing.
151             if (_shape == JsonFormat.Shape.NUMBER_INT || isLenient()) {
152                 return LocalDate.ofEpochDay(parser.getLongValue());
153             }
154             return _failForNotLenient(parser, context, JsonToken.VALUE_STRING);
155         }
156         return _handleUnexpectedToken(context, parser, "Expected array or string.");
157     }
158 }
159