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 com.fasterxml.jackson.annotation.JsonFormat;
20 import com.fasterxml.jackson.core.JsonParser;
21 import com.fasterxml.jackson.core.JsonToken;
22 import com.fasterxml.jackson.core.JsonTokenId;
23 import com.fasterxml.jackson.databind.BeanProperty;
24 import com.fasterxml.jackson.databind.DeserializationContext;
25 import com.fasterxml.jackson.databind.DeserializationFeature;
26 import com.fasterxml.jackson.databind.JsonDeserializer;
27 import com.fasterxml.jackson.databind.JsonMappingException;
28 import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
29
30 import java.io.IOException;
31 import java.math.BigDecimal;
32 import java.time.DateTimeException;
33 import java.time.Instant;
34 import java.time.OffsetDateTime;
35 import java.time.ZoneId;
36 import java.time.ZonedDateTime;
37 import java.time.format.DateTimeFormatter;
38 import java.time.temporal.Temporal;
39 import java.time.temporal.TemporalAccessor;
40 import java.util.function.BiFunction;
41 import java.util.function.Function;
42 import java.util.regex.Pattern;
43
44 /**
45  * Deserializer for Java 8 temporal {@link Instant}s, {@link OffsetDateTime}, and {@link ZonedDateTime}s.
46  *
47  * @author Nick Williams
48  * @since 2.2
49  */

50 public class InstantDeserializer<T extends Temporal>
51     extends JSR310DateTimeDeserializerBase<T>
52 {
53     private static final long serialVersionUID = 1L;
54
55     /**
56      * Constants used to check if the time offset is zero. See [jackson-modules-java8#18]
57      *
58      * @since 2.9.0
59      */

60     private static final Pattern ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX = Pattern.compile("\\+00:?(00)?$");
61
62     public static final InstantDeserializer<Instant> INSTANT = new InstantDeserializer<>(
63             Instant.class, DateTimeFormatter.ISO_INSTANT,
64             Instant::from,
65             a -> Instant.ofEpochMilli(a.value),
66             a -> Instant.ofEpochSecond(a.integer, a.fraction),
67             null,
68             true // yes, replace zero offset with Z
69     );
70
71     public static final InstantDeserializer<OffsetDateTime> OFFSET_DATE_TIME = new InstantDeserializer<>(
72             OffsetDateTime.class, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
73             OffsetDateTime::from,
74             a -> OffsetDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
75             a -> OffsetDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
76             (d, z) -> d.withOffsetSameInstant(z.getRules().getOffset(d.toLocalDateTime())),
77             true // yes, replace zero offset with Z
78     );
79
80     public static final InstantDeserializer<ZonedDateTime> ZONED_DATE_TIME = new InstantDeserializer<>(
81             ZonedDateTime.class, DateTimeFormatter.ISO_ZONED_DATE_TIME,
82             ZonedDateTime::from,
83             a -> ZonedDateTime.ofInstant(Instant.ofEpochMilli(a.value), a.zoneId),
84             a -> ZonedDateTime.ofInstant(Instant.ofEpochSecond(a.integer, a.fraction), a.zoneId),
85             ZonedDateTime::withZoneSameInstant,
86             false // keep zero offset and Z separate since zones explicitly supported
87     );
88
89     protected final Function<FromIntegerArguments, T> fromMilliseconds;
90
91     protected final Function<FromDecimalArguments, T> fromNanoseconds;
92
93     protected final Function<TemporalAccessor, T> parsedToValue;
94
95     protected final BiFunction<T, ZoneId, T> adjust;
96
97     /**
98      * In case of vanilla `Instant` we seem to need to translate "+0000 | +00:00 | +00"
99      * timezone designator into plain "Z" for some reason; see
100      * [jackson-modules-java8#18] for more info
101      *
102      * @since 2.9.0
103      */

104     protected final boolean replaceZeroOffsetAsZ;
105
106     /**
107      * Flag for <code>JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE</code>
108      *
109      * @since 2.8
110      */

111     protected final Boolean _adjustToContextTZOverride;
112
113     protected InstantDeserializer(Class<T> supportedType,
114             DateTimeFormatter formatter,
115             Function<TemporalAccessor, T> parsedToValue,
116             Function<FromIntegerArguments, T> fromMilliseconds,
117             Function<FromDecimalArguments, T> fromNanoseconds,
118             BiFunction<T, ZoneId, T> adjust,
119             boolean replaceZeroOffsetAsZ)
120     {
121         super(supportedType, formatter);
122         this.parsedToValue = parsedToValue;
123         this.fromMilliseconds = fromMilliseconds;
124         this.fromNanoseconds = fromNanoseconds;
125         this.adjust = adjust == null ? ((d, z) -> d) : adjust;
126         this.replaceZeroOffsetAsZ = replaceZeroOffsetAsZ;
127         _adjustToContextTZOverride = null;
128     }
129
130     @SuppressWarnings("unchecked")
131     protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f)
132     {
133         super((Class<T>) base.handledType(), f);
134         parsedToValue = base.parsedToValue;
135         fromMilliseconds = base.fromMilliseconds;
136         fromNanoseconds = base.fromNanoseconds;
137         adjust = base.adjust;
138         replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
139         _adjustToContextTZOverride = base._adjustToContextTZOverride;
140     }
141
142     @SuppressWarnings("unchecked")
143     protected InstantDeserializer(InstantDeserializer<T> base, Boolean adjustToContextTimezoneOverride)
144     {
145         super((Class<T>) base.handledType(), base._formatter);
146         parsedToValue = base.parsedToValue;
147         fromMilliseconds = base.fromMilliseconds;
148         fromNanoseconds = base.fromNanoseconds;
149         adjust = base.adjust;
150         replaceZeroOffsetAsZ = base.replaceZeroOffsetAsZ;
151         _adjustToContextTZOverride = adjustToContextTimezoneOverride;
152     }
153
154     @SuppressWarnings("unchecked")
155     protected InstantDeserializer(InstantDeserializer<T> base, DateTimeFormatter f, Boolean leniency)
156     {
157         super((Class<T>) base.handledType(), f, leniency);
158         parsedToValue = base.parsedToValue;
159         fromMilliseconds = base.fromMilliseconds;
160         fromNanoseconds = base.fromNanoseconds;
161         adjust = base.adjust;
162         replaceZeroOffsetAsZ = (_formatter == DateTimeFormatter.ISO_INSTANT);
163         _adjustToContextTZOverride = base._adjustToContextTZOverride;
164     }
165
166     @Override
167     protected InstantDeserializer<T> withDateFormat(DateTimeFormatter dtf) {
168         if (dtf == _formatter) {
169             return this;
170         }
171         return new InstantDeserializer<T>(this, dtf);
172     }
173
174     @Override
175     protected InstantDeserializer<T> withLeniency(Boolean leniency) {
176         return new InstantDeserializer<T>(this, _formatter, leniency);
177     }
178
179     @Override
180     protected InstantDeserializer<T> withShape(JsonFormat.Shape shape) { return this; }
181
182     @SuppressWarnings("unchecked")
183     @Override
184     public T deserialize(JsonParser parser, DeserializationContext context) throws IOException
185     {
186         //NOTE: Timestamps contain no timezone info, and are always in configured TZ. Only
187         //string values have to be adjusted to the configured TZ.
188         switch (parser.getCurrentTokenId())
189         {
190             case JsonTokenId.ID_NUMBER_FLOAT:
191                 return _fromDecimal(context, parser.getDecimalValue());
192
193             case JsonTokenId.ID_NUMBER_INT:
194                 return _fromLong(context, parser.getLongValue());
195
196             case JsonTokenId.ID_STRING:
197             {
198                 String string = parser.getText().trim();
199                 if (string.length() == 0) {
200                     if (!isLenient()) {
201                         return _failForNotLenient(parser, context, JsonToken.VALUE_STRING);
202                     }
203                     return null;
204                 }
205                 // only check for other parsing modes if we are using default formatter
206                 if (_formatter == DateTimeFormatter.ISO_INSTANT ||
207                     _formatter == DateTimeFormatter.ISO_OFFSET_DATE_TIME ||
208                     _formatter == DateTimeFormatter.ISO_ZONED_DATE_TIME) {
209                     // 22-Jan-2016, [datatype-jsr310#16]: Allow quoted numbers too
210                     int dots = _countPeriods(string);
211                     if (dots >= 0) { // negative if not simple number
212                         try {
213                             if (dots == 0) {
214                                 return _fromLong(context, Long.parseLong(string));
215                             }
216                             if (dots == 1) {
217                                 return _fromDecimal(context, new BigDecimal(string));
218                             }
219                         } catch (NumberFormatException e) {
220                             // fall through to default handling, to get error there
221                         }
222                     }
223
224                     string = replaceZeroOffsetAsZIfNecessary(string);
225                 }
226
227                 T value;
228                 try {
229                     TemporalAccessor acc = _formatter.parse(string);
230                     value = parsedToValue.apply(acc);
231                     if (shouldAdjustToContextTimezone(context)) {
232                         return adjust.apply(value, this.getZone(context));
233                     }
234                 } catch (DateTimeException e) {
235                     value = _handleDateTimeException(context, e, string);
236                 }
237                 return value;
238             }
239
240             case JsonTokenId.ID_EMBEDDED_OBJECT:
241                 // 20-Apr-2016, tatu: Related to [databind#1208], can try supporting embedded
242                 //    values quite easily
243                 return (T) parser.getEmbeddedObject();
244
245             case JsonTokenId.ID_START_ARRAY:
246                 return _deserializeFromArray(parser, context);
247         }
248         return _handleUnexpectedToken(context, parser, JsonToken.VALUE_STRING,
249                 JsonToken.VALUE_NUMBER_INT, JsonToken.VALUE_NUMBER_FLOAT);
250     }
251
252     @SuppressWarnings("unchecked")
253     @Override
254     public JsonDeserializer<T> createContextual(DeserializationContext ctxt,
255             BeanProperty property) throws JsonMappingException
256     {
257         InstantDeserializer<T> deserializer =
258                 (InstantDeserializer<T>)super.createContextual(ctxt, property);
259         if (deserializer != this) {
260             JsonFormat.Value val = findFormatOverrides(ctxt, property, handledType());
261             if (val != null) {
262                 deserializer = new InstantDeserializer<>(deserializer, val.getFeature(JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE));
263                 if (val.hasLenient()) {
264                     Boolean leniency = val.getLenient();
265                     if (leniency != null) {
266                         deserializer = deserializer.withLeniency(leniency);
267                     }
268                 }
269             }
270         }
271         return deserializer;
272     }
273
274     protected boolean shouldAdjustToContextTimezone(DeserializationContext context) {
275         return (_adjustToContextTZOverride != null) ? _adjustToContextTZOverride :
276                 context.isEnabled(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
277     }
278
279     // Helper method to find Strings of form "all digits" and "digits-comma-digits"
280     protected int _countPeriods(String str)
281     {
282         int commas = 0;
283         for (int i = 0, end = str.length(); i < end; ++i) {
284             int ch = str.charAt(i);
285             if (ch < '0' || ch > '9') {
286                 if (ch == '.') {
287                     ++commas;
288                 } else {
289                     return -1;
290                 }
291             }
292         }
293         return commas;
294     }
295
296     protected T _fromLong(DeserializationContext context, long timestamp)
297     {
298         if(context.isEnabled(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)){
299             return fromNanoseconds.apply(new FromDecimalArguments(
300                     timestamp, 0, this.getZone(context)
301             ));
302         }
303         return fromMilliseconds.apply(new FromIntegerArguments(
304                 timestamp, this.getZone(context)));
305     }
306
307     protected T _fromDecimal(DeserializationContext context, BigDecimal value)
308     {
309         FromDecimalArguments args =
310             DecimalUtils.extractSecondsAndNanos(value, (s, ns) -> new FromDecimalArguments(s, ns, getZone(context)));
311         return fromNanoseconds.apply(args);
312     }
313
314     private ZoneId getZone(DeserializationContext context)
315     {
316         // Instants are always in UTC, so don't waste compute cycles
317         return (_valueClass == Instant.class) ? null : context.getTimeZone().toZoneId();
318     }
319
320     private String replaceZeroOffsetAsZIfNecessary(String text)
321     {
322         if (replaceZeroOffsetAsZ) {
323             return ISO8601_UTC_ZERO_OFFSET_SUFFIX_REGEX.matcher(text).replaceFirst("Z");
324         }
325
326         return text;
327     }
328
329     public static class FromIntegerArguments // since 2.8.3
330     {
331         public final long value;
332         public final ZoneId zoneId;
333
334         private FromIntegerArguments(long value, ZoneId zoneId)
335         {
336             this.value = value;
337             this.zoneId = zoneId;
338         }
339     }
340
341     public static class FromDecimalArguments // since 2.8.3
342     {
343         public final long integer;
344         public final int fraction;
345         public final ZoneId zoneId;
346
347         private FromDecimalArguments(long integer, int fraction, ZoneId zoneId)
348         {
349             this.integer = integer;
350             this.fraction = fraction;
351             this.zoneId = zoneId;
352         }
353     }
354 }
355