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<String></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 }