-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathPropertyBuilder.java
More file actions
408 lines (375 loc) · 19.2 KB
/
PropertyBuilder.java
File metadata and controls
408 lines (375 loc) · 19.2 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
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
package tools.jackson.databind.ser;
import com.fasterxml.jackson.annotation.JsonInclude;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.introspect.*;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.util.*;
/**
* Helper class for {@link BeanSerializerFactory} that is used to
* construct {@link BeanPropertyWriter} instances. Can be sub-classed
* to change behavior.
*/
public class PropertyBuilder
{
private final static Object NO_DEFAULT_MARKER = Boolean.FALSE;
protected final SerializationConfig _config;
protected final BeanDescription _beanDesc;
protected final AnnotationIntrospector _annotationIntrospector;
/**
* If a property has serialization inclusion value of
* {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT},
* we may need to know the default value of the bean, to know if property value
* equals default one.
*<p>
* NOTE: only used if enclosing class defines NON_DEFAULT, but NOT if it is the
* global default OR per-property override.
*/
protected Object _defaultBean;
/**
* Default inclusion mode for properties of the POJO for which
* properties are collected; possibly overridden on
* per-property basis. Combines global inclusion defaults and
* per-type (annotation and type-override) inclusion overrides.
*/
protected final JsonInclude.Value _defaultInclusion;
/**
* Marker flag used to indicate that "real" default values are to be used
* for properties, as per per-type value inclusion of type <code>NON_DEFAULT</code>
*/
protected final boolean _useRealPropertyDefaults;
public PropertyBuilder(SerializationConfig config, BeanDescription beanDesc)
{
_config = config;
_beanDesc = beanDesc;
// 08-Sep-2016, tatu: This gets tricky, with 3 levels of definitions:
// (a) global default inclusion
// (b) per-type default inclusion (from annotation or config overrides;
// config override having precedence)
// (c) per-property override (from annotation on specific property or
// config overrides per type of property;
// annotation having precedence)
//
// and not only requiring merging, but also considering special handling
// for NON_DEFAULT in case of (b) (vs (a) or (c))
JsonInclude.Value inclPerType = JsonInclude.Value.merge(
beanDesc.findPropertyInclusion(JsonInclude.Value.empty()),
config.getDefaultPropertyInclusion(beanDesc.getBeanClass(),
JsonInclude.Value.empty()));
_defaultInclusion = JsonInclude.Value.merge(config.getDefaultPropertyInclusion(),
inclPerType);
// [databind#1757]: Use "real" property defaults (from default bean instance)
// when NON_DEFAULT is set per-type (b), or when set as global default (a)
// AND MapperFeature.USE_REAL_NON_DEFAULT is enabled. This ensures round-trip
// consistency: values that match the no-arg constructor defaults are suppressed
// during serialization and restored by the constructor during deserialization.
if (inclPerType.getValueInclusion() == JsonInclude.Include.NON_DEFAULT) {
_useRealPropertyDefaults = true;
} else if (_defaultInclusion.getValueInclusion() == JsonInclude.Include.NON_DEFAULT
&& config.isEnabled(MapperFeature.USE_REAL_INCLUDE_NON_DEFAULT)) {
_useRealPropertyDefaults = true;
} else {
_useRealPropertyDefaults = false;
}
_annotationIntrospector = _config.getAnnotationIntrospector();
}
/*
/**********************************************************************
/* Public API
/**********************************************************************
*/
public Annotations getClassAnnotations() {
return _beanDesc.getClassAnnotations();
}
/**
* @param contentTypeSer Optional explicit type information serializer
* to use for contained values (only used for properties that are
* of container type)
*/
protected BeanPropertyWriter buildWriter(SerializationContext ctxt,
BeanPropertyDefinition propDef, JavaType declaredType, ValueSerializer<?> ser,
TypeSerializer typeSer, TypeSerializer contentTypeSer,
AnnotatedMember am, boolean defaultUseStaticTyping)
{
// do we have annotation that forces type to use (to declared type or its super type)?
JavaType serializationType;
try {
serializationType = findSerializationType(ctxt, am, defaultUseStaticTyping, declaredType);
} catch (DatabindException e) {
if (propDef == null) {
return ctxt.reportBadDefinition(declaredType, ClassUtil.exceptionMessage(e));
}
return ctxt.reportBadPropertyDefinition(_beanDesc, propDef, ClassUtil.exceptionMessage(e));
}
// Container types can have separate type serializers for content (value / element) type
if (contentTypeSer != null) {
// 04-Feb-2010, tatu: Let's force static typing for collection, if there is
// type information for contents. Should work well (for JAXB case); can be
// revisited if this causes problems.
if (serializationType == null) {
// serializationType = TypeFactory.type(am.getGenericType(), _beanDesc.getType());
serializationType = declaredType;
}
JavaType ct = serializationType.getContentType();
// Not exactly sure why, but this used to occur; better check explicitly:
if (ct == null) {
ctxt.reportBadPropertyDefinition(_beanDesc, propDef,
"serialization type "+serializationType+" has no content");
}
serializationType = serializationType.withContentTypeHandler(contentTypeSer);
ct = serializationType.getContentType();
}
Object valueToSuppress = null;
boolean suppressNulls = false;
// 12-Jul-2016, tatu: [databind#1256] Need to make sure we consider type refinement
JavaType actualType = (serializationType == null) ? declaredType : serializationType;
// 17-Mar-2017: [databind#1522] Allow config override per property type
AnnotatedMember accessor = propDef.getAccessor(); // lgtm [java/dereferenced-value-may-be-null]
if (accessor == null) {
// neither Setter nor ConstructorParameter are expected here
return ctxt.reportBadPropertyDefinition(_beanDesc, propDef,
"could not determine property type");
}
Class<?> rawPropertyType = accessor.getRawType();
// 17-Aug-2016, tatu: Default inclusion covers global default (for all types), as well
// as type-default for enclosing POJO. What we need, then, is per-type default (if any)
// for declared property type... and finally property annotation overrides
JsonInclude.Value inclV = _config.getDefaultInclusion(actualType.getRawClass(),
rawPropertyType, _defaultInclusion);
// property annotation override
inclV = inclV.withOverrides(propDef.findInclusion());
JsonInclude.Include inclusion = inclV.getValueInclusion();
if (inclusion == JsonInclude.Include.USE_DEFAULTS) { // should not occur but...
inclusion = JsonInclude.Include.ALWAYS;
}
switch (inclusion) {
case NON_DEFAULT:
// 11-Nov-2015, tatu: This is tricky because semantics differ between cases,
// so that if enclosing class has this, we may need to access values of property,
// whereas for global defaults OR per-property overrides, we have more
// static definition. Sigh.
// First: case of class/type specifying it; try to find POJO property defaults
Object defaultBean;
// 16-Oct-2016, tatu: Note: if we cannot for some reason create "default instance",
// revert logic to the case of general/per-property handling, so both
// type-default AND null are to be excluded.
// (as per [databind#1417]
if (_useRealPropertyDefaults && (defaultBean = getDefaultBean()) != null) {
// 07-Sep-2016, tatu: may also need to front-load access forcing now
if (ctxt.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) {
am.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
}
try {
valueToSuppress = am.getValue(defaultBean);
} catch (Exception e) {
_throwWrapped(e, propDef.getName(), defaultBean);
}
// [databind#1757]: With NON_DEFAULT, always suppress nulls:
// null is never a meaningful "non-default" value when we have
// a real default instance to compare against
suppressNulls = true;
} else {
valueToSuppress = BeanUtil.propertyDefaultValue(ctxt, actualType);
suppressNulls = true;
}
if (valueToSuppress == null) {
suppressNulls = true;
// [databind#4471] Different behavior when Include.NON_DEFAULT
// setting is used on POJO vs global setting, as per documentation.
if (!_useRealPropertyDefaults) {
// [databind#4464] NON_DEFAULT does not work with NON_EMPTY for custom serializer
// [databind#5312] But for Record types, default value for reference-type
// properties is always null; no need to fall back to MARKER_FOR_EMPTY
// (which would cause @JsonValue delegation to incorrectly suppress
// non-null values)
if (!_beanDesc.isRecordType()) {
valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
}
}
} else {
if (valueToSuppress.getClass().isArray()) {
valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
}
}
break;
case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals
// always suppress nulls
suppressNulls = true;
// and for referential types, also "empty", which in their case means "absent"
if (actualType.isReferenceType()) {
valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
}
break;
case NON_EMPTY:
// always suppress nulls
suppressNulls = true;
// but possibly also 'empty' values:
valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
break;
case CUSTOM: // new with 2.9
valueToSuppress = ctxt.includeFilterInstance(propDef, inclV.getValueFilter());
break;
case NON_NULL:
suppressNulls = true;
// fall through
case ALWAYS: // default
default:
// we may still want to suppress empty collections
final SerializationFeature emptyJsonArrays = SerializationFeature.WRITE_EMPTY_JSON_ARRAYS;
if (actualType.isContainerType() && !_config.isEnabled(emptyJsonArrays)) {
valueToSuppress = BeanPropertyWriter.MARKER_FOR_EMPTY;
}
break;
}
Class<?>[] views = propDef.findViews();
if (views == null) {
views = _beanDesc.findDefaultViews();
}
Class<?> applyView = propDef.findApplyView();
// [databind#1649]: Pass the computed inclusion value (which includes
// contextual annotations) so BeanPropertyWriter can use it directly
// instead of re-computing in findPropertyInclusion()
BeanPropertyWriter bpw = _constructPropertyWriter(propDef,
am, _beanDesc.getClassAnnotations(), declaredType,
ser, typeSer, serializationType, suppressNulls, valueToSuppress, views,
inclV, applyView);
// How about custom null serializer?
Object serDef = _annotationIntrospector.findNullSerializer(_config, am);
if (serDef != null) {
bpw.assignNullSerializer(ctxt.serializerInstance(am, serDef));
}
// And then, handling of unwrapping
NameTransformer unwrapper = _annotationIntrospector.findUnwrappingNameTransformer(_config, am);
if (unwrapper != null) {
// [databind#1298]: @JsonUnwrapped incompatible with @JsonIdentityInfo
// on the unwrapped property type
if (_annotationIntrospector.findObjectIdInfo(_config,
ctxt.introspectClassAnnotations(declaredType)) != null) {
ctxt.reportBadPropertyDefinition(_beanDesc, propDef,
"cannot use `@JsonUnwrapped` on property with type that has `@JsonIdentityInfo`");
}
bpw = bpw.unwrappingWriter(unwrapper);
}
return bpw;
}
/**
* Overridable factory method for actual construction of {@link BeanPropertyWriter};
* often needed if subclassing {@link #buildWriter} method.
*
* @since 3.1 Added {@code inclusion} parameter
*/
protected BeanPropertyWriter _constructPropertyWriter(BeanPropertyDefinition propDef,
AnnotatedMember member, Annotations contextAnnotations,
JavaType declaredType,
ValueSerializer<?> ser, TypeSerializer typeSer, JavaType serType,
boolean suppressNulls, Object suppressableValue,
Class<?>[] includeInViews, JsonInclude.Value inclusion, Class<?> applyView)
{
return new BeanPropertyWriter(propDef,
member, contextAnnotations, declaredType,
ser, typeSer, serType, suppressNulls, suppressableValue, includeInViews,
inclusion, applyView);
}
/*
/**********************************************************************
/* Helper methods; annotation access
/**********************************************************************
*/
/**
* Method that will try to determine statically defined type of property
* being serialized, based on annotations (for overrides), and alternatively
* declared type (if static typing for serialization is enabled).
* If neither can be used (no annotations, dynamic typing), returns null.
*/
protected JavaType findSerializationType(SerializationContext ctxt,
Annotated a, boolean useStaticTyping, JavaType declaredType)
{
JavaType secondary = _annotationIntrospector.refineSerializationType(_config, a, declaredType);
// 11-Oct-2015, tatu: As of 2.7, not 100% sure following checks are needed.
// But keeping for now, just in case
if (secondary != declaredType) {
Class<?> serClass = secondary.getRawClass();
// Must be a super type to be usable
Class<?> rawDeclared = declaredType.getRawClass();
if (serClass.isAssignableFrom(rawDeclared)) {
; // fine as is
} else {
/* 18-Nov-2010, tatu: Related to fixing [JACKSON-416], an issue with such
* check is that for deserialization more specific type makes sense;
* and for serialization more generic. But alas JAXB uses but a single
* annotation to do both... Hence, we must just discard type, as long as
* types are related
*/
if (!rawDeclared.isAssignableFrom(serClass)) {
throw new IllegalArgumentException("Illegal concrete-type annotation for method '"+a.getName()+"': class "+serClass.getName()+" not a super-type of (declared) class "+rawDeclared.getName());
}
/* 03-Dec-2010, tatu: Actually, ugh, we may need to further relax this
* and actually accept subtypes too for serialization. Bit dangerous in theory
* but need to trust user here...
*/
}
useStaticTyping = true;
declaredType = secondary;
}
// If using static typing, declared type is known to be the type...
// First: check property-level (member) annotation
JsonSerialize.Typing typing = _annotationIntrospector.findSerializationTyping(_config, a);
if ((typing != null) && (typing != JsonSerialize.Typing.DEFAULT_TYPING)) {
useStaticTyping = (typing == JsonSerialize.Typing.STATIC);
}
// [databind#1515]: if still static, check declared type's class-level annotation;
// allows @JsonSerialize(typing=DYNAMIC) on class to override global USE_STATIC_TYPING
else if (useStaticTyping) {
JsonSerialize.Typing classTyping = _annotationIntrospector.findSerializationTyping(
_config, ctxt.introspectClassAnnotations(declaredType));
if (classTyping == JsonSerialize.Typing.DYNAMIC) {
useStaticTyping = false;
}
}
if (useStaticTyping) {
// 11-Oct-2015, tatu: Make sure JavaType also "knows" static-ness...
return declaredType.withStaticTyping();
}
return null;
}
/*
/**********************************************************************
/* Helper methods for default value handling
/**********************************************************************
*/
protected Object getDefaultBean()
{
Object def = _defaultBean;
if (def == null) {
// If we can fix access rights, we should; otherwise non-public
// classes or default constructor will prevent instantiation
def = _beanDesc.instantiateBean(_config.canOverrideAccessModifiers());
if (def == null) {
// 06-Nov-2015, tatu: As per [databind#998], do not fail.
/*
Class<?> cls = _beanDesc.getClassInfo().getAnnotated();
throw new IllegalArgumentException("Class "+cls.getName()+" has no default constructor; cannot instantiate default bean value to support 'properties=JsonSerialize.Inclusion.NON_DEFAULT' annotation");
*/
// And use a marker
def = NO_DEFAULT_MARKER;
}
_defaultBean = def;
}
return (def == NO_DEFAULT_MARKER) ? null : _defaultBean;
}
/*
/**********************************************************************
/* Helper methods for exception handling
/**********************************************************************
*/
protected Object _throwWrapped(Exception e, String propName, Object defaultBean)
{
Throwable t = e;
while (t.getCause() != null) {
t = t.getCause();
}
ClassUtil.throwIfError(t);
ClassUtil.throwIfRTE(t);
throw new IllegalArgumentException("Failed to get property '"+propName+"' of default "+defaultBean.getClass().getName()+" instance");
}
}