Skip to content

Instantly share code, notes, and snippets.

@rednaxelafx
Created November 18, 2010 06:36
Show Gist options
  • Save rednaxelafx/704698 to your computer and use it in GitHub Desktop.
Save rednaxelafx/704698 to your computer and use it in GitHub Desktop.
Generate DOT graphs to visualize a Velocity template's AST. To run this program, the following dependencies are needed: velocity-1.6.4.jar/commons-collections-3.2.1.jar/commons-lang-2.5.jar
import static org.apache.commons.lang.StringEscapeUtils.escapeJava;
import static org.apache.commons.lang.StringUtils.isEmpty;
import java.util.HashMap;
import java.util.Map;
import org.apache.velocity.runtime.parser.node.ASTAddNode;
import org.apache.velocity.runtime.parser.node.ASTAndNode;
import org.apache.velocity.runtime.parser.node.ASTAssignment;
import org.apache.velocity.runtime.parser.node.ASTBlock;
import org.apache.velocity.runtime.parser.node.ASTComment;
import org.apache.velocity.runtime.parser.node.ASTDirective;
import org.apache.velocity.runtime.parser.node.ASTDivNode;
import org.apache.velocity.runtime.parser.node.ASTEQNode;
import org.apache.velocity.runtime.parser.node.ASTElseIfStatement;
import org.apache.velocity.runtime.parser.node.ASTElseStatement;
import org.apache.velocity.runtime.parser.node.ASTEscape;
import org.apache.velocity.runtime.parser.node.ASTEscapedDirective;
import org.apache.velocity.runtime.parser.node.ASTExpression;
import org.apache.velocity.runtime.parser.node.ASTFalse;
import org.apache.velocity.runtime.parser.node.ASTFloatingPointLiteral;
import org.apache.velocity.runtime.parser.node.ASTGENode;
import org.apache.velocity.runtime.parser.node.ASTGTNode;
import org.apache.velocity.runtime.parser.node.ASTIdentifier;
import org.apache.velocity.runtime.parser.node.ASTIfStatement;
import org.apache.velocity.runtime.parser.node.ASTIntegerLiteral;
import org.apache.velocity.runtime.parser.node.ASTIntegerRange;
import org.apache.velocity.runtime.parser.node.ASTLENode;
import org.apache.velocity.runtime.parser.node.ASTLTNode;
import org.apache.velocity.runtime.parser.node.ASTMap;
import org.apache.velocity.runtime.parser.node.ASTMethod;
import org.apache.velocity.runtime.parser.node.ASTModNode;
import org.apache.velocity.runtime.parser.node.ASTMulNode;
import org.apache.velocity.runtime.parser.node.ASTNENode;
import org.apache.velocity.runtime.parser.node.ASTNotNode;
import org.apache.velocity.runtime.parser.node.ASTObjectArray;
import org.apache.velocity.runtime.parser.node.ASTOrNode;
import org.apache.velocity.runtime.parser.node.ASTReference;
import org.apache.velocity.runtime.parser.node.ASTSetDirective;
import org.apache.velocity.runtime.parser.node.ASTStop;
import org.apache.velocity.runtime.parser.node.ASTStringLiteral;
import org.apache.velocity.runtime.parser.node.ASTSubtractNode;
import org.apache.velocity.runtime.parser.node.ASTText;
import org.apache.velocity.runtime.parser.node.ASTTrue;
import org.apache.velocity.runtime.parser.node.ASTWord;
import org.apache.velocity.runtime.parser.node.ASTprocess;
import org.apache.velocity.runtime.parser.node.NodeUtils;
import org.apache.velocity.runtime.parser.node.ParserVisitor;
import org.apache.velocity.runtime.parser.node.SimpleNode;
/**
* Generate DOT graphs to visualize a Velocity template's AST.
* This class is not thread-safe. DO NOT use share between threads.
*
* @author RednaxelaFX
*
*/
public class ASTDotGenerator implements ParserVisitor {
private StringBuilder output;
private Map<SimpleNode, String> nodeNames;
private int nodeCount;
public String generateDot(SimpleNode node) {
init();
output = new StringBuilder();
output.append("digraph {\n node [fontsize=12, font=Courier, shape=ellipse]");
// recursively traverse the AST
node.jjtAccept(this, null);
output.append("}");
return output.toString();
}
private void init() {
nodeNames = new HashMap<SimpleNode, String>();
nodeCount = 0;
}
private void showNodeCommon(SimpleNode node) {
// show parent-child relationship
SimpleNode parent = (SimpleNode) node.jjtGetParent();
if (parent != null) {
showRelationship(parent, node);
}
// show children
node.childrenAccept(this, null);
}
private String getName(SimpleNode node) {
String cachedName = nodeNames.get(node);
if (cachedName != null)
return cachedName;
String newName = "node_" + nodeCount++;
nodeNames.put(node, newName);
return newName;
}
private void declareNode(SimpleNode node, String details) {
output
.append(" ")
.append(getName(node))
.append(" [label=\"")
.append(node.getClass().getSimpleName());
if (isEmpty(details)) {
output.append("\"]\n");
} else {
output.append("\\n[").append(escapeJava(details)).append("]\"]\n");
}
}
private void showRelationship(SimpleNode parent, SimpleNode child) {
output
.append(" ")
.append(getName(parent))
.append(" -> ")
.append(getName(child))
.append("\n");
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.SimpleNode, java.lang.Object)
*/
@Override
public Object visit(SimpleNode node, Object data) {
// NOTE: ParserVisitor interface is missing a visit(ASTMathNode) method,
// so calling ASTMathNode.visit(ParserVisitor, Object) will end up
// calling this overload.
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTprocess, java.lang.Object)
*/
@Override
public Object visit(ASTprocess node, Object data) {
declareNode(node, "templateName=" + node.getTemplateName());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTEscapedDirective, java.lang.Object)
*/
@Override
public Object visit(ASTEscapedDirective node, Object data) {
declareNode(node, node.literal());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTEscape, java.lang.Object)
*/
@Override
public Object visit(ASTEscape node, Object data) {
declareNode(node, node.val);
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTComment, java.lang.Object)
*/
@Override
public Object visit(ASTComment node, Object data) {
declareNode(node, node.literal());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTFloatingPointLiteral, java.lang.Object)
*/
@Override
public Object visit(ASTFloatingPointLiteral node, Object data) {
declareNode(node, node.value(null).toString());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTIntegerLiteral, java.lang.Object)
*/
@Override
public Object visit(ASTIntegerLiteral node, Object data) {
declareNode(node, node.value(null).toString());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTStringLiteral, java.lang.Object)
*/
@Override
public Object visit(ASTStringLiteral node, Object data) {
declareNode(node, "\"" + node.literal() + "\"");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTIdentifier, java.lang.Object)
*/
@Override
public Object visit(ASTIdentifier node, Object data) {
declareNode(node, node.literal());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTWord, java.lang.Object)
*/
@Override
public Object visit(ASTWord node, Object data) {
declareNode(node, "keyword=" + node.literal());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTDirective, java.lang.Object)
*/
@Override
public Object visit(ASTDirective node, Object data) {
declareNode(node, "directive=" + node.getDirectiveName());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTBlock, java.lang.Object)
*/
@Override
public Object visit(ASTBlock node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTMap, java.lang.Object)
*/
@Override
public Object visit(ASTMap node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTObjectArray, java.lang.Object)
*/
@Override
public Object visit(ASTObjectArray node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTIntegerRange, java.lang.Object)
*/
@Override
public Object visit(ASTIntegerRange node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTMethod, java.lang.Object)
*/
@Override
public Object visit(ASTMethod node, Object data) {
declareNode(node, "method=" + node.getMethodName());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTReference, java.lang.Object)
*/
@Override
public Object visit(ASTReference node, Object data) {
declareNode(node, "identifier=" + node.getRootString());
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTTrue, java.lang.Object)
*/
@Override
public Object visit(ASTTrue node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTFalse, java.lang.Object)
*/
@Override
public Object visit(ASTFalse node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTText, java.lang.Object)
*/
@Override
public Object visit(ASTText node, Object data) {
declareNode(node, NodeUtils.tokenLiteral(node.getFirstToken()));
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTIfStatement, java.lang.Object)
*/
@Override
public Object visit(ASTIfStatement node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTElseStatement, java.lang.Object)
*/
@Override
public Object visit(ASTElseStatement node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTElseIfStatement, java.lang.Object)
*/
@Override
public Object visit(ASTElseIfStatement node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTSetDirective, java.lang.Object)
*/
@Override
public Object visit(ASTSetDirective node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTStop, java.lang.Object)
*/
@Override
public Object visit(ASTStop node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTExpression, java.lang.Object)
*/
@Override
public Object visit(ASTExpression node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTAssignment, java.lang.Object)
*/
@Override
public Object visit(ASTAssignment node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTOrNode, java.lang.Object)
*/
@Override
public Object visit(ASTOrNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTAndNode, java.lang.Object)
*/
@Override
public Object visit(ASTAndNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTEQNode, java.lang.Object)
*/
@Override
public Object visit(ASTEQNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTNENode, java.lang.Object)
*/
@Override
public Object visit(ASTNENode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTLTNode, java.lang.Object)
*/
@Override
public Object visit(ASTLTNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTGTNode, java.lang.Object)
*/
@Override
public Object visit(ASTGTNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTLENode, java.lang.Object)
*/
@Override
public Object visit(ASTLENode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTGENode, java.lang.Object)
*/
@Override
public Object visit(ASTGENode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTAddNode, java.lang.Object)
*/
@Override
public Object visit(ASTAddNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTSubtractNode, java.lang.Object)
*/
@Override
public Object visit(ASTSubtractNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTMulNode, java.lang.Object)
*/
@Override
public Object visit(ASTMulNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTDivNode, java.lang.Object)
*/
@Override
public Object visit(ASTDivNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTModNode, java.lang.Object)
*/
@Override
public Object visit(ASTModNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache
* .velocity.runtime.parser.node.ASTNotNode, java.lang.Object)
*/
@Override
public Object visit(ASTNotNode node, Object data) {
declareNode(node, "");
showNodeCommon(node);
return null;
}
}
digraph {
node [fontsize=12, font=Courier, shape=ellipse] node_0 [label="ASTprocess\n[templateName=vm/test1.vm]"]
node_1 [label="ASTText\n[Testing iterative rendering contents:\r\n]"]
node_0 -> node_1
node_2 [label="ASTDirective\n[directive=foreach]"]
node_0 -> node_2
node_3 [label="ASTReference\n[identifier=i]"]
node_2 -> node_3
node_4 [label="ASTWord\n[keyword=in]"]
node_2 -> node_4
node_5 [label="ASTIntegerRange"]
node_2 -> node_5
node_6 [label="ASTIntegerLiteral\n[0]"]
node_5 -> node_6
node_7 [label="ASTIntegerLiteral\n[10]"]
node_5 -> node_7
node_8 [label="ASTBlock"]
node_2 -> node_8
node_9 [label="ASTSetDirective"]
node_8 -> node_9
node_10 [label="ASTReference\n[identifier=a]"]
node_9 -> node_10
node_11 [label="ASTExpression"]
node_9 -> node_11
node_12 [label="ASTAddNode"]
node_11 -> node_12
node_13 [label="ASTReference\n[identifier=i]"]
node_12 -> node_13
node_14 [label="ASTIntegerLiteral\n[1]"]
node_12 -> node_14
node_15 [label="ASTText\n[ ]"]
node_8 -> node_15
node_16 [label="ASTReference\n[identifier=i]"]
node_8 -> node_16
node_17 [label="ASTText\n[ is an ]"]
node_8 -> node_17
node_18 [label="ASTIfStatement"]
node_8 -> node_18
node_19 [label="ASTExpression"]
node_18 -> node_19
node_20 [label="ASTEQNode"]
node_19 -> node_20
node_21 [label="ASTModNode"]
node_20 -> node_21
node_22 [label="ASTReference\n[identifier=i]"]
node_21 -> node_22
node_23 [label="ASTIntegerLiteral\n[2]"]
node_21 -> node_23
node_24 [label="ASTIntegerLiteral\n[0]"]
node_20 -> node_24
node_25 [label="ASTBlock"]
node_18 -> node_25
node_26 [label="ASTText\n[even]"]
node_25 -> node_26
node_27 [label="ASTElseStatement"]
node_18 -> node_27
node_28 [label="ASTBlock"]
node_27 -> node_28
node_29 [label="ASTText\n[odd]"]
node_28 -> node_29
node_30 [label="ASTText\n[ number.\r\n]"]
node_8 -> node_30
node_31 [label="ASTText\n[Done testing\r\n]"]
node_0 -> node_31
}
Testing iterative rendering contents:
#foreach ($i in [0..10])
#set ($a = $i + 1)
$i is an #if ($i % 2 == 0)even#{else}odd#end number.
#end
Done testing
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.context.InternalContextAdapterImpl;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.node.SimpleNode;
/**
* Test ASTDotGenerator with a simple Velocity template.
*
* @author RednaxelaFX
*
*/
public class TestDrive {
private static final String TEMPLATE_FILE_PATH = "vm/test1.vm";
public static void main(String[] args) throws Exception {
initVelocity();
SimpleNode ast = getInitedAST();
ASTDotGenerator dotGen = new ASTDotGenerator();
String dot = dotGen.generateDot(ast);
System.out.println(dot);
}
private static void initVelocity() throws Exception {
Velocity.init();
}
private static SimpleNode getInitedAST() throws Exception {
SimpleNode ast = RuntimeSingleton.parse(getTemplateReader(), TEMPLATE_FILE_PATH);
InternalContextAdapter context = new InternalContextAdapterImpl(new VelocityContext());
context.pushCurrentTemplateName(TEMPLATE_FILE_PATH);
ast.init(context, RuntimeSingleton.getRuntimeServices());
return ast;
}
private static Reader getTemplateReader() throws Exception {
return new BufferedReader(new InputStreamReader(new FileInputStream(TEMPLATE_FILE_PATH)));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment