/* * Copyright (C) 2010 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package lombok.ast.javac; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import lombok.ast.StringLiteral; import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.TypeTags; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.main.OptionName; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCArrayAccess; import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; import com.sun.tools.javac.tree.JCTree.JCAssert; import com.sun.tools.javac.tree.JCTree.JCAssign; import com.sun.tools.javac.tree.JCTree.JCAssignOp; import com.sun.tools.javac.tree.JCTree.JCBinary; import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCBreak; import com.sun.tools.javac.tree.JCTree.JCCase; import com.sun.tools.javac.tree.JCTree.JCCatch; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCConditional; import com.sun.tools.javac.tree.JCTree.JCContinue; import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; import com.sun.tools.javac.tree.JCTree.JCErroneous; import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCForLoop; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.tree.JCTree.JCInstanceOf; import com.sun.tools.javac.tree.JCTree.JCLabeledStatement; import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCNewArray; import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.JCTree.JCParens; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCReturn; import com.sun.tools.javac.tree.JCTree.JCSkip; import com.sun.tools.javac.tree.JCTree.JCSwitch; import com.sun.tools.javac.tree.JCTree.JCSynchronized; import com.sun.tools.javac.tree.JCTree.JCThrow; import com.sun.tools.javac.tree.JCTree.JCTry; import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.tree.JCTree.JCTypeCast; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCUnary; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCWhileLoop; import com.sun.tools.javac.tree.JCTree.JCWildcard; import com.sun.tools.javac.tree.JCTree.LetExpr; import com.sun.tools.javac.tree.JCTree.TypeBoundKind; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Options; /** * Diagnostic tool that turns a {@code JCTree} (javac) based tree into a hierarchical dump that should make * it easy to analyse the exact structure of the AST. */ public class JcTreePrinter { private final StringBuilder output = new StringBuilder(); private final boolean includePositions; private final boolean includeObjectRefs; private int indent; private String rel; private Map<JCTree, Integer> endPosTable; private boolean modsOfEnum; private Map<Object, Integer> visited; private int objectCounter = 0; private static final Method GET_TAG_METHOD; private static final Field TAG_FIELD; //TODO Adopt the reflective printer principle used in the EcjTreePrinter. For example, we don't currently know if the type ref is shared or unique amongst // JCVarDecls that all come from the same line: int[] x, y; static { Method m = null; Field f = null; try { m = JCTree.class.getDeclaredMethod("getTag"); } catch (NoSuchMethodException e) { try { f = JCTree.class.getDeclaredField("tag"); } catch (NoSuchFieldException e1) { e1.printStackTrace(); } } GET_TAG_METHOD = m; TAG_FIELD = f; } static int getTag(JCTree tree) { if (GET_TAG_METHOD != null) { try { return (Integer) GET_TAG_METHOD.invoke(tree); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); } } try { return TAG_FIELD.getInt(tree); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } private JcTreePrinter(boolean includePositions) { this.includePositions = includePositions; this.includeObjectRefs = true; } public static JcTreePrinter printerWithPositions() { return new JcTreePrinter(true); } public static JcTreePrinter printerWithoutPositions() { return new JcTreePrinter(false); } @Override public String toString() { return output.toString(); } public static void main(String[] args) throws IOException { if (args.length == 0) { System.out.println("Usage: Supply a file name to print."); return; } Context context = new Context(); Options.instance(context).put(OptionName.ENCODING, "UTF-8"); JavaCompiler compiler = new JavaCompiler(context); compiler.genEndPos = true; compiler.keepComments = true; @SuppressWarnings("deprecation") JCCompilationUnit cu = compiler.parse(args[0]); JcTreePrinter printer = new JcTreePrinter(true); printer.visit(cu); System.out.println(printer); } private void printNode(JCTree tree) { if (tree == null) { printNode("NULL"); } else { String suffix = ""; int objId; Integer backRef = visited.get(tree); if (backRef == null) { objId = ++objectCounter; visited.put(tree, objId); } else { objId = backRef; } if (includePositions) { Integer endPos_ = null; if (endPosTable != null) endPos_ = endPosTable.get(tree); int endPos = endPos_ == null ? tree.getEndPosition(endPosTable) : endPos_; int startPos = tree.pos; if (tree instanceof JCTypeApply || tree instanceof JCWildcard || tree instanceof JCTypeParameter) { // Javac itself actually has bugs in generating the right endpos. To make sure our tests that compare end pos don't fail, // as we do set the end pos at the right place, we overwrite it with magic value -2 which means: javac screws this up. endPos = -2; } // When there are no modifiers but an internal flag is set, javac screws up, // and sets start to the beginning of the node, and end to the last non-whitespace thing before it; // yes - that would make for negatively sized modifier nodes. if (tree instanceof JCModifiers && endPos-tree.pos <= 0) { startPos = -1; endPos = -1; } // The end value of an annotation is end-of-annotation-type in older javac, and end-of-parenslist in new javac. if (tree instanceof JCAnnotation || tree instanceof JCModifiers) { endPos = -2; } // Modifiers of enums never get their position set, but we do set the position. if (modsOfEnum) { startPos = -1; endPos = -1; modsOfEnum = false; } //In rare conditions, the end pos table is filled with a silly value, but start is -1. if (startPos == -1 && endPos >= 0) endPos = -1; /*Javac bug: sometimes the startpos of a binary expression is set to the wrong operator.*/ { if (tree instanceof JCBinary && ((JCBinary)tree).rhs instanceof JCBinary) { if (getTag(tree) != getTag(((JCBinary)tree).rhs)) { startPos = -2; } } if (tree instanceof JCBinary && ((JCBinary)tree).rhs instanceof JCInstanceOf) { startPos = -2; } } /* Javac bug: The end position of a "super.FOO" node which is itself the LHS of a select-like concept (Select, Method Invocation, etcetera) are set to right after the dot following it. This doesn't happen with 'this' or anything other than super. */ { if (tree instanceof JCMethodInvocation) { JCMethodInvocation invoke = (JCMethodInvocation) tree; if (invoke.meth instanceof JCFieldAccess && ((JCFieldAccess) invoke.meth).selected instanceof JCIdent) { JCIdent selected = (JCIdent) ((JCFieldAccess) invoke.meth).selected; if (selected.name.toString().equals("super")) endPos = -2; } } if (tree instanceof JCFieldAccess && ((JCFieldAccess) tree).selected instanceof JCIdent) { JCIdent selected = (JCIdent) ((JCFieldAccess) tree).selected; if (selected.name.toString().equals("super")) endPos = -2; } } /* Javac bug: the 'JCAssign' starts at a dot (if present) in the expression instead of at the start of it, which is weird and inconsistent. */ { if (tree instanceof JCAssign && ((JCAssign) tree).rhs instanceof JCFieldAccess) { startPos = -2; } } suffix += String.format("(%d-%d)", startPos, endPos); } if (includeObjectRefs) { suffix += String.format("(id: %d%s)", objId, backRef != null ? " BACKREF" : ""); } printNode(String.format("%s%s", tree.getClass().getSimpleName(), suffix)); } } private void printNode(String nodeKind) { printIndent(); if (rel != null) output.append(rel).append(": "); rel = null; output.append("[").append(nodeKind).append("]\n"); indent++; } private void printIndent() { for (int i = 0; i < indent; i++) { output.append("\t"); } } private void property(String rel, Object val) { printIndent(); if (rel != null) output.append(rel).append(": "); if (val instanceof JCTree) output.append("!!JCTree-AS-PROP!!"); if (val == null) { output.append("[NULL]\n"); } else { String content; if (val instanceof String) { content = new StringLiteral().astValue(val.toString()).rawValue(); } else { content = String.valueOf(val); } output.append("[").append(val.getClass().getSimpleName()).append(" ").append(content).append("]\n"); } } private void child(String rel, JCTree node) { this.rel = rel; if (node != null) { node.accept(visitor); } else { printNode("NULL"); indent--; } } private void children(String rel, List<? extends JCTree> nodes) { this.rel = rel; if (nodes == null) { ; printNode("LISTNULL"); indent--; } else if (nodes.isEmpty()) { printNode("LISTEMPTY"); indent--; } else { int i = 0; for (JCTree node : nodes) { child(String.format("%s[%d]", rel, i++), node); } } } public void visit(JCTree tree) { visited = new HashMap<Object, Integer>(); tree.accept(visitor); visited = null; endPosTable = null; } private final JCTree.Visitor visitor = new JCTree.Visitor() { @Override public void visitTopLevel(JCCompilationUnit tree) { printNode(tree); endPosTable = tree.endPositions; child("pid", tree.pid); children("defs", tree.defs); indent--; } @Override public void visitImport(JCImport tree) { printNode(tree); property("staticImport", tree.staticImport); child("qualid", tree.qualid); indent--; } @Override public void visitClassDef(JCClassDecl tree) { printNode(tree); modsOfEnum = (tree.mods != null && (tree.mods.flags & Flags.ENUM) != 0); child("mods", tree.mods); property("name", tree.name); children("typarams", tree.typarams); child("extends", tree.extending); children("implementing", tree.implementing); children("defs", tree.defs); indent--; } @Override public void visitMethodDef(JCMethodDecl tree) { printNode(tree); property("name", tree.name); child("mods", tree.mods); children("typarams", tree.typarams); children("params", tree.params); children("thrown", tree.thrown); child("default", tree.defaultValue); child("body", tree.body); indent--; } @Override public void visitVarDef(JCVariableDecl tree) { printNode(tree); child("mods", tree.mods); child("vartype", tree.vartype); property("name", tree.name); child("init", tree.init); indent--; } @Override public void visitSkip(JCSkip tree) { printNode(tree); indent--; } @Override public void visitBlock(JCBlock tree) { printNode(tree); property("flags", "0x" + Long.toString(tree.flags, 0x10)); children("stats", tree.stats); indent--; } @Override public void visitDoLoop(JCDoWhileLoop tree) { printNode(tree); child("body", tree.body); child("cond", tree.cond); indent--; } @Override public void visitWhileLoop(JCWhileLoop tree) { printNode(tree); child("cond", tree.cond); child("body", tree.body); indent--; } @Override public void visitForLoop(JCForLoop tree) { printNode(tree); children("init", tree.init); child("cond", tree.cond); children("step", tree.step); child("body", tree.body); indent--; } @Override public void visitForeachLoop(JCEnhancedForLoop tree) { printNode(tree); child("var", tree.var); child("expr", tree.expr); child("body", tree.body); indent--; } @Override public void visitLabelled(JCLabeledStatement tree) { printNode(tree); property("label", tree.label); child("body", tree.body); indent--; } @Override public void visitSwitch(JCSwitch tree) { printNode(tree); child("selector", tree.selector); children("cases", tree.cases); indent--; } @Override public void visitCase(JCCase tree) { printNode(tree); child("pat", tree.pat); children("stats", tree.stats); indent--; } @Override public void visitSynchronized(JCSynchronized tree) { printNode(tree); child("lock", tree.lock); child("body", tree.body); indent--; } @Override public void visitTry(JCTry tree) { printNode(tree); child("body", tree.body); children("catchers", tree.catchers); child("finalizer", tree.finalizer); indent--; } @Override public void visitCatch(JCCatch tree) { printNode(tree); child("param", tree.param); child("body", tree.body); indent--; } @Override public void visitConditional(JCConditional tree) { printNode(tree); child("cond", tree.cond); child("truepart", tree.truepart); child("falsepart", tree.falsepart); indent--; } @Override public void visitIf(JCIf tree) { printNode(tree); child("cond", tree.cond); child("thenpart", tree.thenpart); child("elsepart", tree.elsepart); indent--; } @Override public void visitExec(JCExpressionStatement tree) { printNode(tree); child("expr", tree.expr); indent--; } @Override public void visitBreak(JCBreak tree) { printNode(tree); property("label", tree.label); indent--; } @Override public void visitContinue(JCContinue tree) { printNode(tree); property("label", tree.label); indent--; } @Override public void visitReturn(JCReturn tree) { printNode(tree); child("expr", tree.expr); indent--; } @Override public void visitThrow(JCThrow tree) { printNode(tree); child("expr", tree.expr); indent--; } @Override public void visitAssert(JCAssert tree) { printNode(tree); child("cond", tree.cond); child("detail", tree.detail); indent--; } @Override public void visitApply(JCMethodInvocation tree) { printNode(tree); children("typeargs", tree.typeargs); child("meth", tree.meth); children("args", tree.args); indent--; } @Override public void visitNewClass(JCNewClass tree) { printNode(tree); child("encl", tree.encl); children("typeargs", tree.typeargs); child("clazz", tree.clazz); children("args", tree.args); child("def", tree.def); indent--; } @Override public void visitNewArray(JCNewArray tree) { printNode(tree); child("elemtype", tree.elemtype); children("dims", tree.dims); children("elems", tree.elems); indent--; } @Override public void visitParens(JCParens tree) { printNode(tree); child("expr", tree.expr); indent--; } @Override public void visitAssign(JCAssign tree) { printNode(tree); child("lhs", tree.lhs); child("rhs", tree.rhs); indent--; } public String operatorName(int tag) { switch (tag) { case JCTree.POS: return "+"; case JCTree.NEG: return "-"; case JCTree.NOT: return "!"; case JCTree.COMPL: return "~"; case JCTree.PREINC: return "++X"; case JCTree.PREDEC: return "--X"; case JCTree.POSTINC: return "X++"; case JCTree.POSTDEC: return "X--"; case JCTree.NULLCHK: return "<*nullchk*>"; case JCTree.OR: return "||"; case JCTree.AND: return "&&"; case JCTree.EQ: return "=="; case JCTree.NE: return "!="; case JCTree.LT: return "<"; case JCTree.GT: return ">"; case JCTree.LE: return "<="; case JCTree.GE: return ">="; case JCTree.BITOR: return "|"; case JCTree.BITXOR: return "^"; case JCTree.BITAND: return "&"; case JCTree.SL: return "<<"; case JCTree.SR: return ">>"; case JCTree.USR: return ">>>"; case JCTree.PLUS: return "+"; case JCTree.MINUS: return "-"; case JCTree.MUL: return "*"; case JCTree.DIV: return "/"; case JCTree.MOD: return "%"; case JCTree.PLUS_ASG: return "+="; case JCTree.MINUS_ASG: return "-="; case JCTree.MUL_ASG: return "*="; case JCTree.DIV_ASG: return "/="; case JCTree.MOD_ASG: return "%="; case JCTree.BITAND_ASG: return "&="; case JCTree.BITXOR_ASG: return "^="; case JCTree.BITOR_ASG: return "|="; case JCTree.SL_ASG: return "<<="; case JCTree.SR_ASG: return ">>="; case JCTree.USR_ASG: return ">>>="; case JCTree.TYPETEST: return "instanceof"; default: throw new Error("Unexpected operator: " + tag); } } @Override public void visitAssignop(JCAssignOp tree) { printNode(tree); child("lhs", tree.lhs); property("(operator)", operatorName(getTag(tree) - JCTree.ASGOffset) + "="); child("rhs", tree.rhs); indent--; } @Override public void visitUnary(JCUnary tree) { printNode(tree); child("arg", tree.arg); property("(operator)", operatorName(getTag(tree))); indent--; } @Override public void visitBinary(JCBinary tree) { printNode(tree); child("lhs", tree.lhs); property("(operator)", operatorName(getTag(tree))); child("rhs", tree.rhs); indent--; } @Override public void visitTypeCast(JCTypeCast tree) { printNode(tree); child("clazz", tree.clazz); child("expr", tree.expr); indent--; } @Override public void visitTypeTest(JCInstanceOf tree) { printNode(tree); child("expr", tree.expr); child("clazz", tree.clazz); indent--; } @Override public void visitIndexed(JCArrayAccess tree) { printNode(tree); child("indexed", tree.indexed); child("index", tree.index); indent--; } @Override public void visitSelect(JCFieldAccess tree) { printNode(tree); child("selected", tree.selected); property("name", tree.name); indent--; } @Override public void visitIdent(JCIdent tree) { printNode(tree); property("name", tree.name); indent--; } public String literalName(int typeTag) { switch (typeTag) { case TypeTags.BYTE: return "BYTE"; case TypeTags.SHORT: return "SHORT"; case TypeTags.INT: return "INT"; case TypeTags.LONG: return "LONG"; case TypeTags.FLOAT: return "FLOAT"; case TypeTags.DOUBLE: return "DOUBLE"; case TypeTags.CHAR: return "CHAR"; case TypeTags.BOOLEAN: return "BOOLEAN"; case TypeTags.VOID: return "VOID"; case TypeTags.CLASS: return "CLASS/STRING"; case TypeTags.BOT: return "BOT"; default: return "ERROR(" + typeTag + ")"; } } @Override public void visitLiteral(JCLiteral tree) { printNode(tree); property("typetag", literalName(tree.typetag)); property("value", tree.value); indent--; } @Override public void visitTypeIdent(JCPrimitiveTypeTree tree) { printNode(tree); property("typetag", literalName(tree.typetag)); indent--; } @Override public void visitTypeArray(JCArrayTypeTree tree) { printNode(tree); child("elemtype", tree.elemtype); indent--; } @Override public void visitTypeApply(JCTypeApply tree) { printNode(tree); child("clazz", tree.clazz); children("arguments", tree.arguments); indent--; } @Override public void visitTypeParameter(JCTypeParameter tree) { printNode(tree); property("name", tree.name); children("bounds", tree.bounds); indent--; } @Override public void visitWildcard(JCWildcard tree) { printNode(tree); Object o; // In some javacs (older ones), JCWildcard.kind is a BoundKind, which is an enum. In newer ones its a TypeBoundKind which is a JCTree, i.e. has positions. try { o = tree.getClass().getField("kind").get(tree); } catch (Exception e) { throw new RuntimeException("There's no field at all named 'kind' in JCWildcard? This is not a javac I understand.", e); } if (o instanceof JCTree) { child("kind", (JCTree)o); } else if (o instanceof BoundKind) { property("kind", String.valueOf(o)); } child("inner", tree.inner); indent--; } // In older javacs this method does not exist, so no @Override here public void visitTypeBoundKind(TypeBoundKind tree) { printNode(tree); property("kind", String.valueOf(tree.kind)); indent--; } @Override public void visitErroneous(JCErroneous tree) { printNode(tree); children("errs", tree.errs); indent--; } @Override public void visitLetExpr(LetExpr tree) { printNode(tree); children("defs", tree.defs); child("expr", tree.expr); indent--; } @Override public void visitModifiers(JCModifiers tree) { printNode(tree); children("annotations", tree.annotations); property("flags", "0x" + Long.toString(tree.flags, 0x10)); indent--; } @Override public void visitAnnotation(JCAnnotation tree) { printNode(tree); child("annotationType", tree.annotationType); children("args", tree.args); indent--; } @Override public void visitTree(JCTree tree) { String typeName = tree == null ? "NULL" : tree.getClass().getSimpleName(); printNode("UNKNOWN(" + typeName + ")"); indent--; } }; }