-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathCreatorProperty.java
More file actions
383 lines (340 loc) · 14.7 KB
/
CreatorProperty.java
File metadata and controls
383 lines (340 loc) · 14.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
package tools.jackson.databind.deser;
import java.lang.annotation.Annotation;
import com.fasterxml.jackson.annotation.JacksonInject;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.*;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.introspect.AnnotatedMember;
import tools.jackson.databind.introspect.AnnotatedParameter;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.util.Annotations;
import tools.jackson.databind.util.ClassUtil;
/**
* This concrete sub-class implements property that is passed
* via Creator (constructor or static factory method).
* It is not a full-featured implementation in that its set method should
* usually not be called for primary mutation -- instead, value must be passed
* separately -- but some aspects are still needed (specifically, injection).
*<p>
* Note on injectable values: unlike with other mutators, where
* deserializer and injecting are separate, here we treat the two as related
* things. This is necessary to add proper priority, as well as to simplify
* coordination.
*/
public class CreatorProperty
extends SettableBeanProperty
{
/**
* Placeholder that represents constructor parameter, when it is created
* from actual constructor.
* May be null when a synthetic instance is created.
*/
protected final AnnotatedParameter _annotated;
/**
* Injection settings, if value injection should be used for this parameter
* (in addition to, or instead of, regular deserialization).
*<p>
* NOTE: badly named, should be more like "_injectionDefinition" but
* renaming would be a breaking (internal) change.
*/
protected final JacksonInject.Value _injectableValue;
/**
* In special cases, when implementing "updateValue", we cannot use
* constructors or factory methods, but have to fall back on using a
* setter (or mutable field property). If so, this refers to that fallback
* accessor.
*<p>
* Mutable only to allow setting after construction, but must be strictly
* set before any use.
*/
protected SettableBeanProperty _fallbackSetter;
/**
* Pre-computed flag that is {@code true} if {@link #_fallbackSetter}'s
* declared type matches this property's (creator parameter) type, so that
* the existing value deserializer can be reused when reading into an
* existing instance. Cached to avoid repeating the type comparison on
* every call to {@link #deserializeAndSet}/{@link #deserializeSetAndReturn}.
*
* @since 3.2
*/
protected boolean _fallbackSetterTypeMatches;
protected final int _creatorIndex;
/**
* Marker flag that may have to be set during construction, to indicate that
* although property may have been constructed and added as a placeholder,
* it represents something that should be ignored during deserialization.
* This mostly concerns Creator properties which may not be easily deleted
* during processing.
*/
protected boolean _ignorable;
protected CreatorProperty(PropertyName name, JavaType type, PropertyName wrapperName,
TypeDeserializer typeDeser,
Annotations contextAnnotations, AnnotatedParameter param,
int index, JacksonInject.Value injectable,
PropertyMetadata metadata)
{
super(name, type, wrapperName, typeDeser, contextAnnotations, metadata);
_annotated = param;
_creatorIndex = index;
_injectableValue = injectable;
_fallbackSetter = null;
}
/**
* Factory method for creating {@link CreatorProperty} instances
*
* @param name Name of the logical property
* @param type Type of the property, used to find deserializer
* @param wrapperName Possible wrapper to use for logical property, if any
* @param typeDeser Type deserializer to use for handling polymorphic type
* information, if one is needed
* @param contextAnnotations Contextual annotations (usually by class that
* declares creator [constructor, factory method] that includes
* this property)
* @param param Representation of property, constructor or factory
* method parameter; used for accessing annotations of the property
* @param injectable Information about injectable value, if any
* @param index Index of this property within creator invocation
*/
public static CreatorProperty construct(PropertyName name, JavaType type, PropertyName wrapperName,
TypeDeserializer typeDeser,
Annotations contextAnnotations, AnnotatedParameter param,
int index, JacksonInject.Value injectable,
PropertyMetadata metadata)
{
return new CreatorProperty(name, type, wrapperName, typeDeser, contextAnnotations,
param, index, injectable, metadata);
}
protected CreatorProperty(CreatorProperty src, PropertyName newName) {
super(src, newName);
_annotated = src._annotated;
_injectableValue = src._injectableValue;
_fallbackSetter = src._fallbackSetter;
_fallbackSetterTypeMatches = src._fallbackSetterTypeMatches;
_creatorIndex = src._creatorIndex;
_ignorable = src._ignorable;
}
protected CreatorProperty(CreatorProperty src, ValueDeserializer<?> deser,
NullValueProvider nva) {
super(src, deser, nva);
_annotated = src._annotated;
_injectableValue = src._injectableValue;
_fallbackSetter = src._fallbackSetter;
_fallbackSetterTypeMatches = src._fallbackSetterTypeMatches;
_creatorIndex = src._creatorIndex;
_ignorable = src._ignorable;
}
protected CreatorProperty(CreatorProperty src, TypeDeserializer typeDeser)
{
super(src, typeDeser);
_annotated = src._annotated;
_injectableValue = src._injectableValue;
_fallbackSetter = src._fallbackSetter;
_fallbackSetterTypeMatches = src._fallbackSetterTypeMatches;
_creatorIndex = src._creatorIndex;
_ignorable = src._ignorable;
}
@Override
public SettableBeanProperty withName(PropertyName newName) {
return new CreatorProperty(this, newName);
}
@Override
public SettableBeanProperty withValueDeserializer(ValueDeserializer<?> deser) {
if (_valueDeserializer == deser) {
return this;
}
// 07-May-2019, tatu: As per [databind#2303], must keep VD/NVP in-sync if they were
NullValueProvider nvp = (_valueDeserializer == _nullProvider) ? deser : _nullProvider;
return new CreatorProperty(this, deser, nvp);
}
@Override
public SettableBeanProperty withNullProvider(NullValueProvider nva) {
return new CreatorProperty(this, _valueDeserializer, nva);
}
// @since 3.0
public SettableBeanProperty withValueTypeDeserializer(TypeDeserializer typeDeser) {
if (_valueTypeDeserializer == typeDeser) {
return this;
}
return new CreatorProperty(this, typeDeser);
}
@Override
public void fixAccess(DeserializationConfig config) {
if (_fallbackSetter != null) {
_fallbackSetter.fixAccess(config);
}
}
/**
* NOTE: one exception to immutability, due to problems with CreatorProperty instances
* being shared between Bean, separate PropertyBasedCreator
*/
public void setFallbackSetter(SettableBeanProperty fallbackSetter) {
_fallbackSetter = fallbackSetter;
_fallbackSetterTypeMatches = (fallbackSetter != null)
&& _type.equals(fallbackSetter.getType());
}
@Override
public void markAsIgnorable() {
_ignorable = true;
}
@Override
public boolean isIgnorable() {
return _ignorable;
}
/*
/**********************************************************************
/* BeanProperty impl
/**********************************************************************
*/
@Override
public <A extends Annotation> A getAnnotation(Class<A> acls) {
if (_annotated == null) {
return null;
}
return _annotated.getAnnotation(acls);
}
@Override public AnnotatedMember getMember() { return _annotated; }
@Override public int getCreatorIndex() {
return _creatorIndex;
}
/*
/**********************************************************************
/* Overridden methods, SettableBeanProperty
/**********************************************************************
*/
@Override
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object instance) throws JacksonException
{
_verifySetter();
_fallbackSetter.set(ctxt, instance, _deserializeForSetter(p, ctxt));
}
@Override
public Object deserializeSetAndReturn(JsonParser p,
DeserializationContext ctxt, Object instance) throws JacksonException
{
_verifySetter();
return _fallbackSetter.setAndReturn(ctxt, instance, _deserializeForSetter(p, ctxt));
}
@Override
public void set(DeserializationContext ctxt, Object instance, Object value)
{
_verifySetter();
_fallbackSetter.set(ctxt, instance, value);
}
@Override
public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value)
{
_verifySetter();
return _fallbackSetter.setAndReturn(ctxt,instance, value);
}
@Override
public PropertyMetadata getMetadata() {
// 03-Jun-2019, tatu: Added as per [databind#2280] to support merge.
// Not 100% sure why it would be needed (or fixes things) but... appears to.
// Need to understand better in future as it seems like it should probably be
// linked earlier during construction or something.
// 22-Sep-2019, tatu: Was hoping [databind#2458] fixed this, too, but no such luck
PropertyMetadata md = super.getMetadata();
if (_fallbackSetter != null) {
return md.withMergeInfo(_fallbackSetter.getMetadata().getMergeInfo());
}
return md;
}
// Perhaps counter-intuitively, ONLY creator properties return non-null id
@Override
public Object getInjectableValueId() {
return (_injectableValue == null) ? null : _injectableValue.getId();
}
@Override // since 2.21
public JacksonInject.Value getInjectionDefinition() {
return _injectableValue;
}
@Override
public boolean isInjectionOnly() {
return (_injectableValue != null) && !_injectableValue.willUseInput(true);
}
// public boolean isInjectionOnly() { return false; }
@Override // @since 3.1
public boolean isCreatorProperty() { return true; }
/*
/**********************************************************************
/* Overridden methods, other
/**********************************************************************
*/
@Override
public String toString() { return "[creator property, name "+ClassUtil.name(getName())+"; inject id '"+getInjectableValueId()+"']"; }
/*
/**********************************************************************
/* Internal helper methods
/**********************************************************************
*/
/**
* Helper method for {@code deserializeAndSet} and {@code deserializeSetAndReturn}:
* deserializes value using the fallback setter's type if it differs from the
* creator parameter type.
*<p>
* [databind#5281]: When updating an existing instance, the creator parameter type
* (e.g. {@code String[]} from varargs) may differ from the setter/field type
* (e.g. {@code Collection<String>}). Must deserialize using the setter's type
* to avoid {@code ClassCastException}.
*
* @since 3.2
*/
private Object _deserializeForSetter(JsonParser p, DeserializationContext ctxt)
throws JacksonException
{
// Common case: types match, use this property's (already resolved) deserializer
if (_fallbackSetterTypeMatches) {
return deserialize(p, ctxt);
}
// Types differ: find deserializer for the fallback setter's type.
// Note: we use `_nullProvider` (this CreatorProperty's) rather than the
// fallback setter's: `BeanPropertyDefinition` merges annotations across
// accessors, so `@JsonSetter(nulls=...)` on the setter is already reflected
// here via `BeanDeserializerBase.resolve()`, whereas the fallback setter
// itself is stored as a raw `MethodProperty` and never contextualized.
if (p.hasToken(JsonToken.VALUE_NULL)) {
return _nullProvider.getNullValue(ctxt);
}
ValueDeserializer<Object> deser = ctxt.findContextualValueDeserializer(
_fallbackSetter.getType(), _fallbackSetter);
// If fallback setter has a TypeDeserializer (polymorphism via @JsonTypeInfo),
// honor it instead of plain deserialize()
final TypeDeserializer typeDeser = _fallbackSetter.getValueTypeDeserializer();
Object value = (typeDeser == null)
? deser.deserialize(p, ctxt)
: deser.deserializeWithType(p, ctxt, typeDeser);
if (value == null) {
value = _nullProvider.getNullValue(ctxt);
}
return value;
}
private final void _verifySetter() throws JacksonException {
if (_fallbackSetter == null) {
_reportMissingSetter(null, null);
}
}
private void _reportMissingSetter(JsonParser p, DeserializationContext ctxt)
throws JacksonException
{
String clsDesc = (_annotated == null) ? "UNKNOWN TYPE"
: ClassUtil.getClassDescription(_annotated.getOwner().getDeclaringClass());
final String msg = String.format(
"No fallback setter/field defined for creator property %s (of %s)"
+"; this is needed when a creator property is part of a forward reference"
+" (e.g. circular Object Id reference resolved via `@JsonIdentityInfo`)."
+" To fix this, either add a setter method for the property,"
+" make the field non-private (public),"
+" or use a no-argument constructor instead of `@JsonCreator`",
ClassUtil.name(getName()), clsDesc);
// Hmmmh. Should we return quietly (NOP), or error?
// Perhaps better to throw an exception, since it's generally an error.
if (ctxt != null ) {
ctxt.reportBadDefinition(getType(), msg);
} else {
throw InvalidDefinitionException.from(p, msg, getType());
}
}
}