/** * Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite * contributors * * This file is part of EvoSuite. * * EvoSuite is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3.0 of the License, or * (at your option) any later version. * * EvoSuite is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>. */ package org.evosuite.utils.generic; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang3.reflect.TypeUtils; import org.evosuite.utils.ParameterizedTypeImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class GenericUtils { public static boolean isAssignable(Type type, TypeVariable<?> typeVariable) { boolean isAssignable = true; for (Type boundType : typeVariable.getBounds()) { // Have to resolve the type because a typevariable may have a reference to itself // in its bounds Type resolvedBoundType = GenericUtils.replaceTypeVariable(boundType, typeVariable, type); if (!GenericClass.isAssignable(resolvedBoundType, type)) { isAssignable = false; break; } } return isAssignable; } private static final Logger logger = LoggerFactory.getLogger(GenericUtils.class); public static Type replaceTypeVariables(Type targetType, Map<TypeVariable<?>, Type> typeMap) { Type returnType = targetType; for(Entry<TypeVariable<?>, Type> entry : typeMap.entrySet()) { returnType = replaceTypeVariable(returnType, entry.getKey(), entry.getValue()); } // for (TypeVariable<?> var : typeMap.keySet()) { // //logger.debug("Current variable: "+var+" of type "+typeMap.get(var)+" in "+returnType); // returnType = replaceTypeVariable(returnType, var, typeMap.get(var)); // } return returnType; } public static Type replaceTypeVariablesWithWildcards(Type targetType) { if (targetType instanceof TypeVariable) { TypeVariable<?> typeVariable = (TypeVariable<?>) targetType; return new WildcardTypeImpl(typeVariable.getBounds(), new Type[] {}); } else if (targetType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) targetType; Type owner = null; if (parameterizedType.getOwnerType() != null) owner = replaceTypeVariablesWithWildcards(parameterizedType.getOwnerType()); Type[] currentParameters = parameterizedType.getActualTypeArguments(); Type[] parameters = new Type[currentParameters.length]; for (int i = 0; i < parameters.length; i++) { parameters[i] = replaceTypeVariablesWithWildcards(currentParameters[i]); } return new ParameterizedTypeImpl((Class<?>) parameterizedType.getRawType(), parameters, owner); } return targetType; } public static Type replaceTypeVariable(Type targetType, TypeVariable<?> variable, Type variableType) { if (targetType instanceof Class<?>) return targetType; else if (targetType instanceof GenericArrayType) { GenericArrayType gType = (GenericArrayType) targetType; Type componentType = replaceTypeVariable(gType.getGenericComponentType(), variable, variableType); return GenericArrayTypeImpl.createArrayType(componentType); } else if (targetType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) targetType; Type ownerType = null; if (pType.getOwnerType() != null) { ownerType = replaceTypeVariable(pType.getOwnerType(), variable, variableType); } Type[] originalParameterTypes = pType.getActualTypeArguments(); Type[] parameterTypes = new Type[originalParameterTypes.length]; for (int i = 0; i < originalParameterTypes.length; i++) { parameterTypes[i] = replaceTypeVariable(originalParameterTypes[i], variable, variableType); } /* if (variableType instanceof ParameterizedType) { ParameterizedType parameterizedVars = (ParameterizedType) variableType; Map<TypeVariable<?>, Type> subTypes = TypeUtils.getTypeArguments(parameterizedVars); for (Entry<TypeVariable<?>, Type> subTypeEntry : subTypes.entrySet()) { if (pType.getOwnerType() != null) { ownerType = replaceTypeVariable(pType.getOwnerType(), subTypeEntry.getKey(), subTypeEntry.getValue()); } for (int i = 0; i < originalParameterTypes.length; i++) { parameterTypes[i] = replaceTypeVariable(originalParameterTypes[i], subTypeEntry.getKey(), subTypeEntry.getValue()); } } } */ return new ParameterizedTypeImpl((Class<?>) pType.getRawType(), parameterTypes, ownerType); } else if (targetType instanceof WildcardType) { WildcardType wType = (WildcardType) targetType; Type[] originalUpperBounds = wType.getUpperBounds(); Type[] originalLowerBounds = wType.getLowerBounds(); Type[] upperBounds = new Type[originalUpperBounds.length]; Type[] lowerBounds = new Type[originalLowerBounds.length]; for (int i = 0; i < originalUpperBounds.length; i++) { upperBounds[i] = replaceTypeVariable(originalUpperBounds[i], variable, variableType); } for (int i = 0; i < originalLowerBounds.length; i++) { lowerBounds[i] = replaceTypeVariable(originalLowerBounds[i], variable, variableType); } return new WildcardTypeImpl(upperBounds, lowerBounds); } else if (targetType instanceof TypeVariable<?>) { if (targetType.equals(variable)) { //logger.debug("Do equal: " + variable + "/" + targetType); return variableType; } else { //logger.debug("Do not equal: " + variable + "/" + targetType); //logger.debug("Do not equal: " + variable.getGenericDeclaration() + "/" // + ((TypeVariable<?>) targetType).getGenericDeclaration()); return targetType; } } else { //logger.debug("Unknown type of class " + targetType.getClass() + ": " // + targetType); return targetType; } } public Map<TypeVariable<?>, Type> getMatchingTypeParameters(GenericArrayType p1, GenericArrayType p2) { if (p1.getGenericComponentType() instanceof ParameterizedType && p2.getGenericComponentType() instanceof ParameterizedType) { return getMatchingTypeParameters((ParameterizedType) p1.getGenericComponentType(), (ParameterizedType) p2.getGenericComponentType()); } else { Map<TypeVariable<?>, Type> map = new HashMap<TypeVariable<?>, Type>(); return map; } } /** * TODO: Try to match p2 superclasses? * * @param p1 Desired TypeVariable assignment * @param p2 Generic type with the TypeVariables that need assignment * @return */ public static Map<TypeVariable<?>, Type> getMatchingTypeParameters( ParameterizedType p1, ParameterizedType p2) { logger.debug("Matching generic types between "+p1+" and "+p2); Map<TypeVariable<?>, Type> map = new HashMap<TypeVariable<?>, Type>(); if (!p1.getRawType().equals(p2.getRawType())) { logger.debug("Raw types do not match!"); GenericClass ownerClass = new GenericClass(p2); if(GenericClass.isSubclass(p1.getRawType(), p2.getRawType())) { logger.debug(p1 +" is a super type of "+p2); Map<TypeVariable<?>, Type> commonsMap = TypeUtils.determineTypeArguments((Class<?>)p2.getRawType(), p1); logger.debug("Adding to map: "+commonsMap); // TODO: Now we would need to iterate over the type parameters, and update the map? //map.putAll(commonsMap); for(TypeVariable<?> t : map.keySet()) { logger.debug(t+": "+t.getGenericDeclaration()); } // For each type variable of the raw type, map the parameter type to that type Type[] p2TypesA = ((Class<?>)p2.getRawType()).getTypeParameters(); Type[] p2TypesB = p2.getActualTypeArguments(); for(int i = 0 ; i < p2TypesA.length; i++) { Type a = p2TypesA[i]; Type b = p2TypesB[i]; logger.debug("Should be mapping "+a+" and "+b); if(a instanceof TypeVariable<?>) { logger.debug(a+" is a type variable: "+((TypeVariable<?>)a).getGenericDeclaration()); if(b instanceof TypeVariable<?>) { logger.debug(b+" is a type variable: "+((TypeVariable<?>)b).getGenericDeclaration()); if(commonsMap.containsKey((TypeVariable<?>)a) && !(commonsMap.get((TypeVariable<?>)a) instanceof WildcardType) && !(commonsMap.get((TypeVariable<?>)a) instanceof TypeVariable<?>)) map.put((TypeVariable<?>)b, commonsMap.get((TypeVariable<?>)a)); //else // map.put((TypeVariable<?>)a, b); } } // if(b instanceof TypeVariable<?>) { // if(map.containsKey(a)) // map.put((TypeVariable<?>)b, map.get(a)); // //else // // map.put((TypeVariable<?>)b, a); // } logger.debug("Updated map: "+map); } } for(GenericClass interfaceClass : ownerClass.getInterfaces()) { if(interfaceClass.isParameterizedType()) map.putAll(getMatchingTypeParameters(p1, (ParameterizedType)interfaceClass.getType())); else logger.debug("Interface "+interfaceClass+" is not parameterized"); } if(ownerClass.getRawClass().getSuperclass() != null) { GenericClass ownerSuperClass = ownerClass.getSuperClass(); if(ownerSuperClass.isParameterizedType()) map.putAll(getMatchingTypeParameters(p1, (ParameterizedType)ownerSuperClass.getType())); else logger.debug("Super type "+ownerSuperClass+" is not parameterized"); } return map; } for (int i = 0; i < p1.getActualTypeArguments().length; i++) { Type t1 = p1.getActualTypeArguments()[i]; Type t2 = p2.getActualTypeArguments()[i]; if(t1 == t2) continue; logger.debug("First match: " + t1 + " - " + t2); if (t1 instanceof TypeVariable<?>) { map.put((TypeVariable<?>) t1, t2); } if (t2 instanceof TypeVariable<?>) { map.put((TypeVariable<?>) t2, t1); } else if (t2 instanceof ParameterizedType && t1 instanceof ParameterizedType) { map.putAll(getMatchingTypeParameters((ParameterizedType) t1, (ParameterizedType) t2)); } logger.debug("Updated map: "+map); } if (p1.getOwnerType() != null && p1.getOwnerType() instanceof ParameterizedType && p2.getOwnerType() instanceof ParameterizedType) { map.putAll(getMatchingTypeParameters((ParameterizedType) p1.getOwnerType(), (ParameterizedType) p2.getOwnerType())); } return map; } }