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