use of com.randomnoun.build.javaToGraphviz.dag.DagSubgraph in project java-to-graphviz by randomnoun.
the class ControlFlowEdger method addMethodDeclarationEdges.
// draw lines from each statement to each other, with an artifical node at the end
// that all the thrown exceptions throw to, and that all the return statements return to.
//
// methods don't have any edges leaving them, but we add one anyway so that we can layout
// methods inside anonymous classes better in graphviz. ( this edge will be transparent )
private List<ExitEdge> addMethodDeclarationEdges(Dag dag, DagNode method, LexicalScope scope) {
MethodDeclaration md = (MethodDeclaration) method.astNode;
// method.label = "method " + md.getName();
method.gvAttributes.put("methodName", md.getName().toString());
// last child is the block
DagNode methodBlock = method.children.get(method.children.size() - 1);
addEdge(method, methodBlock);
LexicalScope lexicalScope = new LexicalScope();
List<ExitEdge> ee = addBlockEdges(dag, methodBlock, lexicalScope);
// add a node which all the return edges return to
// this is an artificial node so maybe only construct it based on some gv declaration earlier on ?
// (whereas all the other nodes are about as concrete as anything else in IT)
// CompilationUnit cu = methodBlock.astNode.getParent();
CompilationUnit cu = ASTResolving.findParentCompilationUnit(methodBlock.astNode);
int endOfMethodLine = cu.getLineNumber(methodBlock.astNode.getStartPosition() + methodBlock.astNode.getLength());
DagNode returnNode = new DagNode();
returnNode.keepNode = method.keepNodeMatcher.matches("methodDeclarationEnd");
// label this 'end' if it's a void method ?
returnNode.type = "methodDeclarationEnd";
returnNode.lineNumber = endOfMethodLine;
// rn.name = dag.getUniqueName("m_" + endOfMethodLine);
returnNode.classes.add("method");
returnNode.classes.add("end");
// rn.label = "return";
returnNode.astNode = null;
// keep the return node in the method grouping
method.children.add(returnNode);
DagSubgraph sg = dag.dagNodeToSubgraph.get(method);
dag.addNode(sg, returnNode);
for (ExitEdge e : lexicalScope.returnEdges) {
e.n2 = returnNode;
addEdge(e);
}
// and everything that was thrown connects to this node as well
for (ExitEdge e : lexicalScope.throwEdges) {
e.n2 = returnNode;
addEdge(e);
}
// and everything flowing out of the first block connects to this node as well
for (ExitEdge e : ee) {
e.n2 = returnNode;
addEdge(e);
}
// there's no exit edges out of a method
// return Collections.emptyList();
// maybe there is now so we can draw an edge out of anonymous classes
ExitEdge e = new ExitEdge();
e.n1 = returnNode;
return Collections.singletonList(e);
}
use of com.randomnoun.build.javaToGraphviz.dag.DagSubgraph in project java-to-graphviz by randomnoun.
the class ControlFlowEdger method addInfixExpressionEdges.
private List<ExitEdge> addInfixExpressionEdges(Dag dag, DagNode infixNode, LexicalScope scope) {
InfixExpression ie = (InfixExpression) infixNode.astNode;
DagNode leftDag = getDagChild(infixNode.children, ie.getLeftOperand(), null);
DagNode rightDag = getDagChild(infixNode.children, ie.getRightOperand(), null);
List<DagNode> extendedDags = getDagChildren(infixNode.children, ie.extendedOperands(), null);
// Operator is a class, not an enum (!)
Operator op = ie.getOperator();
infixNode.gvAttributes.put("operatorToken", op.toString());
// @TODO camelcase
infixNode.gvAttributes.put("operatorName", Text.getLastComponent(op.getClass().getName()));
// a + b becomes a -> b -> +
// a + b + c should becomes a -> b -> + -> c -> + , which has two + nodes, even though there's only one in the AST. because you know. eclipse.
// move infixExpression node after the a and b nodes
Rejigger rejigger = hoistNode(dag, infixNode, leftDag);
List<ExitEdge> prevNodes = null;
if (op == Operator.CONDITIONAL_AND || op == Operator.CONDITIONAL_OR) {
// InfixExpressions also include shortcut || which doesn't evaluate the second parameter if the first is true
// so it should be a something a bit more complicated, control-flow wise
// a
// true? -N-> b
// :Y :
// v v
infixNode.classes.add("infixConditional");
prevNodes = addExpressionEdges(dag, leftDag, scope);
prevNodes = rejigger.unhoistNode(dag, prevNodes);
// graphviz diagram is a bit mong unless we swap the false and true edge orders. maybe.
ExitEdge trueEdge = prevNodes.get(0);
trueEdge.classes.add("infixConditional");
trueEdge.classes.add(op == Operator.CONDITIONAL_OR ? "true" : "false");
DagEdge falseEdge = addEdge(infixNode, rightDag);
// well this is the non-shortcut branch, but hey
falseEdge.classes.add("infixConditional");
falseEdge.classes.add(op == Operator.CONDITIONAL_OR ? "false" : "true");
List<ExitEdge> lastPrevNodes = addExpressionEdges(dag, rightDag, scope);
for (int i = 0; i < extendedDags.size(); i++) {
// actually probably need to add a new node here
DagNode n = extendedDags.get(i);
DagNode extInfixNode = new DagNode();
extInfixNode.keepNode = infixNode.keepNodeMatcher.matches("infixExpressionCondition");
// even though it isn't
extInfixNode.type = "infixExpressionCondition";
// even though it isn't
extInfixNode.lineNumber = n.lineNumber;
extInfixNode.classes.add("infixExpression");
extInfixNode.classes.add("infixConditional");
extInfixNode.astNode = null;
extInfixNode.gvAttributes.put("operatorToken", op.toString());
// @TODO camelcase
extInfixNode.gvAttributes.put("operatorName", Text.getLastComponent(op.getClass().getName()));
DagSubgraph sg = dag.dagNodeToSubgraph.get(infixNode);
dag.addNode(sg, extInfixNode);
// needs to be a child of ieNode as well so it's moved to subgraphs when that node moves
infixNode.children.add(extInfixNode);
for (ExitEdge e : lastPrevNodes) {
e.n2 = extInfixNode;
dag.edges.add(e);
}
trueEdge = new ExitEdge();
trueEdge.n1 = extInfixNode;
trueEdge.classes.add("infixConditional");
trueEdge.classes.add(op == Operator.CONDITIONAL_OR ? "true" : "false");
prevNodes.add(0, trueEdge);
falseEdge = addEdge(extInfixNode, n);
// well this is the non-shortcut branch, but hey
falseEdge.classes.add("infixConditional");
falseEdge.classes.add(op == Operator.CONDITIONAL_OR ? "false" : "true");
lastPrevNodes = addExpressionEdges(dag, n, scope);
}
prevNodes.addAll(lastPrevNodes);
} else {
// non-shortcut e.g. +, just evaluate in order
for (DagNode a : infixNode.children) {
if (prevNodes != null) {
for (ExitEdge e : prevNodes) {
e.n2 = a;
addEdge(e);
}
}
prevNodes = addExpressionEdges(dag, a, scope);
}
prevNodes = rejigger.unhoistNode(dag, prevNodes);
}
return prevNodes;
}
use of com.randomnoun.build.javaToGraphviz.dag.DagSubgraph in project java-to-graphviz by randomnoun.
the class ControlFlowEdger method addClassInstanceCreationEdges.
private List<ExitEdge> addClassInstanceCreationEdges(Dag dag, DagNode node, LexicalScope scope) {
ClassInstanceCreation cic = (ClassInstanceCreation) node.astNode;
// maybe we evaluate the index first ? not sure. reckon it's probably the array ref
DagNode expressionDag = getDagChild(node.children, cic.getExpression(), null);
List<DagNode> argumentDags = getDagChildren(node.children, cic.arguments(), null);
DagNode anonClassDag = getDagChild(node.children, cic.getAnonymousClassDeclaration(), "anonymousClass");
// AnonymousClassDeclaration
node.gvAttributes.put("type", cic.getType().toString());
// DagNode indexDag = getDagChild(node.children, fa.getIndex(), null);
// DagNode fieldDag = getDagChild(node.children, fa.getName(), null); // will put the name on the FA node
List<ExitEdge> prevNodes = null;
if (expressionDag != null || argumentDags.size() > 0) {
// move methodInvocation node after the expression & argument nodes
Rejigger rejigger = hoistNode(dag, node, expressionDag != null ? expressionDag : argumentDags.get(0));
// expression is null for method calls within the same object
if (expressionDag != null) {
prevNodes = addExpressionEdges(dag, expressionDag, scope);
}
for (DagNode a : argumentDags) {
if (prevNodes != null) {
for (ExitEdge e : prevNodes) {
e.n2 = a;
e.classes.add("invocationArgument");
addEdge(e);
}
}
prevNodes = addExpressionEdges(dag, a, scope);
}
prevNodes = rejigger.unhoistNode(dag, prevNodes);
} else {
ExitEdge e = new ExitEdge();
e.n1 = node;
prevNodes = Collections.singletonList(e);
}
// jam the anonymous class in here as if it was a lambda, but with methods like a class
if (anonClassDag != null) {
for (ExitEdge e : prevNodes) {
e.n2 = anonClassDag;
addEdge(e);
}
AnonymousClassDeclaration acdNode = (AnonymousClassDeclaration) anonClassDag.astNode;
List<DagNode> bodyDeclarationDags = getDagChildren(anonClassDag.children, acdNode.bodyDeclarations(), null);
LexicalScope lexicalScope = scope.newTypeScope();
// add edges for everything in the class
// @TODO check fields don't appear though. unless they initalise things, then maybe. Or maybe that goes into the constructors.
// or not.
// yeesh what about the static initializers. what about them indeed.
List<ExitEdge> ees = new ArrayList<>();
for (DagNode n : bodyDeclarationDags) {
// add a transparent edge to each thing defined in this class so that the 'AnonymousClassDeclaration' node appears above them
DagEdge e = addEdge(anonClassDag, n);
e.classes.add("anonymousClassDeclarationBegin");
ees.addAll(addEdges(dag, n, lexicalScope));
}
// add an artificial node so we can create an edge out of this thing
CompilationUnit cu = ASTResolving.findParentCompilationUnit(acdNode);
int endOfAnonClassLine = cu.getLineNumber(acdNode.getStartPosition() + acdNode.getLength());
DagNode returnNode = new DagNode();
returnNode.keepNode = anonClassDag.keepNodeMatcher.matches("anonymousClassDeclarationEnd");
returnNode.type = "anonymousClassDeclarationEnd";
returnNode.lineNumber = endOfAnonClassLine;
// rn.name = dag.getUniqueName("m_" + endOfMethodLine);
returnNode.classes.add("anonymousClassDeclaration");
returnNode.classes.add("end");
// rn.label = "return";
returnNode.astNode = null;
// include the artificial return inside the lambda grouping
anonClassDag.children.add(returnNode);
DagSubgraph sg = dag.dagNodeToSubgraph.get(anonClassDag);
dag.addNode(sg, returnNode);
for (ExitEdge ee : ees) {
// add a transparent edge from each thing defined in this class to the artifical node
// so that it appears underneath them
ee.n2 = returnNode;
ee.classes.add("anonymousClassDeclarationEnd");
addEdge(ee);
}
// there's no exit edges out of an anonymous class
// this gets truncated to the subgraph boundary in some css
ExitEdge e = new ExitEdge();
e.n1 = returnNode;
prevNodes = Collections.singletonList(e);
}
return prevNodes;
}
use of com.randomnoun.build.javaToGraphviz.dag.DagSubgraph in project java-to-graphviz by randomnoun.
the class ControlFlowEdger method addLambdaExpressionEdges.
private List<ExitEdge> addLambdaExpressionEdges(Dag dag, DagNode lambdaNode, LexicalScope scope) {
// lambda expression is going to be a big like a method declaration in the middle of a method.
// but has an exit edge
LambdaExpression le = (LambdaExpression) lambdaNode.astNode;
// method.label = "method " + md.getName();
// method.gvAttributes.put("methodName", md.getName().toString());
// we don't create nodes for method parameters yet, so not going to do that for lambdas either
// (maybe we should ? )
// when a lambda is defined control flow doesn't pass to the block,
// so maybe I skip all of that somehow. OK so let's create an edge so it's grouped together but hide it in the diagram.
DagNode blockNode = lambdaNode.children.get(lambdaNode.children.size() - 1);
DagEdge lambdaEntryEdge = addEdge(lambdaNode, blockNode);
lambdaEntryEdge.classes.add("lambdaEntry");
// @TODO these probably need a new lexical scope
LexicalScope lexicalScope = scope.newLambdaScope();
List<ExitEdge> ee;
if (blockNode.type.equals("Block")) {
ee = addBlockEdges(dag, blockNode, lexicalScope);
} else if (blockNode.astNode instanceof Expression) {
ee = addExpressionEdges(dag, blockNode, lexicalScope);
} else {
throw new IllegalStateException("expected Block or Expression in lambda");
}
// add a node which all the return edges return to
// this is an artificial node so maybe only construct it based on some gv declaration earlier on ?
// (whereas all the other nodes are about as concrete as anything else in IT)
// CompilationUnit cu = methodBlock.astNode.getParent();
CompilationUnit cu = ASTResolving.findParentCompilationUnit(le);
int endOfLambdaLine = cu.getLineNumber(le.getStartPosition() + le.getLength());
DagNode returnNode = new DagNode();
returnNode.keepNode = lambdaNode.keepNodeMatcher.matches("lambdaExpressionEnd");
// label this 'end' if it has no return value ?
returnNode.type = "lambdaExpressionEnd";
returnNode.lineNumber = endOfLambdaLine;
// rn.name = dag.getUniqueName("m_" + endOfMethodLine);
returnNode.classes.add("lambdaExpression");
returnNode.classes.add("end");
// rn.label = "return";
returnNode.astNode = null;
// include the artificial return inside the lambda grouping
lambdaNode.children.add(returnNode);
DagSubgraph sg = dag.dagNodeToSubgraph.get(lambdaNode);
dag.addNode(sg, returnNode);
for (ExitEdge e : lexicalScope.returnEdges) {
e.n2 = returnNode;
addEdge(e);
}
for (ExitEdge e : ee) {
e.n2 = returnNode;
addEdge(e);
}
// and everything that was thrown connects to this node as well
for (ExitEdge e : lexicalScope.throwEdges) {
e.n2 = returnNode;
addEdge(e);
}
// there's no exit edges out of a method, but let's say there is from a lambda
// (it's not a real edge, it gets truncated to the subgraph boundary in some css)
ExitEdge e = new ExitEdge();
e.n1 = returnNode;
return Collections.singletonList(e);
}
use of com.randomnoun.build.javaToGraphviz.dag.DagSubgraph in project java-to-graphviz by randomnoun.
the class JavaToGraphviz method writeGraphviz.
/**
* Generate a single graphviz diagram.
*
* @param writer
* @return true if there are more diagrams, false otherwise
* @throws IOException
*/
public boolean writeGraphviz(Writer writer) throws IOException {
DagSubgraph rootGraph = dag.rootGraphs.get(rootGraphIdx);
PrintWriter pw = new PrintWriter(writer);
// remove edges from previous runs
dag.edges.clear();
// a rootGraph can now contain multiple rootNodes
// but a rootNode can only be in a single rootGraph
int c = 0;
for (DagNode rootNode : dag.rootNodes) {
c++;
if (rootGraph.nodes.contains(rootNode)) {
// logger.info("including rootNode " + c);
String edgerNamesCsv = rootNode.options.get("edgerNamesCsv");
if (Text.isBlank(edgerNamesCsv)) {
edgerNamesCsv = "control-flow";
}
boolean enableKeepNodeFilter = "true".equals(rootNode.options.get("enableKeepNodeFilter"));
List<String> edgerNames;
try {
edgerNames = Text.parseCsv(edgerNamesCsv);
} catch (ParseException e1) {
throw new IllegalArgumentException("edgerNamesCsv is not valid CSV", e1);
}
rootNode.options.get("removeNodes");
LexicalScope lexicalScope = new LexicalScope();
for (String edgerName : edgerNames) {
if (edgerName.equals("control-flow")) {
ControlFlowEdger edger = new ControlFlowEdger(dag);
edger.addEdges(dag, rootNode, lexicalScope);
} else if (edgerName.equals("ast")) {
AstEdger edger = new AstEdger(dag);
edger.addEdges(dag, rootNode, lexicalScope);
// @TODO data-flow
} else {
throw new IllegalArgumentException("unknown edgerName '" + edgerName + "'");
}
}
// going to assume that edges don't cross rootNodes, which they don't yet either.
for (DagNode n : dag.nodes) {
n.inEdges = new ArrayList<>();
n.outEdges = new ArrayList<>();
}
for (DagEdge e : dag.edges) {
e.n1.outEdges.add(e);
e.n2.inEdges.add(e);
}
if (enableKeepNodeFilter) {
DagNodeFilter filter = new DagNodeFilter(dag);
if (rootNode.keepNodeMatcher.matches("startNode")) {
rootNode.keepNode = true;
filter.setLastKeepNode(rootNode, rootNode);
} else {
rootNode.keepNode = false;
}
filter.removeNodes(rootNode);
}
}
}
// subgraphs are now defined in the Dag from the stylesheet
DagStyleApplier dsa = new DagStyleApplier(dag, rootGraph);
Document doc = dsa.createDom();
if (format.equals("dom1")) {
pw.println(doc.toString());
} else {
dsa.inlineStyles(styleSheet);
}
if (format.equals("dom1")) {
// already generated output
} else if (format.equals("dom2")) {
doc = dsa.getDocument();
pw.println(doc.toString());
} else {
pw.println(rootGraph.toGraphviz(0));
}
rootGraphIdx++;
return (rootGraphIdx < dag.rootGraphs.size());
}
Aggregations