001 /******************************************************************************* 002 * Portions created by Sebastian Thomschke are copyright (c) 2005-2013 Sebastian 003 * Thomschke. 004 * 005 * All Rights Reserved. This program and the accompanying materials 006 * are made available under the terms of the Eclipse Public License v1.0 007 * which accompanies this distribution, and is available at 008 * http://www.eclipse.org/legal/epl-v10.html 009 * 010 * Contributors: 011 * Sebastian Thomschke - initial implementation. 012 *******************************************************************************/ 013 package net.sf.oval.internal.util; 014 015 import static net.sf.oval.Validator.getCollectionFactory; 016 017 import java.lang.annotation.Annotation; 018 import java.lang.reflect.Field; 019 import java.lang.reflect.InvocationTargetException; 020 import java.lang.reflect.Member; 021 import java.lang.reflect.Method; 022 import java.lang.reflect.Modifier; 023 import java.lang.reflect.ReflectPermission; 024 import java.security.AccessController; 025 import java.util.HashSet; 026 import java.util.List; 027 import java.util.Locale; 028 import java.util.Set; 029 030 import net.sf.oval.exception.AccessingFieldValueFailedException; 031 import net.sf.oval.exception.ConstraintsViolatedException; 032 import net.sf.oval.exception.InvokingMethodFailedException; 033 import net.sf.oval.exception.ReflectionException; 034 import net.sf.oval.internal.ContextCache; 035 import net.sf.oval.internal.Log; 036 037 /** 038 * @author Sebastian Thomschke 039 */ 040 public final class ReflectionUtils 041 { 042 private static final Log LOG = Log.getLog(ReflectionUtils.class); 043 044 private static final ReflectPermission SUPPRESS_ACCESS_CHECKS_PERMISSION = new ReflectPermission("suppressAccessChecks"); 045 046 /** 047 * @throws SecurityException 048 */ 049 public static void assertPrivateAccessAllowed() 050 { 051 final SecurityManager manager = System.getSecurityManager(); 052 if (manager != null) 053 try 054 { 055 manager.checkPermission(SUPPRESS_ACCESS_CHECKS_PERMISSION); 056 } 057 catch (final SecurityException ex) 058 { 059 throw new ReflectionException( 060 "Current security manager configuration does not allow access to private fields and methods.", ex); 061 } 062 } 063 064 /** 065 * Returns all annotations present on this class. 066 * @param clazz the class to inspect 067 * @param inspectInterfaces whether to also return annotations declared on interface declaration 068 * @return all annotations present on this class. 069 */ 070 public static Annotation[] getAnnotations(final Class< ? > clazz, final boolean inspectInterfaces) 071 { 072 if (!inspectInterfaces) return clazz.getAnnotations(); 073 074 final List<Annotation> annotations = ArrayUtils.asList(clazz.getAnnotations()); 075 for (final Class< ? > next : ReflectionUtils.getInterfacesRecursive(clazz)) 076 { 077 final Annotation[] declaredAnnotations = next.getDeclaredAnnotations(); 078 annotations.addAll(ArrayUtils.asList(declaredAnnotations)); 079 } 080 return annotations.toArray(new Annotation[annotations.size()]); 081 } 082 083 /** 084 * Returns all annotations present on this method. 085 * @param method the method to inspect 086 * @param inspectInterfaces whether to also return annotations declared on interface method declaration 087 * @return all annotations present on this method. 088 */ 089 public static Annotation[] getAnnotations(final Method method, final boolean inspectInterfaces) 090 { 091 if (!inspectInterfaces || !isPublic(method)) return method.getAnnotations(); 092 093 final String methodName = method.getName(); 094 final Class< ? >[] methodParameterTypes = method.getParameterTypes(); 095 096 final List<Annotation> annotations = ArrayUtils.asList(method.getAnnotations()); 097 for (final Class< ? > nextClass : ReflectionUtils.getInterfacesRecursive(method.getDeclaringClass())) 098 try 099 { 100 ArrayUtils.addAll(annotations, nextClass.getDeclaredMethod(methodName, methodParameterTypes).getDeclaredAnnotations()); 101 } 102 catch (final NoSuchMethodException e) 103 { 104 // ignore 105 } 106 return annotations.toArray(new Annotation[annotations.size()]); 107 } 108 109 /** 110 * @return the field or null if the field does not exist 111 */ 112 public static Field getField(final Class< ? > clazz, final String fieldName) 113 { 114 try 115 { 116 return clazz.getDeclaredField(fieldName); 117 } 118 catch (final NoSuchFieldException e) 119 { 120 return null; 121 } 122 } 123 124 /** 125 * @param setter 126 * @return the corresponding field for a setter method. Returns null if the method is not a 127 * JavaBean style setter or the field could not be located. 128 */ 129 public static Field getFieldForSetter(final Method setter) 130 { 131 if (!isSetter(setter)) return null; 132 133 final Class< ? >[] methodParameterTypes = setter.getParameterTypes(); 134 final String methodName = setter.getName(); 135 final Class< ? > clazz = setter.getDeclaringClass(); 136 137 // calculate the corresponding field name based on the name of the setter method (e.g. method setName() => field 138 // name) 139 String fieldName = methodName.substring(3, 4).toLowerCase(Locale.getDefault()); 140 if (methodName.length() > 4) fieldName += methodName.substring(4); 141 142 Field field = null; 143 try 144 { 145 field = clazz.getDeclaredField(fieldName); 146 147 // check if field and method parameter are of the same type 148 if (!field.getType().equals(methodParameterTypes[0])) 149 { 150 LOG.warn("Found field <{1}> in class <{2}>that matches setter <{3}> name, but mismatches parameter type.", fieldName, 151 clazz.getName(), methodName); 152 field = null; 153 } 154 } 155 catch (final NoSuchFieldException e) 156 { 157 LOG.debug("Field not found", e); 158 } 159 160 // if method parameter type is boolean then check if a field with name isXXX exists (e.g. method setEnabled() => 161 // field isEnabled) 162 if (field == null && (boolean.class.equals(methodParameterTypes[0]) || Boolean.class.equals(methodParameterTypes[0]))) 163 { 164 fieldName = "is" + methodName.substring(3); 165 166 try 167 { 168 field = clazz.getDeclaredField(fieldName); 169 170 // check if found field is of boolean or Boolean 171 if (!boolean.class.equals(field.getType()) && Boolean.class.equals(field.getType())) 172 { 173 LOG.warn("Found field <{1}> in class <{2}>that matches setter <{3}> name, but mismatches parameter type.", fieldName, 174 clazz.getName(), methodName); 175 field = null; 176 } 177 } 178 catch (final NoSuchFieldException ex) 179 { 180 LOG.debug("Field not found", ex); 181 } 182 } 183 184 return field; 185 } 186 187 public static Field getFieldRecursive(final Class< ? > clazz, final String fieldName) 188 { 189 final Field f = getField(clazz, fieldName); 190 if (f != null) return f; 191 192 final Class< ? > superclazz = clazz.getSuperclass(); 193 if (superclazz == null) return null; 194 195 return getFieldRecursive(superclazz, fieldName); 196 } 197 198 public static Object getFieldValue(final Field field, final Object target) throws AccessingFieldValueFailedException 199 { 200 try 201 { 202 if (!field.isAccessible()) AccessController.doPrivileged(new SetAccessibleAction(field)); 203 return field.get(target); 204 } 205 catch (final Exception ex) 206 { 207 throw new AccessingFieldValueFailedException(field.getName(), target, ContextCache.getFieldContext(field), ex); 208 } 209 } 210 211 public static Method getGetter(final Class< ? > clazz, final String propertyName) 212 { 213 final String appendix = propertyName.substring(0, 1).toUpperCase(Locale.getDefault()) + propertyName.substring(1); 214 try 215 { 216 return clazz.getDeclaredMethod("get" + appendix); 217 } 218 catch (final NoSuchMethodException ex) 219 { 220 LOG.trace("getXXX method not found.", ex); 221 } 222 try 223 { 224 return clazz.getDeclaredMethod("is" + appendix); 225 } 226 catch (final NoSuchMethodException ex) 227 { 228 LOG.trace("isXXX method not found.", ex); 229 return null; 230 } 231 } 232 233 public static Method getGetterRecursive(final Class< ? > clazz, final String propertyName) 234 { 235 final Method m = getGetter(clazz, propertyName); 236 if (m != null) return m; 237 238 final Class< ? > superclazz = clazz.getSuperclass(); 239 if (superclazz == null) return null; 240 241 return getGetterRecursive(superclazz, propertyName); 242 } 243 244 public static List<Method> getInterfaceMethods(final Method method) 245 { 246 // static methods cannot be overridden 247 if (isStatic(method)) return null; 248 249 final Class< ? >[] interfaces = method.getDeclaringClass().getInterfaces(); 250 if (interfaces.length == 0) return null; 251 252 final String methodName = method.getName(); 253 final Class< ? >[] parameterTypes = method.getParameterTypes(); 254 255 final List<Method> methods = getCollectionFactory().createList(interfaces.length); 256 for (final Class< ? > iface : interfaces) 257 { 258 final Method m = getMethod(iface, methodName, parameterTypes); 259 if (m != null) methods.add(m); 260 } 261 return methods; 262 } 263 264 /** 265 * @param clazz the class to inspect 266 * @return a set with all implemented interfaces 267 */ 268 public static Set<Class< ? >> getInterfacesRecursive(final Class< ? > clazz) 269 { 270 final Set<Class< ? >> interfaces = getCollectionFactory().createSet(2); 271 return getInterfacesRecursive(clazz, interfaces); 272 } 273 274 private static Set<Class< ? >> getInterfacesRecursive(Class< ? > clazz, final Set<Class< ? >> interfaces) 275 { 276 while (clazz != null) 277 { 278 for (final Class< ? > next : clazz.getInterfaces()) 279 { 280 interfaces.add(next); 281 getInterfacesRecursive(next, interfaces); 282 } 283 clazz = clazz.getSuperclass(); 284 } 285 return interfaces; 286 } 287 288 /** 289 * @return the method or null if the method does not exist 290 */ 291 public static Method getMethod(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes) 292 { 293 try 294 { 295 return clazz.getDeclaredMethod(methodName, parameterTypes); 296 } 297 catch (final NoSuchMethodException e) 298 { 299 return null; 300 } 301 } 302 303 /** 304 * @return the method or null if the method does not exist 305 */ 306 public static Method getMethodRecursive(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes) 307 { 308 final Method m = getMethod(clazz, methodName, parameterTypes); 309 if (m != null) return m; 310 311 final Class< ? > superclazz = clazz.getSuperclass(); 312 if (superclazz == null) return null; 313 314 return getMethodRecursive(superclazz, methodName, parameterTypes); 315 } 316 317 /** 318 * Returns an array of arrays that represent the annotations on the formal parameters, in declaration order, 319 * of the method represented by this method. 320 * 321 * @param method the method to inspect 322 * @param inspectInterfaces whether to also return annotations declared on interface method declaration 323 * @return an array of arrays that represent the annotations on the formal parameters, in declaration order, 324 * of the method represented by this method. 325 */ 326 public static Annotation[][] getParameterAnnotations(final Method method, final boolean inspectInterfaces) 327 { 328 if (!inspectInterfaces || !isPublic(method)) return method.getParameterAnnotations(); 329 330 final String methodName = method.getName(); 331 final Class< ? >[] methodParameterTypes = method.getParameterTypes(); 332 final int methodParameterTypesCount = methodParameterTypes.length; 333 334 @SuppressWarnings("unchecked") 335 final HashSet<Annotation>[] methodParameterAnnotations = new HashSet[methodParameterTypesCount]; 336 337 final Class< ? > clazz = method.getDeclaringClass(); 338 final Set<Class< ? >> classes = ReflectionUtils.getInterfacesRecursive(clazz); 339 classes.add(clazz); 340 for (final Class< ? > nextClass : classes) 341 try 342 { 343 final Method nextMethod = nextClass.getDeclaredMethod(methodName, methodParameterTypes); 344 for (int i = 0; i < methodParameterTypesCount; i++) 345 { 346 final Annotation[] paramAnnos = nextMethod.getParameterAnnotations()[i]; 347 if (paramAnnos.length > 0) 348 { 349 HashSet<Annotation> cummulatedParamAnnos = methodParameterAnnotations[i]; 350 if (cummulatedParamAnnos == null) methodParameterAnnotations[i] = cummulatedParamAnnos = new HashSet<Annotation>(); 351 for (final Annotation anno : paramAnnos) 352 cummulatedParamAnnos.add(anno); 353 } 354 } 355 } 356 catch (final NoSuchMethodException e) 357 { 358 // ignore 359 } 360 361 final Annotation[][] result = new Annotation[methodParameterTypesCount][]; 362 for (int i = 0; i < methodParameterTypesCount; i++) 363 { 364 final HashSet<Annotation> paramAnnos = methodParameterAnnotations[i]; 365 result[i] = paramAnnos == null ? new Annotation[0] : methodParameterAnnotations[i] 366 .toArray(new Annotation[methodParameterAnnotations[i].size()]); 367 368 } 369 return result; 370 } 371 372 public static Method getSetter(final Class< ? > clazz, final String propertyName) 373 { 374 final String methodName = "set" + propertyName.substring(0, 1).toUpperCase(Locale.getDefault()) + propertyName.substring(1); 375 376 final Method[] declaredMethods = clazz.getDeclaredMethods(); 377 for (final Method method : declaredMethods) 378 if (methodName.equals(method.getName()) && method.getParameterTypes().length == 1) return method; 379 LOG.trace("No setter for {} not found on class {}.", propertyName, clazz); 380 return null; 381 } 382 383 public static Method getSetterRecursive(final Class< ? > clazz, final String propertyName) 384 { 385 final Method m = getSetter(clazz, propertyName); 386 if (m != null) return m; 387 388 final Class< ? > superclazz = clazz.getSuperclass(); 389 if (superclazz == null) return null; 390 391 return getSetterRecursive(superclazz, propertyName); 392 } 393 394 public static Method getSuperMethod(final Method method) 395 { 396 // static methods cannot be overridden 397 if (isStatic(method)) return null; 398 399 final String methodName = method.getName(); 400 final Class< ? >[] parameterTypes = method.getParameterTypes(); 401 402 Class< ? > currentClass = method.getDeclaringClass(); 403 404 while (currentClass != null && currentClass != Object.class) 405 { 406 currentClass = currentClass.getSuperclass(); 407 408 final Method m = getMethod(currentClass, methodName, parameterTypes); 409 if (m != null && !isPrivate(m)) return m; 410 } 411 return null; 412 } 413 414 public static String guessFieldName(final Method getter) 415 { 416 String fieldName = getter.getName(); 417 418 if (fieldName.startsWith("get") && fieldName.length() > 3) 419 { 420 fieldName = fieldName.substring(3); 421 if (fieldName.length() == 1) 422 fieldName = fieldName.toLowerCase(Locale.getDefault()); 423 else 424 fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1); 425 } 426 else if (fieldName.startsWith("is") && fieldName.length() > 2) 427 { 428 fieldName = fieldName.substring(2); 429 if (fieldName.length() == 1) 430 fieldName = fieldName.toLowerCase(Locale.getDefault()); 431 else 432 fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1); 433 } 434 435 return fieldName; 436 } 437 438 public static boolean hasField(final Class< ? > clazz, final String fieldName) 439 { 440 return getField(clazz, fieldName) != null; 441 } 442 443 public static boolean hasMethod(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes) 444 { 445 return getMethod(clazz, methodName, parameterTypes) != null; 446 } 447 448 /** 449 * 450 * @param method the method to invoke 451 * @param obj the object on which to invoke the method 452 * @param args the method arguments 453 * @return the return value of the invoked method 454 * @throws InvokingMethodFailedException 455 */ 456 @SuppressWarnings("unchecked") 457 public static <T> T invokeMethod(final Method method, final Object obj, final Object... args) throws InvokingMethodFailedException, 458 ConstraintsViolatedException 459 { 460 try 461 { 462 if (!method.isAccessible()) AccessController.doPrivileged(new SetAccessibleAction(method)); 463 return (T) method.invoke(obj, args); 464 } 465 catch (final Exception ex) 466 { 467 if (ex.getCause() instanceof ConstraintsViolatedException) throw (ConstraintsViolatedException) ex.getCause(); 468 throw new InvokingMethodFailedException("Executing method " + method.getName() + " failed.", obj, 469 ContextCache.getMethodReturnValueContext(method), ex); 470 } 471 } 472 473 /** 474 * Returns true if an annotation for the specified type is present on this method, else false. 475 * 476 * @param method the method to inspect 477 * @param annotationClass the Class object corresponding to the annotation type 478 * @param inspectInterfaces whether to also check annotations declared on interface method declaration 479 * @return true if an annotation for the specified annotation type is present on this method, else false 480 */ 481 public static boolean isAnnotationPresent(final Method method, final Class< ? extends Annotation> annotationClass, 482 final boolean inspectInterfaces) 483 { 484 if (method.isAnnotationPresent(annotationClass)) return true; 485 486 if (!inspectInterfaces || !isPublic(method)) return false; 487 488 final String methodName = method.getName(); 489 final Class< ? >[] methodParameterTypes = method.getParameterTypes(); 490 491 for (final Class< ? > next : getInterfacesRecursive(method.getDeclaringClass())) 492 try 493 { 494 if (next.getDeclaredMethod(methodName, methodParameterTypes).isAnnotationPresent(annotationClass)) return true; 495 } 496 catch (final NoSuchMethodException e) 497 { 498 // ignore 499 } 500 return false; 501 } 502 503 public static boolean isClassPresent(final String className) 504 { 505 try 506 { 507 Class.forName(className); 508 return true; 509 } 510 catch (final ClassNotFoundException e) 511 { 512 return false; 513 } 514 } 515 516 public static boolean isFinal(final Member member) 517 { 518 return (member.getModifiers() & Modifier.FINAL) != 0; 519 } 520 521 /** 522 * determines if a method is a JavaBean style getter method 523 */ 524 public static boolean isGetter(final Method method) 525 { 526 return method.getParameterTypes().length == 0 && (method.getName().startsWith("is") || method.getName().startsWith("get")); 527 } 528 529 // public Constructor getDeclaredConstructorOfNonStaticInnerClass(Class) 530 public static boolean isNonStaticInnerClass(final Class< ? > clazz) 531 { 532 return clazz.getName().indexOf('$') > -1 && (clazz.getModifiers() & Modifier.STATIC) == 0; 533 } 534 535 public static boolean isPackage(final Member member) 536 { 537 return (member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED)) == 0; 538 } 539 540 public static boolean isPrivate(final Member member) 541 { 542 return (member.getModifiers() & Modifier.PRIVATE) != 0; 543 } 544 545 public static boolean isPrivateAccessAllowed() 546 { 547 final SecurityManager manager = System.getSecurityManager(); 548 if (manager != null) try 549 { 550 manager.checkPermission(SUPPRESS_ACCESS_CHECKS_PERMISSION); 551 } 552 catch (final SecurityException ex) 553 { 554 return false; 555 } 556 return true; 557 } 558 559 public static boolean isProtected(final Member member) 560 { 561 return (member.getModifiers() & Modifier.PROTECTED) != 0; 562 } 563 564 public static boolean isPublic(final Member member) 565 { 566 return (member.getModifiers() & Modifier.PUBLIC) != 0; 567 } 568 569 /** 570 * determines if a method is a JavaBean style setter method 571 */ 572 public static boolean isSetter(final Method method) 573 { 574 final Class< ? >[] methodParameterTypes = method.getParameterTypes(); 575 576 // check if method has exactly one parameter 577 if (methodParameterTypes.length != 1) return false; 578 579 final String methodName = method.getName(); 580 final int methodNameLen = methodName.length(); 581 582 // check if the method's name starts with setXXX 583 if (methodNameLen < 4 || !methodName.startsWith("set")) return false; 584 585 return true; 586 } 587 588 public static boolean isStatic(final Member member) 589 { 590 return (member.getModifiers() & Modifier.STATIC) != 0; 591 } 592 593 public static boolean isTransient(final Member member) 594 { 595 return (member.getModifiers() & Modifier.TRANSIENT) != 0; 596 } 597 598 /** 599 * determines if a method is a void method 600 */ 601 public static boolean isVoidMethod(final Method method) 602 { 603 return method.getReturnType() == void.class; 604 } 605 606 public static boolean setViaSetter(final Object target, final String propertyName, final Object propertyValue) 607 { 608 assert target != null; 609 assert propertyName != null; 610 final Method setter = getSetterRecursive(target.getClass(), propertyName); 611 if (setter != null) try 612 { 613 setter.invoke(target, propertyValue); 614 } 615 catch (final IllegalArgumentException ex) 616 { 617 LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex); 618 return false; 619 } 620 catch (final IllegalAccessException ex) 621 { 622 LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex); 623 return false; 624 } 625 catch (final InvocationTargetException ex) 626 { 627 LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex); 628 return false; 629 } 630 return false; 631 } 632 633 private ReflectionUtils() 634 { 635 super(); 636 } 637 }