package org.deved.antlride.internal.core.parser;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.antlr.runtime.tree.Tree;
/**
* Utility for generating pretty "ASCII art" representations of syntax trees.
*
* @author Joshua Davis
* @author Steve Ebersole
*/
public class TreePrinter implements TreeToString {
private final Map<Integer, String> tokenTypeNameCache;
private final boolean showClassNames;
private TreeToString toStringStrategy = this;
/**
* Constructs a printer.
* <p/>
* Delegates to {@link #TreePrinter(Class, boolean)} with
* {@link #isShowClassNames showClassNames} as <tt>false</tt>
*
* @param tokenTypeConstants
* The token types to use during printing; typically the
* {vocabulary}TokenTypes.java interface generated by ANTLR.
*/
public TreePrinter(Class<?> tokenTypeConstants) {
this(generateTokenNameCache(tokenTypeConstants), false);
}
public void setToStringStrategy(TreeToString toStringStrategy) {
this.toStringStrategy = toStringStrategy;
}
public static Map<Integer, String> generateTokenNameCache(Class<?> tokenTypeInterface) {
final Field[] fields = tokenTypeInterface.getFields();
Map<Integer, String> cache = new HashMap<Integer, String>((int) (fields.length * .75) + 1);
for (int i = 0; i < fields.length; i++) {
final Field field = fields[i];
if (Modifier.isStatic(field.getModifiers())) {
try {
cache.put((Integer) field.get(null), field.getName());
} catch (Throwable ignore) {
}
}
}
return cache;
}
public TreePrinter(boolean showClassNames) {
this((Map<Integer, String>) null, showClassNames);
}
/**
* Constructs a printer.
*
* @param tokenTypeConstants
* The token types to use during printing; typically the
* {vocabulary}TokenTypes.java interface generated by ANTLR.
* @param showClassNames
* Should the class names of the tree nodes impls be displayed.
*/
public TreePrinter(Class<?> tokenTypeConstants, boolean showClassNames) {
this(generateTokenNameCache(tokenTypeConstants), showClassNames);
}
private TreePrinter(Map<Integer, String> tokenTypeNameCache, boolean showClassNames) {
this.tokenTypeNameCache = tokenTypeNameCache;
this.showClassNames = showClassNames;
}
/**
* Getter for property 'showClassNames'.
*
* @return Value for property 'showClassNames'.
*/
public boolean isShowClassNames() {
return showClassNames;
}
/**
* Renders the tree into 'ASCII art' form and returns that string
* representation.
*
* @param tree
* The tree to display.
* @param header
* The header for the display.
*
* @return The'ASCII art' form, as a string.
*/
public String renderAsString(Tree tree, String header) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
ps.println(header);
render(tree, ps);
ps.flush();
return new String(baos.toByteArray());
}
/**
* Prints the tree in 'ASCII art' form to the specified print stream.
*
* @param tree
* The tree to print.
* @param out
* The print stream to which the tree should be printed.
*/
public void render(Tree tree, PrintStream out) {
render(tree, new PrintWriter(out));
}
/**
* Prints the tree in 'ASCII art' tree form to the specified print writer.
*
* @param tree
* The tree to print.
* @param pw
* The print writer to which the tree should be written.
*/
public void render(Tree tree, PrintWriter pw) {
ArrayList<Tree> parents = new ArrayList<Tree>();
render(parents, pw, tree);
pw.flush();
}
/**
* Returns the token type name for the given token type.
*
* @param type
* The token type.
* @return String - The token type name from the token type constant class,
* or just the integer as a string if none exists.
*/
public String getTokenTypeName(int type) {
final Integer typeInteger = new Integer(type);
String value = null;
if (tokenTypeNameCache != null) {
value = tokenTypeNameCache.get(typeInteger);
}
if (value == null) {
value = typeInteger.toString();
}
return value;
}
private void render(ArrayList<Tree> parents, PrintWriter pw, Tree tree) {
if (tree == null) {
pw.println("tree is null!");
return;
}
// Tree.getChildIndex() -> the tree's position within its parent's
// children...
for (int i = 0; i < parents.size(); i++) {
final Tree parent = parents.get(i);
if (hasNextSibling(parent)) {
pw.print(" ");
} else {
pw.print(" | ");
}
}
if (hasNextSibling(tree)) {
pw.print(" \\-");
} else {
pw.print(" +-");
}
writeNode(pw, tree);
ArrayList<Tree> newParents = new ArrayList<Tree>(parents);
newParents.add(tree);
for (int i = 0; i < tree.getChildCount(); i++) {
render(newParents, pw, tree.getChild(i));
}
newParents.clear();
}
private boolean hasNextSibling(Tree tree) {
return tree.getParent() != null
&& tree.getParent().getChildCount() <= tree.getChildIndex();
}
private void writeNode(PrintWriter pw, Tree tree) {
pw.println(nodeToString(tree));
}
private String nodeToString(Tree tree) {
if (tree == null) {
return "{node:null}";
}
StringBuffer buf = new StringBuffer();
buf.append("[").append(getTokenTypeName(tree.getType()));
if (showClassNames) {
buf.append(" (").append(tree.getClass().getSimpleName())
.append(')');
}
buf.append("] ");
String stringTree = toStringStrategy.toString(tree);
appendEscapedMultibyteChars(stringTree, buf);
// if ( tree instanceof DisplayableNode ) {
// DisplayableNode displayableNode = ( DisplayableNode ) tree;
// buf.append( " => " ).append( displayableNode.getDisplayText() );
// }
return buf.toString();
}
private static void appendEscapedMultibyteChars(String text,
StringBuffer buf) {
char[] chars = text.toCharArray();
for (int i = 0; i < chars.length; i++) {
char aChar = chars[i];
if (aChar > 256) {
buf.append("\\u");
buf.append(Integer.toHexString(aChar));
} else {
buf.append(aChar);
}
}
}
public static String escapeMultibyteChars(String text) {
StringBuffer buf = new StringBuffer();
appendEscapedMultibyteChars(text, buf);
return buf.toString();
}
public String toString(Tree node) {
String text = node.getText();
StringBuffer buf = new StringBuffer();
if (text != null) {
buf.append("'");
buf.append(text);
buf.append("'");
}
return buf.toString();
}
}