/**
* Copyright (C) 2013-2014 Olaf Lessenich
* Copyright (C) 2014-2015 University of Passau, Germany
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* Contributors:
* Olaf Lessenich <lessenic@fim.uni-passau.de>
* Georg Seibt <seibt@fim.uni-passau.de>
*/
package de.fosd.jdime.strdump;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import de.fosd.jdime.artifact.Artifact;
import de.fosd.jdime.artifact.ArtifactList;
import de.fosd.jdime.artifact.Artifacts;
import de.fosd.jdime.matcher.matching.Matching;
import de.fosd.jdime.matcher.matching.Matchings;
import de.fosd.jdime.strdump.graphviz.GraphvizEdge;
import de.fosd.jdime.strdump.graphviz.GraphvizGraph;
import de.fosd.jdime.strdump.graphviz.GraphvizNode;
import de.fosd.jdime.strdump.graphviz.GraphvizSubGraph;
import static de.fosd.jdime.strdump.graphviz.GraphvizAttributeStmtType.EDGE;
import static de.fosd.jdime.strdump.graphviz.GraphvizAttributeStmtType.NODE;
import static de.fosd.jdime.strdump.graphviz.GraphvizGraphType.GRAPH;
public class MatchingsTreeDump {
/**
* Converts the two <code>Artifact</code> trees being matched by <code>matchings</code> to a
* <code>GraphvizGraph</code> visualizing the trees and the matchings between them. It is assumed that all left
* and right sides of all <code>Matching</code>s are from the same (left and right) tree.
*
* @param matchings
* the <code>Matchings</code> to convert
* @param <T>
* the type of the <code>Artifact</code>s
* @return the resulting <code>GraphvizGraph</code> that can be dumped
*/
public <T extends Artifact<T>> GraphvizGraph toGraphvizGraph(Matchings<T> matchings) {
if (matchings.isEmpty()) {
return new GraphvizGraph(false, GRAPH);
}
T lRoot, rRoot;
{
Matching<T> first = matchings.iterator().next();
lRoot = Artifacts.root(first.getLeft());
rRoot = Artifacts.root(first.getRight());
}
return toGraphvizGraph(matchings, lRoot, rRoot);
}
/**
* Converts the two given <code>Artifact</code> trees and the matchings between elements of the trees to a
* <code>GraphvizGraph</code> visualizing them.
*
* @param matchings
* the <code>Matchings</code> between elements of the two trees
* @param lRoot
* the root of the left tree
* @param rRoot
* the root of the right tree
* @param <T>
* the type of the <code>Artifact</code>s
* @return the resulting <code>GraphvizGraph</code> that can be dumped
*/
public <T extends Artifact<T>> GraphvizGraph toGraphvizGraph(Matchings<T> matchings, T lRoot, T rRoot) {
GraphvizGraph graph = new GraphvizGraph(false, GRAPH);
graph.attributeStmt(NODE).attribute("shape", "box");
graph.attribute("rankdir", "TB");
graph.attribute("compound", "true");
Map<T, GraphvizNode> lNodes = addSubGraph(lRoot, "Left", graph);
Map<T, GraphvizNode> rNodes = addSubGraph(rRoot, "Right", graph);
GraphvizNode lRootNode = lNodes.get(lRoot);
GraphvizNode rRootNode = rNodes.get(rRoot);
GraphvizEdge align = graph.edge(lRootNode, rRootNode);
align.attribute("ltail", lRootNode.getGraph().getId());
align.attribute("lhead", rRootNode.getGraph().getId());
align.attribute("style", "invis");
align.attribute("constraint", "false");
for (Matching<T> matching : matchings) {
GraphvizNode from = lNodes.get(matching.getLeft());
GraphvizNode to = rNodes.get(matching.getRight());
if (from == null || to == null) {
continue;
}
graph.edge(from, to).attribute("constraint", "false");
}
return graph;
}
/**
* Adds a clustered sub-graph containing nodes for all for all artifacts in the tree under <code>root</code> and
* appropriate edges between them.
*
* @param root
* the root of the tree to add as a <code>GraphvizSubGraph</code>
* @param label
* the label for the <code>GraphvizSubGraph</code>
* @param graph
* the <code>GraphvizGraph</code> to add the <code>GraphvizSubGraph</code> to
* @param <T>
* the type of the <code>Artifact</code>s
* @return the <code>GraphvizNode</code>s that were added for the tree under <code>root</code>
*/
private <T extends Artifact<T>> Map<T, GraphvizNode> addSubGraph(T root, String label, GraphvizGraph graph) {
GraphvizSubGraph subGraph = graph.subGraphCluster();
subGraph.attribute("label", label);
subGraph.attributeStmt(EDGE).attribute("color", toHexString(0, 0, 0, 100));
List<T> bfs = Artifacts.bfs(root);
Map<T, GraphvizNode> nodes = new HashMap<>();
for (T artifact : bfs) {
GraphvizNode node = subGraph.node();
nodes.put(artifact, node);
node.attribute("label", artifact.toString());
}
for (T artifact : bfs) {
GraphvizNode fromNode = nodes.get(artifact);
for (T c : artifact.getChildren()) {
subGraph.edge(fromNode, nodes.get(c));
}
}
// now we force dot to respect the order of the children
for (T artifact : bfs) {
ArtifactList<T> ch = artifact.getChildren();
if (ch.size() > 1) {
GraphvizSubGraph oSubGraph = subGraph.subGraph();
oSubGraph.attributeStmt(EDGE).attribute("style", "invis");
oSubGraph.attribute("rank", "same");
Iterator<T> it = ch.iterator();
GraphvizNode last = nodes.get(it.next());
while (it.hasNext()) {
GraphvizNode next = nodes.get(it.next());
oSubGraph.edge(last, next);
last = next;
}
}
}
return nodes;
}
/**
* Constructs a hex color <code>String</code> from the given color components.
*
* @param r
* the red component
* @param g
* the green component
* @param b
* the blue component
* @param a
* the alpha component
* @return the hex color <code>String</code>
*/
private String toHexString(int r, int g, int b, int a) {
return String.format("#%02x%02x%02x%02x", r, g, b, a);
}
}