1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.di;
17
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.apache.commons.beanutils.PropertyUtils;
28 import org.apache.commons.lang.ClassUtils;
29
30 /**
31 * <p>
32 * An utility class that provides some functionality related to reflection and
33 * dependency injection.
34 * </p>
35 * <p>
36 * This class implements some basic functionality that is needed by other parts
37 * of the dependency injection package. It especially deals with row reflection
38 * calls and exception handling. It is not intended to be used directly by
39 * applications using this framework. It will be called under the hood.
40 * </p>
41 *
42 * @author Oliver Heger
43 * @version $Id: ReflectionUtils.java 205 2012-01-29 18:29:57Z oheger $
44 */
45 public final class ReflectionUtils
46 {
47 /**
48 * A private constructor, so that no instances can be created.
49 */
50 private ReflectionUtils()
51 {
52 // empty
53 }
54
55 /**
56 * Loads the class with the specified name using the given class loader.
57 * This is a thin wrapper over the <code>Class.forName()</code> method.
58 * <code>ClassNotFoundException</code> exceptions are caught and re-thrown
59 * as <code>InjectionException</code> exceptions.
60 *
61 * @param className the name of the class to be loaded
62 * @param loader the class loader to use
63 * @return the loaded class
64 * @throws IllegalArgumentException if the class name or the class loader is
65 * undefined
66 * @throws InjectionException if the class cannot be resolved
67 */
68 public static Class<?> loadClass(String className, ClassLoader loader)
69 {
70 if (className == null)
71 {
72 throw new IllegalArgumentException("Class name must not be null!");
73 }
74 if (loader == null)
75 {
76 throw new IllegalArgumentException("Class loader must not be null!");
77 }
78
79 try
80 {
81 return ClassUtils.getClass(loader, className);
82 }
83 catch (ClassNotFoundException cnfex)
84 {
85 throw new InjectionException(cnfex);
86 }
87 }
88
89 /**
90 * Helper method for invoking a method using reflection. This method catches
91 * the variety of possible exceptions and re-throws them as runtime
92 * exceptions.
93 *
94 * @param method the method to be invoked
95 * @param target the target object
96 * @param args the arguments of the method
97 * @return the return value of the method
98 * @throws InjectionException if invoking the method causes an error
99 * @throws IllegalArgumentException if the method object is <b>null</b>
100 */
101 public static Object invokeMethod(Method method, Object target,
102 Object... args)
103 {
104 if (method == null)
105 {
106 throw new IllegalArgumentException("Method must not be null!");
107 }
108
109 try
110 {
111 return method.invoke(target, args);
112 }
113 catch (Exception ex)
114 {
115 // catch all related exceptions
116 throw new InjectionException("Exception when invoking method "
117 + method.getName(), ex);
118 }
119 }
120
121 /**
122 * Creates an object by invoking the specified constructor with the given
123 * arguments. Like <code>invokeMethod()</code>, this is a helper method
124 * that deals with all possible exceptions and redirects them as
125 * <code>InjectionException</code>s.
126 *
127 * @param <T> the type of the constructor
128 * @param ctor the constructor to be invoked (must not be <b>null</b>)
129 * @param args the arguments to be passed to the constructor
130 * @return the newly created instance
131 * @throws InjectionException if construction of the object fails
132 * @throws IllegalArgumentException if the constructor object is <b>null</b>
133 */
134 public static <T> T invokeConstructor(Constructor<T> ctor, Object... args)
135 {
136 if (ctor == null)
137 {
138 throw new IllegalArgumentException("Constructor must not be null!");
139 }
140
141 try
142 {
143 return ctor.newInstance(args);
144 }
145 catch (Exception ex)
146 {
147 // redirect all exceptions
148 throw new InjectionException("Exception when invoking constructor "
149 + ctor, ex);
150 }
151 }
152
153 /**
154 * Finds all methods matching the given search criteria. With this method a
155 * list of the methods of the specified target class can be obtained that
156 * have the given name and are compatible with the given parameter types.
157 * Wild cards are supported as follows:
158 * <ul>
159 * <li>The method name can be <b>null</b>, then only parameter types are
160 * compared.</li>
161 * <li>Each element of the {@code paramTypes} can be <b>null</b>, then
162 * arbitrary parameter types are accepted at this place.</li>
163 * <li>The whole {@code paramTypes} array can be <b>null</b>, then all
164 * methods with the given name and arbitrary signature are accepted.</li>
165 * </ul>
166 * The {@code exactTypeMatch} parameter controls how parameter types are
167 * compared: If set to <b>true</b> the parameter types must match exactly;
168 * otherwise, the parameters of the method in the target class can be super
169 * classes of the provided parameter types. So this method allows searching
170 * for methods of a given class in a flexible way including the following
171 * use cases:
172 * <ul>
173 * <li>Search for all methods with a given name: just pass in <b>null</b>
174 * for the parameter types array.</li>
175 * <li>Search for all methods with a given signature: pass in <b>null</b>
176 * for the method name and specify a corresponding array with parameter
177 * types.</li>
178 * <li>Search for methods with a given name and a partly known signature:
179 * here the name of the method and a type array with all known types set has
180 * to be passed in.</li>
181 * </ul>
182 *
183 * @param targetClass the target class (must not be <b>null</b>)
184 * @param methodName the name of the method to be searched for
185 * @param paramTypes an array with the parameter types
186 * @param exactTypeMatch a flag whether parameter types should be matched
187 * exactly
188 * @return a list with methods matching the search criteria
189 * @throws IllegalArgumentException if the target class is <b>null</b>
190 */
191 public static List<Method> findMethods(Class<?> targetClass,
192 String methodName, Class<?>[] paramTypes, boolean exactTypeMatch)
193 {
194 checkTargetClass(targetClass);
195 List<Method> methods = new LinkedList<Method>();
196
197 for (Method m : targetClass.getMethods())
198 {
199 if (methodName == null || methodName.equals(m.getName()))
200 {
201 if (isSignatureCompatible(m.getParameterTypes(), paramTypes,
202 exactTypeMatch))
203 {
204 methods.add(m);
205 }
206 }
207 }
208
209 return methods;
210 }
211
212 /**
213 * Removes duplicate methods from the specified list. When calling
214 * {@link #findMethods(Class, String, Class[], boolean)} it is possible that
215 * the resulting list contains multiple methods with the same name and
216 * signature, but with a different result type. This is the case for
217 * instance if a class overrides a super class method with a covariant
218 * return type. With this method such duplicates can be eliminated. It
219 * searches for methods with the same name and signature and drops all of
220 * them except for the one with the most specific return type. The result of
221 * the method depends on the operations performed: If no duplicates have
222 * been found, the same list is returned without changes. Otherwise, a new
223 * list is created and returned.
224 *
225 * @param methods the list with methods to be checked (must not be
226 * <b>null</b>
227 * @return a list with duplicates removed
228 * @throws IllegalArgumentException if the passed in list is <b>null</b> or
229 * contain <b>null</b> elements
230 */
231 public static List<Method> removeCovariantDuplicates(List<Method> methods)
232 {
233 if (methods == null)
234 {
235 throw new IllegalArgumentException("Method list must not be null!");
236 }
237 if (methods.size() < 1)
238 {
239 // there can't be duplicates
240 return methods;
241 }
242
243 Map<Signature, Method> methodMap = new HashMap<Signature, Method>();
244 for (Method m : methods)
245 {
246 Signature sig = new Signature(m);
247 Method m2 = methodMap.get(sig);
248 if (m2 == null
249 || m2.getReturnType().isAssignableFrom(m.getReturnType()))
250 {
251 methodMap.put(sig, m);
252 }
253 }
254
255 if (methodMap.size() == methods.size())
256 {
257 // no changes, return input list
258 return methods;
259 }
260 else
261 {
262 return new ArrayList<Method>(methodMap.values());
263 }
264 }
265
266 /**
267 * Finds all constructors matching the specified search criteria. This
268 * method works like {@link #findMethods(Class, String, Class[], boolean)},
269 * but deals with constructors of the target class.
270 *
271 * @param <T> the type of the constructor
272 * @param targetClass the target class (must not be <b>null</b>)
273 * @param paramTypes an array with the parameter types
274 * @param exactTypeMatch a flag whether parameter types should be matched
275 * exactly
276 * @return a list with constructors matching the search criteria
277 * @throws IllegalArgumentException if the target class is <b>null</b>
278 */
279 public static <T> List<Constructor<T>> findConstructors(
280 Class<T> targetClass, Class<?>[] paramTypes, boolean exactTypeMatch)
281 {
282 checkTargetClass(targetClass);
283 List<Constructor<T>> constr = new LinkedList<Constructor<T>>();
284
285 for (Constructor<?> c : targetClass.getConstructors())
286 {
287 if (isSignatureCompatible(c.getParameterTypes(), paramTypes,
288 exactTypeMatch))
289 {
290 // should be a constructor of the target class
291 @SuppressWarnings("unchecked")
292 Constructor<T> ctor = (Constructor<T>) c;
293 constr.add(ctor);
294 }
295 }
296
297 return constr;
298 }
299
300 /**
301 * Returns the value of the specified property from the given bean.
302 *
303 * @param bean the bean
304 * @param name the name of the property to retrieve
305 * @return the value of this property
306 * @throws InjectionException if accessing the property fails
307 * @throws IllegalArgumentException if invalid parameters are specified
308 */
309 public static Object getProperty(Object bean, String name)
310 {
311 try
312 {
313 return PropertyUtils.getProperty(bean, name);
314 }
315 catch (IllegalArgumentException iex)
316 {
317 throw iex;
318 }
319 catch (Exception ex)
320 {
321 // redirect all reflection exceptions
322 throw new InjectionException("Error when accessing property "
323 + name, ex);
324 }
325 }
326
327 /**
328 * Sets the value of a property for the specified bean. The given value will
329 * be directly written into the property, without performing any type
330 * conversions. Occurring exceptions will be re-thrown as
331 * <code>InjectionException</code>s.
332 *
333 * @param bean the bean, on which to set the property
334 * @param name the name of the property to be set
335 * @param value the new value of the property
336 * @throws InjectionException if an error occurs when setting the property
337 * @throws IllegalArgumentException if invalid parameters are passed in
338 */
339 public static void setProperty(Object bean, String name, Object value)
340 {
341 try
342 {
343 PropertyUtils.setProperty(bean, name, value);
344 }
345 catch (IllegalArgumentException iex)
346 {
347 throw iex;
348 }
349 catch (Exception ex)
350 {
351 // handle all reflection exceptions the same way
352 throw new InjectionException("Error when setting property " + name,
353 ex);
354 }
355 }
356
357 /**
358 * Tests whether the specified actual parameter value can be assigned to a
359 * parameter of the given type.
360 *
361 * @param paramClass the parameter type
362 * @param param the current parameter value
363 * @return a flag whether this assignment is possible
364 */
365 static boolean isParamAssignable(Class<?> paramClass, Object param)
366 {
367 if (paramClass == null)
368 {
369 throw new IllegalArgumentException(
370 "Parameter class must not be null!");
371 }
372
373 if (param == null && paramClass.isPrimitive())
374 {
375 // cannot assign null to a primitive parameter
376 return false;
377 }
378 Class<?> valueClass = (param != null) ? param.getClass() : null;
379 return isParameterCompatible(paramClass, valueClass, false);
380 }
381
382 /**
383 * Tests whether the specified value objects match the given parameter
384 * types. With this method the signature of a method can be tested for
385 * compatibility with a set of parameter objects.
386 *
387 * @param parameterTypes the parameter types to be checked
388 * @param definedTypes an array with defined parameter types; all defined
389 * elements in this array must be exactly the same as in the parameterTypes
390 * array
391 * @param values an array with the current parameter values
392 * @return a flag whether the method signature is compatible with the
393 * parameter values
394 */
395 static boolean matchParameterTypes(Class<?>[] parameterTypes,
396 Class<?>[] definedTypes, Object[] values)
397 {
398 assert parameterTypes != null && definedTypes != null && values != null
399 : "Input parameters are null";
400 assert definedTypes.length == values.length : "Invalid array lengths";
401
402 if (parameterTypes.length != values.length)
403 {
404 return false;
405 }
406 for (int i = 0; i < values.length; i++)
407 {
408 if (definedTypes[i] != null)
409 {
410 if (!definedTypes[i].equals(parameterTypes[i]))
411 {
412 return false;
413 }
414 }
415 else
416 {
417 if (!isParamAssignable(parameterTypes[i], values[i]))
418 {
419 return false;
420 }
421 }
422 }
423
424 return true;
425 }
426
427 /**
428 * Tests whether a method signature matches search criteria. This method is
429 * used by methods for finding specific methods or constructors. All wild
430 * cards supported by search criteria for signatures are supported.
431 *
432 * @param methodTypes the parameter types of a method in question
433 * @param callTypes the search criteria for method types
434 * @param exactMatch the exact match flag
435 * @return a flag whether the parameters match the criteria
436 */
437 private static boolean isSignatureCompatible(Class<?>[] methodTypes,
438 Class<?>[] callTypes, boolean exactMatch)
439 {
440 if (callTypes == null)
441 {
442 // wild card for whole signature
443 return true;
444 }
445
446 if (methodTypes.length != callTypes.length)
447 {
448 return false;
449 }
450
451 for (int i = 0; i < methodTypes.length; i++)
452 {
453 if (!isParameterCompatible(methodTypes[i], callTypes[i], exactMatch))
454 {
455 return false;
456 }
457 }
458
459 return true;
460 }
461
462 /**
463 * Tests whether an argument class is compatible with the parameter class of
464 * a method. This method compares the classes either exactly or checks
465 * whether they are assignment compatible. Primitive types and wrappers are
466 * also handled correctly.
467 *
468 * @param methodParamCls the class of the method parameter
469 * @param argCls the class of the argument
470 * @param exactMatch the exact match flag
471 * @return a flag whether the parameter is compatible
472 */
473 private static boolean isParameterCompatible(Class<?> methodParamCls,
474 Class<?> argCls, boolean exactMatch)
475 {
476 if (argCls == null)
477 {
478 return true;
479 }
480
481 if (compareParameterClasses(methodParamCls, argCls, exactMatch))
482 {
483 return true;
484 }
485
486 // Check for unboxing conversions of wrapper types
487 if (methodParamCls.isPrimitive())
488 {
489 Class<?> unboxedCls = wrapperToPrimitive(argCls);
490 if (unboxedCls != null
491 && ClassUtils.isAssignable(unboxedCls, methodParamCls))
492 {
493 return true;
494 }
495 }
496
497 return false;
498 }
499
500 /**
501 * Helper method for comparing the classes of a method parameter. Depending
502 * on the exact match flag either a strict comparison or a test for
503 * assignment compatibility is performed.
504 *
505 * @param methodParamCls the class of the method parameter
506 * @param argCls the class of the argument
507 * @param exactMatch the exact match flag
508 * @return a flag whether the classes are compatible
509 */
510 private static boolean compareParameterClasses(Class<?> methodParamCls,
511 Class<?> argCls, boolean exactMatch)
512 {
513 return exactMatch ? methodParamCls.equals(argCls) : ClassUtils
514 .isAssignable(argCls, methodParamCls);
515 }
516
517 /**
518 * Tests whether the target class parameter is valid. Throws an exception if
519 * not.
520 *
521 * @param targetClass the target class
522 */
523 private static void checkTargetClass(Class<?> targetClass)
524 {
525 if (targetClass == null)
526 {
527 throw new IllegalArgumentException("Target class must not be null!");
528 }
529 }
530
531 /**
532 * Returns the corresponding primitive type to a wrapper class. If the
533 * passed in class is not a wrapper class, <b>null</b> is returned.
534 *
535 * @param wrapperCls the wrapper class
536 * @return the corresponding primitive type
537 */
538 private static Class<?> wrapperToPrimitive(Class<?> wrapperCls)
539 {
540 return ClassUtils.wrapperToPrimitive(wrapperCls);
541 }
542
543 /**
544 * An internally used helper class representing a method signature.
545 */
546 private static class Signature
547 {
548 /** The name of the method. */
549 private final String methodName;
550
551 /** The parameter types of the method. */
552 private final Class<?>[] parameterTypes;
553
554 /**
555 * Creates a new instance of {@code Signature} and initializes it from
556 * the given {@code Method} instance.
557 *
558 * @param m the represented method instance
559 * @throws IllegalArgumentException if the method is <b>null</b>
560 */
561 public Signature(Method m)
562 {
563 if (m == null)
564 {
565 throw new IllegalArgumentException("Method must not be null!");
566 }
567 methodName = m.getName();
568 parameterTypes = m.getParameterTypes();
569 }
570
571 /**
572 * Returns a hash code for this object. The result is based on the
573 * method name and the parameter types.
574 *
575 * @return a hash code for this object
576 */
577 @Override
578 public int hashCode()
579 {
580 final int factor = 31;
581 final int seed = 17;
582
583 int result = seed;
584 result = result * factor + methodName.hashCode();
585 result = result * factor + Arrays.hashCode(parameterTypes);
586 return result;
587 }
588
589 /**
590 * Checks whether this object equals another one. Two instances are
591 * considered equals if the method name and the parameter types are
592 * equal.
593 *
594 * @param obj the object to compare to
595 * @return a flag whether these objects are equal
596 */
597 @Override
598 public boolean equals(Object obj)
599 {
600 if (this == obj)
601 {
602 return true;
603 }
604 if (!(obj instanceof Signature))
605 {
606 return false;
607 }
608
609 Signature c = (Signature) obj;
610 return methodName.equals(c.methodName)
611 && Arrays.equals(parameterTypes, c.parameterTypes);
612 }
613 }
614 }