Example #1
0
 /**
  * Utility method for converting a search path string to an array of directory and JAR file URLs.
  *
  * @param path the search path string
  * @return the resulting array of directory and JAR file URLs
  */
 static URL[] pathToURLs(String path) {
   StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
   URL[] urls = new URL[st.countTokens()];
   int count = 0;
   while (st.hasMoreTokens()) {
     URL url = fileToURL(new File(st.nextToken()));
     if (url != null) {
       urls[count++] = url;
     }
   }
   if (urls.length != count) {
     URL[] tmp = new URL[count];
     System.arraycopy(urls, 0, tmp, 0, count);
     urls = tmp;
   }
   return urls;
 }
 /** Unconditionally expand the comment buffer. */
 private void expandCommentBuffer() {
   char[] newBuffer = new char[docCommentBuffer.length * 2];
   System.arraycopy(docCommentBuffer, 0, newBuffer, 0, docCommentBuffer.length);
   docCommentBuffer = newBuffer;
 }
Example #3
0
  /**
   * Programmatic interface for main function.
   *
   * @param args The command line parameters.
   */
  public int compile(
      String[] args,
      Context context,
      List<JavaFileObject> fileObjects,
      Iterable<? extends Processor> processors) {
    if (options == null) options = Options.instance(context); // creates a new one

    filenames = new ListBuffer<File>();
    classnames = new ListBuffer<String>();
    JavaCompiler comp = null;
    /*
     * TODO: Logic below about what is an acceptable command line
     * should be updated to take annotation processing semantics
     * into account.
     */
    try {
      if (args.length == 0 && fileObjects.isEmpty()) {
        help();
        return EXIT_CMDERR;
      }

      List<File> files;
      try {
        files = processArgs(CommandLine.parse(args));
        if (files == null) {
          // null signals an error in options, abort
          return EXIT_CMDERR;
        } else if (files.isEmpty() && fileObjects.isEmpty() && classnames.isEmpty()) {
          // it is allowed to compile nothing if just asking for help or version info
          if (options.get("-help") != null
              || options.get("-X") != null
              || options.get("-version") != null
              || options.get("-fullversion") != null) return EXIT_OK;
          error("err.no.source.files");
          return EXIT_CMDERR;
        }
      } catch (java.io.FileNotFoundException e) {
        Log.printLines(
            out, ownName + ": " + getLocalizedString("err.file.not.found", e.getMessage()));
        return EXIT_SYSERR;
      }

      boolean forceStdOut = options.get("stdout") != null;
      if (forceStdOut) {
        out.flush();
        out = new PrintWriter(System.out, true);
      }

      context.put(Log.outKey, out);

      // allow System property in following line as a Mustang legacy
      boolean batchMode =
          (options.get("nonBatchMode") == null && System.getProperty("nonBatchMode") == null);
      if (batchMode) CacheFSInfo.preRegister(context);

      fileManager = context.get(JavaFileManager.class);

      comp = JavaCompiler.instance(context);
      if (comp == null) return EXIT_SYSERR;

      if (!files.isEmpty()) {
        // add filenames to fileObjects
        comp = JavaCompiler.instance(context);
        List<JavaFileObject> otherFiles = List.nil();
        JavacFileManager dfm = (JavacFileManager) fileManager;
        for (JavaFileObject fo : dfm.getJavaFileObjectsFromFiles(files))
          otherFiles = otherFiles.prepend(fo);
        for (JavaFileObject fo : otherFiles) fileObjects = fileObjects.prepend(fo);
      }
      comp.compile(fileObjects, classnames.toList(), processors);

      if (comp.errorCount() != 0) return EXIT_ERROR;
    } catch (IOException ex) {
      ioMessage(ex);
      return EXIT_SYSERR;
    } catch (OutOfMemoryError ex) {
      resourceMessage(ex);
      return EXIT_SYSERR;
    } catch (StackOverflowError ex) {
      resourceMessage(ex);
      return EXIT_SYSERR;
    } catch (FatalError ex) {
      feMessage(ex);
      return EXIT_SYSERR;
    } catch (AnnotationProcessingError ex) {
      apMessage(ex);
      return EXIT_SYSERR;
    } catch (ClientCodeException ex) {
      // as specified by javax.tools.JavaCompiler#getTask
      // and javax.tools.JavaCompiler.CompilationTask#call
      throw new RuntimeException(ex.getCause());
    } catch (PropagatedException ex) {
      throw ex.getCause();
    } catch (Throwable ex) {
      // Nasty.  If we've already reported an error, compensate
      // for buggy compiler error recovery by swallowing thrown
      // exceptions.
      if (comp == null || comp.errorCount() == 0 || options == null || options.get("dev") != null)
        bugMessage(ex);
      return EXIT_ABNORMAL;
    } finally {
      if (comp != null) comp.close();
      filenames = null;
      options = null;
    }
    return EXIT_OK;
  }
  /**
   * Main method: compile a list of files, return all compiled classes
   *
   * @param filenames The names of all files to be compiled.
   */
  public List<ClassSymbol> compile(
      List<String> filenames,
      Map<String, String> origOptions,
      ClassLoader aptCL,
      AnnotationProcessorFactory providedFactory,
      java.util.Set<Class<? extends AnnotationProcessorFactory>> productiveFactories,
      java.util.Set<java.io.File> aggregateGenFiles)
      throws Throwable {
    // as a JavaCompiler can only be used once, throw an exception if
    // it has been used before.
    assert !hasBeenUsed : "attempt to reuse JavaCompiler";
    hasBeenUsed = true;

    this.aggregateGenFiles = aggregateGenFiles;

    long msec = System.currentTimeMillis();

    ListBuffer<ClassSymbol> classes = new ListBuffer<ClassSymbol>();
    try {
      JavacFileManager fm = (JavacFileManager) fileManager;
      // parse all files
      ListBuffer<JCCompilationUnit> trees = new ListBuffer<JCCompilationUnit>();
      for (List<String> l = filenames; l.nonEmpty(); l = l.tail) {
        if (classesAsDecls) {
          if (!l.head.endsWith(".java")) { // process as class file
            ClassSymbol cs = reader.enterClass(names.fromString(l.head));
            try {
              cs.complete();
            } catch (Symbol.CompletionFailure cf) {
              bark.aptError("CantFindClass", l);
              continue;
            }

            classes.append(cs); // add to list of classes
            continue;
          }
        }
        JavaFileObject fo = fm.getJavaFileObjectsFromStrings(List.of(l.head)).iterator().next();
        trees.append(parse(fo));
      }

      // enter symbols for all files
      List<JCCompilationUnit> roots = trees.toList();

      if (errorCount() == 0) {
        boolean prev = bark.setDiagnosticsIgnored(true);
        try {
          enter.main(roots);
        } finally {
          bark.setDiagnosticsIgnored(prev);
        }
      }

      if (errorCount() == 0) {
        apt.main(roots, classes, origOptions, aptCL, providedFactory, productiveFactories);
        genSourceFileNames.addAll(apt.getSourceFileNames());
        genClassFileNames.addAll(apt.getClassFileNames());
      }

    } catch (Abort ex) {
    }

    if (verbose) log.printVerbose("total", Long.toString(System.currentTimeMillis() - msec));

    chk.reportDeferredDiagnostics();

    printCount("error", errorCount());
    printCount("warn", warningCount());

    return classes.toList();
  }
Example #5
0
/**
 * Prints out a tree as an indented Java source program.
 *
 * <p>
 *
 * <p><b>This is NOT part of any supported API. If you write code that depends on this, you do so at
 * your own risk. This code and its internal interfaces are subject to change or deletion without
 * notice.</b>
 */
public class Pretty extends JCTree.Visitor {

  public Pretty(Writer out, boolean sourceOutput) {
    this.out = out;
    this.sourceOutput = sourceOutput;
  }

  /**
   * Set when we are producing source output. If we're not producing source output, we can sometimes
   * give more detail in the output even though that detail would not be valid java soruce.
   */
  private final boolean sourceOutput;

  /** The output stream on which trees are printed. */
  Writer out;

  /** Indentation width (can be reassigned from outside). */
  public int width = 4;

  /** The current left margin. */
  int lmargin = 0;

  /** The enclosing class name. */
  Name enclClassName;

  /** A hashtable mapping trees to their documentation comments (can be null) */
  Map<JCTree, String> docComments = null;

  /** Align code to be indented to left margin. */
  void align() throws IOException {
    for (int i = 0; i < lmargin; i++) out.write(" ");
  }

  /** Increase left margin by indentation width. */
  void indent() {
    lmargin = lmargin + width;
  }

  /** Decrease left margin by indentation width. */
  void undent() {
    lmargin = lmargin - width;
  }

  /**
   * Enter a new precedence level. Emit a `(' if new precedence level is less than precedence level
   * so far.
   *
   * @param contextPrec The precedence level in force so far.
   * @param ownPrec The new precedence level.
   */
  void open(int contextPrec, int ownPrec) throws IOException {
    if (ownPrec < contextPrec) out.write("(");
  }

  /**
   * Leave precedence level. Emit a `(' if inner precedence level is less than precedence level we
   * revert to.
   *
   * @param contextPrec The precedence level we revert to.
   * @param ownPrec The inner precedence level.
   */
  void close(int contextPrec, int ownPrec) throws IOException {
    if (ownPrec < contextPrec) out.write(")");
  }

  /** Print string, replacing all non-ascii character with unicode escapes. */
  public void print(Object s) throws IOException {
    out.write(Convert.escapeUnicode(s.toString()));
  }

  /** Print new line. */
  public void println() throws IOException {
    out.write(lineSep);
  }

  String lineSep = System.getProperty("line.separator");

  /**
   * ************************************************************************ Traversal methods
   * ***********************************************************************
   */

  /** Exception to propogate IOException through visitXXX methods */
  private static class UncheckedIOException extends Error {
    static final long serialVersionUID = -4032692679158424751L;

    UncheckedIOException(IOException e) {
      super(e.getMessage(), e);
    }
  }

  /** Visitor argument: the current precedence level. */
  int prec;

  /**
   * Visitor method: print expression tree.
   *
   * @param prec The current precedence level.
   */
  public void printExpr(JCTree tree, int prec) throws IOException {
    int prevPrec = this.prec;
    try {
      this.prec = prec;
      if (tree == null) print("/*missing*/");
      else {
        tree.accept(this);
      }
    } catch (UncheckedIOException ex) {
      IOException e = new IOException(ex.getMessage());
      e.initCause(ex);
      throw e;
    } finally {
      this.prec = prevPrec;
    }
  }

  /** Derived visitor method: print expression tree at minimum precedence level for expression. */
  public void printExpr(JCTree tree) throws IOException {
    printExpr(tree, TreeInfo.noPrec);
  }

  /** Derived visitor method: print statement tree. */
  public void printStat(JCTree tree) throws IOException {
    printExpr(tree, TreeInfo.notExpression);
  }

  /**
   * Derived visitor method: print list of expression trees, separated by given string.
   *
   * @param sep the separator string
   */
  public <T extends JCTree> void printExprs(List<T> trees, String sep) throws IOException {
    if (trees.nonEmpty()) {
      printExpr(trees.head);
      for (List<T> l = trees.tail; l.nonEmpty(); l = l.tail) {
        print(sep);
        printExpr(l.head);
      }
    }
  }

  /** Derived visitor method: print list of expression trees, separated by commas. */
  public <T extends JCTree> void printExprs(List<T> trees) throws IOException {
    printExprs(trees, ", ");
  }

  /** Derived visitor method: print list of statements, each on a separate line. */
  public void printStats(List<? extends JCTree> trees) throws IOException {
    for (List<? extends JCTree> l = trees; l.nonEmpty(); l = l.tail) {
      align();
      printStat(l.head);
      println();
    }
  }

  /** Print a set of modifiers. */
  public void printFlags(long flags) throws IOException {
    if ((flags & SYNTHETIC) != 0) print("/*synthetic*/ ");
    print(TreeInfo.flagNames(flags));
    if ((flags & StandardFlags) != 0) print(" ");
    if ((flags & ANNOTATION) != 0) print("@");
  }

  public void printAnnotations(List<JCAnnotation> trees) throws IOException {
    for (List<JCAnnotation> l = trees; l.nonEmpty(); l = l.tail) {
      printStat(l.head);
      println();
      align();
    }
  }

  /**
   * Print documentation comment, if it exists
   *
   * @param tree The tree for which a documentation comment should be printed.
   */
  public void printDocComment(JCTree tree) throws IOException {
    if (docComments != null) {
      String dc = docComments.get(tree);
      if (dc != null) {
        print("/**");
        println();
        int pos = 0;
        int endpos = lineEndPos(dc, pos);
        while (pos < dc.length()) {
          align();
          print(" *");
          if (pos < dc.length() && dc.charAt(pos) > ' ') print(" ");
          print(dc.substring(pos, endpos));
          println();
          pos = endpos + 1;
          endpos = lineEndPos(dc, pos);
        }
        align();
        print(" */");
        println();
        align();
      }
    }
  }

  // where
  static int lineEndPos(String s, int start) {
    int pos = s.indexOf('\n', start);
    if (pos < 0) pos = s.length();
    return pos;
  }

  /** If type parameter list is non-empty, print it enclosed in "<...>" brackets. */
  public void printTypeParameters(List<JCTypeParameter> trees) throws IOException {
    if (trees.nonEmpty()) {
      print("<");
      printExprs(trees);
      print(">");
    }
  }

  /** Print a block. */
  public void printBlock(List<? extends JCTree> stats) throws IOException {
    print("{");
    println();
    indent();
    printStats(stats);
    undent();
    align();
    print("}");
  }

  /** Print a block. */
  public void printEnumBody(List<JCTree> stats) throws IOException {
    print("{");
    println();
    indent();
    boolean first = true;
    for (List<JCTree> l = stats; l.nonEmpty(); l = l.tail) {
      if (isEnumerator(l.head)) {
        if (!first) {
          print(",");
          println();
        }
        align();
        printStat(l.head);
        first = false;
      }
    }
    print(";");
    println();
    for (List<JCTree> l = stats; l.nonEmpty(); l = l.tail) {
      if (!isEnumerator(l.head)) {
        align();
        printStat(l.head);
        println();
      }
    }
    undent();
    align();
    print("}");
  }

  /** Is the given tree an enumerator definition? */
  boolean isEnumerator(JCTree t) {
    return t.getTag() == JCTree.VARDEF && (((JCVariableDecl) t).mods.flags & ENUM) != 0;
  }

  /**
   * Print unit consisting of package clause and import statements in toplevel, followed by class
   * definition. if class definition == null, print all definitions in toplevel.
   *
   * @param tree The toplevel tree
   * @param cdef The class definition, which is assumed to be part of the toplevel tree.
   */
  public void printUnit(JCCompilationUnit tree, JCClassDecl cdef) throws IOException {
    docComments = tree.docComments;
    printDocComment(tree);
    if (tree.pid != null) {
      print("package ");
      printExpr(tree.pid);
      print(";");
      println();
    }
    boolean firstImport = true;
    for (List<JCTree> l = tree.defs;
        l.nonEmpty() && (cdef == null || l.head.getTag() == JCTree.IMPORT);
        l = l.tail) {
      if (l.head.getTag() == JCTree.IMPORT) {
        JCImport imp = (JCImport) l.head;
        Name name = TreeInfo.name(imp.qualid);
        if (name == name.table.asterisk
            || cdef == null
            || isUsed(TreeInfo.symbol(imp.qualid), cdef)) {
          if (firstImport) {
            firstImport = false;
            println();
          }
          printStat(imp);
        }
      } else {
        printStat(l.head);
      }
    }
    if (cdef != null) {
      printStat(cdef);
      println();
    }
  }

  // where
  boolean isUsed(final Symbol t, JCTree cdef) {
    class UsedVisitor extends TreeScanner {
      public void scan(JCTree tree) {
        if (tree != null && !result) tree.accept(this);
      }

      boolean result = false;

      public void visitIdent(JCIdent tree) {
        if (tree.sym == t) result = true;
      }
    }
    UsedVisitor v = new UsedVisitor();
    v.scan(cdef);
    return v.result;
  }

  /**
   * ************************************************************************ Visitor methods
   * ***********************************************************************
   */
  public void visitTopLevel(JCCompilationUnit tree) {
    try {
      printUnit(tree, null);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitImport(JCImport tree) {
    try {
      print("import ");
      if (tree.staticImport) print("static ");
      printExpr(tree.qualid);
      print(";");
      println();
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitClassDef(JCClassDecl tree) {
    try {
      println();
      align();
      printDocComment(tree);
      printAnnotations(tree.mods.annotations);
      printFlags(tree.mods.flags & ~INTERFACE);
      Name enclClassNamePrev = enclClassName;
      enclClassName = tree.name;
      if ((tree.mods.flags & INTERFACE) != 0) {
        print("interface " + tree.name);
        printTypeParameters(tree.typarams);
        if (tree.implementing.nonEmpty()) {
          print(" extends ");
          printExprs(tree.implementing);
        }
      } else {
        if ((tree.mods.flags & ENUM) != 0) print("enum " + tree.name);
        else print("class " + tree.name);
        printTypeParameters(tree.typarams);
        if (tree.extending != null) {
          print(" extends ");
          printExpr(tree.extending);
        }
        if (tree.implementing.nonEmpty()) {
          print(" implements ");
          printExprs(tree.implementing);
        }
      }
      print(" ");
      if ((tree.mods.flags & ENUM) != 0) {
        printEnumBody(tree.defs);
      } else {
        printBlock(tree.defs);
      }
      enclClassName = enclClassNamePrev;
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitMethodDef(JCMethodDecl tree) {
    try {
      // when producing source output, omit anonymous constructors
      if (tree.name == tree.name.table.init && enclClassName == null && sourceOutput) return;
      println();
      align();
      printDocComment(tree);
      printExpr(tree.mods);
      printTypeParameters(tree.typarams);
      if (tree.name == tree.name.table.init) {
        print(enclClassName != null ? enclClassName : tree.name);
      } else {
        printExpr(tree.restype);
        print(" " + tree.name);
      }
      print("(");
      printExprs(tree.params);
      print(")");
      if (tree.thrown.nonEmpty()) {
        print(" throws ");
        printExprs(tree.thrown);
      }
      if (tree.body != null) {
        print(" ");
        printStat(tree.body);
      } else {
        print(";");
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitVarDef(JCVariableDecl tree) {
    try {
      if (docComments != null && docComments.get(tree) != null) {
        println();
        align();
      }
      printDocComment(tree);
      if ((tree.mods.flags & ENUM) != 0) {
        print("/*public static final*/ ");
        print(tree.name);
        if (tree.init != null) {
          print(" /* = ");
          printExpr(tree.init);
          print(" */");
        }
      } else {
        printExpr(tree.mods);
        if ((tree.mods.flags & VARARGS) != 0) {
          printExpr(((JCArrayTypeTree) tree.vartype).elemtype);
          print("... " + tree.name);
        } else {
          printExpr(tree.vartype);
          print(" " + tree.name);
        }
        if (tree.init != null) {
          print(" = ");
          printExpr(tree.init);
        }
        if (prec == TreeInfo.notExpression) print(";");
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitSkip(JCSkip tree) {
    try {
      print(";");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitBlock(JCBlock tree) {
    try {
      printFlags(tree.flags);
      printBlock(tree.stats);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitDoLoop(JCDoWhileLoop tree) {
    try {
      print("do ");
      printStat(tree.body);
      align();
      print(" while ");
      if (tree.cond.getTag() == JCTree.PARENS) {
        printExpr(tree.cond);
      } else {
        print("(");
        printExpr(tree.cond);
        print(")");
      }
      print(";");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitWhileLoop(JCWhileLoop tree) {
    try {
      print("while ");
      if (tree.cond.getTag() == JCTree.PARENS) {
        printExpr(tree.cond);
      } else {
        print("(");
        printExpr(tree.cond);
        print(")");
      }
      print(" ");
      printStat(tree.body);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitForLoop(JCForLoop tree) {
    try {
      print("for (");
      if (tree.init.nonEmpty()) {
        if (tree.init.head.getTag() == JCTree.VARDEF) {
          printExpr(tree.init.head);
          for (List<JCStatement> l = tree.init.tail; l.nonEmpty(); l = l.tail) {
            JCVariableDecl vdef = (JCVariableDecl) l.head;
            print(", " + vdef.name + " = ");
            printExpr(vdef.init);
          }
        } else {
          printExprs(tree.init);
        }
      }
      print("; ");
      if (tree.cond != null) printExpr(tree.cond);
      print("; ");
      printExprs(tree.step);
      print(") ");
      printStat(tree.body);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitForeachLoop(JCEnhancedForLoop tree) {
    try {
      print("for (");
      printExpr(tree.var);
      print(" : ");
      printExpr(tree.expr);
      print(") ");
      printStat(tree.body);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitLabelled(JCLabeledStatement tree) {
    try {
      print(tree.label + ": ");
      printStat(tree.body);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitSwitch(JCSwitch tree) {
    try {
      print("switch ");
      if (tree.selector.getTag() == JCTree.PARENS) {
        printExpr(tree.selector);
      } else {
        print("(");
        printExpr(tree.selector);
        print(")");
      }
      print(" {");
      println();
      printStats(tree.cases);
      align();
      print("}");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitCase(JCCase tree) {
    try {
      if (tree.pat == null) {
        print("default");
      } else {
        print("case ");
        printExpr(tree.pat);
      }
      print(": ");
      println();
      indent();
      printStats(tree.stats);
      undent();
      align();
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitSynchronized(JCSynchronized tree) {
    try {
      print("synchronized ");
      if (tree.lock.getTag() == JCTree.PARENS) {
        printExpr(tree.lock);
      } else {
        print("(");
        printExpr(tree.lock);
        print(")");
      }
      print(" ");
      printStat(tree.body);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitTry(JCTry tree) {
    try {
      print("try ");
      printStat(tree.body);
      for (List<JCCatch> l = tree.catchers; l.nonEmpty(); l = l.tail) {
        printStat(l.head);
      }
      if (tree.finalizer != null) {
        print(" finally ");
        printStat(tree.finalizer);
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitCatch(JCCatch tree) {
    try {
      print(" catch (");
      printExpr(tree.param);
      print(") ");
      printStat(tree.body);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitConditional(JCConditional tree) {
    try {
      open(prec, TreeInfo.condPrec);
      printExpr(tree.cond, TreeInfo.condPrec);
      print(" ? ");
      printExpr(tree.truepart, TreeInfo.condPrec);
      print(" : ");
      printExpr(tree.falsepart, TreeInfo.condPrec);
      close(prec, TreeInfo.condPrec);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitIf(JCIf tree) {
    try {
      print("if ");
      if (tree.cond.getTag() == JCTree.PARENS) {
        printExpr(tree.cond);
      } else {
        print("(");
        printExpr(tree.cond);
        print(")");
      }
      print(" ");
      printStat(tree.thenpart);
      if (tree.elsepart != null) {
        print(" else ");
        printStat(tree.elsepart);
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitExec(JCExpressionStatement tree) {
    try {
      printExpr(tree.expr);
      if (prec == TreeInfo.notExpression) print(";");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitBreak(JCBreak tree) {
    try {
      print("break");
      if (tree.label != null) print(" " + tree.label);
      print(";");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitContinue(JCContinue tree) {
    try {
      print("continue");
      if (tree.label != null) print(" " + tree.label);
      print(";");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitReturn(JCReturn tree) {
    try {
      print("return");
      if (tree.expr != null) {
        print(" ");
        printExpr(tree.expr);
      }
      print(";");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitThrow(JCThrow tree) {
    try {
      print("throw ");
      printExpr(tree.expr);
      print(";");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitAssert(JCAssert tree) {
    try {
      print("assert ");
      printExpr(tree.cond);
      if (tree.detail != null) {
        print(" : ");
        printExpr(tree.detail);
      }
      print(";");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitApply(JCMethodInvocation tree) {
    try {
      if (!tree.typeargs.isEmpty()) {
        if (tree.meth.getTag() == JCTree.SELECT) {
          JCFieldAccess left = (JCFieldAccess) tree.meth;
          printExpr(left.selected);
          print(".<");
          printExprs(tree.typeargs);
          print(">" + left.name);
        } else {
          print("<");
          printExprs(tree.typeargs);
          print(">");
          printExpr(tree.meth);
        }
      } else {
        printExpr(tree.meth);
      }
      print("(");
      printExprs(tree.args);
      print(")");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitNewClass(JCNewClass tree) {
    try {
      if (tree.encl != null) {
        printExpr(tree.encl);
        print(".");
      }
      print("new ");
      if (!tree.typeargs.isEmpty()) {
        print("<");
        printExprs(tree.typeargs);
        print(">");
      }
      printExpr(tree.clazz);
      print("(");
      printExprs(tree.args);
      print(")");
      if (tree.def != null) {
        Name enclClassNamePrev = enclClassName;
        enclClassName =
            tree.def.name != null
                ? tree.def.name
                : tree.type != null && tree.type.tsym.name != tree.type.tsym.name.table.empty
                    ? tree.type.tsym.name
                    : null;
        if ((tree.def.mods.flags & Flags.ENUM) != 0) print("/*enum*/");
        printBlock(tree.def.defs);
        enclClassName = enclClassNamePrev;
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitNewArray(JCNewArray tree) {
    try {
      if (tree.elemtype != null) {
        print("new ");
        JCTree elem = tree.elemtype;
        if (elem instanceof JCArrayTypeTree) printBaseElementType((JCArrayTypeTree) elem);
        else printExpr(elem);
        for (List<JCExpression> l = tree.dims; l.nonEmpty(); l = l.tail) {
          print("[");
          printExpr(l.head);
          print("]");
        }
        if (elem instanceof JCArrayTypeTree) printBrackets((JCArrayTypeTree) elem);
      }
      if (tree.elems != null) {
        if (tree.elemtype != null) print("[]");
        print("{");
        printExprs(tree.elems);
        print("}");
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitParens(JCParens tree) {
    try {
      print("(");
      printExpr(tree.expr);
      print(")");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitAssign(JCAssign tree) {
    try {
      open(prec, TreeInfo.assignPrec);
      printExpr(tree.lhs, TreeInfo.assignPrec + 1);
      print(" = ");
      printExpr(tree.rhs, TreeInfo.assignPrec);
      close(prec, TreeInfo.assignPrec);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public String operatorName(int tag) {
    switch (tag) {
      case JCTree.POS:
        return "+";
      case JCTree.NEG:
        return "-";
      case JCTree.NOT:
        return "!";
      case JCTree.COMPL:
        return "~";
      case JCTree.PREINC:
        return "++";
      case JCTree.PREDEC:
        return "--";
      case JCTree.POSTINC:
        return "++";
      case JCTree.POSTDEC:
        return "--";
      case JCTree.NULLCHK:
        return "<*nullchk*>";
      case JCTree.OR:
        return "||";
      case JCTree.AND:
        return "&&";
      case JCTree.EQ:
        return "==";
      case JCTree.NE:
        return "!=";
      case JCTree.LT:
        return "<";
      case JCTree.GT:
        return ">";
      case JCTree.LE:
        return "<=";
      case JCTree.GE:
        return ">=";
      case JCTree.BITOR:
        return "|";
      case JCTree.BITXOR:
        return "^";
      case JCTree.BITAND:
        return "&";
      case JCTree.SL:
        return "<<";
      case JCTree.SR:
        return ">>";
      case JCTree.USR:
        return ">>>";
      case JCTree.PLUS:
        return "+";
      case JCTree.MINUS:
        return "-";
      case JCTree.MUL:
        return "*";
      case JCTree.DIV:
        return "/";
      case JCTree.MOD:
        return "%";
      default:
        throw new Error();
    }
  }

  public void visitAssignop(JCAssignOp tree) {
    try {
      open(prec, TreeInfo.assignopPrec);
      printExpr(tree.lhs, TreeInfo.assignopPrec + 1);
      print(" " + operatorName(tree.getTag() - JCTree.ASGOffset) + "= ");
      printExpr(tree.rhs, TreeInfo.assignopPrec);
      close(prec, TreeInfo.assignopPrec);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitUnary(JCUnary tree) {
    try {
      int ownprec = TreeInfo.opPrec(tree.getTag());
      String opname = operatorName(tree.getTag());
      open(prec, ownprec);
      if (tree.getTag() <= JCTree.PREDEC) {
        print(opname);
        printExpr(tree.arg, ownprec);
      } else {
        printExpr(tree.arg, ownprec);
        print(opname);
      }
      close(prec, ownprec);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitBinary(JCBinary tree) {
    try {
      int ownprec = TreeInfo.opPrec(tree.getTag());
      String opname = operatorName(tree.getTag());
      open(prec, ownprec);
      printExpr(tree.lhs, ownprec);
      print(" " + opname + " ");
      printExpr(tree.rhs, ownprec + 1);
      close(prec, ownprec);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitTypeCast(JCTypeCast tree) {
    try {
      open(prec, TreeInfo.prefixPrec);
      print("(");
      printExpr(tree.clazz);
      print(")");
      printExpr(tree.expr, TreeInfo.prefixPrec);
      close(prec, TreeInfo.prefixPrec);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitTypeTest(JCInstanceOf tree) {
    try {
      open(prec, TreeInfo.ordPrec);
      printExpr(tree.expr, TreeInfo.ordPrec);
      print(" instanceof ");
      printExpr(tree.clazz, TreeInfo.ordPrec + 1);
      close(prec, TreeInfo.ordPrec);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitIndexed(JCArrayAccess tree) {
    try {
      printExpr(tree.indexed, TreeInfo.postfixPrec);
      print("[");
      printExpr(tree.index);
      print("]");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitSelect(JCFieldAccess tree) {
    try {
      printExpr(tree.selected, TreeInfo.postfixPrec);
      print("." + tree.name);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitIdent(JCIdent tree) {
    try {
      print(tree.name);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitLiteral(JCLiteral tree) {
    try {
      switch (tree.typetag) {
        case TypeTags.INT:
          print(tree.value.toString());
          break;
        case TypeTags.LONG:
          print(tree.value + "L");
          break;
        case TypeTags.FLOAT:
          print(tree.value + "F");
          break;
        case TypeTags.DOUBLE:
          print(tree.value.toString());
          break;
        case TypeTags.CHAR:
          print(
              "\'" + Convert.quote(String.valueOf((char) ((Number) tree.value).intValue())) + "\'");
          break;
        case TypeTags.BOOLEAN:
          print(((Number) tree.value).intValue() == 1 ? "true" : "false");
          break;
        case TypeTags.BOT:
          print("null");
          break;
        default:
          print("\"" + Convert.quote(tree.value.toString()) + "\"");
          break;
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitTypeIdent(JCPrimitiveTypeTree tree) {
    try {
      switch (tree.typetag) {
        case TypeTags.BYTE:
          print("byte");
          break;
        case TypeTags.CHAR:
          print("char");
          break;
        case TypeTags.SHORT:
          print("short");
          break;
        case TypeTags.INT:
          print("int");
          break;
        case TypeTags.LONG:
          print("long");
          break;
        case TypeTags.FLOAT:
          print("float");
          break;
        case TypeTags.DOUBLE:
          print("double");
          break;
        case TypeTags.BOOLEAN:
          print("boolean");
          break;
        case TypeTags.VOID:
          print("void");
          break;
        default:
          print("error");
          break;
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitTypeArray(JCArrayTypeTree tree) {
    try {
      printBaseElementType(tree);
      printBrackets(tree);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  // Prints the inner element type of a nested array
  private void printBaseElementType(JCArrayTypeTree tree) throws IOException {
    JCTree elem = tree.elemtype;
    while (elem instanceof JCWildcard) elem = ((JCWildcard) elem).inner;
    if (elem instanceof JCArrayTypeTree) printBaseElementType((JCArrayTypeTree) elem);
    else printExpr(elem);
  }

  // prints the brackets of a nested array in reverse order
  private void printBrackets(JCArrayTypeTree tree) throws IOException {
    JCTree elem;
    while (true) {
      elem = tree.elemtype;
      print("[]");
      if (!(elem instanceof JCArrayTypeTree)) break;
      tree = (JCArrayTypeTree) elem;
    }
  }

  public void visitTypeApply(JCTypeApply tree) {
    try {
      printExpr(tree.clazz);
      print("<");
      printExprs(tree.arguments);
      print(">");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitTypeParameter(JCTypeParameter tree) {
    try {
      print(tree.name);
      if (tree.bounds.nonEmpty()) {
        print(" extends ");
        printExprs(tree.bounds, " & ");
      }
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  @Override
  public void visitWildcard(JCWildcard tree) {
    try {
      print(tree.kind);
      if (tree.kind.kind != BoundKind.UNBOUND) printExpr(tree.inner);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  @Override
  public void visitTypeBoundKind(TypeBoundKind tree) {
    try {
      print(String.valueOf(tree.kind));
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitErroneous(JCErroneous tree) {
    try {
      print("(ERROR)");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitLetExpr(LetExpr tree) {
    try {
      print("(let " + tree.defs + " in " + tree.expr + ")");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitModifiers(JCModifiers mods) {
    try {
      printAnnotations(mods.annotations);
      printFlags(mods.flags);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitAnnotation(JCAnnotation tree) {
    try {
      print("@");
      printExpr(tree.annotationType);
      print("(");
      printExprs(tree.args);
      print(")");
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }

  public void visitTree(JCTree tree) {
    try {
      print("(UNKNOWN: " + tree + ")");
      println();
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }
}