001 /*--------------------------------------------------------------------------+
002 $Id: ReflectionUtils.java 26268 2010-02-18 10:44:30Z juergens $
003 | |
004 | Copyright 2005-2010 Technische Universitaet Muenchen |
005 | |
006 | Licensed under the Apache License, Version 2.0 (the "License"); |
007 | you may not use this file except in compliance with the License. |
008 | You may obtain a copy of the License at |
009 | |
010 | http://www.apache.org/licenses/LICENSE-2.0 |
011 | |
012 | Unless required by applicable law or agreed to in writing, software |
013 | distributed under the License is distributed on an "AS IS" BASIS, |
014 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
015 | See the License for the specific language governing permissions and |
016 | limitations under the License. |
017 +--------------------------------------------------------------------------*/
018 package edu.tum.cs.commons.reflect;
019
020 import java.awt.Color;
021 import java.io.DataInputStream;
022 import java.io.File;
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.lang.reflect.Constructor;
026 import java.lang.reflect.InvocationTargetException;
027 import java.lang.reflect.Method;
028 import java.util.ArrayList;
029 import java.util.Collection;
030 import java.util.Collections;
031 import java.util.Enumeration;
032 import java.util.HashMap;
033 import java.util.LinkedList;
034 import java.util.List;
035 import java.util.Map;
036 import java.util.Queue;
037 import java.util.jar.JarEntry;
038 import java.util.jar.JarFile;
039
040 import edu.tum.cs.commons.color.ColorUtils;
041 import edu.tum.cs.commons.enums.EnumUtils;
042 import edu.tum.cs.commons.string.StringUtils;
043 import edu.tum.cs.commons.version.Version;
044
045 /**
046 * This class provides utility methods for reflection purposes. In particular it
047 * provides access to {@link FormalParameter}.
048 *
049 * @author Florian Deissenboeck
050 * @author Benjamin Hummel
051 * @author $Author: juergens $
052 * @version $Rev: 26268 $
053 * @levd.rating GREEN Hash: AD094410F95DFE6BB4CB12A5DC8A5F27
054 */
055 public class ReflectionUtils {
056
057 /** To compare formal parameters. */
058 private static FormalParameterComparator comparator = new FormalParameterComparator();
059
060 /**
061 * Convert a String to an Object of the provided type. It supports
062 * conversion to primitive types and simple tests (char: use first character
063 * of string, boolean: test for values "true", "on", "1", "yes"). Enums are
064 * handled by the {@link EnumUtils#valueOfIgnoreCase(Class, String)} method.
065 * Otherwise it is checked if the target type has a constructor that takes a
066 * single string and it is invoked. For all other cases an exception is
067 * thrown, as no conversion is possible.
068 *
069 * <i>Maintainer note</i>: Make sure this method is changed in accordance
070 * with method {@link #isConvertibleFromString(Class)}
071 *
072 * @see #convertString(String, Class)
073 *
074 * @param value
075 * the string to be converted.
076 * @param targetType
077 * the type of the resulting object.
078 * @return the converted object.
079 * @throws TypeConversionException
080 * in the case that no conversion could be performed.
081 * @see #isConvertibleFromString(Class)
082 *
083 */
084 @SuppressWarnings("unchecked")
085 public static <T> T convertString(String value, Class<T> targetType)
086 throws TypeConversionException {
087
088 // value must be provided
089 if (value == null) {
090 if (String.class.equals(targetType)) {
091 return (T) StringUtils.EMPTY_STRING;
092 }
093 throw new TypeConversionException(
094 "Null value can't be converted to type '"
095 + targetType.getName() + "'.");
096 }
097
098 if (targetType.equals(Object.class) || targetType.equals(String.class)) {
099 return (T) value;
100 }
101 if (targetType.isPrimitive()
102 || EJavaPrimitive.isWrapperType(targetType)) {
103 return convertPrimitive(value, targetType);
104 }
105 if (targetType.isEnum()) {
106 // we checked manually before
107 Object result = EnumUtils.valueOfIgnoreCase((Class) targetType,
108 value);
109 if (result == null) {
110 throw new TypeConversionException("'" + value
111 + "' is no valid value for enum "
112 + targetType.getName());
113 }
114 return (T) result;
115
116 }
117 if (targetType.equals(Color.class)) {
118 Color result = ColorUtils.fromString(value);
119 if (result == null) {
120 throw new TypeConversionException("'" + value
121 + "' is not a valid color!");
122 }
123 return (T) result;
124 }
125
126 // Check if the target type has a constructor taking a single string.
127 try {
128 Constructor<T> c = targetType.getConstructor(String.class);
129 return c.newInstance(value);
130 } catch (Exception e) {
131 throw new TypeConversionException(
132 "No constructor taking one String argument found for type '"
133 + targetType + "' (" + e.getMessage() + ")", e);
134 }
135
136 }
137
138 /**
139 * This method checks if the provided type can be converted from a string.
140 * With respect to {@link #convertString(String, Class)} the semantics are
141 * the following: If this method returns <code>true</code> a particular
142 * string <i>may</i> be convertible to the target type. If this method
143 * returns <code>false</code>, a call to
144 * {@link #convertString(String, Class)} is guaranteed to result in a
145 * {@link TypeConversionException}. If a call to
146 * {@link #convertString(String, Class)} does not result in an exception, a
147 * call to this method is guaranteed to return <code>true</code>.
148 * <p>
149 * <i>Maintainer note</i>: Make sure this method is change in accordance
150 * with method {@link #convertString(String, Class)}
151 *
152 * @see #convertString(String, Class)
153 */
154 public static boolean isConvertibleFromString(Class<?> targetType) {
155
156 if (targetType.equals(Object.class) || targetType.equals(String.class)) {
157 return true;
158 }
159 if (targetType.isPrimitive()
160 || EJavaPrimitive.isWrapperType(targetType)) {
161 return true;
162 }
163 if (targetType.isEnum()) {
164 return true;
165
166 }
167 if (targetType.equals(Color.class)) {
168 return true;
169 }
170
171 try {
172 targetType.getConstructor(String.class);
173 return true;
174 } catch (SecurityException e) {
175 // case is handled at method end
176 } catch (NoSuchMethodException e) {
177 // case is handled at method end
178 }
179
180 return false;
181 }
182
183 /**
184 * Obtain array of formal parameters for a method.
185 *
186 * @see FormalParameter
187 */
188 public static FormalParameter[] getFormalParameters(Method method) {
189
190 int parameterCount = method.getParameterTypes().length;
191
192 FormalParameter[] parameters = new FormalParameter[parameterCount];
193
194 for (int i = 0; i < parameterCount; i++) {
195 parameters[i] = new FormalParameter(method, i);
196 }
197
198 return parameters;
199 }
200
201 /**
202 * Get super class list of a class.
203 *
204 * @param clazz
205 * the class to start traversal from
206 * @return a list of super class where the direct super class of the
207 * provided class is the first member of the list. <br>
208 * For {@link Object}, primitives and interfaces this returns an
209 * empty list. <br>
210 * For arrays this returns a list containing only {@link Object}. <br>
211 * For enums this returns a list containing {@link Enum} and
212 * {@link Object}
213 */
214 public static List<Class<?>> getSuperClasses(Class<?> clazz) {
215 ArrayList<Class<?>> superClasses = new ArrayList<Class<?>>();
216 findSuperClasses(clazz, superClasses);
217 return superClasses;
218 }
219
220 /**
221 * Invoke a method with parameters.
222 *
223 * @param method
224 * the method to invoke
225 * @param object
226 * the object the underlying method is invoked from
227 * @param parameterMap
228 * this maps from the formal parameter of the method to the
229 * parameter value
230 * @return the result of dispatching the method
231 * @throws IllegalArgumentException
232 * if the method is an instance method and the specified object
233 * argument is not an instance of the class or interface
234 * declaring the underlying method (or of a subclass or
235 * implementor thereof); if the number of actual and formal
236 * parameters differ; if an unwrapping conversion for primitive
237 * arguments fails; or if, after possible unwrapping, a
238 * parameter value cannot be converted to the corresponding
239 * formal parameter type by a method invocation conversion; if
240 * formal parameters belong to different methods.
241 *
242 * @throws IllegalAccessException
243 * if this Method object enforces Java language access control
244 * and the underlying method is inaccessible.
245 * @throws InvocationTargetException
246 * if the underlying method throws an exception.
247 * @throws NullPointerException
248 * if the specified object is null and the method is an instance
249 * method.
250 * @throws ExceptionInInitializerError
251 * if the initialization provoked by this method fails.
252 */
253 public static Object invoke(Method method, Object object,
254 Map<FormalParameter, Object> parameterMap)
255 throws IllegalArgumentException, IllegalAccessException,
256 InvocationTargetException {
257
258 for (FormalParameter formalParameter : parameterMap.keySet()) {
259 if (!formalParameter.getMethod().equals(method)) {
260 throw new IllegalArgumentException(
261 "Parameters must belong to method.");
262 }
263 }
264
265 Object[] parameters = obtainParameters(parameterMap);
266
267 return method.invoke(object, parameters);
268
269 }
270
271 /**
272 * Check whether an Object of the source type can be used instead of an
273 * Object of the target type. This method is required, as the
274 * {@link Class#isAssignableFrom(java.lang.Class)} does not handle primitive
275 * types.
276 *
277 * @param source
278 * type of the source object
279 * @param target
280 * type of the target object
281 * @return whether an assignment would be possible.
282 */
283 public static boolean isAssignable(Class<?> source, Class<?> target) {
284 return resolvePrimitiveClass(target).isAssignableFrom(
285 resolvePrimitiveClass(source));
286 }
287
288 /**
289 * Returns the wrapper class type for a primitive type (e.g.
290 * <code>Integer</code> for an <code>int</code>). If the given class is not
291 * a primitive, the class itself is returned.
292 *
293 * @param clazz
294 * the class.
295 * @return the corresponding class type.
296 */
297 public static Class<?> resolvePrimitiveClass(Class<?> clazz) {
298 if (!clazz.isPrimitive()) {
299 return clazz;
300 }
301
302 EJavaPrimitive primitive = EJavaPrimitive.getForPrimitiveClass(clazz);
303 if (primitive == null) {
304 throw new IllegalStateException("Did Java get a new primitive? "
305 + clazz.getName());
306 }
307 return primitive.getWrapperClass();
308 }
309
310 /**
311 * Convert a String to an Object of the provided type. This only works for
312 * primitive types and wrapper types.
313 *
314 * @param value
315 * the string to be converted.
316 * @param targetType
317 * the type of the resulting object.
318 * @return the converted object.
319 * @throws TypeConversionException
320 * in the case that no conversion could be performed.
321 */
322 @SuppressWarnings("unchecked")
323 /* package */static <T> T convertPrimitive(String value, Class<T> targetType)
324 throws TypeConversionException {
325
326 EJavaPrimitive primitive = EJavaPrimitive
327 .getForPrimitiveOrWrapperClass(targetType);
328 if (primitive == null) {
329 throw new IllegalArgumentException("Type '" + targetType.getName()
330 + "' is not a primitive type!");
331 }
332
333 try {
334
335 switch (primitive) {
336 case BOOLEAN:
337 boolean b = "1".equalsIgnoreCase(value)
338 || "true".equalsIgnoreCase(value)
339 || "on".equalsIgnoreCase(value)
340 || "yes".equalsIgnoreCase(value);
341 return (T) Boolean.valueOf(b);
342
343 case CHAR:
344 return (T) Character.valueOf(value.charAt(0));
345
346 case BYTE:
347 return (T) Byte.valueOf(value);
348
349 case SHORT:
350 return (T) Short.valueOf(value);
351
352 case INT:
353 return (T) Integer.valueOf(value);
354
355 case LONG:
356 return (T) Long.valueOf(value);
357
358 case FLOAT:
359 return (T) Float.valueOf(value);
360
361 case DOUBLE:
362 return (T) Double.valueOf(value);
363
364 default:
365 throw new TypeConversionException("No conversion possible for "
366 + primitive);
367 }
368
369 } catch (NumberFormatException e) {
370 throw new TypeConversionException("Value'" + value
371 + "' can't be converted to type '" + targetType.getName()
372 + "' (" + e.getMessage() + ").", e);
373 }
374 }
375
376 /**
377 * Resolves the class object for a type name. Type name can be a primitive.
378 * For resolution, {@link Class#forName(String)} is used, that uses the
379 * caller's class loader.
380 * <p>
381 * While method <code>Class.forName(...)</code> resolves fully qualified
382 * names, it does not resolve primitives, e.g. "java.lang.Boolean" can be
383 * resolved but "boolean" cannot.
384 *
385 * @param typeName
386 * name of the type. For primitives case is ignored.
387 *
388 * @throws ClassNotFoundException
389 * if the typeName neither resolves to a primitive, nor to a
390 * known class.
391 */
392 public static Class<?> resolveType(String typeName)
393 throws ClassNotFoundException {
394 return resolveType(typeName, null);
395 }
396
397 /**
398 * Resolves the class object for a type name. Type name can be a primitive.
399 * For resolution, the given class loader is used.
400 * <p>
401 * While method <code>Class.forName(...)</code> resolves fully qualified
402 * names, it does not resolve primitives, e.g. "java.lang.Boolean" can be
403 * resolved but "boolean" cannot.
404 *
405 * @param typeName
406 * name of the type. For primitives case is ignored.
407 *
408 * @param classLoader
409 * the class loader used for loading the class. If this is null,
410 * the caller class loader is used.
411 *
412 * @throws ClassNotFoundException
413 * if the typeName neither resolves to a primitive, nor to a
414 * known class.
415 */
416 public static Class<?> resolveType(String typeName, ClassLoader classLoader)
417 throws ClassNotFoundException {
418
419 EJavaPrimitive primitive = EJavaPrimitive
420 .getPrimitiveIgnoreCase(typeName);
421
422 if (primitive != null) {
423 return primitive.getClassObject();
424 }
425
426 if (classLoader == null) {
427 return Class.forName(typeName);
428 }
429 return Class.forName(typeName, true, classLoader);
430 }
431
432 /**
433 * Recursively add super classes to a list.
434 *
435 * @param clazz
436 * class to start from
437 * @param superClasses
438 * list to store super classes.
439 */
440 private static void findSuperClasses(Class<?> clazz,
441 List<Class<?>> superClasses) {
442 Class<?> superClass = clazz.getSuperclass();
443 if (superClass == null) {
444 return;
445 }
446 superClasses.add(superClass);
447 findSuperClasses(superClass, superClasses);
448 }
449
450 /**
451 * Obtain parameter array from parameter map.
452 */
453 private static Object[] obtainParameters(
454 Map<FormalParameter, Object> parameterMap) {
455
456 ArrayList<FormalParameter> formalParameters = new ArrayList<FormalParameter>(
457 parameterMap.keySet());
458
459 Collections.sort(formalParameters, comparator);
460
461 Object[] result = new Object[formalParameters.size()];
462
463 for (int i = 0; i < formalParameters.size(); i++) {
464 result[i] = parameterMap.get(formalParameters.get(i));
465 }
466
467 return result;
468 }
469
470 /**
471 * Obtain the return type of method. This method deals with bridge methods
472 * introduced by generics. This works for methods without parameters only.
473 *
474 * @param clazz
475 * the class
476 * @param methodName
477 * the name of the method.
478 * @return the return type
479 * @throws NoSuchMethodException
480 * if the class doesn't contain the desired method
481 */
482 public static Class<?> obtainMethodReturnType(Class<?> clazz,
483 String methodName) throws NoSuchMethodException {
484
485 // due to the potential presense of bridge methods we can't use
486 // Clazz.getMethod() and have to iterate over all methods.
487 for (Method method : clazz.getMethods()) {
488 if (isValid(method, methodName)) {
489 return method.getReturnType();
490 }
491 }
492 // method not found
493 throw new NoSuchMethodException("Class " + clazz.getName()
494 + " doesn't have parameterless method named " + methodName);
495 }
496
497 /**
498 * Obtain the generic return type of method. This method deals with the gory
499 * details of bridge methods and generics. This works for methods without
500 * parameters only. This doesn't work for interfaces, arrays and enums.
501 *
502 * @param clazz
503 * the class
504 * @param methodName
505 * the name of the method.
506 * @return the return type
507 * @throws NoSuchMethodException
508 * if the class doesn't contain the desired method
509 */
510 public static Class<?> obtainGenericMethodReturnType(Class<?> clazz,
511 String methodName) throws NoSuchMethodException {
512
513 if (clazz.isArray() || clazz.isEnum()) {
514 throw new IllegalArgumentException(
515 "Doesn't work for arrays and enums.");
516 }
517 if (clazz.getTypeParameters().length != 0) {
518 throw new IllegalArgumentException(
519 "Doesn't work for generic classes.");
520 }
521
522 for (Method method : clazz.getMethods()) {
523 if (isValid(method, methodName)) {
524 return new GenericTypeResolver(clazz).resolveGenericType(method
525 .getGenericReturnType());
526 }
527 }
528
529 // method not found
530 throw new NoSuchMethodException("Class " + clazz.getName()
531 + " doesn't have parameterless method named " + methodName);
532 }
533
534 /**
535 * Tests if a method has the correct name, no parameters and is no bridge
536 * method.
537 */
538 private static boolean isValid(Method method, String methodName) {
539 return method.getName().equals(methodName)
540 && method.getParameterTypes().length == 0 && !method.isBridge();
541 }
542
543 /**
544 * Returns the value from the map, whose key is the best match for the given
545 * class. The best match is defined by the first match occurring in a breath
546 * first search of the inheritance tree, where the base class is always
547 * visited before the implemented interfaces. Interfaces are traversed in
548 * the order they are defined in the source file. The only exception is
549 * {@link Object}, which is considered only as the very last option.
550 * <p>
551 * As this lookup can be expensive (reflective iteration over the entire
552 * inheritance tree) the results should be cached if multiple lookups for
553 * the same class are expected.
554 *
555 *
556 * @param clazz
557 * the class being looked up.
558 * @param classMap
559 * the map to perform the lookup in.
560 * @return the best match found or <code>null</code> if no matching entry
561 * was found. Note that <code>null</code> will also be returned if
562 * the entry for the best matching class was <code>null</code>.
563 */
564 public static <T> T performNearestClassLookup(Class<?> clazz,
565 Map<Class<?>, T> classMap) {
566 Queue<Class<?>> q = new LinkedList<Class<?>>();
567 q.add(clazz);
568
569 while (!q.isEmpty()) {
570 Class<?> current = q.poll();
571 if (classMap.containsKey(current)) {
572 return classMap.get(current);
573 }
574
575 Class<?> superClass = current.getSuperclass();
576 if (superClass != null && superClass != Object.class) {
577 q.add(superClass);
578 }
579
580 for (Class<?> iface : current.getInterfaces()) {
581 q.add(iface);
582 }
583 }
584 return classMap.get(Object.class);
585 }
586
587 /**
588 * Returns whether the given object is an instance of at least one of the
589 * given classes.
590 */
591 public static boolean isInstanceOfAny(Object o, Class<?>... classes) {
592 for (Class<?> c : classes) {
593 if (c.isInstance(o)) {
594 return true;
595 }
596 }
597 return false;
598 }
599
600 /**
601 * Returns whether the given object is an instance of all of the given
602 * classes.
603 */
604 public static boolean isInstanceOfAll(Object o, Class<?>... classes) {
605 for (Class<?> c : classes) {
606 if (!c.isInstance(o)) {
607 return false;
608 }
609 }
610 return true;
611 }
612
613 /**
614 * Returns the first object in the given collection which is an instance of
615 * the given class (or null otherwise).
616 */
617 @SuppressWarnings("unchecked")
618 public static <T> T pickInstanceOf(Class<T> clazz, Collection<?> objects) {
619 for (Object o : objects) {
620 if (clazz.isInstance(o)) {
621 return (T) o;
622 }
623 }
624 return null;
625 }
626
627 /**
628 * Obtains the version of a Java class file.
629 *
630 * Class file versions (from
631 * http://thiamteck.blogspot.com/2007/11/determine-
632 * java-class-file-version.html):
633 *
634 * <pre>
635 * major minor Java Version
636 * 45 3 1.0
637 * 45 3 1.1
638 * 46 0 1.2
639 * 47 0 1.3
640 * 48 0 1.4
641 * 49 0 1.5
642 * 50 0 1.6
643 * </pre>
644 *
645 * @param inputStream
646 * stream to read class file from.
647 * @return the class file version or <code>null</code> if stream does not
648 * contain a class file.
649 * @throws IOException
650 * if an IO problem occurs.
651 */
652 public static Version obtainClassFileVersion(InputStream inputStream)
653 throws IOException {
654 DataInputStream classfile = new DataInputStream(inputStream);
655 int magic = classfile.readInt();
656 if (magic != 0xcafebabe) {
657 return null;
658 }
659 int minorVersion = classfile.readUnsignedShort();
660 int majorVersion = classfile.readUnsignedShort();
661
662 return new Version(majorVersion, minorVersion);
663 }
664
665 /**
666 * This method extracts the class file version from each class file in the
667 * provided jar.
668 *
669 * @return the result maps from the class file to its version.
670 */
671 public static HashMap<String, Version> getClassFileVersions(File jarFile)
672 throws IOException {
673
674 HashMap<String, Version> result = new HashMap<String, Version>();
675
676 JarFile jar = new JarFile(jarFile);
677 Enumeration<JarEntry> entries = jar.entries();
678
679 while (entries.hasMoreElements()) {
680 JarEntry entry = entries.nextElement();
681 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
682 InputStream entryStream = jar.getInputStream(entry);
683 Version version = obtainClassFileVersion(entryStream);
684 result.put(entry.getName(), version);
685 entryStream.close();
686 }
687 }
688
689 jar.close();
690
691 return result;
692 }
693
694 /**
695 * Creates a list that contains only the types that are instances of a
696 * specified type from the objects of an input list. The input list is not
697 * modified.
698 *
699 * @param objects
700 * List of objects that gets filtered
701 *
702 * @param type
703 * target type whose instances are returned
704 */
705 @SuppressWarnings("unchecked")
706 public static <T> List<T> listInstances(List<?> objects, Class<T> type) {
707 List<T> filtered = new ArrayList<T>();
708
709 for (Object object : objects) {
710 if (type.isInstance(object)) {
711 filtered.add((T) object);
712 }
713 }
714
715 return filtered;
716 }
717 }