/** * Copyright (C) 2015 Valkyrie RCP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.valkyriercp.binding.beans; import org.springframework.core.MethodParameter; import java.lang.reflect.*; import java.util.Collection; import java.util.Map; /** * Helper class for determining element types of collections and maps. * * <p>Mainly intended for usage within the framework, determining the * target type of values to be added to a collection or map * (to be able to attempt type conversion if appropriate). * * <p>Only usable on Java 5. Use an appropriate JdkVersion check before * calling this class, if a fallback for JDK 1.3/1.4 is desirable. * * This implementation fixes some bugs of its superclass. * * @author Arne Limburg */ public abstract class GenericCollectionTypeResolver extends org.springframework.core.GenericCollectionTypeResolver { /** * Determine the generic element type of the given Collection class * (if it declares one through a generic superclass or generic interface). * @param collectionClass the collection class to introspect * @return the generic type, or <code>null</code> if none */ public static Class getCollectionType(Class collectionClass) { return toClass(extractType(collectionClass, Collection.class, 0)); } /** * Determine the generic key type of the given Map class * (if it declares one through a generic superclass or generic interface). * @param mapClass the map class to introspect * @return the generic type, or <code>null</code> if none */ public static Class getMapKeyType(Class mapClass) { return toClass(extractType(mapClass, Map.class, 0)); } /** * Determine the generic value type of the given Map class * (if it declares one through a generic superclass or generic interface). * @param mapClass the map class to introspect * @return the generic type, or <code>null</code> if none */ public static Class getMapValueType(Class mapClass) { return toClass(extractType(mapClass, Map.class, 1)); } /** * Determine the generic element type of the given Collection field. * @param collectionField the collection field to introspect * @return the generic type, or <code>null</code> if none */ public static Class getCollectionFieldType(Field collectionField) { return getGenericFieldType(collectionField, 0, 0); } /** * Determine the generic element type of the given Collection field. * @param collectionField the collection field to introspect * @param nestingLevel the nesting level of the target type * (typically 1; e.g. in case of a List of Lists, 1 would indicate the * nested List, whereas 2 would indicate the element of the nested List) * @return the generic type, or <code>null</code> if none */ public static Class getCollectionFieldType(Field collectionField, int nestingLevel) { return getGenericFieldType(collectionField, 0, nestingLevel); } /** * Determine the generic key type of the given Map field. * @param mapField the map field to introspect * @return the generic type, or <code>null</code> if none */ public static Class getMapKeyFieldType(Field mapField) { return getGenericFieldType(mapField, 0, 0); } /** * Determine the generic key type of the given Map field. * @param mapField the map field to introspect * @param nestingLevel the nesting level of the target type * (typically 1; e.g. in case of a List of Lists, 1 would indicate the * nested List, whereas 2 would indicate the element of the nested List) * @return the generic type, or <code>null</code> if none */ public static Class getMapKeyFieldType(Field mapField, int nestingLevel) { return getGenericFieldType(mapField, 0, nestingLevel); } /** * Determine the generic value type of the given Map field. * @param mapField the map field to introspect * @return the generic type, or <code>null</code> if none */ public static Class getMapValueFieldType(Field mapField) { return getGenericFieldType(mapField, 1, 0); } /** * Determine the generic value type of the given Map field. * @param mapField the map field to introspect * @param nestingLevel the nesting level of the target type * (typically 1; e.g. in case of a List of Lists, 1 would indicate the * nested List, whereas 2 would indicate the element of the nested List) * @return the generic type, or <code>null</code> if none */ public static Class getMapValueFieldType(Field mapField, int nestingLevel) { return getGenericFieldType(mapField, 1, nestingLevel); } /** * Determine the generic element type of the given Collection parameter. * @param methodParam the method parameter specification * @return the generic type, or <code>null</code> if none */ public static Class getCollectionParameterType(MethodParameter methodParam) { return toClass(getGenericParameterType(methodParam, 0)); } /** * Determine the generic key type of the given Map parameter. * @param methodParam the method parameter specification * @return the generic type, or <code>null</code> if none */ public static Class getMapKeyParameterType(MethodParameter methodParam) { return toClass(getGenericParameterType(methodParam, 0)); } /** * Determine the generic value type of the given Map parameter. * @param methodParam the method parameter specification * @return the generic type, or <code>null</code> if none */ public static Class getMapValueParameterType(MethodParameter methodParam) { return toClass(getGenericParameterType(methodParam, 1)); } /** * Determine the generic element type of the given Collection return type. * @param method the method to check the return type for * @return the generic type, or <code>null</code> if none */ public static Class getCollectionReturnType(Method method) { return toClass(getGenericReturnType(method, 0, 1)); } /** * Determine the generic element type of the given Collection return type. * <p>If the specified nesting level is higher than 1, the element type of * a nested Collection/Map will be analyzed. * @param method the method to check the return type for * @param nestingLevel the nesting level of the target type * (typically 1; e.g. in case of a List of Lists, 1 would indicate the * nested List, whereas 2 would indicate the element of the nested List) * @return the generic type, or <code>null</code> if none */ public static Class getCollectionReturnType(Method method, int nestingLevel) { return toClass(getGenericReturnType(method, 0, nestingLevel)); } /** * Determine the generic key type of the given Map return type. * @param method the method to check the return type for * @return the generic type, or <code>null</code> if none */ public static Class getMapKeyReturnType(Method method) { return toClass(getGenericReturnType(method, 0, 1)); } /** * Determine the generic key type of the given Map return type. * @param method the method to check the return type for * @param nestingLevel the nesting level of the target type * (typically 1; e.g. in case of a List of Lists, 1 would indicate the * nested List, whereas 2 would indicate the element of the nested List) * @return the generic type, or <code>null</code> if none */ public static Class getMapKeyReturnType(Method method, int nestingLevel) { return toClass(getGenericReturnType(method, 0, nestingLevel)); } /** * Determine the generic value type of the given Map return type. * @param method the method to check the return type for * @return the generic type, or <code>null</code> if none */ public static Class getMapValueReturnType(Method method) { return toClass(getGenericReturnType(method, 1, 1)); } /** * Determine the generic value type of the given Map return type. * @param method the method to check the return type for * @param nestingLevel the nesting level of the target type * (typically 1; e.g. in case of a List of Lists, 1 would indicate the * nested List, whereas 2 would indicate the element of the nested List) * @return the generic type, or <code>null</code> if none */ public static Class getMapValueReturnType(Method method, int nestingLevel) { return toClass(getGenericReturnType(method, 1, nestingLevel)); } public static Class getIndexedValueFieldType(Field field) { return getIndexedValueFieldType(field, 1); } public static Class getIndexedValueFieldType(Field field, int nestingLevel) { return getGenericIndexedValueType(field.getGenericType(), nestingLevel); } public static Class getIndexedValueMethodType(MethodParameter methodParameter) { return getIndexedValueMethodType(methodParameter, methodParameter.getNestingLevel()); } public static Class getIndexedValueMethodType(MethodParameter methodParameter, int nestingLevel) { Type type = null; Method method = methodParameter.getMethod(); if (method != null) { if (methodParameter.getParameterIndex() >= 0) { type = method.getGenericParameterTypes()[methodParameter.getParameterIndex()]; } else { type = method.getGenericReturnType(); } } else { type = methodParameter.getConstructor().getGenericParameterTypes()[methodParameter.getParameterIndex()]; } return getGenericIndexedValueType(type, nestingLevel); } /** * Extract the generic parameter type from the given method or constructor. * @param methodParam the method parameter specification * @param typeIndex the index of the type (e.g. 0 for Collections, * 0 for Map keys, 1 for Map values) * @return the generic type, or <code>null</code> if none */ private static Class getGenericParameterType(MethodParameter methodParam, int typeIndex) { return toClass(extractType(methodParam, getTargetType(methodParam), typeIndex, methodParam.getNestingLevel())); } /** * Determine the target type for the given parameter specification. * @param methodParam the method parameter specification * @return the corresponding generic parameter or return type */ private static Type getTargetType(MethodParameter methodParam) { if (methodParam.getConstructor() != null) { return methodParam.getConstructor().getGenericParameterTypes()[methodParam.getParameterIndex()]; } else { if (methodParam.getParameterIndex() >= 0) { return methodParam.getMethod().getGenericParameterTypes()[methodParam.getParameterIndex()]; } else { return methodParam.getMethod().getGenericReturnType(); } } } /** * Returns the source class for the specified type. * This is either <code>Collection.class</code> or <code>Map.class</code>. * If none of both matches the baseclass of the specified type is returned. * @param type the type to determine the source class for * @return the source class */ private static Class getSourceClass(Type type) { Class sourceClass = toClass(type); if (Collection.class.isAssignableFrom(sourceClass)) { return Collection.class; } else if (Map.class.isAssignableFrom(sourceClass)) { return Map.class; } else { return sourceClass; } } /** * Extract the generic type from the given field. * @param field the field to check the type for * @param typeIndex the index of the type (e.g. 0 for Collections, * 0 for Map keys, 1 for Map values) * @param nestingLevel the nesting level of the target type * @return the generic type, or <code>null</code> if none */ private static Class getGenericFieldType(Field field, int typeIndex, int nestingLevel) { return toClass(extractType(null, field.getGenericType(), typeIndex, nestingLevel)); } /** * Extract the generic return type from the given method. * @param method the method to check the return type for * @param typeIndex the index of the type (e.g. 0 for Collections, * 0 for Map keys, 1 for Map values) * @param nestingLevel the nesting level of the target type * @return the generic type, or <code>null</code> if none */ private static Class getGenericReturnType(Method method, int typeIndex, int nestingLevel) { return toClass(extractType(null, method.getGenericReturnType(), typeIndex, nestingLevel)); } private static Class getGenericIndexedValueType(Type type, int nestingLevel) { Class actualType = toClass(type); for (int i = 0; i < nestingLevel; i++) { if (actualType.isArray()) { if (type instanceof GenericArrayType) { type = ((GenericArrayType)type).getGenericComponentType(); } else { type = actualType.getComponentType(); } } else if (Collection.class.isAssignableFrom(actualType)) { type = extractType(type, Collection.class, 0); } else if (Map.class.isAssignableFrom(actualType)) { type = extractType(type, Map.class, 1); } actualType = toClass(type); } return actualType; } private static Type extractType(MethodParameter methodParameter, Type type, int typeIndex, int nestingLevel) { for (int i = 0; i <= nestingLevel; i++) { Integer currentTypeIndex = (methodParameter != null? methodParameter.getTypeIndexForLevel(i): null); int indexToUse = (currentTypeIndex != null? currentTypeIndex.intValue(): i == nestingLevel? typeIndex: getValueTypeIndex(type)); type = extractType(type, getSourceClass(type), indexToUse); } return type; } private static int getValueTypeIndex(Type type) { return getSourceClass(type) == Map.class? 1: 0; } private static Type extractType(Type type, Class source, int typeIndex) { if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType)type; Type actualType = getActualType(parameterizedType, source, typeIndex); if (actualType != null) { return actualType; } type = parameterizedType.getRawType(); } if (type instanceof GenericArrayType) { if (typeIndex == 0) { return ((GenericArrayType)type).getGenericComponentType(); } else { return null; } } if (type instanceof Class) { Class classType = (Class)type; if (classType.isArray()) { return classType.getComponentType(); } Type returnType = extractType(classType.getGenericSuperclass(), source, typeIndex); if (returnType != null) { return returnType; } Type[] genericInterfaces = classType.getGenericInterfaces(); for (int i = 0; i < genericInterfaces.length; i++) { returnType = extractType(genericInterfaces[i], source, typeIndex); if (returnType != null) { return returnType; } } } return null; } public static Type getActualType(ParameterizedType type, Class superType, int typeIndex) { if (superType.equals(type.getRawType())) { return type.getActualTypeArguments()[typeIndex]; } if (!(type.getRawType() instanceof Class)) { return null; } Class rawType = (Class)type.getRawType(); Type genericSuperclass = rawType.getGenericSuperclass(); if (genericSuperclass instanceof ParameterizedType) { Type actualType = getActualType((ParameterizedType)genericSuperclass, superType, typeIndex); if (actualType instanceof TypeVariable) { actualType = getActualType((TypeVariable)actualType, rawType.getTypeParameters(), type.getActualTypeArguments()); } if (actualType != null) { return actualType; } } Type[] genericInterfaces = ((Class)type.getRawType()).getGenericInterfaces(); for (int i = 0; i < genericInterfaces.length; i++) { if (genericInterfaces[i] instanceof ParameterizedType) { Type actualType = getActualType((ParameterizedType)genericInterfaces[i], superType, typeIndex); if (actualType instanceof TypeVariable) { actualType = getActualType((TypeVariable)actualType, rawType.getTypeParameters(), type.getActualTypeArguments()); } if (actualType != null) { return actualType; } } } return null; } private static Type getActualType(TypeVariable typeVariable, TypeVariable[] typeVariables, Type[] actualTypeArguments) { if (typeVariable == null) { return null; } for (int i = 0; i < typeVariables.length; i++) { if (typeVariables[i].equals(typeVariable)) { return actualTypeArguments[i]; } } return null; } public static Class toClass(Type type) { if (type instanceof Class) { return (Class)type; } else if (type instanceof ParameterizedType) { return toClass(((ParameterizedType)type).getRawType()); } else if (type instanceof TypeVariable) { return toClass(((TypeVariable)type).getBounds()[0]); } else if (type instanceof WildcardType) { return toClass(((WildcardType)type).getUpperBounds()[0]); } else if (type instanceof GenericArrayType) { return Array.newInstance(toClass(((GenericArrayType)type).getGenericComponentType()), 0).getClass(); } else { return Object.class; } } }