1 /*
   2  * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.javac.tree;
  27 
  28 import java.io.*;
  29 
  30 import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
  31 import com.sun.source.tree.ModuleTree.ModuleKind;
  32 import com.sun.tools.javac.code.*;
  33 import com.sun.tools.javac.tree.JCTree.*;
  34 import com.sun.tools.javac.util.*;
  35 
  36 import static com.sun.tools.javac.code.Flags.*;
  37 import static com.sun.tools.javac.code.Flags.ANNOTATION;
  38 import static com.sun.tools.javac.tree.JCTree.Tag.*;
  39 
  40 /** Prints out a tree as an indented Java source program.
  41  *
  42  *  <p><b>This is NOT part of any supported API.
  43  *  If you write code that depends on this, you do so at your own risk.
  44  *  This code and its internal interfaces are subject to change or
  45  *  deletion without notice.</b>
  46  */
  47 public class Pretty extends JCTree.Visitor {
  48 
  49     public Pretty(Writer out, boolean sourceOutput) {
  50         this.out = out;
  51         this.sourceOutput = sourceOutput;
  52     }
  53 
  54     /** Set when we are producing source output.  If we're not
  55      *  producing source output, we can sometimes give more detail in
  56      *  the output even though that detail would not be valid java
  57      *  source.
  58      */
  59     private final boolean sourceOutput;
  60 
  61     /** The output stream on which trees are printed.
  62      */
  63     Writer out;
  64 
  65     /** Indentation width (can be reassigned from outside).
  66      */
  67     public int width = 4;
  68 
  69     /** The current left margin.
  70      */
  71     int lmargin = 0;
  72 
  73     /** The enclosing class name.
  74      */
  75     Name enclClassName;
  76 
  77     /** A table mapping trees to their documentation comments
  78      *  (can be null)
  79      */
  80     DocCommentTable docComments = null;
  81 
  82     /**
  83      * A string sequence to be used when Pretty output should be constrained
  84      * to fit into a given size
  85      */
  86     private static final String trimSequence = "[...]";
  87 
  88     /**
  89      * Max number of chars to be generated when output should fit into a single line
  90      */
  91     private static final int PREFERRED_LENGTH = 20;
  92 
  93     /** Align code to be indented to left margin.
  94      */
  95     void align() throws IOException {
  96         for (int i = 0; i < lmargin; i++) out.write(" ");
  97     }
  98 
  99     /** Increase left margin by indentation width.
 100      */
 101     void indent() {
 102         lmargin = lmargin + width;
 103     }
 104 
 105     /** Decrease left margin by indentation width.
 106      */
 107     void undent() {
 108         lmargin = lmargin - width;
 109     }
 110 
 111     /** Enter a new precedence level. Emit a `(' if new precedence level
 112      *  is less than precedence level so far.
 113      *  @param contextPrec    The precedence level in force so far.
 114      *  @param ownPrec        The new precedence level.
 115      */
 116     void open(int contextPrec, int ownPrec) throws IOException {
 117         if (ownPrec < contextPrec) out.write("(");
 118     }
 119 
 120     /** Leave precedence level. Emit a `(' if inner precedence level
 121      *  is less than precedence level we revert to.
 122      *  @param contextPrec    The precedence level we revert to.
 123      *  @param ownPrec        The inner precedence level.
 124      */
 125     void close(int contextPrec, int ownPrec) throws IOException {
 126         if (ownPrec < contextPrec) out.write(")");
 127     }
 128 
 129     /** Print string, replacing all non-ascii character with unicode escapes.
 130      */
 131     public void print(Object s) throws IOException {
 132         out.write(Convert.escapeUnicode(s.toString()));
 133     }
 134 
 135     /** Print new line.
 136      */
 137     public void println() throws IOException {
 138         out.write(lineSep);
 139     }
 140 
 141     public static String toSimpleString(JCTree tree) {
 142         return toSimpleString(tree, PREFERRED_LENGTH);
 143     }
 144 
 145     public static String toSimpleString(JCTree tree, int maxLength) {
 146         StringWriter s = new StringWriter();
 147         try {
 148             new Pretty(s, false).printExpr(tree);
 149         }
 150         catch (IOException e) {
 151             // should never happen, because StringWriter is defined
 152             // never to throw any IOExceptions
 153             throw new AssertionError(e);
 154         }
 155         //we need to (i) replace all line terminators with a space and (ii) remove
 156         //occurrences of 'missing' in the Pretty output (generated when types are missing)
 157         String res = s.toString().trim().replaceAll("\\s+", " ").replaceAll("/\\*missing\\*/", "");
 158         if (res.length() < maxLength) {
 159             return res;
 160         } else {
 161             int head = (maxLength - trimSequence.length()) * 2 / 3;
 162             int tail = maxLength - trimSequence.length() - head;
 163             return res.substring(0, head) + trimSequence + res.substring(res.length() - tail);
 164         }
 165     }
 166 
 167     String lineSep = System.getProperty("line.separator");
 168 
 169     /* ************************************************************************
 170      * Traversal methods
 171      *************************************************************************/
 172 
 173     /** Exception to propagate IOException through visitXYZ methods */
 174     private static class UncheckedIOException extends Error {
 175         static final long serialVersionUID = -4032692679158424751L;
 176         UncheckedIOException(IOException e) {
 177             super(e.getMessage(), e);
 178         }
 179     }
 180 
 181     /** Visitor argument: the current precedence level.
 182      */
 183     int prec;
 184 
 185     /** Visitor method: print expression tree.
 186      *  @param prec  The current precedence level.
 187      */
 188     public void printExpr(JCTree tree, int prec) throws IOException {
 189         int prevPrec = this.prec;
 190         try {
 191             this.prec = prec;
 192             if (tree == null) print("/*missing*/");
 193             else {
 194                 tree.accept(this);
 195             }
 196         } catch (UncheckedIOException ex) {
 197             throw new IOException(ex.getMessage(), ex);
 198         } finally {
 199             this.prec = prevPrec;
 200         }
 201     }
 202 
 203     /** Derived visitor method: print expression tree at minimum precedence level
 204      *  for expression.
 205      */
 206     public void printExpr(JCTree tree) throws IOException {
 207         printExpr(tree, TreeInfo.noPrec);
 208     }
 209 
 210     /** Derived visitor method: print statement tree.
 211      */
 212     public void printStat(JCTree tree) throws IOException {
 213         printExpr(tree, TreeInfo.notExpression);
 214     }
 215 
 216     /** Derived visitor method: print list of expression trees, separated by given string.
 217      *  @param sep the separator string
 218      */
 219     public <T extends JCTree> void printExprs(List<T> trees, String sep) throws IOException {
 220         if (trees.nonEmpty()) {
 221             printExpr(trees.head);
 222             for (List<T> l = trees.tail; l.nonEmpty(); l = l.tail) {
 223                 print(sep);
 224                 printExpr(l.head);
 225             }
 226         }
 227     }
 228 
 229     /** Derived visitor method: print list of expression trees, separated by commas.
 230      */
 231     public <T extends JCTree> void printExprs(List<T> trees) throws IOException {
 232         printExprs(trees, ", ");
 233     }
 234 
 235 
 236     /** Derived visitor method: print pattern.
 237      */
 238 
 239     public void printPattern(JCTree tree) throws IOException {
 240         printExpr(tree);
 241     }
 242 
 243     /** Derived visitor method: print list of statements, each on a separate line.
 244      */
 245     public void printStats(List<? extends JCTree> trees) throws IOException {
 246         for (List<? extends JCTree> l = trees; l.nonEmpty(); l = l.tail) {
 247             align();
 248             printStat(l.head);
 249             println();
 250         }
 251     }
 252 
 253     /** Print a set of modifiers.
 254      */
 255     public void printFlags(long flags) throws IOException {
 256         if ((flags & SYNTHETIC) != 0) print("/*synthetic*/ ");
 257         print(TreeInfo.flagNames(flags));
 258         if ((flags & ExtendedStandardFlags) != 0) print(" ");
 259         if ((flags & ANNOTATION) != 0) print("@");
 260     }
 261 
 262     public void printAnnotations(List<JCAnnotation> trees) throws IOException {
 263         for (List<JCAnnotation> l = trees; l.nonEmpty(); l = l.tail) {
 264             printStat(l.head);
 265             println();
 266             align();
 267         }
 268     }
 269 
 270     public void printTypeAnnotations(List<JCAnnotation> trees) throws IOException {
 271         for (List<JCAnnotation> l = trees; l.nonEmpty(); l = l.tail) {
 272             printExpr(l.head);
 273             print(" ");
 274         }
 275     }
 276 
 277     /** Print documentation comment, if it exists
 278      *  @param tree    The tree for which a documentation comment should be printed.
 279      */
 280     public void printDocComment(JCTree tree) throws IOException {
 281         if (docComments != null) {
 282             String dc = docComments.getCommentText(tree);
 283             if (dc != null) {
 284                 print("/**"); println();
 285                 int pos = 0;
 286                 int endpos = lineEndPos(dc, pos);
 287                 while (pos < dc.length()) {
 288                     align();
 289                     print(" *");
 290                     if (pos < dc.length() && dc.charAt(pos) > ' ') print(" ");
 291                     print(dc.substring(pos, endpos)); println();
 292                     pos = endpos + 1;
 293                     endpos = lineEndPos(dc, pos);
 294                 }
 295                 align(); print(" */"); println();
 296                 align();
 297             }
 298         }
 299     }
 300 //where
 301     static int lineEndPos(String s, int start) {
 302         int pos = s.indexOf('\n', start);
 303         if (pos < 0) pos = s.length();
 304         return pos;
 305     }
 306 
 307     /** If type parameter list is non-empty, print it enclosed in
 308      *  {@literal "<...>"} brackets.
 309      */
 310     public void printTypeParameters(List<JCTypeParameter> trees) throws IOException {
 311         if (trees.nonEmpty()) {
 312             print("<");
 313             printExprs(trees);
 314             print(">");
 315         }
 316     }
 317 
 318     /** Print a block.
 319      */
 320     public void printBlock(List<? extends JCTree> stats) throws IOException {
 321         print("{");
 322         println();
 323         indent();
 324         printStats(stats);
 325         undent();
 326         align();
 327         print("}");
 328     }
 329 
 330     /** Print a block.
 331      */
 332     public void printEnumBody(List<JCTree> stats) throws IOException {
 333         print("{");
 334         println();
 335         indent();
 336         boolean first = true;
 337         for (List<JCTree> l = stats; l.nonEmpty(); l = l.tail) {
 338             if (isEnumerator(l.head)) {
 339                 if (!first) {
 340                     print(",");
 341                     println();
 342                 }
 343                 align();
 344                 printStat(l.head);
 345                 first = false;
 346             }
 347         }
 348         print(";");
 349         println();
 350         for (List<JCTree> l = stats; l.nonEmpty(); l = l.tail) {
 351             if (!isEnumerator(l.head)) {
 352                 align();
 353                 printStat(l.head);
 354                 println();
 355             }
 356         }
 357         undent();
 358         align();
 359         print("}");
 360     }
 361 
 362     /** Is the given tree an enumerator definition? */
 363     boolean isEnumerator(JCTree t) {
 364         return t.hasTag(VARDEF) && (((JCVariableDecl) t).mods.flags & ENUM) != 0;
 365     }
 366 
 367     /** Print unit consisting of package clause and import statements in toplevel,
 368      *  followed by class definition. if class definition == null,
 369      *  print all definitions in toplevel.
 370      *  @param tree     The toplevel tree
 371      *  @param cdef     The class definition, which is assumed to be part of the
 372      *                  toplevel tree.
 373      */
 374     public void printUnit(JCCompilationUnit tree, JCClassDecl cdef) throws IOException {
 375         docComments = tree.docComments;
 376         printDocComment(tree);
 377 
 378         boolean firstImport = true;
 379         for (List<JCTree> l = tree.defs;
 380              l.nonEmpty() &&
 381                  (cdef == null ||
 382                   l.head.hasTag(IMPORT) || l.head.hasTag(PACKAGEDEF));
 383              l = l.tail) {
 384             if (l.head.hasTag(IMPORT)) {
 385                 JCImport imp = (JCImport)l.head;
 386                 Name name = TreeInfo.name(imp.qualid);
 387                 if (name == name.table.names.asterisk ||
 388                         cdef == null ||
 389                         isUsed(TreeInfo.symbol(imp.qualid), cdef)) {
 390                     if (firstImport) {
 391                         firstImport = false;
 392                         println();
 393                     }
 394                     printStat(imp);
 395                 }
 396             } else {
 397                 printStat(l.head);
 398             }
 399         }
 400         if (cdef != null) {
 401             printStat(cdef);
 402             println();
 403         }
 404     }
 405     // where
 406     boolean isUsed(final Symbol t, JCTree cdef) {
 407         class UsedVisitor extends TreeScanner {
 408             public void scan(JCTree tree) {
 409                 if (tree!=null && !result) tree.accept(this);
 410             }
 411             boolean result = false;
 412             public void visitIdent(JCIdent tree) {
 413                 if (tree.sym == t) result = true;
 414             }
 415         }
 416         UsedVisitor v = new UsedVisitor();
 417         v.scan(cdef);
 418         return v.result;
 419     }
 420 
 421     /**************************************************************************
 422      * Visitor methods
 423      *************************************************************************/
 424 
 425     public void visitTopLevel(JCCompilationUnit tree) {
 426         try {
 427             printUnit(tree, null);
 428         } catch (IOException e) {
 429             throw new UncheckedIOException(e);
 430         }
 431     }
 432 
 433     public void visitPackageDef(JCPackageDecl tree) {
 434         try {
 435             printDocComment(tree);
 436             printAnnotations(tree.annotations);
 437             if (tree.pid != null) {
 438                 print("package ");
 439                 printExpr(tree.pid);
 440                 print(";");
 441                 println();
 442             }
 443         } catch (IOException e) {
 444             throw new UncheckedIOException(e);
 445         }
 446     }
 447 
 448     @Override
 449     public void visitModuleDef(JCModuleDecl tree) {
 450         try {
 451             printDocComment(tree);
 452             printAnnotations(tree.mods.annotations);
 453             if (tree.getModuleType() == ModuleKind.OPEN) {
 454                 print("open ");
 455             }
 456             print("module ");
 457             printExpr(tree.qualId);
 458             if (tree.directives == null) {
 459                 print(";");
 460             } else {
 461                 print(" ");
 462                 printBlock(tree.directives);
 463             }
 464             println();
 465         } catch (IOException e) {
 466             throw new UncheckedIOException(e);
 467         }
 468     }
 469 
 470     @Override
 471     public void visitExports(JCExports tree) {
 472         try {
 473             print("exports ");
 474             printExpr(tree.qualid);
 475             if (tree.moduleNames != null) {
 476                 print(" to ");
 477                 printExprs(tree.moduleNames);
 478             }
 479             print(";");
 480         } catch (IOException e) {
 481             throw new UncheckedIOException(e);
 482         }
 483     }
 484 
 485     @Override
 486     public void visitOpens(JCOpens tree) {
 487         try {
 488             print("opens ");
 489             printExpr(tree.qualid);
 490             if (tree.moduleNames != null) {
 491                 print(" to ");
 492                 printExprs(tree.moduleNames);
 493             }
 494             print(";");
 495         } catch (IOException e) {
 496             throw new UncheckedIOException(e);
 497         }
 498     }
 499 
 500     @Override
 501     public void visitProvides(JCProvides tree) {
 502         try {
 503             print("provides ");
 504             printExpr(tree.serviceName);
 505             print(" with ");
 506             printExprs(tree.implNames);
 507             print(";");
 508         } catch (IOException e) {
 509             throw new UncheckedIOException(e);
 510         }
 511     }
 512 
 513     @Override
 514     public void visitRequires(JCRequires tree) {
 515         try {
 516             print("requires ");
 517             if (tree.isStaticPhase)
 518                 print("static ");
 519             if (tree.isTransitive)
 520                 print("transitive ");
 521             printExpr(tree.moduleName);
 522             print(";");
 523         } catch (IOException e) {
 524             throw new UncheckedIOException(e);
 525         }
 526     }
 527 
 528     @Override
 529     public void visitUses(JCUses tree) {
 530         try {
 531             print("uses ");
 532             printExpr(tree.qualid);
 533             print(";");
 534         } catch (IOException e) {
 535             throw new UncheckedIOException(e);
 536         }
 537     }
 538 
 539     public void visitImport(JCImport tree) {
 540         try {
 541             print("import ");
 542             if (tree.staticImport) print("static ");
 543             printExpr(tree.qualid);
 544             print(";");
 545             println();
 546         } catch (IOException e) {
 547             throw new UncheckedIOException(e);
 548         }
 549     }
 550 
 551     public void visitClassDef(JCClassDecl tree) {
 552         try {
 553             println(); align();
 554             printDocComment(tree);
 555             printAnnotations(tree.mods.annotations);
 556             printFlags(tree.mods.flags & ~INTERFACE);
 557             Name enclClassNamePrev = enclClassName;
 558             enclClassName = tree.name;
 559             if ((tree.mods.flags & INTERFACE) != 0) {
 560                 print("interface " + tree.name);
 561                 printTypeParameters(tree.typarams);
 562                 if (tree.implementing.nonEmpty()) {
 563                     print(" extends ");
 564                     printExprs(tree.implementing);
 565                 }
 566                 if (tree.permitting.nonEmpty()) {
 567                     print(" permits ");
 568                     printExprs(tree.permitting);
 569                 }
 570             } else {
 571                 if ((tree.mods.flags & ENUM) != 0)
 572                     print("enum " + tree.name);
 573                 else
 574                     print("class " + tree.name);
 575                 printTypeParameters(tree.typarams);
 576                 if (tree.extending != null) {
 577                     print(" extends ");
 578                     printExpr(tree.extending);
 579                 }
 580                 if (tree.implementing.nonEmpty()) {
 581                     print(" implements ");
 582                     printExprs(tree.implementing);
 583                 }
 584                 if (tree.permitting.nonEmpty()) {
 585                     print(" permits ");
 586                     printExprs(tree.permitting);
 587                 }
 588             }
 589             print(" ");
 590             if ((tree.mods.flags & ENUM) != 0) {
 591                 printEnumBody(tree.defs);
 592             } else {
 593                 printBlock(tree.defs);
 594             }
 595             enclClassName = enclClassNamePrev;
 596         } catch (IOException e) {
 597             throw new UncheckedIOException(e);
 598         }
 599     }
 600 
 601     public void visitMethodDef(JCMethodDecl tree) {
 602         try {
 603             // when producing source output, omit anonymous constructors
 604             if (tree.name == tree.name.table.names.init &&
 605                     enclClassName == null &&
 606                     sourceOutput) return;
 607             println(); align();
 608             printDocComment(tree);
 609             printExpr(tree.mods);
 610             printTypeParameters(tree.typarams);
 611             if (tree.name == tree.name.table.names.init) {
 612                 print(enclClassName != null ? enclClassName : tree.name);
 613             } else {
 614                 printExpr(tree.restype);
 615                 print(" " + tree.name);
 616             }
 617             print("(");
 618             if (tree.recvparam!=null) {
 619                 printExpr(tree.recvparam);
 620                 if (tree.params.size() > 0) {
 621                     print(", ");
 622                 }
 623             }
 624             printExprs(tree.params);
 625             print(")");
 626             if (tree.thrown.nonEmpty()) {
 627                 print(" throws ");
 628                 printExprs(tree.thrown);
 629             }
 630             if (tree.defaultValue != null) {
 631                 print(" default ");
 632                 printExpr(tree.defaultValue);
 633             }
 634             if (tree.body != null) {
 635                 print(" ");
 636                 printStat(tree.body);
 637             } else {
 638                 print(";");
 639             }
 640         } catch (IOException e) {
 641             throw new UncheckedIOException(e);
 642         }
 643     }
 644 
 645     public void visitVarDef(JCVariableDecl tree) {
 646         try {
 647             if (docComments != null && docComments.hasComment(tree)) {
 648                 println(); align();
 649             }
 650             printDocComment(tree);
 651             if ((tree.mods.flags & ENUM) != 0) {
 652                 print("/*public static final*/ ");
 653                 print(tree.name);
 654                 if (tree.init != null) {
 655                     if (tree.init.hasTag(NEWCLASS)) {
 656                         JCNewClass init = (JCNewClass) tree.init;
 657                         if (sourceOutput) {
 658                             print(" /*enum*/ ");
 659                             if (init.args != null && init.args.nonEmpty()) {
 660                                 print("(");
 661                                 print(init.args);
 662                                 print(")");
 663                             }
 664                             if (init.def != null && init.def.defs != null) {
 665                                 print(" ");
 666                                 printBlock(init.def.defs);
 667                             }
 668                             return;
 669                         }else {
 670                             print(" /* = ");
 671                             print("new ");
 672                             if (init.def != null && init.def.mods.annotations.nonEmpty()) {
 673                                 printTypeAnnotations(init.def.mods.annotations);
 674                             }
 675                             printExpr(init.clazz);
 676                             print("(");
 677                             printExprs(init.args);
 678                             print(")");
 679                             print(" */");
 680                             print(" /*enum*/ ");
 681                             if (init.args != null && init.args.nonEmpty()) {
 682                                 print("(");
 683                                 printExprs(init.args);
 684                                 print(")");
 685                             }
 686                             if (init.def != null && init.def.defs != null) {
 687                                 print(" ");
 688                                 printBlock(init.def.defs);
 689                             }
 690                             return;
 691                         }
 692                     }
 693                     print(" /* = ");
 694                     printExpr(tree.init);
 695                     print(" */");
 696                 }
 697             } else {
 698                 printExpr(tree.mods);
 699                 if ((tree.mods.flags & VARARGS) != 0) {
 700                     JCTree vartype = tree.vartype;
 701                     List<JCAnnotation> tas = null;
 702                     if (vartype instanceof JCAnnotatedType annotatedType) {
 703                         tas = annotatedType.annotations;
 704                         vartype = annotatedType.underlyingType;
 705                     }
 706                     printExpr(((JCArrayTypeTree) vartype).elemtype);
 707                     if (tas != null) {
 708                         print(' ');
 709                         printTypeAnnotations(tas);
 710                     }
 711                     print("... " + tree.name);
 712                 } else {
 713                     printExpr(tree.vartype);
 714                     print(" " + tree.name);
 715                 }
 716                 if (tree.init != null) {
 717                     print(" = ");
 718                     printExpr(tree.init);
 719                 }
 720                 if (prec == TreeInfo.notExpression) print(";");
 721             }
 722         } catch (IOException e) {
 723             throw new UncheckedIOException(e);
 724         }
 725     }
 726 
 727     public void visitSkip(JCSkip tree) {
 728         try {
 729             print(";");
 730         } catch (IOException e) {
 731             throw new UncheckedIOException(e);
 732         }
 733     }
 734 
 735     public void visitBlock(JCBlock tree) {
 736         try {
 737             printFlags(tree.flags);
 738             printBlock(tree.stats);
 739         } catch (IOException e) {
 740             throw new UncheckedIOException(e);
 741         }
 742     }
 743 
 744     public void visitDefaultValue(JCDefaultValue tree) {
 745         try {
 746             printExpr(tree.clazz, TreeInfo.postfixPrec);
 747             print(".default");
 748         } catch (IOException e) {
 749             throw new UncheckedIOException(e);
 750         }
 751     }
 752 
 753     public void visitDoLoop(JCDoWhileLoop tree) {
 754         try {
 755             print("do ");
 756             printStat(tree.body);
 757             align();
 758             print(" while ");
 759             if (tree.cond.hasTag(PARENS)) {
 760                 printExpr(tree.cond);
 761             } else {
 762                 print("(");
 763                 printExpr(tree.cond);
 764                 print(")");
 765             }
 766             print(";");
 767         } catch (IOException e) {
 768             throw new UncheckedIOException(e);
 769         }
 770     }
 771 
 772     public void visitWhileLoop(JCWhileLoop tree) {
 773         try {
 774             print("while ");
 775             if (tree.cond.hasTag(PARENS)) {
 776                 printExpr(tree.cond);
 777             } else {
 778                 print("(");
 779                 printExpr(tree.cond);
 780                 print(")");
 781             }
 782             print(" ");
 783             printStat(tree.body);
 784         } catch (IOException e) {
 785             throw new UncheckedIOException(e);
 786         }
 787     }
 788 
 789     public void visitWithField(JCWithField tree) {
 790         try {
 791             print("__WithField(");
 792             printExpr(tree.field);
 793             print(", ");
 794             printExpr(tree.value);
 795             print(")");
 796         } catch (IOException e) {
 797             throw new UncheckedIOException(e);
 798         }
 799     }
 800 
 801     public void visitForLoop(JCForLoop tree) {
 802         try {
 803             print("for (");
 804             if (tree.init.nonEmpty()) {
 805                 if (tree.init.head.hasTag(VARDEF)) {
 806                     printExpr(tree.init.head);
 807                     for (List<JCStatement> l = tree.init.tail; l.nonEmpty(); l = l.tail) {
 808                         JCVariableDecl vdef = (JCVariableDecl)l.head;
 809                         print(", " + vdef.name);
 810                         if (vdef.init != null) {
 811                             print(" = ");
 812                             printExpr(vdef.init);
 813                         }
 814                     }
 815                 } else {
 816                     printExprs(tree.init);
 817                 }
 818             }
 819             print("; ");
 820             if (tree.cond != null) printExpr(tree.cond);
 821             print("; ");
 822             printExprs(tree.step);
 823             print(") ");
 824             printStat(tree.body);
 825         } catch (IOException e) {
 826             throw new UncheckedIOException(e);
 827         }
 828     }
 829 
 830     public void visitForeachLoop(JCEnhancedForLoop tree) {
 831         try {
 832             print("for (");
 833             printExpr(tree.var);
 834             print(" : ");
 835             printExpr(tree.expr);
 836             print(") ");
 837             printStat(tree.body);
 838         } catch (IOException e) {
 839             throw new UncheckedIOException(e);
 840         }
 841     }
 842 
 843     public void visitLabelled(JCLabeledStatement tree) {
 844         try {
 845             print(tree.label + ": ");
 846             printStat(tree.body);
 847         } catch (IOException e) {
 848             throw new UncheckedIOException(e);
 849         }
 850     }
 851 
 852     public void visitSwitch(JCSwitch tree) {
 853         try {
 854             print("switch ");
 855             if (tree.selector.hasTag(PARENS)) {
 856                 printExpr(tree.selector);
 857             } else {
 858                 print("(");
 859                 printExpr(tree.selector);
 860                 print(")");
 861             }
 862             print(" {");
 863             println();
 864             printStats(tree.cases);
 865             align();
 866             print("}");
 867         } catch (IOException e) {
 868             throw new UncheckedIOException(e);
 869         }
 870     }
 871 
 872     public void visitCase(JCCase tree) {
 873         try {
 874             if (tree.labels.size() == 1 && tree.labels.get(0).hasTag(DEFAULTCASELABEL)) {
 875                 print("default");
 876             } else {
 877                 print("case ");
 878                 printExprs(tree.labels);
 879             }
 880             if (tree.caseKind == JCCase.STATEMENT) {
 881                 print(":");
 882                 println();
 883                 indent();
 884                 printStats(tree.stats);
 885                 undent();
 886                 align();
 887             } else {
 888                 print(" -> ");
 889                 if (tree.stats.size() == 1) {
 890                     printStat(tree.stats.head);
 891                 } else {
 892                     printBlock(tree.stats);
 893                 }
 894             }
 895         } catch (IOException e) {
 896             throw new UncheckedIOException(e);
 897         }
 898     }
 899 
 900     @Override
 901     public void visitDefaultCaseLabel(JCTree.JCDefaultCaseLabel that) {
 902         try {
 903             print("default");
 904         } catch (IOException e) {
 905             throw new UncheckedIOException(e);
 906         }
 907     }
 908 
 909     public void visitSwitchExpression(JCSwitchExpression tree) {
 910         try {
 911             print("switch ");
 912             if (tree.selector.hasTag(PARENS)) {
 913                 printExpr(tree.selector);
 914             } else {
 915                 print("(");
 916                 printExpr(tree.selector);
 917                 print(")");
 918             }
 919             print(" {");
 920             println();
 921             printStats(tree.cases);
 922             align();
 923             print("}");
 924         } catch (IOException e) {
 925             throw new UncheckedIOException(e);
 926         }
 927     }
 928 
 929     public void visitBindingPattern(JCBindingPattern patt) {
 930         try {
 931             printExpr(patt.var);
 932         } catch (IOException e) {
 933             throw new UncheckedIOException(e);
 934         }
 935     }
 936 
 937     @Override
 938     public void visitParenthesizedPattern(JCParenthesizedPattern patt) {
 939         try {
 940             print("(");
 941             printExpr(patt.pattern);
 942             print(")");
 943         } catch (IOException e) {
 944             throw new UncheckedIOException(e);
 945         }
 946     }
 947 
 948     @Override
 949     public void visitGuardPattern(JCGuardPattern patt) {
 950         try {
 951             printExpr(patt.patt);
 952             print(" && ");
 953             printExpr(patt.expr);
 954         } catch (IOException e) {
 955             throw new UncheckedIOException(e);
 956         }
 957     }
 958 
 959     public void visitSynchronized(JCSynchronized tree) {
 960         try {
 961             print("synchronized ");
 962             if (tree.lock.hasTag(PARENS)) {
 963                 printExpr(tree.lock);
 964             } else {
 965                 print("(");
 966                 printExpr(tree.lock);
 967                 print(")");
 968             }
 969             print(" ");
 970             printStat(tree.body);
 971         } catch (IOException e) {
 972             throw new UncheckedIOException(e);
 973         }
 974     }
 975 
 976     public void visitTry(JCTry tree) {
 977         try {
 978             print("try ");
 979             if (tree.resources.nonEmpty()) {
 980                 print("(");
 981                 boolean first = true;
 982                 for (JCTree var : tree.resources) {
 983                     if (!first) {
 984                         println();
 985                         indent();
 986                     }
 987                     printStat(var);
 988                     first = false;
 989                 }
 990                 print(") ");
 991             }
 992             printStat(tree.body);
 993             for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) {
 994                 printStat(l.head);
 995             }
 996             if (tree.finalizer != null) {
 997                 print(" finally ");
 998                 printStat(tree.finalizer);
 999             }
1000         } catch (IOException e) {
1001             throw new UncheckedIOException(e);
1002         }
1003     }
1004 
1005     public void visitCatch(JCCatch tree) {
1006         try {
1007             print(" catch (");
1008             printExpr(tree.param);
1009             print(") ");
1010             printStat(tree.body);
1011         } catch (IOException e) {
1012             throw new UncheckedIOException(e);
1013         }
1014     }
1015 
1016     public void visitConditional(JCConditional tree) {
1017         try {
1018             open(prec, TreeInfo.condPrec);
1019             printExpr(tree.cond, TreeInfo.condPrec + 1);
1020             print(" ? ");
1021             printExpr(tree.truepart);
1022             print(" : ");
1023             printExpr(tree.falsepart, TreeInfo.condPrec);
1024             close(prec, TreeInfo.condPrec);
1025         } catch (IOException e) {
1026             throw new UncheckedIOException(e);
1027         }
1028     }
1029 
1030     public void visitIf(JCIf tree) {
1031         try {
1032             print("if ");
1033             if (tree.cond.hasTag(PARENS)) {
1034                 printExpr(tree.cond);
1035             } else {
1036                 print("(");
1037                 printExpr(tree.cond);
1038                 print(")");
1039             }
1040             print(" ");
1041             printStat(tree.thenpart);
1042             if (tree.elsepart != null) {
1043                 print(" else ");
1044                 printStat(tree.elsepart);
1045             }
1046         } catch (IOException e) {
1047             throw new UncheckedIOException(e);
1048         }
1049     }
1050 
1051     public void visitExec(JCExpressionStatement tree) {
1052         try {
1053             printExpr(tree.expr);
1054             if (prec == TreeInfo.notExpression) print(";");
1055         } catch (IOException e) {
1056             throw new UncheckedIOException(e);
1057         }
1058     }
1059 
1060     public void visitBreak(JCBreak tree) {
1061         try {
1062             print("break");
1063             if (tree.label != null) print(" " + tree.label);
1064             print(";");
1065         } catch (IOException e) {
1066             throw new UncheckedIOException(e);
1067         }
1068     }
1069 
1070     public void visitYield(JCYield tree) {
1071         try {
1072             print("yield");
1073             print(" ");
1074             printExpr(tree.value);
1075             print(";");
1076         } catch (IOException e) {
1077             throw new UncheckedIOException(e);
1078         }
1079     }
1080 
1081     public void visitContinue(JCContinue tree) {
1082         try {
1083             print("continue");
1084             if (tree.label != null) print(" " + tree.label);
1085             print(";");
1086         } catch (IOException e) {
1087             throw new UncheckedIOException(e);
1088         }
1089     }
1090 
1091     public void visitReturn(JCReturn tree) {
1092         try {
1093             print("return");
1094             if (tree.expr != null) {
1095                 print(" ");
1096                 printExpr(tree.expr);
1097             }
1098             print(";");
1099         } catch (IOException e) {
1100             throw new UncheckedIOException(e);
1101         }
1102     }
1103 
1104     public void visitThrow(JCThrow tree) {
1105         try {
1106             print("throw ");
1107             printExpr(tree.expr);
1108             print(";");
1109         } catch (IOException e) {
1110             throw new UncheckedIOException(e);
1111         }
1112     }
1113 
1114     public void visitAssert(JCAssert tree) {
1115         try {
1116             print("assert ");
1117             printExpr(tree.cond);
1118             if (tree.detail != null) {
1119                 print(" : ");
1120                 printExpr(tree.detail);
1121             }
1122             print(";");
1123         } catch (IOException e) {
1124             throw new UncheckedIOException(e);
1125         }
1126     }
1127 
1128     public void visitApply(JCMethodInvocation tree) {
1129         try {
1130             if (!tree.typeargs.isEmpty()) {
1131                 if (tree.meth.hasTag(SELECT)) {
1132                     JCFieldAccess left = (JCFieldAccess)tree.meth;
1133                     printExpr(left.selected);
1134                     print(".<");
1135                     printExprs(tree.typeargs);
1136                     print(">" + left.name);
1137                 } else {
1138                     print("<");
1139                     printExprs(tree.typeargs);
1140                     print(">");
1141                     printExpr(tree.meth);
1142                 }
1143             } else {
1144                 printExpr(tree.meth);
1145             }
1146             print("(");
1147             printExprs(tree.args);
1148             print(")");
1149         } catch (IOException e) {
1150             throw new UncheckedIOException(e);
1151         }
1152     }
1153 
1154     public void visitNewClass(JCNewClass tree) {
1155         try {
1156             if (tree.encl != null) {
1157                 printExpr(tree.encl);
1158                 print(".");
1159             }
1160             print("new ");
1161             if (!tree.typeargs.isEmpty()) {
1162                 print("<");
1163                 printExprs(tree.typeargs);
1164                 print(">");
1165             }
1166             if (tree.def != null && tree.def.mods.annotations.nonEmpty()) {
1167                 printTypeAnnotations(tree.def.mods.annotations);
1168             }
1169             printExpr(tree.clazz);
1170             print("(");
1171             printExprs(tree.args);
1172             print(")");
1173             if (tree.def != null) {
1174                 Name enclClassNamePrev = enclClassName;
1175                 enclClassName =
1176                         tree.def.name != null ? tree.def.name :
1177                             tree.type != null && tree.type.tsym.name != tree.type.tsym.name.table.names.empty
1178                                 ? tree.type.tsym.name : null;
1179                 if ((tree.def.mods.flags & Flags.ENUM) != 0) print("/*enum*/");
1180                 printBlock(tree.def.defs);
1181                 enclClassName = enclClassNamePrev;
1182             }
1183         } catch (IOException e) {
1184             throw new UncheckedIOException(e);
1185         }
1186     }
1187 
1188     public void visitNewArray(JCNewArray tree) {
1189         try {
1190             if (tree.elemtype != null) {
1191                 print("new ");
1192                 JCTree elem = tree.elemtype;
1193                 printBaseElementType(elem);
1194 
1195                 if (!tree.annotations.isEmpty()) {
1196                     print(' ');
1197                     printTypeAnnotations(tree.annotations);
1198                 }
1199                 if (tree.elems != null) {
1200                     print("[]");
1201                 }
1202 
1203                 int i = 0;
1204                 List<List<JCAnnotation>> da = tree.dimAnnotations;
1205                 for (List<JCExpression> l = tree.dims; l.nonEmpty(); l = l.tail) {
1206                     if (da.size() > i && !da.get(i).isEmpty()) {
1207                         print(' ');
1208                         printTypeAnnotations(da.get(i));
1209                     }
1210                     print("[");
1211                     i++;
1212                     printExpr(l.head);
1213                     print("]");
1214                 }
1215                 printBrackets(elem);
1216             }
1217             if (tree.elems != null) {
1218                 print("{");
1219                 printExprs(tree.elems);
1220                 print("}");
1221             }
1222         } catch (IOException e) {
1223             throw new UncheckedIOException(e);
1224         }
1225     }
1226 
1227     public void visitLambda(JCLambda tree) {
1228         try {
1229             print("(");
1230             if (tree.paramKind == JCLambda.ParameterKind.EXPLICIT) {
1231                 printExprs(tree.params);
1232             } else {
1233                 String sep = "";
1234                 for (JCVariableDecl param : tree.params) {
1235                     print(sep);
1236                     print(param.name);
1237                     sep = ",";
1238                 }
1239             }
1240             print(")->");
1241             printExpr(tree.body);
1242         } catch (IOException e) {
1243             throw new UncheckedIOException(e);
1244         }
1245     }
1246 
1247     public void visitParens(JCParens tree) {
1248         try {
1249             print("(");
1250             printExpr(tree.expr);
1251             print(")");
1252         } catch (IOException e) {
1253             throw new UncheckedIOException(e);
1254         }
1255     }
1256 
1257     public void visitAssign(JCAssign tree) {
1258         try {
1259             open(prec, TreeInfo.assignPrec);
1260             printExpr(tree.lhs, TreeInfo.assignPrec + 1);
1261             print(" = ");
1262             printExpr(tree.rhs, TreeInfo.assignPrec);
1263             close(prec, TreeInfo.assignPrec);
1264         } catch (IOException e) {
1265             throw new UncheckedIOException(e);
1266         }
1267     }
1268 
1269     public String operatorName(JCTree.Tag tag) {
1270         switch(tag) {
1271             case POS:     return "+";
1272             case NEG:     return "-";
1273             case NOT:     return "!";
1274             case COMPL:   return "~";
1275             case PREINC:  return "++";
1276             case PREDEC:  return "--";
1277             case POSTINC: return "++";
1278             case POSTDEC: return "--";
1279             case NULLCHK: return "<*nullchk*>";
1280             case OR:      return "||";
1281             case AND:     return "&&";
1282             case EQ:      return "==";
1283             case NE:      return "!=";
1284             case LT:      return "<";
1285             case GT:      return ">";
1286             case LE:      return "<=";
1287             case GE:      return ">=";
1288             case BITOR:   return "|";
1289             case BITXOR:  return "^";
1290             case BITAND:  return "&";
1291             case SL:      return "<<";
1292             case SR:      return ">>";
1293             case USR:     return ">>>";
1294             case PLUS:    return "+";
1295             case MINUS:   return "-";
1296             case MUL:     return "*";
1297             case DIV:     return "/";
1298             case MOD:     return "%";
1299             default: throw new Error();
1300         }
1301     }
1302 
1303     public void visitAssignop(JCAssignOp tree) {
1304         try {
1305             open(prec, TreeInfo.assignopPrec);
1306             printExpr(tree.lhs, TreeInfo.assignopPrec + 1);
1307             print(" " + operatorName(tree.getTag().noAssignOp()) + "= ");
1308             printExpr(tree.rhs, TreeInfo.assignopPrec);
1309             close(prec, TreeInfo.assignopPrec);
1310         } catch (IOException e) {
1311             throw new UncheckedIOException(e);
1312         }
1313     }
1314 
1315     public void visitUnary(JCUnary tree) {
1316         try {
1317             int ownprec = TreeInfo.opPrec(tree.getTag());
1318             String opname = operatorName(tree.getTag());
1319             open(prec, ownprec);
1320             if (!tree.getTag().isPostUnaryOp()) {
1321                 print(opname);
1322                 printExpr(tree.arg, ownprec);
1323             } else {
1324                 printExpr(tree.arg, ownprec);
1325                 print(opname);
1326             }
1327             close(prec, ownprec);
1328         } catch (IOException e) {
1329             throw new UncheckedIOException(e);
1330         }
1331     }
1332 
1333     public void visitBinary(JCBinary tree) {
1334         try {
1335             int ownprec = TreeInfo.opPrec(tree.getTag());
1336             String opname = operatorName(tree.getTag());
1337             open(prec, ownprec);
1338             printExpr(tree.lhs, ownprec);
1339             print(" " + opname + " ");
1340             printExpr(tree.rhs, ownprec + 1);
1341             close(prec, ownprec);
1342         } catch (IOException e) {
1343             throw new UncheckedIOException(e);
1344         }
1345     }
1346 
1347     public void visitTypeCast(JCTypeCast tree) {
1348         try {
1349             open(prec, TreeInfo.prefixPrec);
1350             print("(");
1351             printExpr(tree.clazz);
1352             print(")");
1353             printExpr(tree.expr, TreeInfo.prefixPrec);
1354             close(prec, TreeInfo.prefixPrec);
1355         } catch (IOException e) {
1356             throw new UncheckedIOException(e);
1357         }
1358     }
1359 
1360     public void visitTypeTest(JCInstanceOf tree) {
1361         try {
1362             open(prec, TreeInfo.ordPrec);
1363             printExpr(tree.expr, TreeInfo.ordPrec);
1364             print(" instanceof ");
1365             if (tree.pattern instanceof JCPattern) {
1366                 printPattern(tree.pattern);
1367             } else {
1368                 printExpr(tree.getType(), TreeInfo.ordPrec + 1);
1369             }
1370             close(prec, TreeInfo.ordPrec);
1371         } catch (IOException e) {
1372             throw new UncheckedIOException(e);
1373         }
1374     }
1375 
1376     public void visitIndexed(JCArrayAccess tree) {
1377         try {
1378             printExpr(tree.indexed, TreeInfo.postfixPrec);
1379             print("[");
1380             printExpr(tree.index);
1381             print("]");
1382         } catch (IOException e) {
1383             throw new UncheckedIOException(e);
1384         }
1385     }
1386 
1387     public void visitSelect(JCFieldAccess tree) {
1388         try {
1389             printExpr(tree.selected, TreeInfo.postfixPrec);
1390             print("." + tree.name);
1391         } catch (IOException e) {
1392             throw new UncheckedIOException(e);
1393         }
1394     }
1395 
1396     public void visitReference(JCMemberReference tree) {
1397         try {
1398             printExpr(tree.expr);
1399             print("::");
1400             if (tree.typeargs != null) {
1401                 print("<");
1402                 printExprs(tree.typeargs);
1403                 print(">");
1404             }
1405             print(tree.getMode() == ReferenceMode.INVOKE ? tree.name : "new");
1406         } catch (IOException e) {
1407             throw new UncheckedIOException(e);
1408         }
1409     }
1410 
1411     public void visitIdent(JCIdent tree) {
1412         try {
1413             print(tree.name);
1414         } catch (IOException e) {
1415             throw new UncheckedIOException(e);
1416         }
1417     }
1418 
1419     public void visitLiteral(JCLiteral tree) {
1420         try {
1421             switch (tree.typetag) {
1422                 case INT:
1423                     print(tree.value.toString());
1424                     break;
1425                 case LONG:
1426                     print(tree.value + "L");
1427                     break;
1428                 case FLOAT:
1429                     print(tree.value + "F");
1430                     break;
1431                 case DOUBLE:
1432                     print(tree.value.toString());
1433                     break;
1434                 case CHAR:
1435                     print("\'" +
1436                             Convert.quote(
1437                             String.valueOf((char)((Number)tree.value).intValue())) +
1438                             "\'");
1439                     break;
1440                 case BOOLEAN:
1441                     print(((Number)tree.value).intValue() == 1 ? "true" : "false");
1442                     break;
1443                 case BOT:
1444                     print("null");
1445                     break;
1446                 default:
1447                     print("\"" + Convert.quote(tree.value.toString()) + "\"");
1448                     break;
1449             }
1450         } catch (IOException e) {
1451             throw new UncheckedIOException(e);
1452         }
1453     }
1454 
1455     public void visitTypeIdent(JCPrimitiveTypeTree tree) {
1456         try {
1457             switch(tree.typetag) {
1458                 case BYTE:
1459                     print("byte");
1460                     break;
1461                 case CHAR:
1462                     print("char");
1463                     break;
1464                 case SHORT:
1465                     print("short");
1466                     break;
1467                 case INT:
1468                     print("int");
1469                     break;
1470                 case LONG:
1471                     print("long");
1472                     break;
1473                 case FLOAT:
1474                     print("float");
1475                     break;
1476                 case DOUBLE:
1477                     print("double");
1478                     break;
1479                 case BOOLEAN:
1480                     print("boolean");
1481                     break;
1482                 case VOID:
1483                     print("void");
1484                     break;
1485                 default:
1486                     print("error");
1487                     break;
1488             }
1489         } catch (IOException e) {
1490             throw new UncheckedIOException(e);
1491         }
1492     }
1493 
1494     public void visitTypeArray(JCArrayTypeTree tree) {
1495         try {
1496             printBaseElementType(tree);
1497             printBrackets(tree);
1498         } catch (IOException e) {
1499             throw new UncheckedIOException(e);
1500         }
1501     }
1502 
1503     // Prints the inner element type of a nested array
1504     private void printBaseElementType(JCTree tree) throws IOException {
1505         printExpr(TreeInfo.innermostType(tree, false));
1506     }
1507 
1508     // prints the brackets of a nested array in reverse order
1509     // tree is either JCArrayTypeTree or JCAnnotatedTypeTree
1510     private void printBrackets(JCTree tree) throws IOException {
1511         JCTree elem = tree;
1512         while (true) {
1513             if (elem.hasTag(ANNOTATED_TYPE)) {
1514                 JCAnnotatedType atype = (JCAnnotatedType) elem;
1515                 elem = atype.underlyingType;
1516                 if (elem.hasTag(TYPEARRAY)) {
1517                     print(' ');
1518                     printTypeAnnotations(atype.annotations);
1519                 }
1520             }
1521             if (elem.hasTag(TYPEARRAY)) {
1522                 print("[]");
1523                 elem = ((JCArrayTypeTree)elem).elemtype;
1524             } else {
1525                 break;
1526             }
1527         }
1528     }
1529 
1530     public void visitTypeApply(JCTypeApply tree) {
1531         try {
1532             printExpr(tree.clazz);
1533             print("<");
1534             printExprs(tree.arguments);
1535             print(">");
1536         } catch (IOException e) {
1537             throw new UncheckedIOException(e);
1538         }
1539     }
1540 
1541     public void visitTypeUnion(JCTypeUnion tree) {
1542         try {
1543             printExprs(tree.alternatives, " | ");
1544         } catch (IOException e) {
1545             throw new UncheckedIOException(e);
1546         }
1547     }
1548 
1549     public void visitTypeIntersection(JCTypeIntersection tree) {
1550         try {
1551             printExprs(tree.bounds, " & ");
1552         } catch (IOException e) {
1553             throw new UncheckedIOException(e);
1554         }
1555     }
1556 
1557     public void visitTypeParameter(JCTypeParameter tree) {
1558         try {
1559             if (tree.annotations.nonEmpty()) {
1560                 this.printTypeAnnotations(tree.annotations);
1561             }
1562             print(tree.name);
1563             if (tree.bounds.nonEmpty()) {
1564                 print(" extends ");
1565                 printExprs(tree.bounds, " & ");
1566             }
1567         } catch (IOException e) {
1568             throw new UncheckedIOException(e);
1569         }
1570     }
1571 
1572     @Override
1573     public void visitWildcard(JCWildcard tree) {
1574         try {
1575             print(tree.kind);
1576             if (tree.kind.kind != BoundKind.UNBOUND)
1577                 printExpr(tree.inner);
1578         } catch (IOException e) {
1579             throw new UncheckedIOException(e);
1580         }
1581     }
1582 
1583     @Override
1584     public void visitTypeBoundKind(TypeBoundKind tree) {
1585         try {
1586             print(String.valueOf(tree.kind));
1587         } catch (IOException e) {
1588             throw new UncheckedIOException(e);
1589         }
1590     }
1591 
1592     public void visitErroneous(JCErroneous tree) {
1593         try {
1594             print("(ERROR)");
1595         } catch (IOException e) {
1596             throw new UncheckedIOException(e);
1597         }
1598     }
1599 
1600     public void visitLetExpr(LetExpr tree) {
1601         try {
1602             print("(let " + tree.defs + " in " + tree.expr + ")");
1603         } catch (IOException e) {
1604             throw new UncheckedIOException(e);
1605         }
1606     }
1607 
1608     public void visitModifiers(JCModifiers mods) {
1609         try {
1610             printAnnotations(mods.annotations);
1611             printFlags(mods.flags);
1612         } catch (IOException e) {
1613             throw new UncheckedIOException(e);
1614         }
1615     }
1616 
1617     public void visitAnnotation(JCAnnotation tree) {
1618         try {
1619             print("@");
1620             printExpr(tree.annotationType);
1621             if (!tree.args.isEmpty()) {
1622                 print("(");
1623                 printExprs(tree.args);
1624                 print(")");
1625             }
1626         } catch (IOException e) {
1627             throw new UncheckedIOException(e);
1628         }
1629     }
1630 
1631     public void visitAnnotatedType(JCAnnotatedType tree) {
1632         try {
1633             if (tree.underlyingType.hasTag(SELECT)) {
1634                 JCFieldAccess access = (JCFieldAccess) tree.underlyingType;
1635                 printExpr(access.selected, TreeInfo.postfixPrec);
1636                 print(".");
1637                 printTypeAnnotations(tree.annotations);
1638                 print(access.name);
1639             } else if (tree.underlyingType.hasTag(TYPEARRAY)) {
1640                 printBaseElementType(tree);
1641                 printBrackets(tree);
1642             } else {
1643                 printTypeAnnotations(tree.annotations);
1644                 printExpr(tree.underlyingType);
1645             }
1646         } catch (IOException e) {
1647             throw new UncheckedIOException(e);
1648         }
1649     }
1650 
1651     public void visitTree(JCTree tree) {
1652         try {
1653             print("(UNKNOWN: " + tree.getTag() + ")");
1654             println();
1655         } catch (IOException e) {
1656             throw new UncheckedIOException(e);
1657         }
1658     }
1659 
1660 }