• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
okNameForGetter(AnnotatedMethod am, boolean stdNaming)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      */
okNameForRegularGetter(AnnotatedMethod am, String name, boolean stdNaming)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      */
okNameForIsGetter(AnnotatedMethod am, String name, boolean stdNaming)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
okNameForSetter(AnnotatedMethod am, boolean stdNaming)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      */
okNameForMutator(AnnotatedMethod am, String prefix, boolean stdNaming)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      */
getDefaultValue(JavaType type)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      */
isCglibGetCallbacks(AnnotatedMethod am)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      */
isGroovyMetaClassSetter(AnnotatedMethod am)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      */
isGroovyMetaClassGetter(AnnotatedMethod am)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      */
legacyManglePropertyName(final String basename, final int offset)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      */
stdManglePropertyName(final String basename, final int offset)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     /*
294     /**********************************************************
295     /* Package-specific type detection for error handling
296     /**********************************************************
297      */
298 
299     /**
300      * Helper method called by {@link com.fasterxml.jackson.databind.deser.BeanDeserializerFactory}
301      * and {@link com.fasterxml.jackson.databind.ser.BeanSerializerFactory} to check
302      * if given unrecognized type (to be (de)serialized as general POJO) is one of
303      * "well-known" types for which there would be a datatype module; and if so,
304      * return appropriate failure message to give to caller.
305      *
306      * @since 2.12
307      */
checkUnsupportedType(JavaType type)308     public static String checkUnsupportedType(JavaType type) {
309         final Class<?> rawType = type.getRawClass();
310         String typeName, moduleName;
311 
312         if (isJava8TimeClass(rawType)) {
313             typeName =  "Java 8 date/time";
314             moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310";
315         } else if (isJodaTimeClass(rawType)) {
316             typeName =  "Joda date/time";
317             moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-joda";
318         } else {
319             return null;
320         }
321         return String.format("%s type %s not supported by default: add Module \"%s\" to enable handling",
322                 typeName, ClassUtil.getTypeDescription(type), moduleName);
323     }
324 
325     /**
326      * @since 2.12
327      */
isJava8TimeClass(Class<?> rawType)328     public static boolean isJava8TimeClass(Class<?> rawType) {
329         return rawType.getName().startsWith("java.time.");
330     }
331 
332     /**
333      * @since 2.12
334      */
isJodaTimeClass(Class<?> rawType)335     public static boolean isJodaTimeClass(Class<?> rawType) {
336         return rawType.getName().startsWith("org.joda.time.");
337     }
338 }
339