src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java
changeset 47216 71c04702a3d5
parent 44192 aff725b7874a
child 47268 48ec75306997
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1515 @@
+/*
+ * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.javac.tree;
+
+import java.io.*;
+
+import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
+import com.sun.source.tree.ModuleTree.ModuleKind;
+import com.sun.tools.javac.code.*;
+import com.sun.tools.javac.tree.JCTree.*;
+import com.sun.tools.javac.util.*;
+import com.sun.tools.javac.util.List;
+import static com.sun.tools.javac.code.Flags.*;
+import static com.sun.tools.javac.code.Flags.ANNOTATION;
+import static com.sun.tools.javac.tree.JCTree.Tag.*;
+
+/** Prints out a tree as an indented Java source program.
+ *
+ *  <p><b>This is NOT part of any supported API.
+ *  If you write code that depends on this, you do so at your own risk.
+ *  This code and its internal interfaces are subject to change or
+ *  deletion without notice.</b>
+ */
+public class Pretty extends JCTree.Visitor {
+
+    public Pretty(Writer out, boolean sourceOutput) {
+        this.out = out;
+        this.sourceOutput = sourceOutput;
+    }
+
+    /** Set when we are producing source output.  If we're not
+     *  producing source output, we can sometimes give more detail in
+     *  the output even though that detail would not be valid java
+     *  source.
+     */
+    private final boolean sourceOutput;
+
+    /** The output stream on which trees are printed.
+     */
+    Writer out;
+
+    /** Indentation width (can be reassigned from outside).
+     */
+    public int width = 4;
+
+    /** The current left margin.
+     */
+    int lmargin = 0;
+
+    /** The enclosing class name.
+     */
+    Name enclClassName;
+
+    /** A table mapping trees to their documentation comments
+     *  (can be null)
+     */
+    DocCommentTable docComments = null;
+
+    /**
+     * A string sequence to be used when Pretty output should be constrained
+     * to fit into a given size
+     */
+    private final static String trimSequence = "[...]";
+
+    /**
+     * Max number of chars to be generated when output should fit into a single line
+     */
+    private final static int PREFERRED_LENGTH = 20;
+
+    /** Align code to be indented to left margin.
+     */
+    void align() throws IOException {
+        for (int i = 0; i < lmargin; i++) out.write(" ");
+    }
+
+    /** Increase left margin by indentation width.
+     */
+    void indent() {
+        lmargin = lmargin + width;
+    }
+
+    /** Decrease left margin by indentation width.
+     */
+    void undent() {
+        lmargin = lmargin - width;
+    }
+
+    /** Enter a new precedence level. Emit a `(' if new precedence level
+     *  is less than precedence level so far.
+     *  @param contextPrec    The precedence level in force so far.
+     *  @param ownPrec        The new precedence level.
+     */
+    void open(int contextPrec, int ownPrec) throws IOException {
+        if (ownPrec < contextPrec) out.write("(");
+    }
+
+    /** Leave precedence level. Emit a `(' if inner precedence level
+     *  is less than precedence level we revert to.
+     *  @param contextPrec    The precedence level we revert to.
+     *  @param ownPrec        The inner precedence level.
+     */
+    void close(int contextPrec, int ownPrec) throws IOException {
+        if (ownPrec < contextPrec) out.write(")");
+    }
+
+    /** Print string, replacing all non-ascii character with unicode escapes.
+     */
+    public void print(Object s) throws IOException {
+        out.write(Convert.escapeUnicode(s.toString()));
+    }
+
+    /** Print new line.
+     */
+    public void println() throws IOException {
+        out.write(lineSep);
+    }
+
+    public static String toSimpleString(JCTree tree) {
+        return toSimpleString(tree, PREFERRED_LENGTH);
+    }
+
+    public static String toSimpleString(JCTree tree, int maxLength) {
+        StringWriter s = new StringWriter();
+        try {
+            new Pretty(s, false).printExpr(tree);
+        }
+        catch (IOException e) {
+            // should never happen, because StringWriter is defined
+            // never to throw any IOExceptions
+            throw new AssertionError(e);
+        }
+        //we need to (i) replace all line terminators with a space and (ii) remove
+        //occurrences of 'missing' in the Pretty output (generated when types are missing)
+        String res = s.toString().trim().replaceAll("\\s+", " ").replaceAll("/\\*missing\\*/", "");
+        if (res.length() < maxLength) {
+            return res;
+        } else {
+            int head = (maxLength - trimSequence.length()) * 2 / 3;
+            int tail = maxLength - trimSequence.length() - head;
+            return res.substring(0, head) + trimSequence + res.substring(res.length() - tail);
+        }
+    }
+
+    String lineSep = System.getProperty("line.separator");
+
+    /**************************************************************************
+     * Traversal methods
+     *************************************************************************/
+
+    /** Exception to propogate IOException through visitXXX methods */
+    private static class UncheckedIOException extends Error {
+        static final long serialVersionUID = -4032692679158424751L;
+        UncheckedIOException(IOException e) {
+            super(e.getMessage(), e);
+        }
+    }
+
+    /** Visitor argument: the current precedence level.
+     */
+    int prec;
+
+    /** Visitor method: print expression tree.
+     *  @param prec  The current precedence level.
+     */
+    public void printExpr(JCTree tree, int prec) throws IOException {
+        int prevPrec = this.prec;
+        try {
+            this.prec = prec;
+            if (tree == null) print("/*missing*/");
+            else {
+                tree.accept(this);
+            }
+        } catch (UncheckedIOException ex) {
+            IOException e = new IOException(ex.getMessage());
+            e.initCause(ex);
+            throw e;
+        } finally {
+            this.prec = prevPrec;
+        }
+    }
+
+    /** Derived visitor method: print expression tree at minimum precedence level
+     *  for expression.
+     */
+    public void printExpr(JCTree tree) throws IOException {
+        printExpr(tree, TreeInfo.noPrec);
+    }
+
+    /** Derived visitor method: print statement tree.
+     */
+    public void printStat(JCTree tree) throws IOException {
+        printExpr(tree, TreeInfo.notExpression);
+    }
+
+    /** Derived visitor method: print list of expression trees, separated by given string.
+     *  @param sep the separator string
+     */
+    public <T extends JCTree> void printExprs(List<T> trees, String sep) throws IOException {
+        if (trees.nonEmpty()) {
+            printExpr(trees.head);
+            for (List<T> l = trees.tail; l.nonEmpty(); l = l.tail) {
+                print(sep);
+                printExpr(l.head);
+            }
+        }
+    }
+
+    /** Derived visitor method: print list of expression trees, separated by commas.
+     */
+    public <T extends JCTree> void printExprs(List<T> trees) throws IOException {
+        printExprs(trees, ", ");
+    }
+
+    /** Derived visitor method: print list of statements, each on a separate line.
+     */
+    public void printStats(List<? extends JCTree> trees) throws IOException {
+        for (List<? extends JCTree> l = trees; l.nonEmpty(); l = l.tail) {
+            align();
+            printStat(l.head);
+            println();
+        }
+    }
+
+    /** Print a set of modifiers.
+     */
+    public void printFlags(long flags) throws IOException {
+        if ((flags & SYNTHETIC) != 0) print("/*synthetic*/ ");
+        print(TreeInfo.flagNames(flags));
+        if ((flags & ExtendedStandardFlags) != 0) print(" ");
+        if ((flags & ANNOTATION) != 0) print("@");
+    }
+
+    public void printAnnotations(List<JCAnnotation> trees) throws IOException {
+        for (List<JCAnnotation> l = trees; l.nonEmpty(); l = l.tail) {
+            printStat(l.head);
+            println();
+            align();
+        }
+    }
+
+    public void printTypeAnnotations(List<JCAnnotation> trees) throws IOException {
+        for (List<JCAnnotation> l = trees; l.nonEmpty(); l = l.tail) {
+            printExpr(l.head);
+            print(" ");
+        }
+    }
+
+    /** Print documentation comment, if it exists
+     *  @param tree    The tree for which a documentation comment should be printed.
+     */
+    public void printDocComment(JCTree tree) throws IOException {
+        if (docComments != null) {
+            String dc = docComments.getCommentText(tree);
+            if (dc != null) {
+                print("/**"); println();
+                int pos = 0;
+                int endpos = lineEndPos(dc, pos);
+                while (pos < dc.length()) {
+                    align();
+                    print(" *");
+                    if (pos < dc.length() && dc.charAt(pos) > ' ') print(" ");
+                    print(dc.substring(pos, endpos)); println();
+                    pos = endpos + 1;
+                    endpos = lineEndPos(dc, pos);
+                }
+                align(); print(" */"); println();
+                align();
+            }
+        }
+    }
+//where
+    static int lineEndPos(String s, int start) {
+        int pos = s.indexOf('\n', start);
+        if (pos < 0) pos = s.length();
+        return pos;
+    }
+
+    /** If type parameter list is non-empty, print it enclosed in
+     *  {@literal "<...>"} brackets.
+     */
+    public void printTypeParameters(List<JCTypeParameter> trees) throws IOException {
+        if (trees.nonEmpty()) {
+            print("<");
+            printExprs(trees);
+            print(">");
+        }
+    }
+
+    /** Print a block.
+     */
+    public void printBlock(List<? extends JCTree> stats) throws IOException {
+        print("{");
+        println();
+        indent();
+        printStats(stats);
+        undent();
+        align();
+        print("}");
+    }
+
+    /** Print a block.
+     */
+    public void printEnumBody(List<JCTree> stats) throws IOException {
+        print("{");
+        println();
+        indent();
+        boolean first = true;
+        for (List<JCTree> l = stats; l.nonEmpty(); l = l.tail) {
+            if (isEnumerator(l.head)) {
+                if (!first) {
+                    print(",");
+                    println();
+                }
+                align();
+                printStat(l.head);
+                first = false;
+            }
+        }
+        print(";");
+        println();
+        for (List<JCTree> l = stats; l.nonEmpty(); l = l.tail) {
+            if (!isEnumerator(l.head)) {
+                align();
+                printStat(l.head);
+                println();
+            }
+        }
+        undent();
+        align();
+        print("}");
+    }
+
+    /** Is the given tree an enumerator definition? */
+    boolean isEnumerator(JCTree t) {
+        return t.hasTag(VARDEF) && (((JCVariableDecl) t).mods.flags & ENUM) != 0;
+    }
+
+    /** Print unit consisting of package clause and import statements in toplevel,
+     *  followed by class definition. if class definition == null,
+     *  print all definitions in toplevel.
+     *  @param tree     The toplevel tree
+     *  @param cdef     The class definition, which is assumed to be part of the
+     *                  toplevel tree.
+     */
+    public void printUnit(JCCompilationUnit tree, JCClassDecl cdef) throws IOException {
+        docComments = tree.docComments;
+        printDocComment(tree);
+
+        boolean firstImport = true;
+        for (List<JCTree> l = tree.defs;
+             l.nonEmpty() &&
+                 (cdef == null ||
+                  l.head.hasTag(IMPORT) || l.head.hasTag(PACKAGEDEF));
+             l = l.tail) {
+            if (l.head.hasTag(IMPORT)) {
+                JCImport imp = (JCImport)l.head;
+                Name name = TreeInfo.name(imp.qualid);
+                if (name == name.table.names.asterisk ||
+                        cdef == null ||
+                        isUsed(TreeInfo.symbol(imp.qualid), cdef)) {
+                    if (firstImport) {
+                        firstImport = false;
+                        println();
+                    }
+                    printStat(imp);
+                }
+            } else {
+                printStat(l.head);
+            }
+        }
+        if (cdef != null) {
+            printStat(cdef);
+            println();
+        }
+    }
+    // where
+    boolean isUsed(final Symbol t, JCTree cdef) {
+        class UsedVisitor extends TreeScanner {
+            public void scan(JCTree tree) {
+                if (tree!=null && !result) tree.accept(this);
+            }
+            boolean result = false;
+            public void visitIdent(JCIdent tree) {
+                if (tree.sym == t) result = true;
+            }
+        }
+        UsedVisitor v = new UsedVisitor();
+        v.scan(cdef);
+        return v.result;
+    }
+
+    /**************************************************************************
+     * Visitor methods
+     *************************************************************************/
+
+    public void visitTopLevel(JCCompilationUnit tree) {
+        try {
+            printUnit(tree, null);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitPackageDef(JCPackageDecl tree) {
+        try {
+            printDocComment(tree);
+            printAnnotations(tree.annotations);
+            if (tree.pid != null) {
+                print("package ");
+                printExpr(tree.pid);
+                print(";");
+                println();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void visitModuleDef(JCModuleDecl tree) {
+        try {
+            printDocComment(tree);
+            printAnnotations(tree.mods.annotations);
+            if (tree.getModuleType() == ModuleKind.OPEN) {
+                print("open ");
+            }
+            print("module ");
+            printExpr(tree.qualId);
+            if (tree.directives == null) {
+                print(";");
+            } else {
+                printBlock(tree.directives);
+            }
+            println();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void visitExports(JCExports tree) {
+        try {
+            print("exports ");
+            printExpr(tree.qualid);
+            if (tree.moduleNames != null) {
+                print(" to ");
+                printExprs(tree.moduleNames);
+            }
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void visitOpens(JCOpens tree) {
+        try {
+            print("opens ");
+            printExpr(tree.qualid);
+            if (tree.moduleNames != null) {
+                print(" to ");
+                printExprs(tree.moduleNames);
+            }
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void visitProvides(JCProvides tree) {
+        try {
+            print("provides ");
+            printExpr(tree.serviceName);
+            print(" with ");
+            printExprs(tree.implNames);
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void visitRequires(JCRequires tree) {
+        try {
+            print("requires ");
+            if (tree.isStaticPhase)
+                print("static ");
+            if (tree.isTransitive)
+                print("transitive ");
+            printExpr(tree.moduleName);
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void visitUses(JCUses tree) {
+        try {
+            print("uses ");
+            printExpr(tree.qualid);
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitImport(JCImport tree) {
+        try {
+            print("import ");
+            if (tree.staticImport) print("static ");
+            printExpr(tree.qualid);
+            print(";");
+            println();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitClassDef(JCClassDecl tree) {
+        try {
+            println(); align();
+            printDocComment(tree);
+            printAnnotations(tree.mods.annotations);
+            printFlags(tree.mods.flags & ~INTERFACE);
+            Name enclClassNamePrev = enclClassName;
+            enclClassName = tree.name;
+            if ((tree.mods.flags & INTERFACE) != 0) {
+                print("interface " + tree.name);
+                printTypeParameters(tree.typarams);
+                if (tree.implementing.nonEmpty()) {
+                    print(" extends ");
+                    printExprs(tree.implementing);
+                }
+            } else {
+                if ((tree.mods.flags & ENUM) != 0)
+                    print("enum " + tree.name);
+                else
+                    print("class " + tree.name);
+                printTypeParameters(tree.typarams);
+                if (tree.extending != null) {
+                    print(" extends ");
+                    printExpr(tree.extending);
+                }
+                if (tree.implementing.nonEmpty()) {
+                    print(" implements ");
+                    printExprs(tree.implementing);
+                }
+            }
+            print(" ");
+            if ((tree.mods.flags & ENUM) != 0) {
+                printEnumBody(tree.defs);
+            } else {
+                printBlock(tree.defs);
+            }
+            enclClassName = enclClassNamePrev;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitMethodDef(JCMethodDecl tree) {
+        try {
+            // when producing source output, omit anonymous constructors
+            if (tree.name == tree.name.table.names.init &&
+                    enclClassName == null &&
+                    sourceOutput) return;
+            println(); align();
+            printDocComment(tree);
+            printExpr(tree.mods);
+            printTypeParameters(tree.typarams);
+            if (tree.name == tree.name.table.names.init) {
+                print(enclClassName != null ? enclClassName : tree.name);
+            } else {
+                printExpr(tree.restype);
+                print(" " + tree.name);
+            }
+            print("(");
+            if (tree.recvparam!=null) {
+                printExpr(tree.recvparam);
+                if (tree.params.size() > 0) {
+                    print(", ");
+                }
+            }
+            printExprs(tree.params);
+            print(")");
+            if (tree.thrown.nonEmpty()) {
+                print(" throws ");
+                printExprs(tree.thrown);
+            }
+            if (tree.defaultValue != null) {
+                print(" default ");
+                printExpr(tree.defaultValue);
+            }
+            if (tree.body != null) {
+                print(" ");
+                printStat(tree.body);
+            } else {
+                print(";");
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitVarDef(JCVariableDecl tree) {
+        try {
+            if (docComments != null && docComments.hasComment(tree)) {
+                println(); align();
+            }
+            printDocComment(tree);
+            if ((tree.mods.flags & ENUM) != 0) {
+                print("/*public static final*/ ");
+                print(tree.name);
+                if (tree.init != null) {
+                    if (sourceOutput && tree.init.hasTag(NEWCLASS)) {
+                        print(" /*enum*/ ");
+                        JCNewClass init = (JCNewClass) tree.init;
+                        if (init.args != null && init.args.nonEmpty()) {
+                            print("(");
+                            print(init.args);
+                            print(")");
+                        }
+                        if (init.def != null && init.def.defs != null) {
+                            print(" ");
+                            printBlock(init.def.defs);
+                        }
+                        return;
+                    }
+                    print(" /* = ");
+                    printExpr(tree.init);
+                    print(" */");
+                }
+            } else {
+                printExpr(tree.mods);
+                if ((tree.mods.flags & VARARGS) != 0) {
+                    JCTree vartype = tree.vartype;
+                    List<JCAnnotation> tas = null;
+                    if (vartype instanceof JCAnnotatedType) {
+                        tas = ((JCAnnotatedType)vartype).annotations;
+                        vartype = ((JCAnnotatedType)vartype).underlyingType;
+                    }
+                    printExpr(((JCArrayTypeTree) vartype).elemtype);
+                    if (tas != null) {
+                        print(' ');
+                        printTypeAnnotations(tas);
+                    }
+                    print("... " + tree.name);
+                } else {
+                    printExpr(tree.vartype);
+                    print(" " + tree.name);
+                }
+                if (tree.init != null) {
+                    print(" = ");
+                    printExpr(tree.init);
+                }
+                if (prec == TreeInfo.notExpression) print(";");
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitSkip(JCSkip tree) {
+        try {
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitBlock(JCBlock tree) {
+        try {
+            printFlags(tree.flags);
+            printBlock(tree.stats);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitDoLoop(JCDoWhileLoop tree) {
+        try {
+            print("do ");
+            printStat(tree.body);
+            align();
+            print(" while ");
+            if (tree.cond.hasTag(PARENS)) {
+                printExpr(tree.cond);
+            } else {
+                print("(");
+                printExpr(tree.cond);
+                print(")");
+            }
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitWhileLoop(JCWhileLoop tree) {
+        try {
+            print("while ");
+            if (tree.cond.hasTag(PARENS)) {
+                printExpr(tree.cond);
+            } else {
+                print("(");
+                printExpr(tree.cond);
+                print(")");
+            }
+            print(" ");
+            printStat(tree.body);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitForLoop(JCForLoop tree) {
+        try {
+            print("for (");
+            if (tree.init.nonEmpty()) {
+                if (tree.init.head.hasTag(VARDEF)) {
+                    printExpr(tree.init.head);
+                    for (List<JCStatement> l = tree.init.tail; l.nonEmpty(); l = l.tail) {
+                        JCVariableDecl vdef = (JCVariableDecl)l.head;
+                        print(", " + vdef.name);
+                        if (vdef.init != null) {
+                            print(" = ");
+                            printExpr(vdef.init);
+                        }
+                    }
+                } else {
+                    printExprs(tree.init);
+                }
+            }
+            print("; ");
+            if (tree.cond != null) printExpr(tree.cond);
+            print("; ");
+            printExprs(tree.step);
+            print(") ");
+            printStat(tree.body);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitForeachLoop(JCEnhancedForLoop tree) {
+        try {
+            print("for (");
+            printExpr(tree.var);
+            print(" : ");
+            printExpr(tree.expr);
+            print(") ");
+            printStat(tree.body);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitLabelled(JCLabeledStatement tree) {
+        try {
+            print(tree.label + ": ");
+            printStat(tree.body);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitSwitch(JCSwitch tree) {
+        try {
+            print("switch ");
+            if (tree.selector.hasTag(PARENS)) {
+                printExpr(tree.selector);
+            } else {
+                print("(");
+                printExpr(tree.selector);
+                print(")");
+            }
+            print(" {");
+            println();
+            printStats(tree.cases);
+            align();
+            print("}");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitCase(JCCase tree) {
+        try {
+            if (tree.pat == null) {
+                print("default");
+            } else {
+                print("case ");
+                printExpr(tree.pat);
+            }
+            print(": ");
+            println();
+            indent();
+            printStats(tree.stats);
+            undent();
+            align();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitSynchronized(JCSynchronized tree) {
+        try {
+            print("synchronized ");
+            if (tree.lock.hasTag(PARENS)) {
+                printExpr(tree.lock);
+            } else {
+                print("(");
+                printExpr(tree.lock);
+                print(")");
+            }
+            print(" ");
+            printStat(tree.body);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTry(JCTry tree) {
+        try {
+            print("try ");
+            if (tree.resources.nonEmpty()) {
+                print("(");
+                boolean first = true;
+                for (JCTree var : tree.resources) {
+                    if (!first) {
+                        println();
+                        indent();
+                    }
+                    printStat(var);
+                    first = false;
+                }
+                print(") ");
+            }
+            printStat(tree.body);
+            for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) {
+                printStat(l.head);
+            }
+            if (tree.finalizer != null) {
+                print(" finally ");
+                printStat(tree.finalizer);
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitCatch(JCCatch tree) {
+        try {
+            print(" catch (");
+            printExpr(tree.param);
+            print(") ");
+            printStat(tree.body);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitConditional(JCConditional tree) {
+        try {
+            open(prec, TreeInfo.condPrec);
+            printExpr(tree.cond, TreeInfo.condPrec + 1);
+            print(" ? ");
+            printExpr(tree.truepart);
+            print(" : ");
+            printExpr(tree.falsepart, TreeInfo.condPrec);
+            close(prec, TreeInfo.condPrec);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitIf(JCIf tree) {
+        try {
+            print("if ");
+            if (tree.cond.hasTag(PARENS)) {
+                printExpr(tree.cond);
+            } else {
+                print("(");
+                printExpr(tree.cond);
+                print(")");
+            }
+            print(" ");
+            printStat(tree.thenpart);
+            if (tree.elsepart != null) {
+                print(" else ");
+                printStat(tree.elsepart);
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitExec(JCExpressionStatement tree) {
+        try {
+            printExpr(tree.expr);
+            if (prec == TreeInfo.notExpression) print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitBreak(JCBreak tree) {
+        try {
+            print("break");
+            if (tree.label != null) print(" " + tree.label);
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitContinue(JCContinue tree) {
+        try {
+            print("continue");
+            if (tree.label != null) print(" " + tree.label);
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitReturn(JCReturn tree) {
+        try {
+            print("return");
+            if (tree.expr != null) {
+                print(" ");
+                printExpr(tree.expr);
+            }
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitThrow(JCThrow tree) {
+        try {
+            print("throw ");
+            printExpr(tree.expr);
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitAssert(JCAssert tree) {
+        try {
+            print("assert ");
+            printExpr(tree.cond);
+            if (tree.detail != null) {
+                print(" : ");
+                printExpr(tree.detail);
+            }
+            print(";");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitApply(JCMethodInvocation tree) {
+        try {
+            if (!tree.typeargs.isEmpty()) {
+                if (tree.meth.hasTag(SELECT)) {
+                    JCFieldAccess left = (JCFieldAccess)tree.meth;
+                    printExpr(left.selected);
+                    print(".<");
+                    printExprs(tree.typeargs);
+                    print(">" + left.name);
+                } else {
+                    print("<");
+                    printExprs(tree.typeargs);
+                    print(">");
+                    printExpr(tree.meth);
+                }
+            } else {
+                printExpr(tree.meth);
+            }
+            print("(");
+            printExprs(tree.args);
+            print(")");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitNewClass(JCNewClass tree) {
+        try {
+            if (tree.encl != null) {
+                printExpr(tree.encl);
+                print(".");
+            }
+            print("new ");
+            if (!tree.typeargs.isEmpty()) {
+                print("<");
+                printExprs(tree.typeargs);
+                print(">");
+            }
+            if (tree.def != null && tree.def.mods.annotations.nonEmpty()) {
+                printTypeAnnotations(tree.def.mods.annotations);
+            }
+            printExpr(tree.clazz);
+            print("(");
+            printExprs(tree.args);
+            print(")");
+            if (tree.def != null) {
+                Name enclClassNamePrev = enclClassName;
+                enclClassName =
+                        tree.def.name != null ? tree.def.name :
+                            tree.type != null && tree.type.tsym.name != tree.type.tsym.name.table.names.empty
+                                ? tree.type.tsym.name : null;
+                if ((tree.def.mods.flags & Flags.ENUM) != 0) print("/*enum*/");
+                printBlock(tree.def.defs);
+                enclClassName = enclClassNamePrev;
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitNewArray(JCNewArray tree) {
+        try {
+            if (tree.elemtype != null) {
+                print("new ");
+                JCTree elem = tree.elemtype;
+                printBaseElementType(elem);
+
+                if (!tree.annotations.isEmpty()) {
+                    print(' ');
+                    printTypeAnnotations(tree.annotations);
+                }
+                if (tree.elems != null) {
+                    print("[]");
+                }
+
+                int i = 0;
+                List<List<JCAnnotation>> da = tree.dimAnnotations;
+                for (List<JCExpression> l = tree.dims; l.nonEmpty(); l = l.tail) {
+                    if (da.size() > i && !da.get(i).isEmpty()) {
+                        print(' ');
+                        printTypeAnnotations(da.get(i));
+                    }
+                    print("[");
+                    i++;
+                    printExpr(l.head);
+                    print("]");
+                }
+                printBrackets(elem);
+            }
+            if (tree.elems != null) {
+                print("{");
+                printExprs(tree.elems);
+                print("}");
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitLambda(JCLambda tree) {
+        try {
+            print("(");
+            if (tree.paramKind == JCLambda.ParameterKind.EXPLICIT) {
+                printExprs(tree.params);
+            } else {
+                String sep = "";
+                for (JCVariableDecl param : tree.params) {
+                    print(sep);
+                    print(param.name);
+                    sep = ",";
+                }
+            }
+            print(")->");
+            printExpr(tree.body);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitParens(JCParens tree) {
+        try {
+            print("(");
+            printExpr(tree.expr);
+            print(")");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitAssign(JCAssign tree) {
+        try {
+            open(prec, TreeInfo.assignPrec);
+            printExpr(tree.lhs, TreeInfo.assignPrec + 1);
+            print(" = ");
+            printExpr(tree.rhs, TreeInfo.assignPrec);
+            close(prec, TreeInfo.assignPrec);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public String operatorName(JCTree.Tag tag) {
+        switch(tag) {
+            case POS:     return "+";
+            case NEG:     return "-";
+            case NOT:     return "!";
+            case COMPL:   return "~";
+            case PREINC:  return "++";
+            case PREDEC:  return "--";
+            case POSTINC: return "++";
+            case POSTDEC: return "--";
+            case NULLCHK: return "<*nullchk*>";
+            case OR:      return "||";
+            case AND:     return "&&";
+            case EQ:      return "==";
+            case NE:      return "!=";
+            case LT:      return "<";
+            case GT:      return ">";
+            case LE:      return "<=";
+            case GE:      return ">=";
+            case BITOR:   return "|";
+            case BITXOR:  return "^";
+            case BITAND:  return "&";
+            case SL:      return "<<";
+            case SR:      return ">>";
+            case USR:     return ">>>";
+            case PLUS:    return "+";
+            case MINUS:   return "-";
+            case MUL:     return "*";
+            case DIV:     return "/";
+            case MOD:     return "%";
+            default: throw new Error();
+        }
+    }
+
+    public void visitAssignop(JCAssignOp tree) {
+        try {
+            open(prec, TreeInfo.assignopPrec);
+            printExpr(tree.lhs, TreeInfo.assignopPrec + 1);
+            print(" " + operatorName(tree.getTag().noAssignOp()) + "= ");
+            printExpr(tree.rhs, TreeInfo.assignopPrec);
+            close(prec, TreeInfo.assignopPrec);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitUnary(JCUnary tree) {
+        try {
+            int ownprec = TreeInfo.opPrec(tree.getTag());
+            String opname = operatorName(tree.getTag());
+            open(prec, ownprec);
+            if (!tree.getTag().isPostUnaryOp()) {
+                print(opname);
+                printExpr(tree.arg, ownprec);
+            } else {
+                printExpr(tree.arg, ownprec);
+                print(opname);
+            }
+            close(prec, ownprec);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitBinary(JCBinary tree) {
+        try {
+            int ownprec = TreeInfo.opPrec(tree.getTag());
+            String opname = operatorName(tree.getTag());
+            open(prec, ownprec);
+            printExpr(tree.lhs, ownprec);
+            print(" " + opname + " ");
+            printExpr(tree.rhs, ownprec + 1);
+            close(prec, ownprec);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTypeCast(JCTypeCast tree) {
+        try {
+            open(prec, TreeInfo.prefixPrec);
+            print("(");
+            printExpr(tree.clazz);
+            print(")");
+            printExpr(tree.expr, TreeInfo.prefixPrec);
+            close(prec, TreeInfo.prefixPrec);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTypeTest(JCInstanceOf tree) {
+        try {
+            open(prec, TreeInfo.ordPrec);
+            printExpr(tree.expr, TreeInfo.ordPrec);
+            print(" instanceof ");
+            printExpr(tree.clazz, TreeInfo.ordPrec + 1);
+            close(prec, TreeInfo.ordPrec);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitIndexed(JCArrayAccess tree) {
+        try {
+            printExpr(tree.indexed, TreeInfo.postfixPrec);
+            print("[");
+            printExpr(tree.index);
+            print("]");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitSelect(JCFieldAccess tree) {
+        try {
+            printExpr(tree.selected, TreeInfo.postfixPrec);
+            print("." + tree.name);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitReference(JCMemberReference tree) {
+        try {
+            printExpr(tree.expr);
+            print("::");
+            if (tree.typeargs != null) {
+                print("<");
+                printExprs(tree.typeargs);
+                print(">");
+            }
+            print(tree.getMode() == ReferenceMode.INVOKE ? tree.name : "new");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitIdent(JCIdent tree) {
+        try {
+            print(tree.name);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitLiteral(JCLiteral tree) {
+        try {
+            switch (tree.typetag) {
+                case INT:
+                    print(tree.value.toString());
+                    break;
+                case LONG:
+                    print(tree.value + "L");
+                    break;
+                case FLOAT:
+                    print(tree.value + "F");
+                    break;
+                case DOUBLE:
+                    print(tree.value.toString());
+                    break;
+                case CHAR:
+                    print("\'" +
+                            Convert.quote(
+                            String.valueOf((char)((Number)tree.value).intValue())) +
+                            "\'");
+                    break;
+                case BOOLEAN:
+                    print(((Number)tree.value).intValue() == 1 ? "true" : "false");
+                    break;
+                case BOT:
+                    print("null");
+                    break;
+                default:
+                    print("\"" + Convert.quote(tree.value.toString()) + "\"");
+                    break;
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTypeIdent(JCPrimitiveTypeTree tree) {
+        try {
+            switch(tree.typetag) {
+                case BYTE:
+                    print("byte");
+                    break;
+                case CHAR:
+                    print("char");
+                    break;
+                case SHORT:
+                    print("short");
+                    break;
+                case INT:
+                    print("int");
+                    break;
+                case LONG:
+                    print("long");
+                    break;
+                case FLOAT:
+                    print("float");
+                    break;
+                case DOUBLE:
+                    print("double");
+                    break;
+                case BOOLEAN:
+                    print("boolean");
+                    break;
+                case VOID:
+                    print("void");
+                    break;
+                default:
+                    print("error");
+                    break;
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTypeArray(JCArrayTypeTree tree) {
+        try {
+            printBaseElementType(tree);
+            printBrackets(tree);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    // Prints the inner element type of a nested array
+    private void printBaseElementType(JCTree tree) throws IOException {
+        printExpr(TreeInfo.innermostType(tree));
+    }
+
+    // prints the brackets of a nested array in reverse order
+    // tree is either JCArrayTypeTree or JCAnnotatedTypeTree
+    private void printBrackets(JCTree tree) throws IOException {
+        JCTree elem = tree;
+        while (true) {
+            if (elem.hasTag(ANNOTATED_TYPE)) {
+                JCAnnotatedType atype = (JCAnnotatedType) elem;
+                elem = atype.underlyingType;
+                if (elem.hasTag(TYPEARRAY)) {
+                    print(' ');
+                    printTypeAnnotations(atype.annotations);
+                }
+            }
+            if (elem.hasTag(TYPEARRAY)) {
+                print("[]");
+                elem = ((JCArrayTypeTree)elem).elemtype;
+            } else {
+                break;
+            }
+        }
+    }
+
+    public void visitTypeApply(JCTypeApply tree) {
+        try {
+            printExpr(tree.clazz);
+            print("<");
+            printExprs(tree.arguments);
+            print(">");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTypeUnion(JCTypeUnion tree) {
+        try {
+            printExprs(tree.alternatives, " | ");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTypeIntersection(JCTypeIntersection tree) {
+        try {
+            printExprs(tree.bounds, " & ");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTypeParameter(JCTypeParameter tree) {
+        try {
+            if (tree.annotations.nonEmpty()) {
+                this.printTypeAnnotations(tree.annotations);
+            }
+            print(tree.name);
+            if (tree.bounds.nonEmpty()) {
+                print(" extends ");
+                printExprs(tree.bounds, " & ");
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void visitWildcard(JCWildcard tree) {
+        try {
+            print(tree.kind);
+            if (tree.kind.kind != BoundKind.UNBOUND)
+                printExpr(tree.inner);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public void visitTypeBoundKind(TypeBoundKind tree) {
+        try {
+            print(String.valueOf(tree.kind));
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitErroneous(JCErroneous tree) {
+        try {
+            print("(ERROR)");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitLetExpr(LetExpr tree) {
+        try {
+            print("(let " + tree.defs + " in " + tree.expr + ")");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitModifiers(JCModifiers mods) {
+        try {
+            printAnnotations(mods.annotations);
+            printFlags(mods.flags);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitAnnotation(JCAnnotation tree) {
+        try {
+            print("@");
+            printExpr(tree.annotationType);
+            print("(");
+            printExprs(tree.args);
+            print(")");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitAnnotatedType(JCAnnotatedType tree) {
+        try {
+            if (tree.underlyingType.hasTag(SELECT)) {
+                JCFieldAccess access = (JCFieldAccess) tree.underlyingType;
+                printExpr(access.selected, TreeInfo.postfixPrec);
+                print(".");
+                printTypeAnnotations(tree.annotations);
+                print(access.name);
+            } else if (tree.underlyingType.hasTag(TYPEARRAY)) {
+                printBaseElementType(tree);
+                printBrackets(tree);
+            } else {
+                printTypeAnnotations(tree.annotations);
+                printExpr(tree.underlyingType);
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void visitTree(JCTree tree) {
+        try {
+            print("(UNKNOWN: " + tree.getTag() + ")");
+            println();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+}