org.evosuite.utils.generic.GenericAccessibleObject.java Source code

Java tutorial

Introduction

Here is the source code for org.evosuite.utils.generic.GenericAccessibleObject.java

Source

/**
 * Copyright (C) 2010-2016 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.io.Serializable;
import java.lang.reflect.AccessibleObject;
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.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.reflect.TypeUtils;
import org.evosuite.ga.ConstructionFailedException;
import org.evosuite.utils.ParameterizedTypeImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.gentyref.GenericTypeReflector;

/**
 * @author Gordon Fraser
 * 
 */
public abstract class GenericAccessibleObject<T extends GenericAccessibleObject<?>> implements Serializable {

    protected static final Logger logger = LoggerFactory.getLogger(GenericAccessibleObject.class);

    private static final long serialVersionUID = 7069749492563662621L;

    protected GenericClass owner;

    protected List<GenericClass> typeVariables = new ArrayList<>();

    protected static Type getTypeFromExactReturnType(GenericArrayType returnType, GenericArrayType type) {
        return GenericArrayTypeImpl.createArrayType(
                getTypeFromExactReturnType(returnType.getGenericComponentType(), type.getGenericComponentType()));
    }

    protected static Type getTypeFromExactReturnType(GenericArrayType returnType, ParameterizedType type) {
        return GenericArrayTypeImpl
                .createArrayType(getTypeFromExactReturnType(returnType.getGenericComponentType(), type));
    }

    protected static Type getTypeFromExactReturnType(ParameterizedType returnType, GenericArrayType type) {
        return GenericArrayTypeImpl
                .createArrayType(getTypeFromExactReturnType(returnType, type.getGenericComponentType()));
    }

    /**
     * Returns the exact return type of the given method in the given type. This
     * may be different from <tt>m.getGenericReturnType()</tt> when the method
     * was declared in a superclass, or <tt>type</tt> has a type parameter that
     * is used in the return type, or <tt>type</tt> is a raw type.
     */
    protected static Type getTypeFromExactReturnType(ParameterizedType returnType, ParameterizedType type) {
        Map<TypeVariable<?>, Type> typeMap = TypeUtils.getTypeArguments(returnType);
        Type[] actualParameters = new Type[type.getActualTypeArguments().length];
        int num = 0;
        for (TypeVariable<?> parameterType : ((Class<?>) type.getRawType()).getTypeParameters()) {
            //for(Type parameterType : type.getActualTypeArguments()) {
            //   if(parameterType instanceof TypeVariable<?>) {
            boolean replaced = false;
            for (TypeVariable<?> var : typeMap.keySet()) {
                // D'oh! Why the heck do we need this?? 
                if (var.getName().equals(parameterType.getName())) {
                    //if(typeMap.containsKey(parameterType)) {
                    actualParameters[num] = typeMap.get(var);
                    replaced = true;
                    break;
                    //} else {
                }
            }
            if (!replaced) {
                actualParameters[num] = parameterType;
            }
            //}
            //       } else {
            //          LoggingUtils.getEvoLogger().info("Not a type variable "+parameterType);
            //          actualParameters[num] = parameterType;
            //          }
            num++;
        }

        return new ParameterizedTypeImpl((Class<?>) type.getRawType(), actualParameters, null);
    }

    protected static Type getTypeFromExactReturnType(Type returnType, Type type) {
        if (returnType instanceof ParameterizedType && type instanceof ParameterizedType)
            return getTypeFromExactReturnType((ParameterizedType) returnType, (ParameterizedType) type);
        else if (returnType instanceof GenericArrayType && type instanceof GenericArrayType)
            return getTypeFromExactReturnType((GenericArrayType) returnType, (GenericArrayType) type);
        else if (returnType instanceof ParameterizedType && type instanceof GenericArrayType)
            return getTypeFromExactReturnType((ParameterizedType) returnType, (GenericArrayType) type);
        else if (returnType instanceof GenericArrayType && type instanceof ParameterizedType)
            return getTypeFromExactReturnType((GenericArrayType) returnType, (ParameterizedType) type);
        else if (returnType instanceof Class<?>)
            return returnType;
        else if (type instanceof Class<?>)
            return type;
        else
            throw new RuntimeException("Incompatible types: " + returnType.getClass() + " and " + type.getClass()
                    + ": " + returnType + " and " + type);
    }

    /**
     * Checks if the given type is a class that is supposed to have type
     * parameters, but doesn't. In other words, if it's a really raw type.
     */
    protected static boolean isMissingTypeParameters(Type type) {
        if (type instanceof Class) {
            for (Class<?> clazz = (Class<?>) type; clazz != null; clazz = clazz.getEnclosingClass()) {
                if (clazz.getTypeParameters().length != 0)
                    return true;
            }
            return false;
        } else if (type instanceof ParameterizedType) {
            return false;
        } else {
            throw new AssertionError("Unexpected type " + type.getClass());
        }
    }

    public GenericAccessibleObject(GenericClass owner) {
        this.owner = owner;
    }

    public void changeClassLoader(ClassLoader loader) {
        owner.changeClassLoader(loader);
        for (GenericClass typeVariable : typeVariables) {
            typeVariable.changeClassLoader(loader);
        }
    }

    protected void copyTypeVariables(GenericAccessibleObject<?> copy) {
        for (GenericClass variable : typeVariables) {
            copy.typeVariables.add(new GenericClass(variable));
        }
    }

    public abstract T copy();

    public abstract T copyWithNewOwner(GenericClass newOwner);

    public abstract T copyWithOwnerFromReturnType(GenericClass returnType) throws ConstructionFailedException;

    public abstract AccessibleObject getAccessibleObject();

    public abstract Class<?> getDeclaringClass();

    public abstract Type getGeneratedType();

    public GenericClass getGeneratedClass() {
        return new GenericClass(getGeneratedType());
    }

    public Type[] getGenericParameterTypes() {
        return new Type[] {};
    }

    public abstract Type getGenericGeneratedType();

    /**
     * Instantiate all generic type parameters
     * 
     * @return
     * @throws ConstructionFailedException
     */
    public T getGenericInstantiation() throws ConstructionFailedException {
        T copy = copy();

        if (!hasTypeParameters()) {
            copy.owner = copy.getOwnerClass().getGenericInstantiation();
            return copy;
        }

        Map<TypeVariable<?>, Type> typeMap = copy.getOwnerClass().getTypeVariableMap();

        logger.debug("Getting random generic instantiation of method: " + toString() + " with owner type map: "
                + typeMap);
        List<GenericClass> typeParameters = new ArrayList<GenericClass>();

        // TODO: The bounds of this type parameter need to be updataed for the owner of the call
        // which may instantiate some of the type parameters
        for (TypeVariable<?> parameter : getTypeParameters()) {
            GenericClass genericType = new GenericClass(parameter);
            GenericClass concreteType = genericType.getGenericInstantiation(typeMap);
            logger.debug("Setting parameter " + parameter + " to type " + concreteType.getTypeName());
            typeParameters.add(concreteType);
        }
        copy.setTypeParameters(typeParameters);
        copy.owner = copy.getOwnerClass().getGenericInstantiation(typeMap);
        return copy;
    }

    /**
     * Instantiate all generic type parameters based on a new callee type
     * 
     * @param calleeType
     * @return
     * @throws ConstructionFailedException
     */
    public T getGenericInstantiation(GenericClass calleeType) throws ConstructionFailedException {

        T copy = copy();

        logger.debug("Getting generic instantiation for callee " + calleeType + " of method: " + toString()
                + " for callee " + calleeType);
        Map<TypeVariable<?>, Type> typeMap = calleeType.getTypeVariableMap();
        if (!hasTypeParameters()) {
            logger.debug("Have no type parameters, just using typeMap of callee");
            copy.owner = copy.getOwnerClass().getGenericInstantiation(typeMap);
            return copy;
        }

        List<GenericClass> typeParameters = new ArrayList<GenericClass>();
        for (TypeVariable<?> parameter : getTypeParameters()) {
            GenericClass concreteType = new GenericClass(parameter);
            logger.debug("(I) Setting parameter " + parameter + " to type " + concreteType.getTypeName());
            typeParameters.add(concreteType.getGenericInstantiation(typeMap));
        }
        copy.setTypeParameters(typeParameters);
        copy.owner = copy.getOwnerClass().getGenericInstantiation(typeMap);

        return copy;
    }

    /**
     * Set type parameters based on return type
     * 
     * @param returnType
     * @return
     * @throws ConstructionFailedException
     */
    public T getGenericInstantiationFromReturnValue(GenericClass generatedType) throws ConstructionFailedException {

        logger.debug("Instantiating generic return for generated Type " + generatedType);
        T copy = copy();

        // We just want to have the type variables defined in the generic method here
        // and not type variables defined in the owner
        Map<TypeVariable<?>, Type> concreteTypes = new HashMap<TypeVariable<?>, Type>();
        logger.debug("Getting type map of generated type");
        Map<TypeVariable<?>, Type> generatorTypes = generatedType.getTypeVariableMap();
        logger.debug("Got type map of generated type: " + generatorTypes);
        Type genericReturnType = getGenericGeneratedType();

        logger.debug(
                "Getting generic instantiation for return type " + generatedType + " of method: " + toString());

        if (genericReturnType instanceof ParameterizedType && generatedType.isParameterizedType()) {
            logger.debug("Return value is a parameterized type, matching variables");
            generatorTypes.putAll(GenericUtils.getMatchingTypeParameters(
                    (ParameterizedType) generatedType.getType(), (ParameterizedType) genericReturnType));
        } else if (genericReturnType instanceof TypeVariable<?>) {
            generatorTypes.put((TypeVariable<?>) genericReturnType, generatedType.getType());
        }

        if (genericReturnType instanceof ParameterizedType) {
            for (Type parameterType : getGenericParameterTypes()) {
                logger.debug("Checking parameter " + parameterType);
                if (parameterType instanceof ParameterizedType) {
                    Map<TypeVariable<?>, Type> matchedMap = GenericUtils.getMatchingTypeParameters(
                            (ParameterizedType) parameterType, (ParameterizedType) genericReturnType);
                    for (TypeVariable<?> var : matchedMap.keySet()) {
                        if (!generatorTypes.containsKey(var))
                            generatorTypes.put(var, matchedMap.get(var));
                    }
                    logger.debug("Map is now " + generatorTypes);
                }
            }
        }
        logger.debug("GeneratorTypes is now: " + generatorTypes);
        List<TypeVariable<?>> parameters = Arrays.asList(getTypeParameters());
        for (TypeVariable<?> var : generatorTypes.keySet()) {
            if (parameters.contains(var) && !(generatorTypes.get(var) instanceof WildcardType)) {
                logger.debug("Parameter " + var + " in map, adding to concrete types: " + generatorTypes.get(var));
                concreteTypes.put(var, generatorTypes.get(var));
            } else {
                logger.debug("Parameter " + var + " not in map, not adding to concrete types: "
                        + generatorTypes.get(var));
                logger.debug("Key: " + var.getGenericDeclaration());
                for (TypeVariable<?> k : parameters) {
                    logger.debug("Param: " + k.getGenericDeclaration());
                }
            }
        }

        // When resolving the type variables on a non-static generic method
        // we need to look at the owner type, and not the return type!

        List<GenericClass> typeParameters = new ArrayList<GenericClass>();
        logger.debug("Setting parameters with map: " + concreteTypes);
        for (TypeVariable<?> parameter : getTypeParameters()) {
            GenericClass concreteType = new GenericClass(parameter);
            logger.debug("(I) Setting parameter " + parameter + " to type " + concreteType.getTypeName());
            GenericClass instantiation = concreteType.getGenericInstantiation(concreteTypes);
            logger.debug("Got instantiation for " + parameter + ": " + instantiation);
            if (!instantiation.satisfiesBoundaries(parameter, concreteTypes)) {
                logger.info("Type parameter does not satisfy boundaries: " + parameter + " " + instantiation);
                logger.info(Arrays.asList(parameter.getBounds()).toString());
                logger.info(instantiation.toString());
                throw new ConstructionFailedException("Type parameter does not satisfy boundaries: " + parameter);
            }
            typeParameters.add(instantiation);
        }
        copy.setTypeParameters(typeParameters);
        copy.owner = copy.getOwnerClass().getGenericInstantiation(concreteTypes);

        return copy;
    }

    public abstract String getName();

    public int getNumParameters() {
        return 0;
    }

    public GenericClass getOwnerClass() {
        return owner;
    }

    public Type getOwnerType() {
        return owner.getType();
    }

    public abstract Class<?> getRawGeneratedType();

    public TypeVariable<?>[] getTypeParameters() {
        return new TypeVariable<?>[] {};
    }

    protected Map<TypeVariable<?>, GenericClass> getTypeVariableMap() {
        Map<TypeVariable<?>, GenericClass> typeMap = new HashMap<TypeVariable<?>, GenericClass>();
        int pos = 0;
        for (TypeVariable<?> variable : getTypeParameters()) {
            if (typeVariables.size() <= pos)
                break;
            typeMap.put(variable, typeVariables.get(pos));
            pos++;
        }
        return typeMap;
    }

    public boolean hasTypeParameters() {
        return getTypeParameters().length != 0;
    }

    public abstract boolean isAccessible();

    public boolean isConstructor() {
        return false;
    }

    public boolean isField() {
        return false;
    }

    public boolean isMethod() {
        return false;
    }

    public boolean isStatic() {
        return false;
    }

    /**
     * Maps type parameters in a type to their values.
     * 
     * @param toMapType
     *            Type possibly containing type arguments
     * @param typeAndParams
     *            must be either ParameterizedType, or (in case there are no
     *            type arguments, or it's a raw type) Class
     * @return toMapType, but with type parameters from typeAndParams replaced.
     */
    protected Type mapTypeParameters(Type toMapType, Type typeAndParams) {
        if (isMissingTypeParameters(typeAndParams)) {
            logger.debug("Is missing type parameters, so erasing types");
            return GenericTypeReflector.erase(toMapType);
        } else {
            VarMap varMap = new VarMap();
            Type handlingTypeAndParams = typeAndParams;
            while (handlingTypeAndParams instanceof ParameterizedType) {
                ParameterizedType pType = (ParameterizedType) handlingTypeAndParams;
                Class<?> clazz = (Class<?>) pType.getRawType(); // getRawType should always be Class
                varMap.addAll(clazz.getTypeParameters(), pType.getActualTypeArguments());
                handlingTypeAndParams = pType.getOwnerType();
            }
            varMap.addAll(getTypeVariableMap());
            return varMap.map(toMapType);
        }
    }

    public void setTypeParameters(List<GenericClass> parameterTypes) {
        typeVariables.clear();
        for (GenericClass parameter : parameterTypes)
            typeVariables.add(new GenericClass(parameter));
    }

    @Override
    public abstract String toString();

    @Override
    public abstract boolean equals(Object other);

    @Override
    public abstract int hashCode();
}