use of com.randomnoun.build.javaToGraphviz.dag.DagNode in project java-to-graphviz by randomnoun.
the class DagNodeFilter method removeNodes.
// @TODO if the incoming keepNodes are different but share a common ancestor, could
// replace one of the edge chains with a single edge
/**
* Clean up the dag by removing unused edges and nodes.
* There are two operations performed here:
* <ul><li>'mergeEdges': if multiple inward edges on a node have the same lastKeepNode,
* then remove everything back to that lastKeepNode
* <pre>
* /--> B --> C --> D --\
* A < > H
* \--> E --> F --> G --/
* </pre>
* becomes
* <pre>
* A -> H
* </pre>
* assuming B through G are not keepNodes
*
* <li>'shortenPath': if a node has 1 inward edge and 1 outward edge, remove the node and merge the edges, i.e.
* <pre>
* A -> B -> C
* </pre>
* becomes
* <pre>
* A -> C
* </pre>
* assuming B is not a keepNode
* </ul>
*
* @param dag
* @param node
* @param mergeEdges
*/
// this will probably overflow the stack
// and will removeNodes() the same nodes over and over again
// maybe keep a stack of nodes to process and iterate over that instead.
private void removeNodes(DagNode node, boolean mergeEdges) {
// Set<DagNode> seenNodes
Set<DagNode> redoNodes = new LinkedHashSet<>();
redoNodes.add(node);
while (!redoNodes.isEmpty()) {
node = redoNodes.iterator().next();
redoNodes.remove(node);
// already removed
if (!dag.nodes.contains(node)) {
continue;
}
List<DagEdge> inEdges = node.inEdges;
List<DagEdge> outEdges = node.outEdges;
if (mergeEdges && inEdges.size() > 0) {
// and merge the edges
for (int i = 0; i < inEdges.size() - 1; i++) {
for (int j = i + 1; j < inEdges.size(); j++) {
DagEdge inEdge1 = inEdges.get(i);
DagEdge inEdge2 = inEdges.get(j);
DagNode inEdge1KeepNode = inEdge1.n1.lastKeepNode;
DagNode inEdge2KeepNode = inEdge2.n1.lastKeepNode;
// if inEdge1KeepNode == null for both, then could remove diagram above this node ?
if (inEdge1KeepNode == inEdge2KeepNode && inEdge1KeepNode != null) {
logger.debug("on node " + node.name + ", merged edges back to " + inEdge1KeepNode.name + ", i=" + i + ", j=" + j);
DagEdge newEdge = new DagEdge();
newEdge.n1 = inEdge1KeepNode;
newEdge.n2 = node;
// newEdge.label = "something";
// format the edge a bit ?
boolean prunedBackEdge = false;
prunedBackEdge |= pruneEdges(inEdge1, node, inEdge1KeepNode);
prunedBackEdge |= pruneEdges(inEdge2, node, inEdge2KeepNode);
newEdge.back = prunedBackEdge;
if ((newEdge.n1 != newEdge.n2) && !dag.hasEdge(newEdge.n1, newEdge.n2)) {
dag.addEdge(newEdge);
}
redoNodes.add(inEdge1KeepNode);
// restart from the first inEdge of this node
i = 0;
j = 1;
}
}
}
}
if (inEdges.size() == 0 && outEdges.size() == 1 && !node.keepNode) {
// if this was a rootNode, then the rootNode is now the next node
DagEdge outEdge = outEdges.get(0);
DagNode nextNode = outEdge.n2;
dag.removeEdge(outEdge);
logger.debug("removed start node " + node.type + ", " + node.lineNumber + ", " + node.name);
dag.nodes.remove(node);
redoNodes.add(nextNode);
} else if (inEdges.size() == 1 && outEdges.size() == 0 && !node.keepNode) {
DagEdge inEdge = inEdges.get(0);
dag.removeEdge(inEdge);
logger.debug("removed terminal node " + node.type + ", " + node.lineNumber + ", " + node.name);
dag.nodes.remove(node);
} else if (inEdges.size() >= 1 && outEdges.size() == 1 && !node.keepNode) {
// DagEdge inEdge = inEdges.get(0);
DagEdge outEdge = outEdges.get(0);
for (DagEdge inEdge : new ArrayList<>(inEdges)) {
// already removed
if (inEdge.n1 == null) {
continue;
}
// already removed
if (inEdge.n2 == null) {
continue;
}
// hmm
if (inEdge.n1 == inEdge.n2) {
continue;
}
DagEdge newEdge = new DagEdge();
newEdge.n1 = inEdge.n1;
newEdge.n2 = outEdge.n2;
newEdge.label = inEdge.label;
newEdge.back = inEdge.back || outEdge.back;
// if (newEdge.back) { newEdge.classes.add("back"); }
newEdge.classes.addAll(inEdge.classes);
newEdge.classes.addAll(outEdge.classes);
// if either edge was colored (break edges), the merged edge is as well
// String color = inEdge.gvAttributes.get("color") == null ? outEdge.gvAttributes.get("color") : inEdge.gvAttributes.get("color");
// if (color != null) { newEdge.gvAttributes.put("color", color); }
newEdge.gvAttributes.putAll(inEdge.gvAttributes);
newEdge.gvAttributes.putAll(outEdge.gvAttributes);
if (!dag.hasEdge(newEdge.n1, newEdge.n2)) {
dag.addEdge(newEdge);
} else {
// merge classes, attributes, back flag into the existing edge ?
}
dag.removeEdge(inEdge);
}
DagNode nextNode = outEdge.n2;
dag.removeEdge(outEdge);
logger.debug("removed node " + node.type + ", " + node.lineNumber + ", " + node.name);
dag.nodes.remove(node);
// start from n1 again as removing this node may make it possible to remove the parent node
redoNodes.add(nextNode);
} else if (true) {
// logger.warn(node.outEdges.size() + " outEdges from node " + node.name);
for (DagEdge e : new ArrayList<DagEdge>(node.outEdges)) {
// logger.info("outEdges from " + node.type + ":" + node.line);
if (dag.edges.contains(e)) {
if (dag.nodes.contains(e.n2)) {
if (!e.back) {
// don't follow back edges
// logger.info("checking " + e.n1.type + ":" + e.n1.line + " -> " + e.n2.type + ":" + e.n2.line + ", " + e.n2.name);
redoNodes.add(e.n2);
}
} else {
logger.warn("subnode " + e.n2.name + " missing from " + node.name);
dag.removeEdge(e);
}
} else {
// logger.warn("edge " + e + " missing from " + node.name);
}
}
// if there's no outEdges or inEdges, check the children.
if (node.inEdges.size() == 0 && node.outEdges.size() == 0) {
for (DagNode n : node.children) {
redoNodes.add(n);
}
}
}
}
}
use of com.randomnoun.build.javaToGraphviz.dag.DagNode in project java-to-graphviz by randomnoun.
the class DagStyleApplier method setDagSubgraphLiterals.
// copy any literal elements into their containing subgraphs
private void setDagSubgraphLiterals(Document document, CSSStyleSheet stylesheet) {
Stack<DagSubgraph> subgraphStack = new Stack<>();
final CSSOMParser inlineParser = new CSSOMParser();
inlineParser.setErrorHandler(new ExceptionErrorHandler());
NodeTraversor.traverse(new NodeVisitor() {
@Override
public void head(Node node, int depth) {
if (node instanceof DagElement) {
DagElement dagElement = (DagElement) node;
String tagName = ((Element) node).tagName();
DagSubgraph dagSubgraph = dagElement.dagSubgraph;
DagNode dagNode = dagElement.dagNode;
if (dagSubgraph != null) {
subgraphStack.push(dagSubgraph);
}
if ("literal".equals(tagName)) {
if (subgraphStack.size() == 0) {
// could add to root subgraph instead
throw new IllegalStateException("literal outside of subgraph");
}
subgraphStack.peek().literals.add(dagNode.label);
}
}
}
@Override
public void tail(Node node, int depth) {
if (node instanceof DagElement) {
DagElement dagElement = (DagElement) node;
if (dagElement.dagSubgraph != null) {
subgraphStack.pop();
}
}
}
}, document.body());
}
use of com.randomnoun.build.javaToGraphviz.dag.DagNode in project java-to-graphviz by randomnoun.
the class DagStyleApplier method getDagElements.
// recursively within this subgraph only
public List<DagElement> getDagElements(Map<DagNode, DagElement> dagNodesToElements, DagSubgraph sg, DagNode node) {
List<DagElement> result = new ArrayList<>();
// clear applied styles
node.gvStyles = new HashMap<>();
boolean isLiteral = node.classes.contains("gv-literal");
DagElement nodeElement = new DagElement(isLiteral ? "literal" : "node", node, node.gvAttributes);
dagNodesToElements.put(node, nodeElement);
for (int i = 0; i < node.children.size(); i++) {
DagNode childNode = node.children.get(i);
if (dagNodesToElements.containsKey(childNode)) {
// should never happen, but skip it
logger.warn("repeated node in Dag");
} else if (!sg.nodes.contains(childNode)) {
// wrong subgraph, skip it
} else {
nodeElement.appendChildren(getDagElements(dagNodesToElements, sg, childNode));
}
}
result.add(nodeElement);
String inNodeIds = null;
String outNodeIds = null;
Set<String> inNodeClasses = new HashSet<>();
Set<String> outNodeClasses = new HashSet<>();
for (int j = 0; j < dag.edges.size(); j++) {
/* TODO: outrageously inefficient */
DagEdge edge = dag.edges.get(j);
// if (sg.nodes.contains(edge.n1)) {
if (edge.n1 == node) {
// clear applied styles
edge.gvStyles = new HashMap<>();
DagElement edgeElement = new DagElement(edge, edge.gvAttributes);
// child.attr("id", edge.name);
if (includeIncomingIdAttributes) {
edgeElement.attr("inNodeId", edge.n1.name);
}
if (includeOutgoingIdAttributes) {
edgeElement.attr("outNodeId", edge.n2.name);
}
if (includeIncomingClassAttributes) {
edgeElement.attr("inNodeClass", Text.join(edge.n1.classes, " "));
}
if (includeOutgoingClassAttributes) {
edgeElement.attr("outNodeClass", Text.join(edge.n2.classes, " "));
}
outNodeIds = outNodeIds == null ? edge.n2.name : outNodeIds + " " + edge.n2.name;
outNodeClasses.addAll(edge.n2.classes);
result.add(edgeElement);
}
if (edge.n2 == node) {
inNodeIds = inNodeIds == null ? edge.n1.name : inNodeIds + " " + edge.n1.name;
inNodeClasses.addAll(edge.n1.classes);
}
}
if (includeIncomingIdAttributes) {
nodeElement.attr("inNodeId", inNodeIds);
}
if (includeOutgoingIdAttributes) {
nodeElement.attr("outNodeId", outNodeIds);
}
if (includeIncomingClassAttributes) {
nodeElement.attr("inNodeClass", Text.join(inNodeClasses, " "));
}
if (includeOutgoingClassAttributes) {
nodeElement.attr("outNodeClass", Text.join(outNodeClasses, " "));
}
return result;
}
use of com.randomnoun.build.javaToGraphviz.dag.DagNode in project java-to-graphviz by randomnoun.
the class DagStyleApplier method setDagStyles.
private void setDagStyles(Document document, CSSStyleSheet stylesheet, boolean setIds) {
// copy the calculated styles from the DOM back into the gvStyles field
final CSSOMParser inlineParser = new CSSOMParser();
inlineParser.setErrorHandler(new ExceptionErrorHandler());
NodeTraversor.traverse(new NodeVisitor() {
@Override
public void head(Node node, int depth) {
if (node instanceof DagElement && node.hasAttr("style")) {
// parse the CSS into a CSSStyleDeclaration
InputSource input = new InputSource(new StringReader(node.attr("style")));
CSSStyleDeclaration declaration = null;
try {
declaration = inlineParser.parseStyleDeclaration(input);
} catch (IOException e) {
throw new IllegalStateException("IOException on string", e);
}
DagElement dagElement = (DagElement) node;
String tagName = ((Element) node).tagName();
// Dag dag = dagElement.dag;
DagNode dagNode = dagElement.dagNode;
DagEdge dagEdge = dagElement.dagEdge;
DagSubgraph dagSubgraph = dagElement.dagSubgraph;
if (dagSubgraph != null) {
for (int i = 0; i < declaration.getLength(); i++) {
String prop = declaration.item(i);
if (tagName.equals("graph") || tagName.equals("subgraph")) {
logger.debug("setting graph prop " + prop + " to " + declaration.getPropertyValue(prop));
dagSubgraph.gvStyles.put(prop, declaration.getPropertyValue(prop));
} else if (tagName.equals("graphNode")) {
logger.debug("setting graphNode prop " + prop + " to " + declaration.getPropertyValue(prop));
dagSubgraph.gvNodeStyles.put(prop, declaration.getPropertyValue(prop));
} else if (tagName.equals("graphEdge")) {
logger.debug("setting graphEdge prop " + prop + " to " + declaration.getPropertyValue(prop));
dagSubgraph.gvEdgeStyles.put(prop, declaration.getPropertyValue(prop));
}
}
if ((tagName.equals("graph") || tagName.equals("subgraph")) && setIds) {
setIdLabel(dagSubgraph);
}
} else if (dagNode != null) {
for (int i = 0; i < declaration.getLength(); i++) {
String prop = declaration.item(i);
logger.debug("setting " + dagNode.name + " prop " + prop + " to " + declaration.getPropertyValue(prop));
dagNode.gvStyles.put(prop, declaration.getPropertyValue(prop));
}
if (setIds) {
setIdLabel(dagNode);
}
} else if (dagEdge != null) {
for (int i = 0; i < declaration.getLength(); i++) {
String prop = declaration.item(i);
logger.debug("setting dagEdge prop " + prop + " to " + declaration.getPropertyValue(prop));
dagEdge.gvStyles.put(prop, declaration.getPropertyValue(prop));
}
if (setIds) {
setIdLabel(dagEdge);
}
}
}
}
@Override
public void tail(Node node, int depth) {
}
}, document.body());
}
use of com.randomnoun.build.javaToGraphviz.dag.DagNode in project java-to-graphviz by randomnoun.
the class DagStyleApplier method inlineStyles.
/**
* Apply the rules in the stylesheet to the Dag, and populates the gvStyles
* fields on the Dag, DagEdge and DagNode objects.
*
* @param stylesheet
*
* @throws IOException
*/
public void inlineStyles(CSSStyleSheet stylesheet) throws IOException {
// OK so before we create the subgraphs, create dag edges for things that are enabled by style properties
// applyDomStyles(document, stylesheet);
// List<DagElement> elementsToExpandInExcruciatingDetail = getElementsWithStyleProperty(document, "gv-fluent", "true"); // enable method nodes
// use the gv-newSubgraph CSS properties to add subgraphs into both the Dag and the DOM
resetDomStyles(document);
applyDomStyles(document, stylesheet);
Set<DagElement> elementsToCreateSubgraphsInside = getElementsWithStyleProperty(document, "gv-newSubgraph", "true").keySet();
Map<DagElement, String> elementsTruncateEdges = getElementsWithStyleProperty(document, "gv-truncateEdges", null);
Set<DagElement> elementsToCreateSubgraphsFrom = getElementsWithStyleProperty(document, "gv-beginOuterSubgraph", "true").keySet();
Set<DagElement> elementsToCreateSubgraphsTo = getElementsWithStyleProperty(document, "gv-endOuterSubgraph", "true").keySet();
// DagElement outsideEl = elementsToCreateSubgraphsInside.get(i);
for (DagElement outsideEl : elementsToCreateSubgraphsInside) {
DagNode outsideNode = outsideEl.dagNode;
DagSubgraph newSg;
DagSubgraph sg = dag.dagNodeToSubgraph.get(outsideNode);
if (sg == null) {
throw new IllegalStateException("this shouldn't happen any more");
} else {
newSg = new DagSubgraph(dag, sg);
sg.subgraphs.add(newSg);
}
newSg.lineNumber = outsideNode.lineNumber;
newSg.gvAttributes = new HashMap<>(outsideNode.gvAttributes);
// moves the nodes in the dom
DagElement newSgEl = new DagElement("subgraph", newSg, newSg.gvAttributes);
while (outsideEl.childrenSize() > 0) {
Element c = outsideEl.child(0);
c.remove();
newSgEl.appendChild(c);
}
outsideEl.appendChild(newSgEl);
// moves the nodes in the dag subgraphs
moveToSubgraph(newSg, newSgEl, dag, dagNodesToElements, outsideNode);
String truncateEdges = elementsTruncateEdges.get(outsideEl);
if (truncateEdges != null && !truncateEdges.equals("none")) {
// outsideEl.addClass("red");
// outsideNode.classes.add("red");
boolean truncateIncoming = false;
boolean truncateOutgoing = false;
if (truncateEdges.equals("incoming")) {
truncateIncoming = true;
} else if (truncateEdges.equals("outgoing")) {
truncateOutgoing = true;
} else if (truncateEdges.equals("both")) {
truncateIncoming = true;
truncateOutgoing = true;
} else {
throw new IllegalArgumentException("Invalid 'gv-truncateEdges' property '" + truncateEdges + "'; expected " + "'none', 'incoming', 'outgoing' or 'both'");
}
List<DagEdge> inEdges = new ArrayList<>();
List<DagEdge> outEdges = new ArrayList<>();
// actually maybe I do now
for (DagEdge e : dag.edges) {
if (truncateOutgoing && newSg.nodes.contains(e.n1) && !newSg.nodes.contains(e.n2)) {
outEdges.add(e);
}
if (truncateIncoming && newSg.nodes.contains(e.n2) && !newSg.nodes.contains(e.n1)) {
inEdges.add(e);
}
}
for (DagEdge e : outEdges) {
// e.n1 = newSg; // needs to be node -> node, not subgraph -> node
// instead, outgoing edges need 'ltail' with the name of the source subgraph
// don't know the name of this subgraph yet
e.gvObjectStyles.put("ltail", newSg);
}
for (DagEdge e : inEdges) {
// don't know the name of this subgraph yet
e.gvObjectStyles.put("lhead", newSg);
}
}
}
// DagElement fromEl = elementsToCreateSubgraphsFrom.get(i);
for (DagElement fromEl : elementsToCreateSubgraphsFrom) {
Element fromParentEl = fromEl.parent();
DagNode fromNode = fromEl.dagNode;
DagSubgraph newSg;
DagSubgraph sg = dag.dagNodeToSubgraph.get(fromNode);
if (sg == null) {
throw new IllegalStateException("this shouldn't happen any more");
} else {
newSg = new DagSubgraph(dag, sg);
sg.subgraphs.add(newSg);
}
newSg.lineNumber = fromNode.lineNumber;
newSg.gvAttributes = new HashMap<>(fromNode.gvAttributes);
// moves the nodes in the dom
DagElement newSgEl = new DagElement("subgraph", newSg, newSg.gvAttributes);
int idx = fromEl.elementSiblingIndex();
boolean done = false;
while (idx < fromParentEl.childrenSize() && !done) {
// TODO: could probably remove the from and to nodes completely, but then I'll have to rejig the edges,
// although they should always be straight-through so that should be easy enough
// and then apply the 'from' styles to the newly-created subgraph
DagElement c = (DagElement) fromParentEl.child(idx);
if (elementsToCreateSubgraphsTo.contains(c)) {
elementsToCreateSubgraphsTo.remove(c);
done = true;
}
c.remove();
newSgEl.appendChild(c);
// moves the nodes in the dag subgraphs
if (c.dagNode != null) {
// dag subgraphs don't contain edges
moveToSubgraph(newSg, newSgEl, dag, dagNodesToElements, c.dagNode);
}
}
if (!done) {
logger.warn("gv-subgraph without gv-end, closing subgraph at AST boundary");
}
fromParentEl.insertChildren(idx, newSgEl);
}
if (elementsToCreateSubgraphsTo.size() > 0) {
throw new IllegalStateException("gv-end without gv-subgraph");
}
// reapply styles now the DOM contains CSS-defined subgraph elements
resetDomStyles(document);
applyDomStyles(document, stylesheet);
// true = set IDs
setDagStyles(document, stylesheet, true);
// TODO: arguably now that the node IDs have changed, couple reapply a third time in case there are any
// ID-specific CSS rules
// TODO: also recreate element inNodeId, outNodeId inNodeIds, outNodeIds attributes
// applyStyles(document, stylesheet, dag);
setDagSubgraphLiterals(document, stylesheet);
}
Aggregations