/**
* 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.graphs;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import org.evosuite.utils.LoggingUtils;
import org.jgrapht.DirectedGraph;
import org.jgrapht.alg.DijkstraShortestPath;
import org.jgrapht.ext.DOTExporter;
import org.jgrapht.ext.IntegerNameProvider;
import org.jgrapht.ext.StringEdgeNameProvider;
import org.jgrapht.ext.StringNameProvider;
import org.jgrapht.ext.ComponentAttributeProvider;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Supposed to become the super class of all kinds of graphs used within
* EvoSuite Examples are the raw and minimal Control Flow Graph and hopefully at
* one point the Control Dependency Tree
*
* This class is supposed to hide the jGraph library from the rest of EvoSuite
* and is supposed to serve as an interface for all kinds of primitive graph-
* functionality such as asking for information about the nodes and edges of the
* graph and the relations between them.
*
* Hopefully at some point only this class and it's sub classes are the only
* files in EvoSuite that import anything from the jGraph library - at least
* that's the idea This is very similar to the way cfg.ASMWrapper is supposed to
* hide the ASM library and serve as an interface for BytecodeInstrucions
*
* So most of this class' methods are just wrappers that redirect the specific
* call to the corresponding jGraph-method
*
* For now an EvoSuiteGraph can always be represented by a DefaultDirectedGraph
* from the jGraph library - that is a directed graph not allowed to contain
* multiple edges between to nodes but allowed to contain cycles
*
* @author Andre Mis
*/
public abstract class EvoSuiteGraph<V, E extends DefaultEdge> {
private static Logger logger = LoggerFactory.getLogger(EvoSuiteGraph.class);
private static int evoSuiteGraphs = 0;
protected int graphId;
protected DirectedGraph<V, E> graph;
protected Class<E> edgeClass;
// for .dot functionality
// TODO need jgrapht-0.8.3
ComponentAttributeProvider<V> vertexAttributeProvider = null;
ComponentAttributeProvider<E> edgeAttributeProvider = null;
/**
* <p>Constructor for EvoSuiteGraph.</p>
*
* @param edgeClass a {@link java.lang.Class} object.
* @param <V> a V object.
* @param <E> a E object.
*/
protected EvoSuiteGraph(Class<E> edgeClass) {
graph = new DefaultDirectedGraph<V, E>(edgeClass);
this.edgeClass = edgeClass;
setId();
}
/**
* <p>Constructor for EvoSuiteGraph.</p>
*
* @param graph a {@link org.jgrapht.DirectedGraph} object.
* @param edgeClass a {@link java.lang.Class} object.
*/
protected EvoSuiteGraph(DirectedGraph<V, E> graph, Class<E> edgeClass) {
if (graph == null || edgeClass == null)
throw new IllegalArgumentException("null given");
this.graph = graph;
this.edgeClass = edgeClass;
setId();
}
private void setId() {
evoSuiteGraphs++;
graphId = evoSuiteGraphs;
}
// retrieving nodes and edges
/**
* <p>getEdgeSource</p>
*
* @param e a E object.
* @return a V object.
*/
public V getEdgeSource(E e) {
if (!containsEdge(e))
throw new IllegalArgumentException("edge not in graph");
return graph.getEdgeSource(e);
}
/**
* <p>getEdgeTarget</p>
*
* @param e a E object.
* @return a V object.
*/
public V getEdgeTarget(E e) {
if (!containsEdge(e))
throw new IllegalArgumentException("edge not in graph");
return graph.getEdgeTarget(e);
}
/**
* <p>outgoingEdgesOf</p>
*
* @param node a V object.
* @return a {@link java.util.Set} object.
*/
public Set<E> outgoingEdgesOf(V node) {
if (!containsVertex(node)) // should this just return null?
throw new IllegalArgumentException(
"node not contained in this graph");
// TODO hash set? can't be sure E implements hash correctly
return new LinkedHashSet<E>(graph.outgoingEdgesOf(node));
}
/**
* <p>incomingEdgesOf</p>
*
* @param node a V object.
* @return a {@link java.util.Set} object.
*/
public Set<E> incomingEdgesOf(V node) {
if (!containsVertex(node)) // should this just return null?
throw new IllegalArgumentException("node not contained in this graph ");
// TODO hash set? can't be sure E implements hash correctly
return new LinkedHashSet<E>(graph.incomingEdgesOf(node));
}
/**
* <p>getChildren</p>
*
* @param node a V object.
* @return a {@link java.util.Set} object.
*/
public Set<V> getChildren(V node) {
if (!containsVertex(node)){
LoggingUtils.getEvoLogger().warn("getChildren call requests a node not contained in the current graph. Node: "+node);
return null;
}
//TODO check why in project 57_hft-bomberman class client.gui.StartFrame this happens
// throw new IllegalArgumentException(
// "node not contained in this graph");
// TODO hash set? can't be sure V implements hash correctly
Set<V> r = new LinkedHashSet<V>();
for (E e : outgoingEdgesOf(node))
r.add(getEdgeTarget(e));
// sanity check
if (r.size() != outDegreeOf(node))
throw new IllegalStateException(
"expect children count and size of set of all children of a graphs node to be equal");
return r;
}
/**
* <p>getParents</p>
*
* @param node a V object.
* @return a {@link java.util.Set} object.
*/
public Set<V> getParents(V node) {
if (!containsVertex(node)) // should this just return null?
throw new IllegalArgumentException(
"node not contained in this graph");
// TODO hash set? can't be sure V implements hash correctly
Set<V> r = new LinkedHashSet<V>();
for (E e : incomingEdgesOf(node))
r.add(getEdgeSource(e));
// sanity check
if (r.size() != inDegreeOf(node))
throw new IllegalStateException(
"expect parent count and size of set of all parents of a graphs node to be equal");
return r;
}
/**
* <p>vertexSet</p>
*
* @return a {@link java.util.Set} object.
*/
public Set<V> vertexSet() {
// TODO hash set? can't be sure V implements hash correctly
return new LinkedHashSet<V>(graph.vertexSet());
/*
* Set<V> r = new HashSet<V>();
*
* for (V v : graph.vertexSet()) r.add(v);
*
* return r;
*/
}
/**
* <p>edgeSet</p>
*
* @return a {@link java.util.Set} object.
*/
public Set<E> edgeSet() {
// TODO hash set? can't be sure E implements hash correctly
return new LinkedHashSet<E>(graph.edgeSet());
/*
* Set<E> r = new HashSet<E>();
*
* for (E e : graph.edgeSet()) r.add(e);
*
* return r;
*/
}
/**
* If the given node is contained within this graph and has exactly one
* child v this method will return v. Otherwise it will return null
*
* @param node a V object.
* @return a V object.
*/
public V getSingleChild(V node) {
if (node == null)
return null;
if (!graph.containsVertex(node))
return null;
if (outDegreeOf(node) != 1)
return null;
for (V r : getChildren(node))
return r;
// should be unreachable
return null;
}
// building the graph
/**
* <p>addVertices</p>
*
* @param other a {@link org.evosuite.graphs.EvoSuiteGraph} object.
*/
protected void addVertices(EvoSuiteGraph<V, E> other) {
addVertices(other.vertexSet());
}
/**
* <p>addVertices</p>
*
* @param vs a {@link java.util.Collection} object.
*/
protected void addVertices(Collection<V> vs) {
if (vs == null)
throw new IllegalArgumentException("null given");
for (V v : vs)
if (!addVertex(v))
throw new IllegalArgumentException(
"unable to add all nodes in given collection: "
+ v.toString());
}
/**
* <p>addVertex</p>
*
* @param v a V object.
* @return a boolean.
*/
protected boolean addVertex(V v) {
return graph.addVertex(v);
}
/**
* <p>addEdge</p>
*
* @param src a V object.
* @param target a V object.
* @return a E object.
*/
protected E addEdge(V src, V target) {
return graph.addEdge(src, target);
}
/**
* <p>addEdge</p>
*
* @param src a V object.
* @param target a V object.
* @param e a E object.
* @return a boolean.
*/
protected boolean addEdge(V src, V target, E e) {
return graph.addEdge(src, target, e);
}
/**
* Redirects all edges going into node from to the node newStart and all
* edges going out of node from to the node newEnd.
*
* All three edges have to be present in the graph prior to a call to this
* method.
*
* @param from a V object.
* @param newStart a V object.
* @param newEnd a V object.
* @return a boolean.
*/
protected boolean redirectEdges(V from, V newStart, V newEnd) {
if (!(containsVertex(from) && containsVertex(newStart) && containsVertex(newEnd)))
throw new IllegalArgumentException(
"expect all given nodes to be present in this graph");
if (!redirectIncomingEdges(from, newStart))
return false;
if (!redirectOutgoingEdges(from, newEnd))
return false;
return true;
}
/**
* Redirects all incoming edges to oldNode to node newNode by calling
* redirectEdgeTarget for each incoming edge of oldNode
*
* @param oldNode a V object.
* @param newNode a V object.
* @return a boolean.
*/
protected boolean redirectIncomingEdges(V oldNode, V newNode) {
Set<E> incomings = incomingEdgesOf(oldNode);
for (E incomingEdge : incomings) {
if (!redirectEdgeTarget(incomingEdge, newNode))
return false;
}
return true;
}
/**
* Redirects all outgoing edges to oldNode to node newNode by calling
* redirectEdgeSource for each outgoing edge of oldNode
*
* @param oldNode a V object.
* @param newNode a V object.
* @return a boolean.
*/
protected boolean redirectOutgoingEdges(V oldNode, V newNode) {
Set<E> outgoings = outgoingEdgesOf(oldNode);
for (E outgoingEdge : outgoings) {
if (!redirectEdgeSource(outgoingEdge, newNode))
return false;
}
return true;
}
/**
* Redirects the edge target of the given edge to the given node by removing
* the given edge from the graph and reinserting it from the original source
* node to the given node
*
* @param edge a E object.
* @param node a V object.
* @return a boolean.
*/
protected boolean redirectEdgeTarget(E edge, V node) {
if (!(containsVertex(node) && containsEdge(edge)))
throw new IllegalArgumentException(
"edge and node must be present in this graph");
V edgeSource = graph.getEdgeSource(edge);
if (!graph.removeEdge(edge))
return false;
if (!addEdge(edgeSource, node, edge))
return false;
return true;
}
/**
* Redirects the edge source of the given edge to the given node by removing
* the given edge from the graph and reinserting it from the given node to
* the original target node
*
* @param edge a E object.
* @param node a V object.
* @return a boolean.
*/
protected boolean redirectEdgeSource(E edge, V node) {
if (!(containsVertex(node) && containsEdge(edge)))
throw new IllegalArgumentException(
"edge and node must be present in this graph");
V edgeTarget = graph.getEdgeTarget(edge);
if (!graph.removeEdge(edge))
return false;
if (!addEdge(node, edgeTarget, edge))
return false;
return true;
}
// different counts
/**
* <p>vertexCount</p>
*
* @return a int.
*/
public int vertexCount() {
return graph.vertexSet().size();
}
/**
* <p>edgeCount</p>
*
* @return a int.
*/
public int edgeCount() {
return graph.edgeSet().size();
}
/**
* <p>outDegreeOf</p>
*
* @param node a V object.
* @return a int.
*/
public int outDegreeOf(V node) { // TODO rename to sth. like childCount()
if (node == null || !containsVertex(node))
return -1;
return graph.outDegreeOf(node);
}
/**
* <p>inDegreeOf</p>
*
* @param node a V object.
* @return a int.
*/
public int inDegreeOf(V node) { // TODO rename sth. like parentCount()
if (node == null || !containsVertex(node))
return -1;
return graph.inDegreeOf(node);
}
// some queries
/**
* <p>getEdge</p>
*
* @param v1 a V object.
* @param v2 a V object.
* @return a E object.
*/
public E getEdge(V v1, V v2) {
return graph.getEdge(v1, v2);
}
/**
* <p>containsVertex</p>
*
* @param v a V object.
* @return a boolean.
*/
public boolean containsVertex(V v) {
// documentation says containsVertex() returns false on when given null
return graph.containsVertex(v);
}
/**
* <p>containsEdge</p>
*
* @param v1 a V object.
* @param v2 a V object.
* @return a boolean.
*/
public boolean containsEdge(V v1, V v2) {
return graph.containsEdge(v1, v2);
}
/**
* <p>containsEdge</p>
*
* @param e a E object.
* @return a boolean.
*/
public boolean containsEdge(E e) {
return graph.containsEdge(e); // TODO this seems to be buggy, at least
// for ControlFlowEdges
}
/**
* <p>isEmpty</p>
*
* @return a boolean.
*/
public boolean isEmpty() {
return graph.vertexSet().isEmpty();
}
/**
* Checks whether each vertex inside this graph is reachable from some other
* vertex
*
* @return a boolean.
*/
public boolean isConnected() {
if (vertexCount() < 2)
return true;
V start = getRandomVertex();
Set<V> connectedToStart = determineConnectedVertices(start);
return connectedToStart.size() == vertexSet().size();
}
/**
* <p>determineEntryPoints</p>
*
* @return Set containing all nodes with in degree 0
*/
public Set<V> determineEntryPoints() {
Set<V> r = new LinkedHashSet<V>();
for (V instruction : vertexSet())
if (inDegreeOf(instruction) == 0) {
r.add(instruction);
}
return r;
}
/**
* <p>determineExitPoints</p>
*
* @return Set containing all nodes with out degree 0
*/
public Set<V> determineExitPoints() {
Set<V> r = new LinkedHashSet<V>();
for (V instruction : vertexSet())
if (outDegreeOf(instruction) == 0)
r.add(instruction);
return r;
}
/**
* Follows all edges adjacent to the given vertex v ignoring edge directions
* and returns a set containing all vertices visited that way
*
* @param v a V object.
* @return a {@link java.util.Set} object.
*/
public Set<V> determineConnectedVertices(V v) {
Set<V> visited = new LinkedHashSet<V>();
Queue<V> queue = new LinkedList<V>();
queue.add(v);
while (!queue.isEmpty()) {
V current = queue.poll();
if (visited.contains(current))
continue;
visited.add(current);
queue.addAll(getParents(current));
queue.addAll(getChildren(current));
}
return visited;
}
/**
* Returns true iff whether the given node is not null, in this graph and
* has exactly n parents and m children.
*
* @param node a V object.
* @param n a int.
* @param m a int.
* @return a boolean.
*/
public boolean hasNPartentsMChildren(V node, int n, int m) {
if (node == null || !containsVertex(node))
return false;
return inDegreeOf(node) == n && outDegreeOf(node) == m;
}
/**
* Returns a Set of all nodes within this graph that neither have incoming
* nor outgoing edges.
*
* @return a {@link java.util.Set} object.
*/
public Set<V> getIsolatedNodes() {
Set<V> r = new LinkedHashSet<V>();
for (V node : graph.vertexSet())
if (inDegreeOf(node) == 0 && outDegreeOf(node) == 0)
r.add(node);
return r;
}
/**
* Returns a Set containing every node in this graph that has no outgoing
* edges.
*
* @return a {@link java.util.Set} object.
*/
public Set<V> getNodesWithoutChildren() {
Set<V> r = new LinkedHashSet<V>();
for (V node : graph.vertexSet())
if (outDegreeOf(node) == 0)
r.add(node);
return r;
}
// utilities
/**
* <p>getRandomVertex</p>
*
* @return a V object.
*/
public V getRandomVertex() {
// TODO that's not really random
for (V v : graph.vertexSet())
return v;
return null;
}
/**
* <p>getDistance</p>
*
* @param v1 a V object.
* @param v2 a V object.
* @return a int.
*/
public int getDistance(V v1, V v2) {
DijkstraShortestPath<V, E> d = new DijkstraShortestPath<V, E>(graph,
v1, v2);
return (int) Math.round(d.getPathLength());
}
/**
* <p>isDirectSuccessor</p>
*
* @param v1 a V object.
* @param v2 a V object.
* @return a boolean.
*/
public boolean isDirectSuccessor(V v1, V v2) {
return (containsEdge(v1, v2) && inDegreeOf(v2) == 1);
}
// TODO make like determineEntry/ExitPoints
/**
* <p>determineBranches</p>
*
* @return a {@link java.util.Set} object.
*/
public Set<V> determineBranches() {
Set<V> r = new LinkedHashSet<V>();
for (V instruction : graph.vertexSet())
if (outDegreeOf(instruction) > 1)
r.add(instruction);
return r;
}
/**
* <p>determineJoins</p>
*
* @return a {@link java.util.Set} object.
*/
public Set<V> determineJoins() {
Set<V> r = new LinkedHashSet<V>();
for (V instruction : vertexSet())
if (inDegreeOf(instruction) > 1)
r.add(instruction);
return r;
}
// building up the reverse graph
/**
* Returns a reverted version of this graph in a jGraph
*
* That is a graph containing exactly the same nodes as this one but for
* each edge from v1 to v2 in this graph the resulting graph will contain an
* edge from v2 to v1 - or in other words the reverted edge
*
* This is used to revert CFGs in order to determine control dependencies
* for example
*
* @return a {@link org.jgrapht.graph.DefaultDirectedGraph} object.
*/
protected DefaultDirectedGraph<V, E> computeReverseJGraph() {
DefaultDirectedGraph<V, E> r = new DefaultDirectedGraph<V, E>(edgeClass);
for (V v : vertexSet())
if (!r.addVertex(v))
throw new IllegalStateException(
"internal error while adding vertices");
for (E e : edgeSet()) {
V src = getEdgeSource(e);
V target = getEdgeTarget(e);
if (r.addEdge(target, src) == null)
throw new IllegalStateException(
"internal error while adding reverse edges");
}
return r;
}
// visualizing the graph TODO clean up!
/**
* <p>toDot</p>
*/
public void toDot() {
createGraphDirectory();
String dotFileName = getGraphDirectory() + toFileString(getName())
+ ".dot";
toDot(dotFileName);
createToPNGScript(dotFileName);
}
private String getGraphDirectory() {
return "evosuite-graphs/" + dotSubFolder();
}
/**
* Subclasses can overwrite this method in order to separate their .dot and
* .png export to a special folder.
*
* @return a {@link java.lang.String} object.
*/
protected String dotSubFolder() {
return "";
}
/**
* <p>toFileString</p>
*
* @param name a {@link java.lang.String} object.
* @return a {@link java.lang.String} object.
*/
protected String toFileString(String name) {
return name.replaceAll("\\(", "_").replaceAll("\\)", "_")
.replaceAll(";", "_").replaceAll("/", "_").replaceAll("<", "_")
.replaceAll(">", "_");
}
private void createGraphDirectory() {
File graphDir = new File(getGraphDirectory());
if (!graphDir.exists() && !graphDir.mkdirs())
throw new IllegalStateException("unable to create directory "
+ getGraphDirectory());
}
private void createToPNGScript(String filename) {
File dotFile = new File(filename);
// dot -Tpng RawCFG11_exe2_III_I.dot > file.png
assert (dotFile.exists() && !dotFile.isDirectory());
try {
String[] cmd = { "dot", "-Tpng",
"-o" + dotFile.getAbsolutePath() + ".png",
dotFile.getAbsolutePath() };
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
logger.error("Problem while generating a graph for a dotFile", e);
}
}
/**
* <p>getName</p>
*
* @return a {@link java.lang.String} object.
*/
public String getName() {
return "EvoSuiteGraph_" + graphId;
}
/**
* <p>registerVertexAttributeProvider</p>
*
* @param vertexAttributeProvider a {@link org.jgrapht.ext.ComponentAttributeProvider} object.
*/
public void registerVertexAttributeProvider(
ComponentAttributeProvider<V> vertexAttributeProvider) {
this.vertexAttributeProvider = vertexAttributeProvider;
}
/**
* <p>registerEdgeAttributeProvider</p>
*
* @param edgeAttributeProvider a {@link org.jgrapht.ext.ComponentAttributeProvider} object.
*/
public void registerEdgeAttributeProvider(
ComponentAttributeProvider<E> edgeAttributeProvider) {
this.edgeAttributeProvider = edgeAttributeProvider;
}
private void toDot(String filename) {
// TODO check if graphviz/dot is actually available on the current
// machine
try {
FileWriter fstream = new FileWriter(filename);
BufferedWriter out = new BufferedWriter(fstream);
if (!graph.vertexSet().isEmpty()) {
// FrameVertexNameProvider nameprovider = new
// FrameVertexNameProvider(mn.instructions);
// DOTExporter<Integer,DefaultEdge> exporter = new
// DOTExporter<Integer,DefaultEdge>();
// DOTExporter<Integer,DefaultEdge> exporter = new
// DOTExporter<Integer,DefaultEdge>(new IntegerNameProvider(),
// nameprovider, new IntegerEdgeNameProvider());
// DOTExporter<Integer,DefaultEdge> exporter = new
// DOTExporter<Integer,DefaultEdge>(new LineNumberProvider(),
// new LineNumberProvider(), new IntegerEdgeNameProvider());
DOTExporter<V, E> exporter = new DOTExporter<V, E>(
new IntegerNameProvider<V>(),
new StringNameProvider<V>(),
new StringEdgeNameProvider<E>(),
vertexAttributeProvider, edgeAttributeProvider);
// new IntegerEdgeNameProvider<E>());
exporter.export(out, graph);
logger.info("exportet " + getName());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}