Flowcharts-R-Us
|Joining the dots
So it turns out that a lot of people are incapable of reading, or if they can read, to actually absorb the information in that text in any meaningful way.
So in order to explain a bit of code that is mostly words and a fair bit of punctuation, you might find yourself creating a flowchart that describe how control passes from one statement to another, with little arrows proceeding from one rectangle to another, with the arrows between those rectangles signifying the relentless forward march of time.
like a recipe, or something with some decision points in it, like
like a choose-your-own adventure book, if those still exist in this day and age.
And even enlightened, sophisticated people like you or I who know that the Dunnig-Kruger effect isn’t real could do with a diagram to explain the exact sequence of steps involved in your multi-modal authentication scheme for your e-commerce site, if they still call them e-commerce sites in this day and age.
Go on
So if you find yourself in this position, what you’ll probably be doing is creating that diagram in some software called graphviz, so that it can work out the layout for you automatically, and because graphviz diagrams are defined in written lines of text, which as a software developer you find easier to deal with than shapes on a diagram.
And the way in which you’ll be doing that will probably be via reading through the code, converting each statement to a node and each if/else decision to another type of node, and joining them all up with edges, with occasional backwards edges for loops and exceptions and continue/breaks and method calls and returns.
And then you’ll look at that and say something like “Jesus H Christ in a handbasket I’m never going to do that again”, and write some software to do that for you.
Jesus H Christ in a handbasket
So here’s some software I’ve created called java-to-graphviz that takes in java source code as input and converts that into a control flow diagram ( CFD ).
Usually when you parse software you get an abstract syntax tree ( AST ), which is great for compilers, but not so great for users.
For example, when a compiler reads (parses) the following bit of code:
{ before(); for (i = 0; i < 10; i++) { println(i); } after(); }
It creates an in-memory structure that looks a bit like this:
So what java-to-graphviz does is create a parallel representation of that, wiring up edges between each node, and the node that executes after each node ( a 'happened-before' relationship ), without thinking too much about memory barriers or threading, or out-of-order optimisations, or class hierarchies, or dynamic dispatch, or any of that sort of malarky.
And nobody cares about the AST, so you can get rid of the red arrows, and shake it out a bit:
And most people, if they can be persuaded to care about any of this at all, don't want to see most of those nodes and to just get the big picture, which is in fact, a smaller picture, vis-à-vis:
Which are the kinds of diagrams you're going to be getting out of this thing, if you can persuade it not to go into infinite loops following those back edges.
But hang on, I want those nodes and edges to have slightly more readable names
You can do that by adding some comments to your code with some nicer labels. Those comments will start with // gv:
( for 'graphviz' ) and the text that follows that will represent that node in the diagram.
// gv: like this
But hang on, I want some of those nodes to be formatted differently
Well in that case you can add some styles to those comments with some graphviz node formatting directives.
Those styles will be contained in curly brackets ( {
and }
) and look a bit like CSS rules. e.g.
// gv: like this { color: red; }
But hang on, I want all nodes of a particular type to be formatted differently without having to specify the style on each node
Well in that case you can add some classes to your comments by changing the gv:
prefix to gv.className:
instead, and define your classes in a stylesheet in a comment block somewhere else in the document with a gv-style:
prefix,
// gv-style: { .forExample { color: red; } } // gv.forExample: like this
That style block is actually CSS, by the way, so you can create more complicated rules that apply to nodes with multiple styles, or nested styles, or nodes with certain attributes.
But hang on, I want all my styles to be externalised in a separate file
Well in that case you can use an @import "mystyles.css";
comment which will read them from there instead
// gv-style: { @import "mystyle.css"; }
But hang on, I want some styles to only apply to specific nodes
Well in that case you can use a gv#nodeId:
prefix instead, and use a CSS id selector for the rule
// gv-style: { #thisOne { color: green; } } // gv#thisOne: like this
But hang on, I want some styles to apply to all AST nodes of a particular type
Well in that case you can use a gv.nodeType:
prefix, because every AST node will have that AST’s type added as a class automatically.
// gv-style: { .if { shape: diamond; } } if (something) { ... }
But hang on, I’ve got multiple nodes on a line, and/or want to label nodes using a comment on the next/previous line
Well in that case you can add a directional modifier ( ^
, >
, v
and <
for up, right, down and left ) to your comment so java-to-graphviz knows which specific node the comment applies to, like this:
// gv: this comment has it's own node println("s1"); // gv: this comment is for s1 // gv:v: this comment is for s2 println("s2"); println("s3"); // gv:^: this comment is for s3 println("s4"); println("s5"); // gv: this comment is for s4 println("s6"); println("s7"); // gv:<: this comment is for s7 /* gv:>:this comment is for s8 */ println("s8"); println("snot8"); println("s9"); /* gv:<--:this comment is for s9 */ println("s10"); println("s11"); /* gv:-->:this comment is for s12 */ println("s12");
But hang on, I don’t want all these bloody nodes and edges
Well in that case you can filter out the nodes that you don’t want, and all the edges to those nodes will get collapsed for you.
// gv-keepNode: -expressionStatement -block -switchCase
But hang on, does it hook in via JVMTI and animate when the program runs ? Does it show AOP pointcuts ? Does it show the disassembled bytecodes in the nodes ? Does it create complexity metrics ? Can it colour in nodes using jacoco coverage .exec
output ?
No, no, no, no and no.
What about lambdas ?
What about lambdas ?
Does it do those
Well it does now.
But hang on, I want a couple of other things that are too tedious to describe in a blog post
Well in that case there’s a more complete description of the other bells and whistles over on the github site.
Well I’m convinced
Thanks, hypothetical other human being who is going to download and use this thing.
How do I run it ?
If you’re running from the command-line, there’s a few options you can supply:
C:\>java -jar java-to-graphviz-cli.jar Usage: java -jar java-to-graphviz-cli.jar [options] filename filename ... where [options] are: -h -? display this help text --verbose -v increase verbosity ( info level ) --verbose2 -vv increase verbosity a bit more ( debug level ) --format -f dot|dom1|dom2 output format: DOT diagram (default), pre-styled DOM, post-styled DOM --output -o filename send output to filename For multiple output files, use {f} for base filename, {i} for diagram index e.g. "{f}-{i}.dot" for dot output, "{f}.html" for dom output default is stdout --source -s version set Java source language version ( 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ) --base-css -b url replace base css with url ( relative to classpath, then file://./ ) default = JavaToGraphviz.css --user-css -u url add url to list of user css imports --css -c {rules} add css rules --option -p key=value set initial option; can be modified in source by 'gv-option' and 'gv-keepNode' directives Options keys and defaults: edgerNamesCsv=control-flow csv list of edger names possible edger names: control-flow, ast enableKeepNodeFilter=false if true, will perform node filtering defaultKeepNode=true if true, filter will keep nodes by default keepNode= space-separated nodeTypes for fine-grained keepNode control prefix nodeType with '-' to exclude, '+' or no prefix to include e.g. "-expressionStatement -block -switchCase" to exclude those nodeTypes when defaultKeepNode=true NB: any node with a 'gv' comment will always be kept
e.g.
C:\>java -jar java-to-graphviz-cli.jar --base-css https://raw.githubusercontent.com/randomnoun/java-to-graphviz/master/src/main/resources/JavaToGraphviz.css src\test\java\com\example\input\ForStatement.java digraph G { node [ shape = rect; fontname = "Handlee"; ] edge [ fontname = "Handlee"; ] bgcolor = transparent; fontname = "Handlee"; compound = true; s_19 [ class = "methodDeclaration"; label = "MethodDeclaration"; fillcolor = white; style = filled; ]; s_19_3 [ class = "block"; label = "Block"; fillcolor = white; style = filled; ]; ...
How do I run it in maven?
There’s a sample pom.xml over on the java-to-graphviz-maven-plugin site
Source
and here’s the maven plugin:
JARs
If you’re after the CLI jar, you can grab that from maven central.
Well, you got me. Hello from Nebraska, USA! Neat trick grabbing the AST using Eclipse tooling. You may enjoy “CFR – another java decompiler”.