-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
BeanUtil.java
357 lines (330 loc) · 12.7 KB
/
BeanUtil.java
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
package com.fasterxml.jackson.databind.util;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
/**
* Helper class that contains functionality needed by both serialization
* and deserialization side.
*/
public class BeanUtil
{
/*
/**********************************************************
/* Property name mangling methods: deprecated
/**********************************************************
*/
/**
* @since 2.5
*
* @deprecated Since 2.12 replaced with {@link com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy}
*/
@Deprecated
public static String okNameForGetter(AnnotatedMethod am, boolean stdNaming) {
String name = am.getName();
String str = okNameForIsGetter(am, name, stdNaming);
if (str == null) {
str = okNameForRegularGetter(am, name, stdNaming);
}
return str;
}
/**
* @since 2.5
*
* @deprecated Since 2.12 replaced with {@link com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy}
*/
@Deprecated
public static String okNameForRegularGetter(AnnotatedMethod am, String name,
boolean stdNaming)
{
if (name.startsWith("get")) {
/* 16-Feb-2009, tatu: To handle [JACKSON-53], need to block
* CGLib-provided method "getCallbacks". Not sure of exact
* safe criteria to get decent coverage without false matches;
* but for now let's assume there's no reason to use any
* such getter from CGLib.
* But let's try this approach...
*/
if ("getCallbacks".equals(name)) {
if (isCglibGetCallbacks(am)) {
return null;
}
} else if ("getMetaClass".equals(name)) {
// 30-Apr-2009, tatu: Need to suppress serialization of a cyclic reference
if (isGroovyMetaClassGetter(am)) {
return null;
}
}
return stdNaming
? stdManglePropertyName(name, 3)
: legacyManglePropertyName(name, 3);
}
return null;
}
/**
* @since 2.5
*
* @deprecated Since 2.12 replaced with {@link com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy}
*/
@Deprecated
public static String okNameForIsGetter(AnnotatedMethod am, String name,
boolean stdNaming)
{
if (name.startsWith("is")) { // plus, must return a boolean
Class<?> rt = am.getRawType();
if (rt == Boolean.class || rt == Boolean.TYPE) {
return stdNaming
? stdManglePropertyName(name, 2)
: legacyManglePropertyName(name, 2);
}
}
return null;
}
// since 2.9, not used any more by databind itself but somehow seems as if
// it may have been used by JAXB module during 2.11
@Deprecated
public static String okNameForSetter(AnnotatedMethod am, boolean stdNaming) {
return okNameForMutator(am, "set", stdNaming);
}
/**
* @since 2.5
*
* @deprecated Since 2.12 replaced with {@link com.fasterxml.jackson.databind.introspect.AccessorNamingStrategy}
*/
@Deprecated
public static String okNameForMutator(AnnotatedMethod am, String prefix,
boolean stdNaming) {
String name = am.getName();
if (name.startsWith(prefix)) {
return stdNaming
? stdManglePropertyName(name, prefix.length())
: legacyManglePropertyName(name, prefix.length());
}
return null;
}
/*
/**********************************************************
/* Value defaulting helpers
/**********************************************************
*/
/**
* Accessor used to find out "default value" to use for comparing values to
* serialize, to determine whether to exclude value from serialization with
* inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
*<p>
* Default logic is such that for primitives and wrapper types for primitives, expected
* defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String,
* and for structured (Maps, Collections, arrays) and reference types, criteria
* {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}
* is used.
*
* @since 2.7
*/
public static Object getDefaultValue(JavaType type)
{
// 06-Nov-2015, tatu: Returning null is fine for Object types; but need special
// handling for primitives since they are never passed as nulls.
Class<?> cls = type.getRawClass();
// 30-Sep-2016, tatu: Also works for Wrappers, so both `Integer.TYPE` and `Integer.class`
// would return `Integer.TYPE`
Class<?> prim = ClassUtil.primitiveType(cls);
if (prim != null) {
return ClassUtil.defaultValue(prim);
}
if (type.isContainerType() || type.isReferenceType()) {
return JsonInclude.Include.NON_EMPTY;
}
if (cls == String.class) {
return "";
}
// 09-Mar-2016, tatu: Not sure how far this path we want to go but for now
// let's add `java.util.Date` and `java.util.Calendar`, as per [databind#1550]
if (type.isTypeOrSubTypeOf(Date.class)) {
return new Date(0L);
}
if (type.isTypeOrSubTypeOf(Calendar.class)) {
Calendar c = new GregorianCalendar();
c.setTimeInMillis(0L);
return c;
}
return null;
}
/*
/**********************************************************
/* Special case handling
/**********************************************************
*/
/**
* This method was added to address the need to weed out
* CGLib-injected "getCallbacks" method.
* At this point caller has detected a potential getter method
* with name "getCallbacks" and we need to determine if it is
* indeed injectect by Cglib. We do this by verifying that the
* result type is "net.sf.cglib.proxy.Callback[]"
*/
protected static boolean isCglibGetCallbacks(AnnotatedMethod am)
{
Class<?> rt = am.getRawType();
// Ok, first: must return an array type
if (rt.isArray()) {
// And that type needs to be "net.sf.cglib.proxy.Callback".
// Theoretically could just be a type that implements it, but
// for now let's keep things simple, fix if need be.
Class<?> compType = rt.getComponentType();
// Actually, let's just verify it's a "net.sf.cglib.*" class/interface
final String className = compType.getName();
if (className.contains(".cglib")) {
return className.startsWith("net.sf.cglib")
// also, as per [JACKSON-177]
|| className.startsWith("org.hibernate.repackage.cglib")
// and [core#674]
|| className.startsWith("org.springframework.cglib");
}
}
return false;
}
/**
* Another helper method to deal with Groovy's problematic metadata accessors
*/
protected static boolean isGroovyMetaClassGetter(AnnotatedMethod am) {
return am.getRawType().getName().startsWith("groovy.lang");
}
/*
/**********************************************************
/* Actual name mangling methods
/**********************************************************
*/
/**
* Method called to figure out name of the property, given
* corresponding suggested name based on a method or field name.
*
* @param basename Name of accessor/mutator method, not including prefix
* ("get"/"is"/"set")
*/
protected static String legacyManglePropertyName(final String basename, final int offset)
{
final int end = basename.length();
if (end == offset) { // empty name, nope
return null;
}
// next check: is the first character upper case? If not, return as is
char c = basename.charAt(offset);
char d = Character.toLowerCase(c);
if (c == d) {
return basename.substring(offset);
}
// otherwise, lower case initial chars. Common case first, just one char
StringBuilder sb = new StringBuilder(end - offset);
sb.append(d);
int i = offset+1;
for (; i < end; ++i) {
c = basename.charAt(i);
d = Character.toLowerCase(c);
if (c == d) {
sb.append(basename, i, end);
break;
}
sb.append(d);
}
return sb.toString();
}
/**
* Note: public only since 2.11
*
* @since 2.5
*/
public static String stdManglePropertyName(final String basename, final int offset)
{
final int end = basename.length();
if (end == offset) { // empty name, nope
return null;
}
// first: if it doesn't start with capital, return as-is
char c0 = basename.charAt(offset);
char c1 = Character.toLowerCase(c0);
if (c0 == c1) {
return basename.substring(offset);
}
// 17-Dec-2014, tatu: As per [databind#653], need to follow more
// closely Java Beans spec; specifically, if two first are upper-case,
// then no lower-casing should be done.
if ((offset + 1) < end) {
if (Character.isUpperCase(basename.charAt(offset+1))) {
return basename.substring(offset);
}
}
StringBuilder sb = new StringBuilder(end - offset);
sb.append(c1);
sb.append(basename, offset+1, end);
return sb.toString();
}
/*
/**********************************************************
/* Package-specific type detection for error handling
/**********************************************************
*/
/**
* Helper method called by {@link com.fasterxml.jackson.databind.deser.BeanDeserializerFactory}
* and {@link com.fasterxml.jackson.databind.ser.BeanSerializerFactory} to check
* if given unrecognized type (to be (de)serialized as general POJO) is one of
* "well-known" types for which there would be a datatype module; and if so,
* return appropriate failure message to give to caller.
*
* @since 2.12
*/
public static String checkUnsupportedType(JavaType type) {
final String className = type.getRawClass().getName();
String typeName, moduleName;
if (isJava8TimeClass(className)) {
// [modules-java8#207]: do NOT check/block helper types in sub-packages,
// but only main-level types (to avoid issues with module)
if (className.indexOf('.', 10) >= 0) {
return null;
}
typeName = "Java 8 date/time";
moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310";
} else if (isJodaTimeClass(className)) {
typeName = "Joda date/time";
moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-joda";
} else if (isJava8OptionalClass(className)) {
typeName = "Java 8 optional";
moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jdk8";
} else {
return null;
}
return String.format("%s type %s not supported by default: add Module \"%s\" to enable handling",
typeName, ClassUtil.getTypeDescription(type), moduleName);
}
/**
* @since 2.12
*/
public static boolean isJava8TimeClass(Class<?> rawType) {
return isJava8TimeClass(rawType.getName());
}
// @since 2.12
private static boolean isJava8TimeClass(String className) {
return className.startsWith("java.time.");
}
/**
* @since 2.16
*/
public static boolean isJava8OptionalClass(Class<?> rawType) {
return isJava8OptionalClass(rawType.getName());
}
// @since 2.16
private static boolean isJava8OptionalClass(String className) {
return className.startsWith("java.util.Optional");
}
/**
* @since 2.12
*/
public static boolean isJodaTimeClass(Class<?> rawType) {
return isJodaTimeClass(rawType.getName());
}
// @since 2.12
private static boolean isJodaTimeClass(String className) {
return className.startsWith("org.joda.time.");
}
}