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