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 }