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.Period;
22 import java.time.ZoneId;
23 import java.time.ZoneOffset;
24
25 import com.fasterxml.jackson.annotation.JsonFormat;
26 import com.fasterxml.jackson.core.JsonParser;
27
28 import com.fasterxml.jackson.core.JsonToken;
29 import com.fasterxml.jackson.databind.BeanProperty;
30 import com.fasterxml.jackson.databind.DeserializationContext;
31 import com.fasterxml.jackson.databind.JsonDeserializer;
32 import com.fasterxml.jackson.databind.JsonMappingException;
33 import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
34 import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
35
36 /**
37  * Deserializer for all Java 8 temporal {@link java.time} types that cannot be represented
38  * with numbers and that have parse functions that can take {@link String}s,
39  * and where format is not configurable.
40  *
41  * @author Nick Williams
42  * @author Tatu Saloranta
43  *
44  * @since 2.2
45  */

46 public class JSR310StringParsableDeserializer
47     extends JSR310DeserializerBase<Object>
48         implements ContextualDeserializer
49 {
50     private static final long serialVersionUID = 1L;
51
52     protected final static int TYPE_PERIOD = 1;
53     protected final static int TYPE_ZONE_ID = 2;
54     protected final static int TYPE_ZONE_OFFSET = 3;
55
56     public static final JsonDeserializer<Period> PERIOD =
57         createDeserializer(Period.class, TYPE_PERIOD);
58
59     public static final JsonDeserializer<ZoneId> ZONE_ID =
60         createDeserializer(ZoneId.class, TYPE_ZONE_ID);
61
62     public static final JsonDeserializer<ZoneOffset> ZONE_OFFSET =
63         createDeserializer(ZoneOffset.class, TYPE_ZONE_OFFSET);
64
65     protected final int _typeSelector;
66
67     @SuppressWarnings("unchecked")
68     protected JSR310StringParsableDeserializer(Class<?> supportedType, int typeSelector)
69     {
70         super((Class<Object>)supportedType);
71         _typeSelector = typeSelector;
72     }
73
74     /**
75      * Since 2.11
76      */

77     protected JSR310StringParsableDeserializer(JSR310StringParsableDeserializer base, Boolean leniency) {
78         super(base, leniency);
79         _typeSelector = base._typeSelector;
80     }
81
82     @SuppressWarnings("unchecked")
83     protected static <T> JsonDeserializer<T> createDeserializer(Class<T> type, int typeId) {
84         return (JsonDeserializer<T>) new JSR310StringParsableDeserializer(type, typeId);
85     }
86
87     @Override
88     protected JSR310StringParsableDeserializer withLeniency(Boolean leniency) {
89         if (_isLenient == !Boolean.FALSE.equals(leniency)) {
90             return this;
91         }
92         // TODO: or should this be casting as above in createDeserializer? But then in createContext, we need to
93         // call the withLeniency method in this class. (See if we can follow InstantDeser convention here?)
94         return new JSR310StringParsableDeserializer(this, leniency);
95     }
96
97     @Override
98     public Object deserialize(JsonParser p, DeserializationContext context) throws IOException
99     {
100         String string = p.getValueAsString();
101         if (string != null) {
102             string = string.trim();
103             if (string.length() == 0) {
104                 if (!isLenient()) {
105                     return _failForNotLenient(p, context, JsonToken.VALUE_STRING);
106                 }
107                 return null;
108             }
109             try {
110                 switch (_typeSelector) {
111                 case TYPE_PERIOD:
112                     return Period.parse(string);
113                 case TYPE_ZONE_ID:
114                     return ZoneId.of(string);
115                 case TYPE_ZONE_OFFSET:
116                     return ZoneOffset.of(string);
117                 }
118             } catch (DateTimeException e) {
119                 return _handleDateTimeException(context, e, string);
120             }
121         }
122         if (p.hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) {
123             // 20-Apr-2016, tatu: Related to [databind#1208], can try supporting embedded
124             //    values quite easily
125             return p.getEmbeddedObject();
126         }
127         if (p.hasToken(JsonToken.START_ARRAY)){
128             return _deserializeFromArray(p, context);
129         }
130         
131         throw context.wrongTokenException(p, handledType(), JsonToken.VALUE_STRING, null);
132     }
133
134     @Override
135     public Object deserializeWithType(JsonParser parser, DeserializationContext context,
136             TypeDeserializer deserializer)
137         throws IOException
138     {
139         // This is a nasty kludge right here, working around issues like
140         // [datatype-jsr310#24]. But should work better than not having the work-around.
141         JsonToken t = parser.getCurrentToken();
142         if ((t != null) && t.isScalarValue()) {
143             return deserialize(parser, context);
144         }
145         return deserializer.deserializeTypedFromAny(parser, context);
146     }
147
148     @Override
149     public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
150                                                 BeanProperty property) throws JsonMappingException
151     {
152         JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
153         JSR310StringParsableDeserializer deser = this;
154         if (format != null) {
155             if (format.hasLenient()) {
156                 Boolean leniency = format.getLenient();
157                 if (leniency != null) {
158                     deser = this.withLeniency(leniency);
159                 }
160             }
161         }
162         return deser;
163     }
164 }
165