1 package com.fasterxml.jackson.databind.util;
2
3 import java.util.Calendar;
4 import java.util.Date;
5 import java.util.GregorianCalendar;
6
7 import com.fasterxml.jackson.annotation.JsonInclude;
8 import com.fasterxml.jackson.databind.JavaType;
9 import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
10
11 /**
12 * Helper class that contains functionality needed by both serialization
13 * and deserialization side.
14 */
15 public class BeanUtil
16 {
17 /*
18 /**********************************************************
19 /* Handling property names
20 /**********************************************************
21 */
22
23 /**
24 * @since 2.5
25 */
26 public static String okNameForGetter(AnnotatedMethod am, boolean stdNaming) {
27 String name = am.getName();
28 String str = okNameForIsGetter(am, name, stdNaming);
29 if (str == null) {
30 str = okNameForRegularGetter(am, name, stdNaming);
31 }
32 return str;
33 }
34
35 /**
36 * @since 2.5
37 */
38 public static String okNameForRegularGetter(AnnotatedMethod am, String name,
39 boolean stdNaming)
40 {
41 if (name.startsWith("get")) {
42 /* 16-Feb-2009, tatu: To handle [JACKSON-53], need to block
43 * CGLib-provided method "getCallbacks". Not sure of exact
44 * safe criteria to get decent coverage without false matches;
45 * but for now let's assume there's no reason to use any
46 * such getter from CGLib.
47 * But let's try this approach...
48 */
49 if ("getCallbacks".equals(name)) {
50 if (isCglibGetCallbacks(am)) {
51 return null;
52 }
53 } else if ("getMetaClass".equals(name)) {
54 // 30-Apr-2009, tatu: Need to suppress serialization of a cyclic reference
55 if (isGroovyMetaClassGetter(am)) {
56 return null;
57 }
58 }
59 return stdNaming
60 ? stdManglePropertyName(name, 3)
61 : legacyManglePropertyName(name, 3);
62 }
63 return null;
64 }
65
66 /**
67 * @since 2.5
68 */
69 public static String okNameForIsGetter(AnnotatedMethod am, String name,
70 boolean stdNaming)
71 {
72 if (name.startsWith("is")) { // plus, must return a boolean
73 Class<?> rt = am.getRawType();
74 if (rt == Boolean.class || rt == Boolean.TYPE) {
75 return stdNaming
76 ? stdManglePropertyName(name, 2)
77 : legacyManglePropertyName(name, 2);
78 }
79 }
80 return null;
81 }
82
83 /**
84 * @since 2.5
85 */
86 @Deprecated // since 2.9, not used any more
87 public static String okNameForSetter(AnnotatedMethod am, boolean stdNaming) {
88 String name = okNameForMutator(am, "set", stdNaming);
89 if ((name != null)
90 // 26-Nov-2009, tatu: need to suppress this internal groovy method
91 && (!"metaClass".equals(name) || !isGroovyMetaClassSetter(am))) {
92 return name;
93 }
94 return null;
95 }
96
97 /**
98 * @since 2.5
99 */
100 public static String okNameForMutator(AnnotatedMethod am, String prefix,
101 boolean stdNaming) {
102 String name = am.getName();
103 if (name.startsWith(prefix)) {
104 return stdNaming
105 ? stdManglePropertyName(name, prefix.length())
106 : legacyManglePropertyName(name, prefix.length());
107 }
108 return null;
109 }
110
111 /*
112 /**********************************************************
113 /* Value defaulting helpers
114 /**********************************************************
115 */
116
117 /**
118 * Accessor used to find out "default value" to use for comparing values to
119 * serialize, to determine whether to exclude value from serialization with
120 * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
121 *<p>
122 * Default logic is such that for primitives and wrapper types for primitives, expected
123 * defaults (0 for `int` and `java.lang.Integer`) are returned; for Strings, empty String,
124 * and for structured (Maps, Collections, arrays) and reference types, criteria
125 * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}
126 * is used.
127 *
128 * @since 2.7
129 */
130 public static Object getDefaultValue(JavaType type)
131 {
132 // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special
133 // handling for primitives since they are never passed as nulls.
134 Class<?> cls = type.getRawClass();
135
136 // 30-Sep-2016, tatu: Also works for Wrappers, so both `Integer.TYPE` and `Integer.class`
137 // would return `Integer.TYPE`
138 Class<?> prim = ClassUtil.primitiveType(cls);
139 if (prim != null) {
140 return ClassUtil.defaultValue(prim);
141 }
142 if (type.isContainerType() || type.isReferenceType()) {
143 return JsonInclude.Include.NON_EMPTY;
144 }
145 if (cls == String.class) {
146 return "";
147 }
148 // 09-Mar-2016, tatu: Not sure how far this path we want to go but for now
149 // let's add `java.util.Date` and `java.util.Calendar`, as per [databind#1550]
150 if (type.isTypeOrSubTypeOf(Date.class)) {
151 return new Date(0L);
152 }
153 if (type.isTypeOrSubTypeOf(Calendar.class)) {
154 Calendar c = new GregorianCalendar();
155 c.setTimeInMillis(0L);
156 return c;
157 }
158 return null;
159 }
160
161 /*
162 /**********************************************************
163 /* Special case handling
164 /**********************************************************
165 */
166
167 /**
168 * This method was added to address the need to weed out
169 * CGLib-injected "getCallbacks" method.
170 * At this point caller has detected a potential getter method
171 * with name "getCallbacks" and we need to determine if it is
172 * indeed injectect by Cglib. We do this by verifying that the
173 * result type is "net.sf.cglib.proxy.Callback[]"
174 */
175 protected static boolean isCglibGetCallbacks(AnnotatedMethod am)
176 {
177 Class<?> rt = am.getRawType();
178 // Ok, first: must return an array type
179 if (rt.isArray()) {
180 /* And that type needs to be "net.sf.cglib.proxy.Callback".
181 * Theoretically could just be a type that implements it, but
182 * for now let's keep things simple, fix if need be.
183 */
184 Class<?> compType = rt.getComponentType();
185 // Actually, let's just verify it's a "net.sf.cglib.*" class/interface
186 String pkgName = ClassUtil.getPackageName(compType);
187 if (pkgName != null) {
188 if (pkgName.contains(".cglib")) {
189 return pkgName.startsWith("net.sf.cglib")
190 // also, as per [JACKSON-177]
191 || pkgName.startsWith("org.hibernate.repackage.cglib")
192 // and [core#674]
193 || pkgName.startsWith("org.springframework.cglib");
194 }
195 }
196 }
197 return false;
198 }
199
200 /**
201 * Similar to {@link #isCglibGetCallbacks}, need to suppress
202 * a cyclic reference.
203 */
204 protected static boolean isGroovyMetaClassSetter(AnnotatedMethod am)
205 {
206 Class<?> argType = am.getRawParameterType(0);
207 String pkgName = ClassUtil.getPackageName(argType);
208 return (pkgName != null) && pkgName.startsWith("groovy.lang");
209 }
210
211 /**
212 * Another helper method to deal with Groovy's problematic metadata accessors
213 */
214 protected static boolean isGroovyMetaClassGetter(AnnotatedMethod am)
215 {
216 String pkgName = ClassUtil.getPackageName(am.getRawType());
217 return (pkgName != null) && pkgName.startsWith("groovy.lang");
218 }
219
220 /*
221 /**********************************************************
222 /* Actual name mangling methods
223 /**********************************************************
224 */
225
226 /**
227 * Method called to figure out name of the property, given
228 * corresponding suggested name based on a method or field name.
229 *
230 * @param basename Name of accessor/mutator method, not including prefix
231 * ("get"/"is"/"set")
232 */
233 protected static String legacyManglePropertyName(final String basename, final int offset)
234 {
235 final int end = basename.length();
236 if (end == offset) { // empty name, nope
237 return null;
238 }
239 // next check: is the first character upper case? If not, return as is
240 char c = basename.charAt(offset);
241 char d = Character.toLowerCase(c);
242
243 if (c == d) {
244 return basename.substring(offset);
245 }
246 // otherwise, lower case initial chars. Common case first, just one char
247 StringBuilder sb = new StringBuilder(end - offset);
248 sb.append(d);
249 int i = offset+1;
250 for (; i < end; ++i) {
251 c = basename.charAt(i);
252 d = Character.toLowerCase(c);
253 if (c == d) {
254 sb.append(basename, i, end);
255 break;
256 }
257 sb.append(d);
258 }
259 return sb.toString();
260 }
261
262 /**
263 * Note: public only since 2.11
264 *
265 * @since 2.5
266 */
267 public static String stdManglePropertyName(final String basename, final int offset)
268 {
269 final int end = basename.length();
270 if (end == offset) { // empty name, nope
271 return null;
272 }
273 // first: if it doesn't start with capital, return as-is
274 char c0 = basename.charAt(offset);
275 char c1 = Character.toLowerCase(c0);
276 if (c0 == c1) {
277 return basename.substring(offset);
278 }
279 // 17-Dec-2014, tatu: As per [databind#653], need to follow more
280 // closely Java Beans spec; specifically, if two first are upper-case,
281 // then no lower-casing should be done.
282 if ((offset + 1) < end) {
283 if (Character.isUpperCase(basename.charAt(offset+1))) {
284 return basename.substring(offset);
285 }
286 }
287 StringBuilder sb = new StringBuilder(end - offset);
288 sb.append(c1);
289 sb.append(basename, offset+1, end);
290 return sb.toString();
291 }
292 }
293