View Javadoc
1   /*******************************************************************************
2    * Portions created by Sebastian Thomschke are copyright (c) 2005-2015 Sebastian
3    * Thomschke.
4    *
5    * All Rights Reserved. This program and the accompanying materials
6    * are made available under the terms of the Eclipse Public License v1.0
7    * which accompanies this distribution, and is available at
8    * http://www.eclipse.org/legal/epl-v10.html
9    *
10   * Contributors:
11   *     Sebastian Thomschke - initial implementation.
12   *******************************************************************************/
13  package net.sf.oval.internal.util;
14  
15  import static net.sf.oval.Validator.*;
16  
17  import java.lang.annotation.Annotation;
18  import java.lang.reflect.Field;
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Member;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  import java.lang.reflect.ReflectPermission;
24  import java.security.AccessController;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  
29  import net.sf.oval.exception.AccessingFieldValueFailedException;
30  import net.sf.oval.exception.ConstraintsViolatedException;
31  import net.sf.oval.exception.InvokingMethodFailedException;
32  import net.sf.oval.exception.ReflectionException;
33  import net.sf.oval.internal.ContextCache;
34  import net.sf.oval.internal.Log;
35  
36  /**
37   * @author Sebastian Thomschke
38   */
39  public final class ReflectionUtils
40  {
41  	private static final Log LOG = Log.getLog(ReflectionUtils.class);
42  
43  	private static final ReflectPermission SUPPRESS_ACCESS_CHECKS_PERMISSION = new ReflectPermission("suppressAccessChecks");
44  
45  	/**
46  	 * @throws SecurityException
47  	 */
48  	public static void assertPrivateAccessAllowed()
49  	{
50  		final SecurityManager manager = System.getSecurityManager();
51  		if (manager != null)
52  		{
53  			try
54  			{
55  				manager.checkPermission(SUPPRESS_ACCESS_CHECKS_PERMISSION);
56  			}
57  			catch (final SecurityException ex)
58  			{
59  				throw new ReflectionException(
60  						"Current security manager configuration does not allow access to private fields and methods.", ex);
61  			}
62  		}
63  	}
64  
65  	/**
66  	 * Returns all annotations present on this class.
67  	 * @param clazz the class to inspect
68  	 * @param inspectInterfaces whether to also return annotations declared on interface declaration
69  	 * @return all annotations present on this class.
70  	 */
71  	public static Annotation[] getAnnotations(final Class< ? > clazz, final boolean inspectInterfaces)
72  	{
73  		if (!inspectInterfaces) return clazz.getAnnotations();
74  
75  		final List<Annotation> annotations = ArrayUtils.asList(clazz.getAnnotations());
76  		for (final Class< ? > next : ReflectionUtils.getInterfacesRecursive(clazz))
77  		{
78  			final Annotation[] declaredAnnotations = next.getDeclaredAnnotations();
79  			annotations.addAll(ArrayUtils.asList(declaredAnnotations));
80  		}
81  		return annotations.toArray(new Annotation[annotations.size()]);
82  	}
83  
84  	/**
85  	 * Returns all annotations present on this method.
86  	 * @param method the method to inspect
87  	 * @param inspectInterfaces whether to also return annotations declared on interface method declaration
88  	 * @return all annotations present on this method.
89  	 */
90  	public static Annotation[] getAnnotations(final Method method, final boolean inspectInterfaces)
91  	{
92  		if (!inspectInterfaces || !isPublic(method)) return method.getAnnotations();
93  
94  		final String methodName = method.getName();
95  		final Class< ? >[] methodParameterTypes = method.getParameterTypes();
96  
97  		final List<Annotation> annotations = ArrayUtils.asList(method.getAnnotations());
98  		for (final Class< ? > nextClass : ReflectionUtils.getInterfacesRecursive(method.getDeclaringClass()))
99  		{
100 			try
101 			{
102 				ArrayUtils.addAll(annotations, nextClass.getDeclaredMethod(methodName, methodParameterTypes).getDeclaredAnnotations());
103 			}
104 			catch (final NoSuchMethodException ex)
105 			{
106 				// ignore
107 			}
108 		}
109 		return annotations.toArray(new Annotation[annotations.size()]);
110 	}
111 
112 	/**
113 	 * @return the field or null if the field does not exist
114 	 */
115 	public static Field getField(final Class< ? > clazz, final String fieldName)
116 	{
117 		try
118 		{
119 			return clazz.getDeclaredField(fieldName);
120 		}
121 		catch (final NoSuchFieldException ex)
122 		{
123 			return null;
124 		}
125 	}
126 
127 	/**
128 	 * @param setter
129 	 * @return the corresponding field for a setter method. Returns null if the method is not a
130 	 * JavaBean style setter or the field could not be located.
131 	 */
132 	public static Field getFieldForSetter(final Method setter)
133 	{
134 		if (!isSetter(setter)) return null;
135 
136 		final Class< ? >[] methodParameterTypes = setter.getParameterTypes();
137 		final String methodName = setter.getName();
138 		final Class< ? > clazz = setter.getDeclaringClass();
139 
140 		// calculate the corresponding field name based on the name of the setter method (e.g. method setName() => field
141 		// name)
142 		String fieldName = methodName.substring(3, 4).toLowerCase(getLocaleProvider().getLocale());
143 		if (methodName.length() > 4)
144 		{
145 			fieldName += methodName.substring(4);
146 		}
147 
148 		Field field = null;
149 		try
150 		{
151 			field = clazz.getDeclaredField(fieldName);
152 
153 			// check if field and method parameter are of the same type
154 			if (!field.getType().equals(methodParameterTypes[0]))
155 			{
156 				LOG.warn("Found field <{1}> in class <{2}>that matches setter <{3}> name, but mismatches parameter type.", fieldName,
157 						clazz.getName(), methodName);
158 				field = null;
159 			}
160 		}
161 		catch (final NoSuchFieldException e)
162 		{
163 			LOG.debug("Field not found", e);
164 		}
165 
166 		// if method parameter type is boolean then check if a field with name isXXX exists (e.g. method setEnabled() =>
167 		// field isEnabled)
168 		if (field == null && (boolean.class.equals(methodParameterTypes[0]) || Boolean.class.equals(methodParameterTypes[0])))
169 		{
170 			fieldName = "is" + methodName.substring(3);
171 
172 			try
173 			{
174 				field = clazz.getDeclaredField(fieldName);
175 
176 				// check if found field is of boolean or Boolean
177 				if (!boolean.class.equals(field.getType()) && Boolean.class.equals(field.getType()))
178 				{
179 					LOG.warn("Found field <{1}> in class <{2}>that matches setter <{3}> name, but mismatches parameter type.", fieldName,
180 							clazz.getName(), methodName);
181 					field = null;
182 				}
183 			}
184 			catch (final NoSuchFieldException ex)
185 			{
186 				LOG.debug("Field not found", ex);
187 			}
188 		}
189 
190 		return field;
191 	}
192 
193 	public static Field getFieldRecursive(final Class< ? > clazz, final String fieldName)
194 	{
195 		final Field f = getField(clazz, fieldName);
196 		if (f != null) return f;
197 
198 		final Class< ? > superclazz = clazz.getSuperclass();
199 		if (superclazz == null) return null;
200 
201 		return getFieldRecursive(superclazz, fieldName);
202 	}
203 
204 	public static Object getFieldValue(final Field field, final Object target) throws AccessingFieldValueFailedException
205 	{
206 		try
207 		{
208 			if (!field.isAccessible())
209 			{
210 				AccessController.doPrivileged(new SetAccessibleAction(field));
211 			}
212 			return field.get(target);
213 		}
214 		catch (final Exception ex)
215 		{
216 			throw new AccessingFieldValueFailedException(field.getName(), target, ContextCache.getFieldContext(field), ex);
217 		}
218 	}
219 
220 	public static Method getGetter(final Class< ? > clazz, final String propertyName)
221 	{
222 		final String appendix = propertyName.substring(0, 1).toUpperCase(getLocaleProvider().getLocale()) + propertyName.substring(1);
223 		try
224 		{
225 			return clazz.getDeclaredMethod("get" + appendix);
226 		}
227 		catch (final NoSuchMethodException ex)
228 		{
229 			LOG.trace("getXXX method not found.", ex);
230 		}
231 		try
232 		{
233 			return clazz.getDeclaredMethod("is" + appendix);
234 		}
235 		catch (final NoSuchMethodException ex)
236 		{
237 			LOG.trace("isXXX method not found.", ex);
238 			return null;
239 		}
240 	}
241 
242 	public static Method getGetterRecursive(final Class< ? > clazz, final String propertyName)
243 	{
244 		final Method m = getGetter(clazz, propertyName);
245 		if (m != null) return m;
246 
247 		final Class< ? > superclazz = clazz.getSuperclass();
248 		if (superclazz == null) return null;
249 
250 		return getGetterRecursive(superclazz, propertyName);
251 	}
252 
253 	public static List<Method> getInterfaceMethods(final Method method)
254 	{
255 		// static methods cannot be overridden
256 		if (isStatic(method)) return null;
257 
258 		final Class< ? >[] interfaces = method.getDeclaringClass().getInterfaces();
259 		if (interfaces.length == 0) return null;
260 
261 		final String methodName = method.getName();
262 		final Class< ? >[] parameterTypes = method.getParameterTypes();
263 
264 		final List<Method> methods = getCollectionFactory().createList(interfaces.length);
265 		for (final Class< ? > iface : interfaces)
266 		{
267 			final Method m = getMethod(iface, methodName, parameterTypes);
268 			if (m != null)
269 			{
270 				methods.add(m);
271 			}
272 		}
273 		return methods;
274 	}
275 
276 	/**
277 	 * @param clazz the class to inspect
278 	 * @return a set with all implemented interfaces
279 	 */
280 	public static Set<Class< ? >> getInterfacesRecursive(final Class< ? > clazz)
281 	{
282 		final Set<Class< ? >> interfaces = getCollectionFactory().createSet(2);
283 		return getInterfacesRecursive(clazz, interfaces);
284 	}
285 
286 	private static Set<Class< ? >> getInterfacesRecursive(Class< ? > clazz, final Set<Class< ? >> interfaces)
287 	{
288 		while (clazz != null)
289 		{
290 			for (final Class< ? > next : clazz.getInterfaces())
291 			{
292 				interfaces.add(next);
293 				getInterfacesRecursive(next, interfaces);
294 			}
295 			clazz = clazz.getSuperclass();
296 		}
297 		return interfaces;
298 	}
299 
300 	/**
301 	 * @return the method or null if the method does not exist
302 	 */
303 	public static Method getMethod(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes)
304 	{
305 		try
306 		{
307 			return clazz.getDeclaredMethod(methodName, parameterTypes);
308 		}
309 		catch (final NoSuchMethodException ex)
310 		{
311 			return null;
312 		}
313 	}
314 
315 	/**
316 	 * @return the method or null if the method does not exist
317 	 */
318 	public static Method getMethodRecursive(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes)
319 	{
320 		final Method m = getMethod(clazz, methodName, parameterTypes);
321 		if (m != null) return m;
322 
323 		final Class< ? > superclazz = clazz.getSuperclass();
324 		if (superclazz == null) return null;
325 
326 		return getMethodRecursive(superclazz, methodName, parameterTypes);
327 	}
328 
329 	/**
330 	 * Returns an array of arrays that represent the annotations on the formal parameters, in declaration order,
331 	 * of the method represented by this method.
332 	 *
333 	 * @param method the method to inspect
334 	 * @param inspectInterfaces whether to also return annotations declared on interface method declaration
335 	 * @return an array of arrays that represent the annotations on the formal parameters, in declaration order,
336 	 * of the method represented by this method.
337 	 */
338 	public static Annotation[][] getParameterAnnotations(final Method method, final boolean inspectInterfaces)
339 	{
340 		if (!inspectInterfaces || !isPublic(method)) return method.getParameterAnnotations();
341 
342 		final String methodName = method.getName();
343 		final Class< ? >[] methodParameterTypes = method.getParameterTypes();
344 		final int methodParameterTypesCount = methodParameterTypes.length;
345 
346 		@SuppressWarnings("unchecked")
347 		final HashSet<Annotation>[] methodParameterAnnotations = new HashSet[methodParameterTypesCount];
348 
349 		final Class< ? > clazz = method.getDeclaringClass();
350 		final Set<Class< ? >> classes = ReflectionUtils.getInterfacesRecursive(clazz);
351 		classes.add(clazz);
352 		for (final Class< ? > nextClass : classes)
353 		{
354 			try
355 			{
356 				final Method nextMethod = nextClass.getDeclaredMethod(methodName, methodParameterTypes);
357 				for (int i = 0; i < methodParameterTypesCount; i++)
358 				{
359 					final Annotation[] paramAnnos = nextMethod.getParameterAnnotations()[i];
360 					if (paramAnnos.length > 0)
361 					{
362 						HashSet<Annotation> cummulatedParamAnnos = methodParameterAnnotations[i];
363 						if (cummulatedParamAnnos == null)
364 						{
365 							methodParameterAnnotations[i] = cummulatedParamAnnos = new HashSet<Annotation>();
366 						}
367 						for (final Annotation anno : paramAnnos)
368 						{
369 							cummulatedParamAnnos.add(anno);
370 						}
371 					}
372 				}
373 			}
374 			catch (final NoSuchMethodException ex)
375 			{
376 				// ignore
377 			}
378 		}
379 
380 		final Annotation[][] result = new Annotation[methodParameterTypesCount][];
381 		for (int i = 0; i < methodParameterTypesCount; i++)
382 		{
383 			final HashSet<Annotation> paramAnnos = methodParameterAnnotations[i];
384 			result[i] = paramAnnos == null ? new Annotation[0] : methodParameterAnnotations[i]
385 					.toArray(new Annotation[methodParameterAnnotations[i].size()]);
386 
387 		}
388 		return result;
389 	}
390 
391 	public static Method getSetter(final Class< ? > clazz, final String propertyName)
392 	{
393 		final String methodName = "set" + propertyName.substring(0, 1).toUpperCase(getLocaleProvider().getLocale())
394 				+ propertyName.substring(1);
395 
396 		final Method[] declaredMethods = clazz.getDeclaredMethods();
397 		for (final Method method : declaredMethods)
398 			if (methodName.equals(method.getName()) && method.getParameterTypes().length == 1) return method;
399 		LOG.trace("No setter for {} not found on class {}.", propertyName, clazz);
400 		return null;
401 	}
402 
403 	public static Method getSetterRecursive(final Class< ? > clazz, final String propertyName)
404 	{
405 		final Method m = getSetter(clazz, propertyName);
406 		if (m != null) return m;
407 
408 		final Class< ? > superclazz = clazz.getSuperclass();
409 		if (superclazz == null) return null;
410 
411 		return getSetterRecursive(superclazz, propertyName);
412 	}
413 
414 	public static Method getSuperMethod(final Method method)
415 	{
416 		// static methods cannot be overridden
417 		if (isStatic(method)) return null;
418 
419 		final String methodName = method.getName();
420 		final Class< ? >[] parameterTypes = method.getParameterTypes();
421 
422 		Class< ? > currentClass = method.getDeclaringClass();
423 
424 		while (currentClass != null && currentClass != Object.class)
425 		{
426 			currentClass = currentClass.getSuperclass();
427 
428 			final Method m = getMethod(currentClass, methodName, parameterTypes);
429 			if (m != null && !isPrivate(m)) return m;
430 		}
431 		return null;
432 	}
433 
434 	public static String guessFieldName(final Method getter)
435 	{
436 		String fieldName = getter.getName();
437 
438 		if (fieldName.startsWith("get") && fieldName.length() > 3)
439 		{
440 			fieldName = fieldName.substring(3);
441 			if (fieldName.length() == 1)
442 			{
443 				fieldName = fieldName.toLowerCase(getLocaleProvider().getLocale());
444 			}
445 			else
446 			{
447 				fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1);
448 			}
449 		}
450 		else if (fieldName.startsWith("is") && fieldName.length() > 2)
451 		{
452 			fieldName = fieldName.substring(2);
453 			if (fieldName.length() == 1)
454 			{
455 				fieldName = fieldName.toLowerCase(getLocaleProvider().getLocale());
456 			}
457 			else
458 			{
459 				fieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1);
460 			}
461 		}
462 
463 		return fieldName;
464 	}
465 
466 	public static boolean hasField(final Class< ? > clazz, final String fieldName)
467 	{
468 		return getField(clazz, fieldName) != null;
469 	}
470 
471 	public static boolean hasMethod(final Class< ? > clazz, final String methodName, final Class< ? >... parameterTypes)
472 	{
473 		return getMethod(clazz, methodName, parameterTypes) != null;
474 	}
475 
476 	/**
477 	 *
478 	 * @param method the method to invoke
479 	 * @param obj the object on which to invoke the method
480 	 * @param args the method arguments
481 	 * @return the return value of the invoked method
482 	 * @throws InvokingMethodFailedException
483 	 */
484 	@SuppressWarnings("unchecked")
485 	public static <T> T invokeMethod(final Method method, final Object obj, final Object... args) throws InvokingMethodFailedException,
486 			ConstraintsViolatedException
487 	{
488 		try
489 		{
490 			if (!method.isAccessible())
491 			{
492 				AccessController.doPrivileged(new SetAccessibleAction(method));
493 			}
494 			return (T) method.invoke(obj, args);
495 		}
496 		catch (final Exception ex)
497 		{
498 			if (ex.getCause() instanceof ConstraintsViolatedException) throw (ConstraintsViolatedException) ex.getCause();
499 			throw new InvokingMethodFailedException("Executing method " + method.getName() + " failed.", obj,
500 					ContextCache.getMethodReturnValueContext(method), ex);
501 		}
502 	}
503 
504 	/**
505 	 * Returns true if an annotation for the specified type is present on this method, else false.
506 	 *
507 	 * @param method the method to inspect
508 	 * @param annotationClass the Class object corresponding to the annotation type
509 	 * @param inspectInterfaces whether to also check annotations declared on interface method declaration
510 	 * @return true if an annotation for the specified annotation type is present on this method, else false
511 	 */
512 	public static boolean isAnnotationPresent(final Method method, final Class< ? extends Annotation> annotationClass,
513 			final boolean inspectInterfaces)
514 	{
515 		if (method.isAnnotationPresent(annotationClass)) return true;
516 
517 		if (!inspectInterfaces || !isPublic(method)) return false;
518 
519 		final String methodName = method.getName();
520 		final Class< ? >[] methodParameterTypes = method.getParameterTypes();
521 
522 		for (final Class< ? > next : getInterfacesRecursive(method.getDeclaringClass()))
523 		{
524 			try
525 			{
526 				if (next.getDeclaredMethod(methodName, methodParameterTypes).isAnnotationPresent(annotationClass)) return true;
527 			}
528 			catch (final NoSuchMethodException ex)
529 			{
530 				// ignore
531 			}
532 		}
533 		return false;
534 	}
535 
536 	public static boolean isClassPresent(final String className)
537 	{
538 		try
539 		{
540 			Class.forName(className);
541 			return true;
542 		}
543 		catch (final ClassNotFoundException ex)
544 		{
545 			return false;
546 		}
547 	}
548 
549 	public static boolean isFinal(final Member member)
550 	{
551 		return (member.getModifiers() & Modifier.FINAL) != 0;
552 	}
553 
554 	/**
555 	 * determines if a method is a JavaBean style getter method
556 	 */
557 	public static boolean isGetter(final Method method)
558 	{
559 		return method.getParameterTypes().length == 0 && (method.getName().startsWith("is") || method.getName().startsWith("get"));
560 	}
561 
562 	// public Constructor getDeclaredConstructorOfNonStaticInnerClass(Class)
563 	public static boolean isNonStaticInnerClass(final Class< ? > clazz)
564 	{
565 		return clazz.getName().indexOf('$') > -1 && (clazz.getModifiers() & Modifier.STATIC) == 0;
566 	}
567 
568 	public static boolean isPackage(final Member member)
569 	{
570 		return (member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED)) == 0;
571 	}
572 
573 	public static boolean isPrivate(final Member member)
574 	{
575 		return (member.getModifiers() & Modifier.PRIVATE) != 0;
576 	}
577 
578 	public static boolean isPrivateAccessAllowed()
579 	{
580 		final SecurityManager manager = System.getSecurityManager();
581 		if (manager != null)
582 		{
583 			try
584 			{
585 				manager.checkPermission(SUPPRESS_ACCESS_CHECKS_PERMISSION);
586 			}
587 			catch (final SecurityException ex)
588 			{
589 				return false;
590 			}
591 		}
592 		return true;
593 	}
594 
595 	public static boolean isProtected(final Member member)
596 	{
597 		return (member.getModifiers() & Modifier.PROTECTED) != 0;
598 	}
599 
600 	public static boolean isPublic(final Member member)
601 	{
602 		return (member.getModifiers() & Modifier.PUBLIC) != 0;
603 	}
604 
605 	/**
606 	 * determines if a method is a JavaBean style setter method
607 	 */
608 	public static boolean isSetter(final Method method)
609 	{
610 		final Class< ? >[] methodParameterTypes = method.getParameterTypes();
611 
612 		// check if method has exactly one parameter
613 		if (methodParameterTypes.length != 1) return false;
614 
615 		final String methodName = method.getName();
616 		final int methodNameLen = methodName.length();
617 
618 		// check if the method's name starts with setXXX
619 		if (methodNameLen < 4 || !methodName.startsWith("set")) return false;
620 
621 		return true;
622 	}
623 
624 	public static boolean isStatic(final Member member)
625 	{
626 		return (member.getModifiers() & Modifier.STATIC) != 0;
627 	}
628 
629 	public static boolean isTransient(final Member member)
630 	{
631 		return (member.getModifiers() & Modifier.TRANSIENT) != 0;
632 	}
633 
634 	/**
635 	 * determines if a method is a void method
636 	 */
637 	public static boolean isVoidMethod(final Method method)
638 	{
639 		return method.getReturnType() == void.class;
640 	}
641 
642 	public static boolean setViaSetter(final Object target, final String propertyName, final Object propertyValue)
643 	{
644 		assert target != null;
645 		assert propertyName != null;
646 		final Method setter = getSetterRecursive(target.getClass(), propertyName);
647 		if (setter != null)
648 		{
649 			try
650 			{
651 				setter.invoke(target, propertyValue);
652 			}
653 			catch (final IllegalArgumentException ex)
654 			{
655 				LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex);
656 				return false;
657 			}
658 			catch (final IllegalAccessException ex)
659 			{
660 				LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex);
661 				return false;
662 			}
663 			catch (final InvocationTargetException ex)
664 			{
665 				LOG.debug("Setting {1} failed on {2} failed.", propertyName, target, ex);
666 				return false;
667 			}
668 		}
669 		return false;
670 	}
671 
672 	private ReflectionUtils()
673 	{
674 		super();
675 	}
676 }