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