org.evosuite.continuous.project.ProjectGraph.java Source code

Java tutorial

Introduction

Here is the source code for org.evosuite.continuous.project.ProjectGraph.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.continuous.project;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.evosuite.continuous.project.ProjectStaticData.ClassInfo;
import org.evosuite.seeding.CastClassAnalyzer;
import org.evosuite.setup.DependencyAnalysis;
import org.evosuite.setup.InheritanceTree;
import org.evosuite.setup.InheritanceTreeGenerator;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * Class representing the class graph. For each class (CUT and non-testable),
 * not only we want to know its hierarchy (parents, interfaces, subclasses, etc)
 * but also where in the project it is used by other classes as input.
 * </p>
 * 
 * <p>
 * For definition of CUT, see {@link ProjectStaticData}
 * </p>
 */
public class ProjectGraph {

    /*
     * Need to guarantee to build the graph once with all needed info, and then the public
     * methods just query the map data structures directly (instead of recalculating
     * on the fly)
     */

    private static final Logger logger = LoggerFactory.getLogger(ProjectGraph.class);

    private final InheritanceTree inheritanceTree;

    /**
     * FIXME
     * Map from TODO (key) to TODO (value)
     */
    private final Map<String, Set<String>> castInformation;

    private final ProjectStaticData data;

    /**
     * Main constructor
     * 
     * @param data
     */
    public ProjectGraph(ProjectStaticData data) {

        this.data = data;
        inheritanceTree = InheritanceTreeGenerator.createFromClassList(data.getClassNames());
        castInformation = new HashMap<String, Set<String>>();

        if (logger.isDebugEnabled()) {
            logger.debug("Classes in inheritance tree: " + inheritanceTree.getAllClasses());
        }
    }

    /**
     * <p>
     * Return the full qualifying names of all classes that are CUTs and that
     * are used as input in any of the public methods of <code>cut</code> (but
     * not of any of its parent hierarchy).
     * </p>
     * 
     * <p>
     * If a method takes as input a reference of a non-SUT class (e.g.,
     * <code>java.lang.Object</code>), but then there is a cast to a CUT (e.g. a
     * class X), then X will be added in the returned set.
     * </p>
     * 
     * <p>
     * If a method takes as input a reference of a SUT class X that is not a CUT
     * (e.g., an interface with no code), then X will <b>not</b> be added in the
     * returned set, although based on <code>includeSubclasses</code> we might
     * add its subclasses.
     * </p>
     * 
     * @param cut
     *            the class under test (CUT)
     * @param includeSubclasses
     *            If a class X is in the returned set, then normally no subclass
     *            Y of X would be added in the returned set, unless Y is
     *            directly used in the CUT as input.
     * @return a set of full qualifying names of CUTs
     * @throws IllegalArgumentException
     *             if the input <code>cut</code> is not a CUT
     */
    public Set<String> getCUTsDirectlyUsedAsInput(String cut, boolean includeSubclasses)
            throws IllegalArgumentException {

        checkCUT(cut);

        Set<String> parameterClasses = recursionToSearchDirectInputs(cut, includeSubclasses);
        parameterClasses.remove(cut);
        removeNonCUT(parameterClasses);

        logger.debug("Parameter classes of " + cut + ": " + parameterClasses);
        return parameterClasses;
    }

    protected Set<String> recursionToSearchDirectInputs(String aClass, boolean includeSubclasses) {
        if (includeSubclasses) {
            Set<String> directlyUsed = recursionToSearchDirectInputs(aClass, false); //recursion
            Set<String> all = new LinkedHashSet<String>(directlyUsed);
            for (String name : directlyUsed) {
                all.addAll(getAllCUTsSubclasses(name));
            }
            return all;
        }

        Set<String> parameterClasses = getParameterClasses(aClass);
        parameterClasses.addAll(getCastClasses(aClass));

        return parameterClasses;
    }

    /**
     * Calculate all the CUTs that use the given <code>cut</code> as input in
     * any of their public methods
     * 
     * @param cut
     *            the class under test (CUT)
     * @param includeSuperClasses
     *            not only using as input the
     *            <code>cut</body>, but also any of its CUT ancestors/interfaces
     * @return a set of full qualifying names of CUTs
     * @throws IllegalArgumentException
     *             if the input <code>cut</code> is not a CUT
     */
    public Set<String> getCUTsThatUseThisCUTasInput(String cut, boolean includeSuperClasses)
            throws IllegalArgumentException {

        checkCUT(cut);

        Set<String> classNames = recursionToSearchWhatUsesItAsInput(cut, includeSuperClasses);
        classNames.remove(cut);
        removeNonCUT(classNames);

        return classNames;
    }

    protected Set<String> recursionToSearchWhatUsesItAsInput(String aClass, boolean includeSuperClasses) {
        if (includeSuperClasses) {
            Set<String> directlyUsed = recursionToSearchWhatUsesItAsInput(aClass, false); //recursion
            Set<String> all = new LinkedHashSet<String>(directlyUsed);
            for (String name : getAllCUTsParents(aClass)) {
                all.addAll(recursionToSearchWhatUsesItAsInput(name, false)); //recursion
            }
            return all;
        }

        Set<String> classNames = new LinkedHashSet<String>();
        for (String className : inheritanceTree.getAllClasses()) {
            if (className.equals(aClass)) {
                continue;
            }

            Set<String> inputClasses = getCUTsDirectlyUsedAsInput(className, true);
            if (inputClasses.contains(aClass)) {
                classNames.add(className);
            }
        }

        return classNames;
    }

    /**
     * Is the given class name representing an interface in the SUT?
     * @param className
     * @return
     * @throws IllegalArgumentException
     */
    public boolean isInterface(String className) throws IllegalArgumentException {
        checkClass(className);
        ClassNode node = getClassNode(className);
        if ((node.access & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE)
            return true;

        return false;
    }

    /**
     * Is the given class name representing a concrete class in the SUT?
     * @param className
     * @return
     * @throws IllegalArgumentException
     */
    public boolean isConcrete(String className) throws IllegalArgumentException {
        checkClass(className);
        return !isInterface(className) && !isAbstract(className);
    }

    /**
     * Is the given class name representing an abstract class in the SUT?
     * @param className
     * @return
     * @throws IllegalArgumentException
     */
    public boolean isAbstract(String className) throws IllegalArgumentException {
        checkClass(className);
        ClassNode node = getClassNode(className);
        return (node.access & Opcodes.ACC_ABSTRACT) == Opcodes.ACC_ABSTRACT;
    }

    /**
     * Check if the given class does belong to the SUT.
     * This is independent on whether it is a CUT (ie testable) or not.
     * 
     * @param className
     * @throws IllegalArgumentException
     */
    private void checkClass(String className) throws IllegalArgumentException {
        if (!data.containsClass(className)) {
            throw new IllegalArgumentException("Class " + className + " is not part of the SUT");
        }
    }

    /**
     * Check if the given class does belong to the SUT and if it is testable (ie a CUT).
     * 
     * @param cut
     */
    private void checkCUT(String cut) throws IllegalArgumentException {
        ClassInfo info = data.getClassInfo(cut);
        if (info == null) {
            throw new IllegalArgumentException("Class " + cut + " is not part of the SUT");
        }
        if (!info.isTestable()) {
            throw new IllegalArgumentException(
                    "Class " + cut + " belongs to the SUT, but it is not a CUT (ie testable)");
        }
    }

    /**
     * Return all the child hierarchy of the <code>aClass</code>. Include only classes
     * that are CUTs.
     * This <code>aClass</code> will not be part of the returned set, even if it is a CUT
     * 
     * @param aClass
     *            a class belonging to the SUT, but not necessarily a CUT
     * @return a set of full qualifying names of CUTs
     * @throws IllegalArgumentException
     *             if the input <code>aClass</code> does not belong to the SUT
     */
    public Set<String> getAllCUTsSubclasses(String aClass) throws IllegalArgumentException {
        checkClass(aClass);

        Set<String> set = null;
        try { //FIXME
            set = inheritanceTree.getSubclasses(aClass);
        } catch (Exception e) {
            logger.error("Bug in inheritanceTree: " + e);
            return new HashSet<String>();
        }
        set.remove(aClass);
        removeNonCUT(set);
        return set;
    }

    /**
     * Return all the CUT classes that this <code>aClass</code> extends/implements
     * (ie, parent hierarchy).
     * This <code>aClass</code> will not be part of the returned set, even if it is a CUT
     * 
     * @param aClass
     *            a class belonging to the SUT, but not necessarily a CUT
     * @return a set of full qualifying names of CUTs
     * @throws IllegalArgumentException
     *             if the input <code>aClass</code> does not belong to the SUT
     */
    public Set<String> getAllCUTsParents(String aClass) throws IllegalArgumentException {
        checkClass(aClass);
        Set<String> set = null;

        try { //FIXME
            set = inheritanceTree.getSuperclasses(aClass);
        } catch (Exception e) {
            logger.error("Bug in inheritanceTree: " + e);
            return new HashSet<String>();
        }
        set.remove(aClass); //it seems inheritanceTree returns 'cut' in the set
        removeNonCUT(set);
        return set;
    }

    private void removeNonCUT(Set<String> set) throws IllegalArgumentException {

        Iterator<String> iter = set.iterator();
        while (iter.hasNext()) {
            String name = iter.next();
            ClassInfo info = data.getClassInfo(name);
            if (info == null) {
                throw new IllegalArgumentException("Class " + name + " does not belong to the SUT");
            }

            if (!info.isTestable()) {
                iter.remove();
            }
        }
    }

    /**
     * For now use the cache provided by dependency analysis
     * 
     * @param className
     * @return
     */
    private ClassNode getClassNode(String className) {
        return DependencyAnalysis.getClassNode(className);
    }

    /**
     * Determine the set of classes that are used in casts in a CUT
     * 
     * @param className
     * @return
     */
    private Set<String> getCastClasses(String className) {
        if (!castInformation.containsKey(className)) {
            CastClassAnalyzer analyzer = new CastClassAnalyzer();
            // The analyzer gets the classnode from the DependencyAnalysis classnode cache
            Map<Type, Integer> castMap = analyzer.analyze(className);
            Set<String> castClasses = new LinkedHashSet<String>();
            for (Type type : castMap.keySet()) {
                String name = type.getClassName();
                if (inheritanceTree.hasClass(name)) {
                    castClasses.add(name);
                }
            }
            castInformation.put(className, castClasses);
        }

        return castInformation.get(className);
    }

    @SuppressWarnings("unchecked")
    private Set<String> getParameterClasses(String cut) {
        Set<String> parameters = new LinkedHashSet<String>();
        ClassNode node = getClassNode(cut);
        List<MethodNode> methods = node.methods;
        for (MethodNode methodNode : methods) {
            addParameterClasses(methodNode, parameters);
        }

        List<FieldNode> fields = node.fields;
        for (FieldNode fieldNode : fields) {
            addParameterClasses(fieldNode, parameters);
        }
        return parameters;
    }

    private void addParameterClasses(MethodNode methodNode, Set<String> classNames) {
        // TODO: Only including public methods for now. Should this be refined to match
        //       TestClusterGenerator.canUse?
        if ((methodNode.access & Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC)
            return;

        for (Type parameterType : Type.getArgumentTypes(methodNode.desc)) {
            String name = parameterType.getClassName();
            if (inheritanceTree.hasClass(name)) {
                classNames.add(name);
            }
        }
    }

    private void addParameterClasses(FieldNode fieldNode, Set<String> classNames) {
        // TODO: Only including public fields for now. Should this be refined to match
        //       TestClusterGenerator.canUse?
        if ((fieldNode.access & Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC)
            return;
        String name = Type.getType(fieldNode.desc).getClassName();
        if (inheritanceTree.hasClass(name)) {
            classNames.add(name);
        }
    }

}