• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.cfg;
2 
3 import java.util.HashMap;
4 import java.util.Map;
5 
6 import com.fasterxml.jackson.databind.DeserializationConfig;
7 import com.fasterxml.jackson.databind.DeserializationFeature;
8 import com.fasterxml.jackson.databind.MapperFeature;
9 import com.fasterxml.jackson.databind.type.LogicalType;
10 
11 /**
12  * @since 2.12
13  */
14 public class CoercionConfigs
15     implements java.io.Serializable
16 {
17     private static final long serialVersionUID = 1L;
18 
19     private final static int TARGET_TYPE_COUNT = LogicalType.values().length;
20 
21     /**
22      * Global default for cases not explicitly covered
23      */
24     protected CoercionAction _defaultAction;
25 
26     /**
27      * Default coercion definitions used if no overrides found
28      * by logical or physical type.
29      */
30     protected final MutableCoercionConfig _defaultCoercions;
31 
32     /**
33      * Coercion definitions by logical type ({@link LogicalType})
34      */
35     protected MutableCoercionConfig[] _perTypeCoercions;
36 
37     /**
38      * Coercion definitions by physical type (Class).
39      */
40     protected Map<Class<?>, MutableCoercionConfig> _perClassCoercions;
41 
42     /*
43     /**********************************************************************
44     /* Life cycle
45     /**********************************************************************
46      */
47 
CoercionConfigs()48     public CoercionConfigs() {
49         this(CoercionAction.TryConvert, new MutableCoercionConfig(),
50                 null, null);
51     }
52 
CoercionConfigs(CoercionAction defaultAction, MutableCoercionConfig defaultCoercions, MutableCoercionConfig[] perTypeCoercions, Map<Class<?>, MutableCoercionConfig> perClassCoercions)53     protected CoercionConfigs(CoercionAction defaultAction,
54             MutableCoercionConfig defaultCoercions,
55             MutableCoercionConfig[] perTypeCoercions,
56             Map<Class<?>, MutableCoercionConfig> perClassCoercions)
57     {
58         _defaultCoercions = defaultCoercions;
59         _defaultAction = defaultAction;
60         _perTypeCoercions = perTypeCoercions;
61         _perClassCoercions = perClassCoercions;
62     }
63 
64     /**
65      * Method called to create a non-shared copy of configuration settings,
66      * to be used by another {@link com.fasterxml.jackson.databind.ObjectMapper}
67      * instance.
68      *
69      * @return A non-shared copy of configuration settings
70      */
copy()71     public CoercionConfigs copy()
72     {
73         MutableCoercionConfig[] newPerType;
74         if (_perTypeCoercions == null) {
75             newPerType = null;
76         } else {
77             final int size = _perTypeCoercions.length;
78             newPerType = new MutableCoercionConfig[size];
79             for (int i = 0; i < size; ++i) {
80                 newPerType[i] = _copy(_perTypeCoercions[i]);
81             }
82         }
83         Map<Class<?>, MutableCoercionConfig> newPerClass;
84         if (_perClassCoercions == null) {
85             newPerClass = null;
86         } else {
87             newPerClass = new HashMap<>();
88             for (Map.Entry<Class<?>, MutableCoercionConfig> entry : _perClassCoercions.entrySet()) {
89                 newPerClass.put(entry.getKey(), entry.getValue().copy());
90             }
91         }
92         return new CoercionConfigs(_defaultAction, _defaultCoercions.copy(),
93                 newPerType, newPerClass);
94     }
95 
_copy(MutableCoercionConfig src)96     private static MutableCoercionConfig _copy(MutableCoercionConfig src) {
97         if (src == null) {
98             return null;
99         }
100         return src.copy();
101     }
102 
103     /*
104     /**********************************************************************
105     /* Mutators: global defaults
106     /**********************************************************************
107      */
108 
defaultCoercions()109     public MutableCoercionConfig defaultCoercions() {
110         return _defaultCoercions;
111     }
112 
113     /*
114     /**********************************************************************
115     /* Mutators: per type
116     /**********************************************************************
117      */
118 
findOrCreateCoercion(LogicalType type)119     public MutableCoercionConfig findOrCreateCoercion(LogicalType type) {
120         if (_perTypeCoercions == null) {
121             _perTypeCoercions = new MutableCoercionConfig[TARGET_TYPE_COUNT];
122         }
123         MutableCoercionConfig config = _perTypeCoercions[type.ordinal()];
124         if (config == null) {
125             _perTypeCoercions[type.ordinal()] = config = new MutableCoercionConfig();
126         }
127         return config;
128     }
129 
findOrCreateCoercion(Class<?> type)130     public MutableCoercionConfig findOrCreateCoercion(Class<?> type) {
131         if (_perClassCoercions == null) {
132             _perClassCoercions = new HashMap<>();
133         }
134         MutableCoercionConfig config = _perClassCoercions.get(type);
135         if (config == null) {
136             config = new MutableCoercionConfig();
137             _perClassCoercions.put(type, config);
138         }
139         return config;
140     }
141 
142     /*
143     /**********************************************************************
144     /* Access
145     /**********************************************************************
146      */
147 
148     /**
149      * General-purpose accessor for finding what to do when specified coercion
150      * from shape that is now always allowed to be coerced from is requested.
151      *
152      * @param config Currently active deserialization configuration
153      * @param targetType Logical target type of coercion
154      * @param targetClass Physical target type of coercion
155      * @param inputShape Input shape to coerce from
156      *
157      * @return CoercionAction configured for specified coercion
158      *
159      * @since 2.12
160      */
findCoercion(DeserializationConfig config, LogicalType targetType, Class<?> targetClass, CoercionInputShape inputShape)161     public CoercionAction findCoercion(DeserializationConfig config,
162             LogicalType targetType,
163             Class<?> targetClass, CoercionInputShape inputShape)
164     {
165         // First, see if there is exact match for physical type
166         if ((_perClassCoercions != null) && (targetClass != null)) {
167             MutableCoercionConfig cc = _perClassCoercions.get(targetClass);
168             if (cc != null) {
169                 CoercionAction act = cc.findAction(inputShape);
170                 if (act != null) {
171                     return act;
172                 }
173             }
174         }
175 
176         // If not, maybe by logical type
177         if ((_perTypeCoercions != null) && (targetType != null)) {
178             MutableCoercionConfig cc = _perTypeCoercions[targetType.ordinal()];
179             if (cc != null) {
180                 CoercionAction act = cc.findAction(inputShape);
181                 if (act != null) {
182                     return act;
183                 }
184             }
185         }
186 
187         // Barring that, default coercion for input shape?
188         CoercionAction act = _defaultCoercions.findAction(inputShape);
189         if (act != null) {
190             return act;
191         }
192 
193         // Otherwise there are some legacy features that can provide answer
194         if (inputShape == CoercionInputShape.EmptyArray) {
195             // Default for setting is false
196             return config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT) ?
197                     CoercionAction.AsNull : CoercionAction.Fail;
198         }
199         if ((inputShape == CoercionInputShape.Float)
200                 && (targetType == LogicalType.Integer)) {
201             // Default for setting in 2.x is true
202             return config.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT) ?
203                     CoercionAction.TryConvert : CoercionAction.Fail;
204         }
205 
206         // classic scalars are numbers, booleans; but date/time also considered
207         // scalar for this particular purpose
208         final boolean baseScalar = (targetType == LogicalType.Float)
209                 || (targetType == LogicalType.Integer)
210                 || (targetType == LogicalType.Boolean)
211                 || (targetType == LogicalType.DateTime);
212 
213         if (baseScalar) {
214             // Default for setting in 2.x is true
215             if (!config.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) {
216                 return CoercionAction.Fail;
217             }
218         }
219 
220         if (inputShape == CoercionInputShape.EmptyString) {
221             // Since coercion of scalar must be enabled (see check above), allow empty-string
222             // coercions by default even without this setting
223             if (baseScalar
224                     // Default for setting is false
225                     || config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) {
226                 return CoercionAction.AsNull;
227             }
228             // 09-Jun-2020, tatu: Seems necessary to support backwards-compatibility with
229             //     2.11, wrt "FromStringDeserializer" supported types
230             if (targetType == LogicalType.OtherScalar) {
231                 return CoercionAction.TryConvert;
232             }
233             // But block from allowing structured types like POJOs, Maps etc
234             return CoercionAction.Fail;
235         }
236 
237         // and all else failing, return default
238         return _defaultAction;
239     }
240 
241     /**
242      * More specialized accessor called in case of input being a blank
243      * String (one consisting of only white space characters with length of at least one).
244      * Will basically first determine if "blank as empty" is allowed: if not,
245      * returns {@code actionIfBlankNotAllowed}, otherwise returns action for
246      * {@link CoercionInputShape#EmptyString}.
247      *
248      * @param config Currently active deserialization configuration
249      * @param targetType Logical target type of coercion
250      * @param targetClass Physical target type of coercion
251      * @param actionIfBlankNotAllowed Return value to use in case "blanks as empty"
252      *    is not allowed
253      *
254      * @return CoercionAction configured for specified coercion from blank string
255      */
findCoercionFromBlankString(DeserializationConfig config, LogicalType targetType, Class<?> targetClass, CoercionAction actionIfBlankNotAllowed)256     public CoercionAction findCoercionFromBlankString(DeserializationConfig config,
257             LogicalType targetType,
258             Class<?> targetClass,
259             CoercionAction actionIfBlankNotAllowed)
260     {
261         Boolean acceptBlankAsEmpty = null;
262         CoercionAction action = null;
263 
264         // First, see if there is exact match for physical type
265         if ((_perClassCoercions != null) && (targetClass != null)) {
266             MutableCoercionConfig cc = _perClassCoercions.get(targetClass);
267             if (cc != null) {
268                 acceptBlankAsEmpty = cc.getAcceptBlankAsEmpty();
269                 action = cc.findAction(CoercionInputShape.EmptyString);
270             }
271         }
272 
273         // If not, maybe by logical type
274         if ((_perTypeCoercions != null) && (targetType != null)) {
275             MutableCoercionConfig cc = _perTypeCoercions[targetType.ordinal()];
276             if (cc != null) {
277                 if (acceptBlankAsEmpty == null) {
278                     acceptBlankAsEmpty = cc.getAcceptBlankAsEmpty();
279                 }
280                 if (action == null) {
281                     action = cc.findAction(CoercionInputShape.EmptyString);
282                 }
283             }
284         }
285 
286         // Barring that, default coercion for input shape?
287         if (acceptBlankAsEmpty == null) {
288             acceptBlankAsEmpty = _defaultCoercions.getAcceptBlankAsEmpty();
289         }
290         if (action == null) {
291             action = _defaultCoercions.findAction(CoercionInputShape.EmptyString);
292         }
293 
294         // First: if using blank as empty is no-go, return what caller specified
295         if (!Boolean.TRUE.equals(acceptBlankAsEmpty)) {
296             return actionIfBlankNotAllowed;
297         }
298 
299         // Otherwise, if action found, return that
300         if (action != null) {
301             return action;
302         }
303         // If not, one specific legacy setting to consider...
304         return config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) ?
305                     CoercionAction.AsNull : CoercionAction.Fail;
306     }
307 }
308