1 /* 2 * Copyright 2002-2008 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.core; 18 19 import java.lang.reflect.Array; 20 import java.lang.reflect.Field; 21 import java.lang.reflect.GenericArrayType; 22 import java.lang.reflect.Method; 23 import java.lang.reflect.ParameterizedType; 24 import java.lang.reflect.Type; 25 import java.lang.reflect.TypeVariable; 26 import java.lang.reflect.WildcardType; 27 import java.util.Collection; 28 import java.util.Map; 29 30 /** 31 * Helper class for determining element types of collections and maps. 32 * 33 * <p>Mainly intended for usage within the framework, determining the 34 * target type of values to be added to a collection or map 35 * (to be able to attempt type conversion if appropriate). 36 * 37 * <p>Only usable on Java 5. Use an appropriate JdkVersion check before 38 * calling this class, if a fallback for JDK 1.4 is desirable. 39 * 40 * @author Juergen Hoeller 41 * @since 2.0 42 * @see org.springframework.beans.BeanWrapperImpl 43 * @see JdkVersion 44 */ 45 public abstract class GenericCollectionTypeResolver { 46 47 /** 48 * Determine the generic element type of the given Collection class 49 * (if it declares one through a generic superclass or generic interface). 50 * @param collectionClass the collection class to introspect 51 * @return the generic type, or <code>null</code> if none 52 */ 53 public static Class getCollectionType(Class collectionClass) { 54 return extractTypeFromClass(collectionClass, Collection.class, 0); 55 } 56 57 /** 58 * Determine the generic key type of the given Map class 59 * (if it declares one through a generic superclass or generic interface). 60 * @param mapClass the map class to introspect 61 * @return the generic type, or <code>null</code> if none 62 */ 63 public static Class getMapKeyType(Class mapClass) { 64 return extractTypeFromClass(mapClass, Map.class, 0); 65 } 66 67 /** 68 * Determine the generic value type of the given Map class 69 * (if it declares one through a generic superclass or generic interface). 70 * @param mapClass the map class to introspect 71 * @return the generic type, or <code>null</code> if none 72 */ 73 public static Class getMapValueType(Class mapClass) { 74 return extractTypeFromClass(mapClass, Map.class, 1); 75 } 76 77 /** 78 * Determine the generic element type of the given Collection field. 79 * @param collectionField the collection field to introspect 80 * @return the generic type, or <code>null</code> if none 81 */ 82 public static Class getCollectionFieldType(Field collectionField) { 83 return getGenericFieldType(collectionField, Collection.class, 0, 1); 84 } 85 86 /** 87 * Determine the generic element type of the given Collection field. 88 * @param collectionField the collection field to introspect 89 * @param nestingLevel the nesting level of the target type 90 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 91 * nested List, whereas 2 would indicate the element of the nested List) 92 * @return the generic type, or <code>null</code> if none 93 */ 94 public static Class getCollectionFieldType(Field collectionField, int nestingLevel) { 95 return getGenericFieldType(collectionField, Collection.class, 0, nestingLevel); 96 } 97 98 /** 99 * Determine the generic key type of the given Map field. 100 * @param mapField the map field to introspect 101 * @return the generic type, or <code>null</code> if none 102 */ 103 public static Class getMapKeyFieldType(Field mapField) { 104 return getGenericFieldType(mapField, Map.class, 0, 1); 105 } 106 107 /** 108 * Determine the generic key type of the given Map field. 109 * @param mapField the map field to introspect 110 * @param nestingLevel the nesting level of the target type 111 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 112 * nested List, whereas 2 would indicate the element of the nested List) 113 * @return the generic type, or <code>null</code> if none 114 */ 115 public static Class getMapKeyFieldType(Field mapField, int nestingLevel) { 116 return getGenericFieldType(mapField, Map.class, 0, nestingLevel); 117 } 118 119 /** 120 * Determine the generic value type of the given Map field. 121 * @param mapField the map field to introspect 122 * @return the generic type, or <code>null</code> if none 123 */ 124 public static Class getMapValueFieldType(Field mapField) { 125 return getGenericFieldType(mapField, Map.class, 1, 1); 126 } 127 128 /** 129 * Determine the generic value type of the given Map field. 130 * @param mapField the map field to introspect 131 * @param nestingLevel the nesting level of the target type 132 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 133 * nested List, whereas 2 would indicate the element of the nested List) 134 * @return the generic type, or <code>null</code> if none 135 */ 136 public static Class getMapValueFieldType(Field mapField, int nestingLevel) { 137 return getGenericFieldType(mapField, Map.class, 1, nestingLevel); 138 } 139 140 /** 141 * Determine the generic element type of the given Collection parameter. 142 * @param methodParam the method parameter specification 143 * @return the generic type, or <code>null</code> if none 144 */ 145 public static Class getCollectionParameterType(MethodParameter methodParam) { 146 return getGenericParameterType(methodParam, Collection.class, 0); 147 } 148 149 /** 150 * Determine the generic key type of the given Map parameter. 151 * @param methodParam the method parameter specification 152 * @return the generic type, or <code>null</code> if none 153 */ 154 public static Class getMapKeyParameterType(MethodParameter methodParam) { 155 return getGenericParameterType(methodParam, Map.class, 0); 156 } 157 158 /** 159 * Determine the generic value type of the given Map parameter. 160 * @param methodParam the method parameter specification 161 * @return the generic type, or <code>null</code> if none 162 */ 163 public static Class getMapValueParameterType(MethodParameter methodParam) { 164 return getGenericParameterType(methodParam, Map.class, 1); 165 } 166 167 /** 168 * Determine the generic element type of the given Collection return type. 169 * @param method the method to check the return type for 170 * @return the generic type, or <code>null</code> if none 171 */ 172 public static Class getCollectionReturnType(Method method) { 173 return getGenericReturnType(method, Collection.class, 0, 1); 174 } 175 176 /** 177 * Determine the generic element type of the given Collection return type. 178 * <p>If the specified nesting level is higher than 1, the element type of 179 * a nested Collection/Map will be analyzed. 180 * @param method the method to check the return type for 181 * @param nestingLevel the nesting level of the target type 182 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 183 * nested List, whereas 2 would indicate the element of the nested List) 184 * @return the generic type, or <code>null</code> if none 185 */ 186 public static Class getCollectionReturnType(Method method, int nestingLevel) { 187 return getGenericReturnType(method, Collection.class, 0, nestingLevel); 188 } 189 190 /** 191 * Determine the generic key type of the given Map return type. 192 * @param method the method to check the return type for 193 * @return the generic type, or <code>null</code> if none 194 */ 195 public static Class getMapKeyReturnType(Method method) { 196 return getGenericReturnType(method, Map.class, 0, 1); 197 } 198 199 /** 200 * Determine the generic key type of the given Map return type. 201 * @param method the method to check the return type for 202 * @param nestingLevel the nesting level of the target type 203 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 204 * nested List, whereas 2 would indicate the element of the nested List) 205 * @return the generic type, or <code>null</code> if none 206 */ 207 public static Class getMapKeyReturnType(Method method, int nestingLevel) { 208 return getGenericReturnType(method, Map.class, 0, nestingLevel); 209 } 210 211 /** 212 * Determine the generic value type of the given Map return type. 213 * @param method the method to check the return type for 214 * @return the generic type, or <code>null</code> if none 215 */ 216 public static Class getMapValueReturnType(Method method) { 217 return getGenericReturnType(method, Map.class, 1, 1); 218 } 219 220 /** 221 * Determine the generic value type of the given Map return type. 222 * @param method the method to check the return type for 223 * @param nestingLevel the nesting level of the target type 224 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the 225 * nested List, whereas 2 would indicate the element of the nested List) 226 * @return the generic type, or <code>null</code> if none 227 */ 228 public static Class getMapValueReturnType(Method method, int nestingLevel) { 229 return getGenericReturnType(method, Map.class, 1, nestingLevel); 230 } 231 232 233 /** 234 * Extract the generic parameter type from the given method or constructor. 235 * @param methodParam the method parameter specification 236 * @param source the source class/interface defining the generic parameter types 237 * @param typeIndex the index of the type (e.g. 0 for Collections, 238 * 0 for Map keys, 1 for Map values) 239 * @return the generic type, or <code>null</code> if none 240 */ 241 private static Class getGenericParameterType(MethodParameter methodParam, Class source, int typeIndex) { 242 return extractType(methodParam, GenericTypeResolver.getTargetType(methodParam), 243 source, typeIndex, methodParam.getNestingLevel(), 1); 244 } 245 246 /** 247 * Extract the generic type from the given field. 248 * @param field the field to check the type for 249 * @param source the source class/interface defining the generic parameter types 250 * @param typeIndex the index of the type (e.g. 0 for Collections, 251 * 0 for Map keys, 1 for Map values) 252 * @param nestingLevel the nesting level of the target type 253 * @return the generic type, or <code>null</code> if none 254 */ 255 private static Class getGenericFieldType(Field field, Class source, int typeIndex, int nestingLevel) { 256 return extractType(null, field.getGenericType(), source, typeIndex, nestingLevel, 1); 257 } 258 259 /** 260 * Extract the generic return type from the given method. 261 * @param method the method to check the return type for 262 * @param source the source class/interface defining the generic parameter types 263 * @param typeIndex the index of the type (e.g. 0 for Collections, 264 * 0 for Map keys, 1 for Map values) 265 * @param nestingLevel the nesting level of the target type 266 * @return the generic type, or <code>null</code> if none 267 */ 268 private static Class getGenericReturnType(Method method, Class source, int typeIndex, int nestingLevel) { 269 return extractType(null, method.getGenericReturnType(), source, typeIndex, nestingLevel, 1); 270 } 271 272 /** 273 * Extract the generic type from the given Type object. 274 * @param methodParam the method parameter specification 275 * @param type the Type to check 276 * @param source the source collection/map Class that we check 277 * @param typeIndex the index of the actual type argument 278 * @param nestingLevel the nesting level of the target type 279 * @param currentLevel the current nested level 280 * @return the generic type as Class, or <code>null</code> if none 281 */ 282 private static Class extractType( 283 MethodParameter methodParam, Type type, Class source, int typeIndex, int nestingLevel, int currentLevel) { 284 285 Type resolvedType = type; 286 if (type instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) { 287 Type mappedType = (Type) methodParam.typeVariableMap.get(type); 288 if (mappedType != null) { 289 resolvedType = mappedType; 290 } 291 } 292 if (resolvedType instanceof ParameterizedType) { 293 return extractTypeFromParameterizedType( 294 methodParam, (ParameterizedType) resolvedType, source, typeIndex, nestingLevel, currentLevel); 295 } 296 else if (resolvedType instanceof Class) { 297 return extractTypeFromClass(methodParam, (Class) resolvedType, source, typeIndex, nestingLevel, currentLevel); 298 } 299 else { 300 return null; 301 } 302 } 303 304 /** 305 * Extract the generic type from the given ParameterizedType object. 306 * @param methodParam the method parameter specification 307 * @param ptype the ParameterizedType to check 308 * @param source the expected raw source type (can be <code>null</code>) 309 * @param typeIndex the index of the actual type argument 310 * @param nestingLevel the nesting level of the target type 311 * @param currentLevel the current nested level 312 * @return the generic type as Class, or <code>null</code> if none 313 */ 314 private static Class extractTypeFromParameterizedType(MethodParameter methodParam, 315 ParameterizedType ptype, Class source, int typeIndex, int nestingLevel, int currentLevel) { 316 317 if (!(ptype.getRawType() instanceof Class)) { 318 return null; 319 } 320 Class rawType = (Class) ptype.getRawType(); 321 Type[] paramTypes = ptype.getActualTypeArguments(); 322 if (nestingLevel - currentLevel > 0) { 323 int nextLevel = currentLevel + 1; 324 Integer currentTypeIndex = (methodParam != null ? methodParam.getTypeIndexForLevel(nextLevel) : null); 325 // Default is last parameter type: Collection element or Map value. 326 int indexToUse = (currentTypeIndex != null ? currentTypeIndex.intValue() : paramTypes.length - 1); 327 Type paramType = paramTypes[indexToUse]; 328 return extractType(methodParam, paramType, source, typeIndex, nestingLevel, nextLevel); 329 } 330 if (source != null && !source.isAssignableFrom(rawType)) { 331 return null; 332 } 333 Class fromSuperclassOrInterface = 334 extractTypeFromClass(methodParam, rawType, source, typeIndex, nestingLevel, currentLevel); 335 if (fromSuperclassOrInterface != null) { 336 return fromSuperclassOrInterface; 337 } 338 if (paramTypes == null || typeIndex >= paramTypes.length) { 339 return null; 340 } 341 Type paramType = paramTypes[typeIndex]; 342 if (paramType instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) { 343 Type mappedType = (Type) methodParam.typeVariableMap.get(paramType); 344 if (mappedType != null) { 345 paramType = mappedType; 346 } 347 } 348 if (paramType instanceof WildcardType) { 349 Type[] lowerBounds = ((WildcardType) paramType).getLowerBounds(); 350 if (lowerBounds != null && lowerBounds.length > 0) { 351 paramType = lowerBounds[0]; 352 } 353 } 354 if (paramType instanceof ParameterizedType) { 355 paramType = ((ParameterizedType) paramType).getRawType(); 356 } 357 if (paramType instanceof GenericArrayType) { 358 // A generic array type... Let's turn it into a straight array type if possible. 359 Type compType = ((GenericArrayType) paramType).getGenericComponentType(); 360 if (compType instanceof Class) { 361 return Array.newInstance((Class) compType, 0).getClass(); 362 } 363 } 364 else if (paramType instanceof Class) { 365 // We finally got a straight Class... 366 return (Class) paramType; 367 } 368 return null; 369 } 370 371 /** 372 * Extract the generic type from the given Class object. 373 * @param clazz the Class to check 374 * @param source the expected raw source type (can be <code>null</code>) 375 * @param typeIndex the index of the actual type argument 376 * @return the generic type as Class, or <code>null</code> if none 377 */ 378 private static Class extractTypeFromClass(Class clazz, Class source, int typeIndex) { 379 return extractTypeFromClass(null, clazz, source, typeIndex, 1, 1); 380 } 381 382 /** 383 * Extract the generic type from the given Class object. 384 * @param methodParam the method parameter specification 385 * @param clazz the Class to check 386 * @param source the expected raw source type (can be <code>null</code>) 387 * @param typeIndex the index of the actual type argument 388 * @param nestingLevel the nesting level of the target type 389 * @param currentLevel the current nested level 390 * @return the generic type as Class, or <code>null</code> if none 391 */ 392 private static Class extractTypeFromClass( 393 MethodParameter methodParam, Class clazz, Class source, int typeIndex, int nestingLevel, int currentLevel) { 394 395 if (clazz.getSuperclass() != null && isIntrospectionCandidate(clazz.getSuperclass())) { 396 return extractType(methodParam, clazz.getGenericSuperclass(), source, typeIndex, nestingLevel, currentLevel); 397 } 398 Type[] ifcs = clazz.getGenericInterfaces(); 399 if (ifcs != null) { 400 for (int i = 0; i < ifcs.length; i++) { 401 Type ifc = ifcs[i]; 402 Type rawType = ifc; 403 if (ifc instanceof ParameterizedType) { 404 rawType = ((ParameterizedType) ifc).getRawType(); 405 } 406 if (rawType instanceof Class && isIntrospectionCandidate((Class) rawType)) { 407 return extractType(methodParam, ifc, source, typeIndex, nestingLevel, currentLevel); 408 } 409 } 410 } 411 return null; 412 } 413 414 /** 415 * Determine whether the given class is a potential candidate 416 * that defines generic collection or map types. 417 * @param clazz the class to check 418 * @return whethe the given class is assignable to Collection or Map 419 */ 420 private static boolean isIntrospectionCandidate(Class clazz) { 421 return (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)); 422 } 423 424 }