All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.key_project.slicing.graph.DotExporter Maven / Gradle / Ivy
/* This file is part of KeY - https://key-project.org
* KeY is licensed under the GNU General Public License Version 2
* SPDX-License-Identifier: GPL-2.0-only */
package org.key_project.slicing.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import de.uka.ilkd.key.proof.Node;
import de.uka.ilkd.key.proof.Proof;
import org.key_project.slicing.DependencyNodeData;
import org.key_project.slicing.analysis.AnalysisResults;
import org.key_project.util.collection.Pair;
/**
* Exports a {@link DependencyGraph} in DOT format.
*
* @author Arne Keller
*/
public final class DotExporter {
/**
* Overrides node shape for specified classes of graph nodes.
*/
private static final Map, String> SHAPES = new IdentityHashMap<>();
static {
SHAPES.put(ClosedGoal.class, "rectangle");
}
private DotExporter() {
}
/**
* Convert the given dependency graph into a text representation (DOT format).
* If analysis results are given, useless nodes and edges are marked in red.
* If abbreviateFormulas
is true, node labels are shortened.
*
* @param proof proof to export
* @param graph dependency graph to show
* @param analysisResults analysis results (may be null)
* @param abbreviateFormulas whether node labels should be shortened
* @return string representing the dependency graph
*/
public static String exportDot(
Proof proof,
DependencyGraph graph,
AnalysisResults analysisResults,
boolean abbreviateFormulas) {
StringBuilder buf = new StringBuilder();
buf.append("digraph {\n");
// expected direction in output rendering is reverse internal order
buf.append("edge [dir=\"back\"];\n");
// output each edge of the dependency graph
List queue = new ArrayList<>();
if (proof.root() != null) { // may be null for proofs with no steps
queue.add(proof.root());
}
while (!queue.isEmpty()) {
Node node = queue.remove(queue.size() - 1);
node.childrenIterator().forEachRemaining(queue::add);
var edges = graph.edgesOf(node);
var data = node.lookup(DependencyNodeData.class);
if (edges == null || edges.isEmpty() || data == null) {
continue;
}
outputEdge(buf, analysisResults, abbreviateFormulas, false, node, data, edges);
}
// colorize useless nodes
if (analysisResults != null) {
for (GraphNode formula : graph.nodes()) {
if (!analysisResults.usefulNodes.contains(formula)) {
buf.append('"').append(formula.toString(abbreviateFormulas, false)).append('"')
.append(" [color=\"red\"]\n");
}
}
}
buf.append("}");
return buf.toString();
}
/**
* Export the graph around a specific node.
*
* @param graph graph
* @param analysisResults analysis results
* @param abbreviateFormulas whether to abbreviate node labels
* @param omitBranch whether to omit branch information
* @param graphNode the graph node to export a drawing around
* @return DOT string of the nodes and edges around {@code graphNode}
*/
public static String exportDotAround(
DependencyGraph graph,
AnalysisResults analysisResults,
boolean abbreviateFormulas,
boolean omitBranch,
GraphNode graphNode) {
StringBuilder buf = new StringBuilder();
buf.append("digraph {\n");
buf.append("edge [dir=\"back\"];\n");
// queue of: (graph node, depth required to find graph node)
List> queue = new ArrayList<>();
queue.add(new Pair<>(graphNode, 0));
Set visited = new HashSet<>();
Set drawn = new HashSet<>();
while (!queue.isEmpty()) {
Pair nodePair = queue.remove(queue.size() - 1);
if (visited.contains(nodePair.first)) {
continue;
}
GraphNode nodeB = nodePair.first;
visited.add(nodeB);
Stream incoming = graph.incomingEdgesOf(nodeB);
Stream outgoing = graph.outgoingEdgesOf(nodeB);
Stream.concat(incoming, outgoing).forEach(node -> {
if (drawn.contains(node)) {
return;
}
drawn.add(node);
DependencyNodeData data = node.lookup(DependencyNodeData.class);
if (data == null) {
return;
}
outputEdge(buf, analysisResults, abbreviateFormulas, omitBranch, node, data);
});
if (nodePair.second < 1) {
graph.neighborsOf(nodeB)
.forEach(newNode -> queue.add(new Pair<>(newNode, nodePair.second + 1)));
}
}
buf.append('"').append(graphNode.toString(abbreviateFormulas, omitBranch))
.append("\" [fontsize=\"28pt\"];");
buf.append('}');
return buf.toString();
}
/**
* Write a single edge to the provided string builder.
* This will emit an edge between every input and output of the provided node.
* It will also style the formula nodes using the shapes specified in {@link #SHAPES}.
*
* @param buf output buffer
* @param analysisResults analysis results (if available)
* @param abbreviateFormulas whether to shorten node labels
* @param omitBranch whether to omit branch labels
* @param node the node to describe
* @param data dependency graph data on the node
*/
private static void outputEdge(StringBuilder buf, AnalysisResults analysisResults,
boolean abbreviateFormulas, boolean omitBranch, Node node, DependencyNodeData data) {
for (Pair in : data.inputs) {
String inString = in.first.toString(abbreviateFormulas, omitBranch);
for (GraphNode out : data.outputs) {
String outString = out.toString(abbreviateFormulas, omitBranch);
buf
.append('"')
.append(inString)
.append("\" -> \"")
.append(outString)
.append("\" [label=\"")
.append(data.label);
// mark useless steps / formulas in red
if (analysisResults != null
&& !analysisResults.usefulSteps.contains(node)) {
buf.append("\" color=\"red");
}
buf
.append("\"]\n");
if (analysisResults != null) {
if (!analysisResults.usefulNodes.contains(in.first)) {
buf.append('"').append(inString).append('"')
.append(" [color=\"red\"]\n");
}
if (!analysisResults.usefulNodes.contains(out)) {
buf.append('"').append(outString).append('"')
.append(" [color=\"red\"]\n");
}
}
// make sure the formulas are drawn with the correct shape
String shape = SHAPES.get(in.first.getClass());
if (shape != null) {
buf.append('"').append(inString).append("\" [shape=\"").append(shape)
.append("\"]\n");
}
shape = SHAPES.get(out.getClass());
if (shape != null) {
buf.append('"').append(outString).append("\" [shape=\"").append(shape)
.append("\"]\n");
}
}
}
}
private static void outputEdge(StringBuilder buf, AnalysisResults analysisResults,
boolean abbreviateFormulas, boolean omitBranch, Node node, DependencyNodeData data,
Collection edges) {
for (var edge : edges) {
var in = ((GraphNode) edge.getSource());
var out = ((GraphNode) edge.getTarget());
// input node label
String inString = in.toString(abbreviateFormulas, omitBranch);
// output node label
String outString = out.toString(abbreviateFormulas, omitBranch);
// label for edge itself
String label = data.label;
if (edge instanceof AnnotatedShortenedEdge ase) {
label = ase.getEdgeLabel();
}
buf
.append('"')
.append(inString)
.append("\" -> \"")
.append(outString)
.append("\" [label=\"")
.append(label);
// mark useless steps / formulas in red
if (analysisResults != null
&& !analysisResults.usefulSteps.contains(node)) {
buf.append("\" color=\"red");
}
buf
.append("\"]\n");
if (analysisResults != null) {
if (!analysisResults.usefulNodes.contains(in)) {
buf.append('"').append(inString).append('"')
.append(" [color=\"red\"]\n");
}
if (!analysisResults.usefulNodes.contains(out)) {
buf.append('"').append(outString).append('"')
.append(" [color=\"red\"]\n");
}
}
// make sure the formulas are drawn with the correct shape
String shape = SHAPES.get(in.getClass());
if (shape != null) {
buf.append('"').append(inString).append("\" [shape=\"").append(shape)
.append("\"]\n");
}
shape = SHAPES.get(out.getClass());
if (shape != null) {
buf.append('"').append(outString).append("\" [shape=\"").append(shape)
.append("\"]\n");
}
}
}
}