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    }