Skip to content
DotFactory.java 11.4 KiB
Newer Older
/*
 * EDG, a library to generate and slice Expression Dependence Graphs.
 * Copyright (c) 2021. David Insa, Sergio Pérez, Josep Silva, Salvador Tamarit.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package edg;

import java.io.File;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import edg.constraint.EdgeConstraint;
import edg.graph.EDG;
import edg.graph.Edge;
import edg.graph.Node;
import org.jgrapht.graph.AsSubgraph;
import org.jgrapht.io.Attribute;
import org.jgrapht.io.DOTExporter;
import org.jgrapht.io.DefaultAttribute;
import org.jgrapht.io.ExportException;

public class DotFactory {
	// ============================= DEBUG CONFIGURATION ============================= //
	// This configuration will get applied to filter out some edges from the dot
	// representation, to ease the debugging of graphs.

	// Edge types that will be ignored (none if empty)
	static final List<Edge.Type> ignoreEdgeTypes = new LinkedList<>();
	// Edge types that will be included (all if empty)
	static final List<Edge.Type> edgeTypes = Arrays.asList();
	// static final List<Edge.Type> edgeTypes = Arrays.asList(Edge.Type.ControlFlow);
	// Lower and upper bound for node inclusion (both ends of an edge must be included for
	// the edge to be included)
	static final int lowerBound = Integer.MIN_VALUE;
	static final int upperBound = Integer.MAX_VALUE;
	// Specific nodes, for which all edges must be included (all if empty)
	static final List<Integer> nodeIds = Arrays.asList();
	static final List<Integer> ignoredNodeIds = Arrays.asList();
	// =========================== END DEBUG CONFIGURATION =========================== //

	// Reads the commandline options (-Dedgecontrol=false removes control edges)
	static {
		Map<String, List<Edge.Type>> edgeNameMap = new HashMap<>();
		edgeNameMap.put("structural", List.of(Edge.Type.Structural));
		edgeNameMap.put("controlflow", List.of(Edge.Type.ControlFlow, Edge.Type.NonExecControlFlow));
		edgeNameMap.put("control", List.of(Edge.Type.Control));
		edgeNameMap.put("flow", List.of(Edge.Type.Flow));
		edgeNameMap.put("objectflow", List.of(Edge.Type.ObjectFlow));
		edgeNameMap.put("value", List.of(Edge.Type.Value));
		edgeNameMap.put("totaldefinition", List.of(Edge.Type.TotalDefinition));
		edgeNameMap.put("call", List.of(Edge.Type.Call));
		edgeNameMap.put("input", List.of(Edge.Type.Input));
		edgeNameMap.put("output", List.of(Edge.Type.Output));
		edgeNameMap.put("summary", List.of(Edge.Type.Summary));
		for (String key : edgeNameMap.keySet())
			if (System.getProperty("edge" + key, "true").equalsIgnoreCase("false"))
				ignoreEdgeTypes.addAll(edgeNameMap.get(key));
	}

	public static void createDot(File outputFile, EDG edg)
	{
		DotFactory.createDot(outputFile, edg, null, null, null);
	}

	public static void createDot(File outputFile, EDG edg, Map<Edge.Type, Boolean> edgeFlags)
	{
		DotFactory.createDot(outputFile, edg, null, null, edgeFlags);
	}

	public static void createDot(File outputFile, EDG edg, Node slicingCriterion, Set<Node> slice)
	{
		DotFactory.createDot(outputFile, edg, slicingCriterion, slice, null);
	}

	public static void createDot(File outputFile, EDG edg, Node slicingCriterion, Set<Node> slice, Map<Edge.Type, Boolean> edgeFlags)
	{
		SlicedGraph slicedGraph = new SlicedGraph(slicingCriterion, slice);
		slicedGraph.setEdgeFilter(edge -> {
			Edge.Type edgeType = edge.getType();
			int idFrom = edg.getEdgeSource(edge).getId();
			int idTo = edg.getEdgeTarget(edge).getId();

			// Structural edges are always kept!
			if (edgeType == Edge.Type.Structural)
				return true;

// SHOW ONLY VALUE EDGES WITH ACCESS CONSTRAINTS
//if (edgeType == Edge.Type.Value)
// 	return edge.getConstraint() instanceof AccessConstraint;

			return (edgeFlags == null || edgeFlags.get(edgeType)) &&
					!ignoreEdgeTypes.contains(edgeType) &&
					(edgeTypes.isEmpty() || edgeTypes.contains(edgeType)) &&
					(idFrom >= lowerBound && idTo >= lowerBound && idFrom <= upperBound && idTo <= upperBound) &&
					(nodeIds.isEmpty() || nodeIds.contains(idFrom) || nodeIds.contains(idTo))
					&& ((!ignoredNodeIds.contains(idFrom) && !ignoredNodeIds.contains(idTo))
					&& !(edgeType == Edge.Type.Control && (edg.getNode(idFrom).getType() == Node.Type.Clause || edg.getNode(idFrom).getType() == Node.Type.Parameters))); //IGNORE CLAUSE CONTROL EDGES TO ALL ELEMENTS
		AsSubgraph<Node, Edge> subGraph = new AsSubgraph<>(edg, edg.vertexSet(),
				edg.edgeSet().stream().filter(slicedGraph.edgeFilter).collect(Collectors.toSet()));

		DOTExporter<Node, Edge> exporter = new DOTExporter<>(
				n -> String.valueOf(n.getId()), // Node --> id
				slicedGraph::getNodeLabel,
				slicedGraph::getEdgeLabel,
				slicedGraph::getNodeAttributes,
				slicedGraph::getEdgeAttributes);

		try
		{
			exporter.exportGraph(subGraph, outputFile);
		}
		catch (ExportException e)
		{
			System.err.println("Error generating dot from EDG and writing it to " + outputFile.getPath());
			e.printStackTrace();
		}
	}

	// ======================== DOT ATTRIBUTES ======================== //
	// Colors
	private static final Attribute BLACK  = DefaultAttribute.createAttribute("black");
	private static final Attribute BLUE   = DefaultAttribute.createAttribute("blue");
	private static final Attribute GREEN  = DefaultAttribute.createAttribute("green");
	private static final Attribute GREEN4  = DefaultAttribute.createAttribute("green4");
	private static final Attribute GREEN3 = DefaultAttribute.createAttribute("green3");
	private static final Attribute GRAY   = DefaultAttribute.createAttribute("gray");
	private static final Attribute RED    = DefaultAttribute.createAttribute("red");
	private static final Attribute ORANGE = DefaultAttribute.createAttribute("orange");
	private static final Attribute PINK   = DefaultAttribute.createAttribute("pink");
	private static final Attribute BROWN  = DefaultAttribute.createAttribute("brown");
	private static final Attribute TURQUOISE  = DefaultAttribute.createAttribute("turquoise");
	private static final Attribute DEEPPINK = DefaultAttribute.createAttribute("deeppink");
	private static final Attribute SKYBLUE = DefaultAttribute.createAttribute("skyblue");
	private static final Attribute STEELBLUE = DefaultAttribute.createAttribute("steelblue1");
	// Numbers
	private static final Attribute ONE   = DefaultAttribute.createAttribute(1);
	private static final Attribute TWO   = DefaultAttribute.createAttribute(2);
	private static final Attribute THREE = DefaultAttribute.createAttribute(3);
	private static final Attribute FOUR  = DefaultAttribute.createAttribute(4);
	// Booleans
	private static final Attribute FALSE = DefaultAttribute.createAttribute(false);
	// Shapes
	private static final Attribute ELLIPSE = DefaultAttribute.createAttribute("ellipse");
	// Styles
	private static final Attribute FILLED    = DefaultAttribute.createAttribute("filled");
	private static final Attribute DASHED    = DefaultAttribute.createAttribute("dashed");
	private static final Attribute DOTTED    = DefaultAttribute.createAttribute("dotted");
	private static final Attribute INVISIBLE = DefaultAttribute.createAttribute("invis");

	private static class SlicedGraph {
		private final Node slicingCriterion;
		private final Set<Node> slice;
		private Predicate<Edge> edgeFilter = null;

		public SlicedGraph(Node slicingCriterion, Set<Node> slice)
		{
			this.slicingCriterion = slicingCriterion;
			this.slice = slice;
		}

		public void setEdgeFilter(Predicate<Edge> edgeFilter)
		{
			this.edgeFilter = edgeFilter;
		}

		//private String getNodeLabel(Node node) { return String.format("Id = %d\n%s", node.getId(), node.getLabel()); }
		private String getNodeLabel(Node node)
		{
			return String.format("Id = %d\nSDGId = %d\n%s", node.getId(), node.getSDGId(), node.getLabel());
		}

		private String getEdgeLabel(Edge edge)
		{
			final Edge.Type edgeType = edge.getType();
			final EdgeConstraint constraint = edge.getConstraint();
			if (constraint != null && edgeType != Edge.Type.Structural && edgeType != Edge.Type.Control)
				return constraint.toString();
			return null;
		}

		private Map<String, Attribute> getNodeAttributes(Node node)
		{
			boolean inSlice = slice != null && slice.contains(node);
			Map<String, Attribute> attrs = new HashMap<>();
			attrs.put("shape", ELLIPSE);
			attrs.put("style", FILLED);
			attrs.put("color", node == slicingCriterion ? BLUE : GRAY);
			attrs.put("penwidth", node == slicingCriterion ? FOUR : ONE);
			attrs.put("fontcolor", inSlice ? BLUE : BLACK);
			attrs.put("fillcolor", inSlice ? GREEN : GRAY);
			return attrs;
		}

		private Map<String, Attribute> getEdgeAttributes(Edge edge)
		{
			final Edge.Type edgeType = edge.getType();
			Map<String, Attribute> attrs = new HashMap<>();
			switch (edgeType)
			{
				case Structural:
					attrs.put("color", edge.isMarked() ? GREEN : BLACK);
					attrs.put("penwidth", THREE);
					break;
				case ControlFlow:
					attrs.put("color", RED);
					attrs.put("penwidth", THREE);
					attrs.put("constraint", FALSE);
					break;
				case NonExecControlFlow:
					attrs.put("color", RED);
					attrs.put("penwidth", THREE);
					attrs.put("constraint", FALSE);
					attrs.put("style", DASHED);
					break;
				case Control:
					attrs.put("color", ORANGE);
					attrs.put("constraint", FALSE);
					attrs.put("penwidth", THREE);
					break;
				case Value:
					attrs.put("color", RED);
					attrs.put("constraint", FALSE);
					attrs.put("style", DOTTED);
					break;
				case Flow:
					attrs.put("color", BLUE);
					attrs.put("constraint", FALSE);
					attrs.put("style", DOTTED);
					break;
				case ObjectFlow:
					attrs.put("color", GREEN4);
					attrs.put("penwidth", TWO);
					attrs.put("constraint", FALSE);
					attrs.put("style", DASHED);
					break;
				case Call:
				case Input:
					attrs.put("color", GREEN3);
					attrs.put("penwidth", THREE);
					attrs.put("constraint", FALSE);
					attrs.put("style", DASHED);
					break;
				case CallReq:
					attrs.put("color", SKYBLUE);
					attrs.put("penwidth", TWO);
					attrs.put("constraint", FALSE);
					attrs.put("style", DASHED);
					break;
				case Output:
					attrs.put("color", PINK);
					attrs.put("penwidth", THREE);
					attrs.put("constraint", FALSE);
					attrs.put("style", DASHED);
					break;
				case Summary:
					attrs.put("color", BROWN);
					attrs.put("penwidth", THREE);
					attrs.put("constraint", FALSE);
					break;
				case Exception:
					attrs.put("color", ORANGE);
					attrs.put("penwidth", THREE);
					attrs.put("constraint", FALSE);
					break;
				case TotalDefinition:
					attrs.put("color", TURQUOISE);
					attrs.put("penwidth", THREE);
					attrs.put("constraint", FALSE);
					break;
				case Class:
					attrs.put("color", DEEPPINK);
					attrs.put("penwidth", TWO);
					attrs.put("constraint", FALSE);
					attrs.put("style", DASHED);
					break;
				default:
					throw new RuntimeException("Edge type not contemplated: " + edgeType);
			}
			return attrs;
		}
	}
Sergio Pérez's avatar
Sergio Pérez committed
}