/**
* 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.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.evosuite.testcase.statements.*;
import org.evosuite.testcase.TestCase;
import org.evosuite.testcase.TestVisitor;
import org.evosuite.testcase.variable.VariableReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.googlecode.gentyref.GenericTypeReflector;
/**
* @author Gordon Fraser
*
*/
public class GenericTypeInference extends TestVisitor {
private static Logger logger = LoggerFactory.getLogger(GenericTypeInference.class);
private final Map<VariableReference, Set<Type>> variableMap = new LinkedHashMap<VariableReference, Set<Type>>();
private final Map<Type, Set<VariableReference>> typeMap = new LinkedHashMap<Type, Set<VariableReference>>();
private TestCase test;
public void inferTypes(TestCase test) {
this.test = test;
logger.debug("Inferring generic types");
// calculateExactTypes();
for (int i = test.size() - 1; i >= 0; i--) {
Statement statement = test.getStatement(i);
if (statement instanceof ConstructorStatement) {
determineExactType((ConstructorStatement) statement);
}
}
logger.debug("Resulting test: "+test.toCode());
}
private void addVariable(Statement statement) {
VariableReference retVal = statement.getReturnValue();
variableMap.put(retVal, new LinkedHashSet<Type>());
}
private void addTypeAssignment(Type type, VariableReference value) {
if (!typeMap.containsKey(type)) {
typeMap.put(type, new LinkedHashSet<VariableReference>());
}
typeMap.get(type).add(value);
variableMap.get(value).add(type);
}
protected void calculateExactTypes() {
// For each type, if it is a parameterized type determine most specific instantiation
logger.info("Types to consider: " + typeMap.size());
for (Type type : typeMap.keySet()) {
logger.info("Current type: " + type);
if (type instanceof ParameterizedType)
calculateExactType((ParameterizedType) type);
else if (type instanceof WildcardType)
calculateExactType((WildcardType) type);
else if (type instanceof TypeVariable<?>)
calculateExactType((TypeVariable<?>) type);
else if (type instanceof GenericArrayType)
calculateExactType((GenericArrayType) type);
}
}
private void calculateExactType(ParameterizedType type) {
logger.info("Calculating exact tyep for parameterized type " + type);
Class<?> rawClass = GenericTypeReflector.erase(type);
Type exactType = type;
for (VariableReference var : typeMap.get(type)) {
ParameterizedType currentType = (ParameterizedType) var.getType();
logger.info("Assigned variable of type: " + currentType);
Type candidateType = GenericTypeReflector.getExactSuperType(currentType,
rawClass);
logger.info("Resulting type: " + candidateType);
if (TypeUtils.isAssignable(candidateType, exactType)) {
exactType = candidateType;
}
}
logger.info("Result: " + exactType);
}
private void calculateExactType(WildcardType type) {
logger.info("Calculating exact tyep for wildcard type " + type);
}
private void calculateExactType(GenericArrayType type) {
logger.info("Calculating exact tyep for generic array type " + type);
}
private void calculateExactType(TypeVariable<?> type) {
logger.info("Calculating exact type for typevariable " + type);
Type[] bounds = TypeUtils.getImplicitBounds(type);
Type exactType = bounds[0];
for (VariableReference var : typeMap.get(type)) {
Type candidateType = var.getType();
logger.info("Candidate type: " + candidateType);
if (TypeUtils.isAssignable(candidateType, exactType)) {
exactType = candidateType;
}
}
logger.info("Result: " + exactType);
}
private void addToMap(TypeVariable<?> type, Type actualType,
Map<TypeVariable<?>, Type> typeMap) {
typeMap.put(type, actualType);
}
private void addToMap(ParameterizedType type, Type actualType,
Map<TypeVariable<?>, Type> typeMap) {
Type[] parameterTypes = type.getActualTypeArguments();
TypeVariable<?>[] variables = ((Class<?>) type.getRawType()).getTypeParameters();
for (int i = 0; i < parameterTypes.length; i++) {
typeMap.put(variables[i], parameterTypes[i]);
}
}
private void addToMap(Type type, Type actualType, Map<TypeVariable<?>, Type> typeMap) {
if (type instanceof ParameterizedType) {
addToMap((ParameterizedType) type, actualType, typeMap);
}
else if (type instanceof TypeVariable<?>) {
addToMap((TypeVariable<?>) type, actualType, typeMap);
} else if (type instanceof GenericArrayType) {
logger.info("Is generic array with component type "+((GenericArrayType)type).getGenericComponentType());
logger.info("Actual type "+actualType+", "+actualType.getClass());
if(actualType instanceof GenericArrayType) {
addToMap(((GenericArrayType)type).getGenericComponentType(), ((GenericArrayType)actualType).getGenericComponentType(), typeMap);
} else if(actualType instanceof Class<?> && ((Class<?>)actualType).isArray()) {
addToMap(((GenericArrayType)type).getGenericComponentType(), ((Class<?>)actualType).getComponentType(), typeMap);
}
} else {
logger.info("Is unexpected type: "+type+", "+type.getClass());
}
}
private Map<TypeVariable<?>, Type> getParameterType(Type parameterType, Type valueType) {
Map<TypeVariable<?>, Type> typeMap = new LinkedHashMap<TypeVariable<?>, Type>();
addToMap(parameterType, valueType, typeMap);
return typeMap;
}
private void determineVariableFromParameter(VariableReference parameter,
Type parameterType, Map<TypeVariable<?>, Type> typeMap) {
Map<TypeVariable<?>, Type> parameterTypeMap = getParameterType(parameterType,
parameter.getType());
logger.info("Resulting map: " + parameterTypeMap);
for (TypeVariable<?> typeVar : parameterTypeMap.keySet()) {
Type actualType = parameterTypeMap.get(typeVar);
if (typeMap.containsKey(typeVar)) {
logger.info("Variable is in map: " + typeVar);
Type currentType = typeMap.get(typeVar);
if (currentType == null
|| TypeUtils.isAssignable(actualType, currentType)) {
typeMap.put(typeVar, actualType);
} else {
logger.info("Not assignable: " + typeVar + " with bounds "
+ Arrays.asList(typeVar.getBounds()) + " and current type "
+ currentType + " from " + actualType);
logger.info(""
+ GenericTypeReflector.isSuperType(currentType, actualType));
logger.info("" + TypeUtils.isAssignable(actualType, typeVar));
}
} else {
logger.debug("Variable is not in map: " + typeVar);
typeMap.put(typeVar, actualType);
}
}
}
private void determineVariablesFromParameters(List<VariableReference> parameters,
Type[] parameterTypes, Map<TypeVariable<?>, Type> parameterTypeMap) {
for (int i = 0; i < parameterTypes.length; i++) {
logger.debug("Current parameter: " + parameterTypes[i]);
Type parameterType = parameterTypes[i];
VariableReference parameter = parameters.get(i);
determineVariableFromParameter(parameter, parameterType, parameterTypeMap);
}
}
private void determineExactType(ConstructorStatement constructorStatement) {
GenericConstructor constructor = constructorStatement.getConstructor();
logger.debug("Inferring types for: " + constructorStatement.getCode()
+ " at position " + constructorStatement.getPosition());
Map<TypeVariable<?>, Type> typeMap = constructor.getOwnerClass().getTypeVariableMap();
if (constructor.getOwnerClass().hasTypeVariables()) {
// if (!typeMap.isEmpty()) {
logger.info("Has types: " + constructor.getOwnerClass());
logger.info("Initial type map: " + typeMap);
for (TypeVariable<?> var : typeMap.keySet()) {
typeMap.put(var, null);
}
Type[] parameterTypes = constructor.getGenericParameterTypes(); //.getParameterTypes();
List<VariableReference> parameterValues = constructorStatement.getParameterReferences();
determineVariablesFromParameters(parameterValues, parameterTypes, typeMap);
for (int pos = constructorStatement.getPosition() + 1; pos < test.size(); pos++) {
if (test.getStatement(pos) instanceof MethodStatement) {
MethodStatement ms = (MethodStatement) test.getStatement(pos);
if (ms.isStatic())
continue;
if (!ms.getCallee().equals(constructorStatement.getReturnValue()))
continue;
logger.info("Found relevant statement: " + ms.getCode());
parameterTypes = ms.getMethod().getGenericParameterTypes();
parameterValues = ms.getParameterReferences();
determineVariablesFromParameters(parameterValues, parameterTypes,
typeMap);
}
}
logger.info("Setting types based on map: " + typeMap);
GenericClass owner = constructor.getOwnerClass();
List<TypeVariable<?>> variables = owner.getTypeVariables();
List<Type> types = new ArrayList<Type>();
for (TypeVariable<?> var : variables) {
Type type = typeMap.get(var);
if (type == null) {
types.add(new WildcardTypeImpl(TypeUtils.getImplicitBounds(var),
new Type[] {}));
} else {
Class<?> paramClass = GenericTypeReflector.erase(type);
if (paramClass.isPrimitive()) {
types.add(ClassUtils.primitiveToWrapper(paramClass));
} else {
types.add(typeMap.get(var));
}
}
}
constructorStatement.setConstructor(constructor.copyWithNewOwner(owner.getWithParameterTypes(types)));
logger.info("New type: " + constructorStatement);
updateMethodCallsOfGenericOwner(constructorStatement.getReturnValue());
} else {
logger.info("Type map empty");
}
}
private void updateMethodCallsOfGenericOwner(VariableReference callee) {
for (int pos = callee.getStPosition() + 1; pos < test.size(); pos++) {
Statement statement = test.getStatement(pos);
if (!(statement instanceof MethodStatement))
continue;
MethodStatement ms = (MethodStatement) statement;
if (ms.isStatic())
continue;
if (!ms.getCallee().equals(callee))
continue;
GenericMethod method = ms.getMethod();
logger.info("Updating callee of statement " + statement.getCode());
ms.setMethod(method.copyWithNewOwner(callee.getGenericClass()));
ms.getReturnValue().setType(ms.getMethod().getReturnType());
logger.info("Result: " + statement.getCode());
}
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitTestCase(org.evosuite.testcase.TestCase)
*/
@Override
public void visitTestCase(TestCase test) {
this.test = test;
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitPrimitiveStatement(org.evosuite.testcase.PrimitiveStatement)
*/
@Override
public void visitPrimitiveStatement(PrimitiveStatement<?> statement) {
// Primitives have no generic type
addVariable(statement);
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitFieldStatement(org.evosuite.testcase.FieldStatement)
*/
@Override
public void visitFieldStatement(FieldStatement statement) {
addVariable(statement);
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitMethodStatement(org.evosuite.testcase.MethodStatement)
*/
@Override
public void visitMethodStatement(MethodStatement statement) {
addVariable(statement);
GenericMethod method = statement.getMethod();
List<VariableReference> parameterVariables = statement.getParameterReferences();
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
Type genericType = genericParameterTypes[i];
VariableReference value = parameterVariables.get(i);
addTypeAssignment(genericType, value);
}
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitConstructorStatement(org.evosuite.testcase.ConstructorStatement)
*/
@Override
public void visitConstructorStatement(ConstructorStatement statement) {
addVariable(statement);
GenericConstructor constructor = statement.getConstructor();
List<VariableReference> parameterVariables = statement.getParameterReferences();
Type[] genericParameterTypes = constructor.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
Type genericType = genericParameterTypes[i];
VariableReference value = parameterVariables.get(i);
addTypeAssignment(genericType, value);
}
determineExactType(statement);
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitArrayStatement(org.evosuite.testcase.ArrayStatement)
*/
@Override
public void visitArrayStatement(ArrayStatement statement) {
addVariable(statement);
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitAssignmentStatement(org.evosuite.testcase.AssignmentStatement)
*/
@Override
public void visitAssignmentStatement(AssignmentStatement statement) {
addVariable(statement);
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitNullStatement(org.evosuite.testcase.NullStatement)
*/
@Override
public void visitNullStatement(NullStatement statement) {
addVariable(statement);
}
/* (non-Javadoc)
* @see org.evosuite.testcase.TestVisitor#visitPrimitiveExpression(org.evosuite.testcase.PrimitiveExpression)
*/
@Override
public void visitPrimitiveExpression(PrimitiveExpression primitiveExpression) {
// TODO Auto-generated method stub
}
@Override
public void visitFunctionalMockStatement(FunctionalMockStatement functionalMockStatement) {
}
}