001    /*--------------------------------------------------------------------------+
002    $Id: GenericTypeResolver.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.lang.reflect.Method;
021    import java.lang.reflect.ParameterizedType;
022    import java.lang.reflect.Type;
023    import java.lang.reflect.TypeVariable;
024    import java.util.HashMap;
025    import java.util.Map;
026    
027    /**
028     * This is a class for helping with the resolution of generic types in the
029     * context of reflection. Unfortunately there is no easy way to get the actual
030     * return type or parameter type of a method if some generic class is within the
031     * class hierarchy. This class handles the messy aspects of this.
032     * <p>
033     * The instances of this class are bound to single classes, for which they are
034     * constructed. They only work correctly when querying parameters originating
035     * from this class or one of its methods. Furthermore this class does not work,
036     * if the class the lookup is performed for is generic itself.
037     * <p>
038     * The error handling of this class is rather crude. If any of the assumptions
039     * (either specified above or we learned from playing with reflection) is not
040     * met, an exception is thrown (currently {@link IllegalStateException}).
041     * 
042     * @author Benjamin Hummel
043     * @author $Author: juergens $
044     * @version $Rev: 26268 $
045     * @levd.rating GREEN Hash: 4364A48643065AF4C13524C2A465E19B
046     */
047    public class GenericTypeResolver {
048    
049            /** The map for looking up generic parameters. */
050            private final Map<TypeVariable<?>, Class<?>> parameterLookup = new HashMap<TypeVariable<?>, Class<?>>();
051    
052            /**
053             * Creates a new generic type resolver for the given class.
054             * 
055             * @throws IllegalArgumentException
056             *             if called for generic class.
057             */
058            public GenericTypeResolver(Class<?> clazz) {
059                    if (clazz.getTypeParameters().length != 0) {
060                            throw new IllegalArgumentException(
061                                            "This only works for non-generic classes!");
062                    }
063                    fillParamMap(clazz);
064            }
065    
066            /**
067             * Initializes the generic parameter lookup table by comparing for each
068             * super class and interface the type parameters with the actual type
069             * arguments. This process then is repeated recursively. It is important to
070             * fill from the current class before going to the super class, as the super
071             * class may reference parameters used in this class.
072             */
073            private void fillParamMap(Class<?> clazz) {
074                    Class<?> superClass = clazz.getSuperclass();
075                    if (superClass != null) {
076                            Type superType = clazz.getGenericSuperclass();
077                            fillInParameters(superClass, superType);
078                            fillParamMap(superClass);
079                    }
080    
081                    Class<?>[] interfaces = clazz.getInterfaces();
082                    Type[] genericInterfaces = clazz.getGenericInterfaces();
083                    check(interfaces.length == genericInterfaces.length,
084                                    "Interface lists should be equally long!");
085                    for (int i = 0; i < interfaces.length; ++i) {
086                            fillInParameters(interfaces[i], genericInterfaces[i]);
087                            fillParamMap(interfaces[i]);
088                    }
089            }
090    
091            /**
092             * Fill the generic parameter lookup map from an explicit (class, type) pair
093             * by comparing the type parameters with the actual type parameters (if the
094             * class is generic at all).
095             * 
096             * @param clazz
097             *            the class (potentially) containing type parameters.
098             * @param type
099             *            the corresponding (potentially) generic type.
100             */
101            private void fillInParameters(Class<?> clazz, Type type) {
102                    if (type instanceof ParameterizedType) {
103                            Type[] actualTypeArguments = ((ParameterizedType) type)
104                                            .getActualTypeArguments();
105                            TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
106                            check(actualTypeArguments.length == typeParameters.length,
107                                            "Type parameters and actual arguments should be equally long!");
108    
109                            for (int i = 0; i < typeParameters.length; ++i) {
110                                    parameterLookup.put(typeParameters[i],
111                                                    resolveGenericType(actualTypeArguments[i]));
112                            }
113                    }
114            }
115    
116            /**
117             * Returns the actual type from a (potentially) generic type. If the
118             * argument is a plain class, it is returned, otherwise a lookup in the
119             * internal generic parameter map is performed. For parameterized types
120             * (e.g. <code>List&lt;String&gt;</code> the raw type (here: List) is
121             * returned.
122             * <p>
123             * Note that this only works for return values and parameters of methods
124             * belonging to the class for which this instance was constructed for.
125             * Otherwise the behaviour is underfined (either returning nonsense or
126             * throwing an exception).
127             * 
128             * @param genericType
129             *            a type such as returned from
130             *            {@link Method#getGenericReturnType()} or
131             *            {@link Method#getGenericParameterTypes()}.
132             */
133            public Class<?> resolveGenericType(Type genericType) {
134                    if (genericType instanceof Class<?>) {
135                            return (Class<?>) genericType;
136                    }
137                    if (genericType instanceof TypeVariable<?>) {
138                            check(parameterLookup.containsKey(genericType),
139                                            "All generic parameters should be bound.");
140                            return parameterLookup.get(genericType);
141                    }
142                    if (genericType instanceof ParameterizedType) {
143                            ParameterizedType pt = (ParameterizedType) genericType;
144                            return (Class<?>) pt.getRawType();
145                    }
146    
147                    check(
148                                    false,
149                                    "Generic types should be either concrete classes, type variables, or parametrized types: "
150                                                    + genericType.getClass());
151                    return null; // this line is never reached
152            }
153    
154            /**
155             * This is an assertion method to simplify changing the type of error
156             * handling. Many of the assertions rather document our assumptions about
157             * the JVM, than really checking errors.
158             */
159            private void check(boolean assumedCondition, String errorMessage) {
160                    if (!assumedCondition) {
161                            throw new IllegalStateException(errorMessage);
162                    }
163            }
164    }