6975005: improve JavacProcessingEnvironment.Round abstraction
authorjjg
Mon, 23 Aug 2010 11:56:53 -0700
changeset 6355 f01ebbf5a5f7
parent 6354 f50c012cd1f0
child 6356 af24929939ca
6975005: improve JavacProcessingEnvironment.Round abstraction Reviewed-by: darcy
langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java
langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java
langtools/test/tools/javac/T6358024.java
langtools/test/tools/javac/T6403466.out
langtools/test/tools/javac/processing/filer/TestLastRound.out
--- a/langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java	Mon Aug 23 17:00:07 2010 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/main/JavaCompiler.java	Mon Aug 23 11:56:53 2010 -0700
@@ -529,7 +529,7 @@
                 log.error("warnings.and.werror");
             }
         }
-            return log.nerrors;
+        return log.nerrors;
     }
 
     protected final <T> Queue<T> stopIfError(CompileState cs, Queue<T> queue) {
@@ -868,7 +868,7 @@
     /**
      * Parses a list of files.
      */
-   public List<JCCompilationUnit> parseFiles(List<JavaFileObject> fileObjects) throws IOException {
+   public List<JCCompilationUnit> parseFiles(Iterable<JavaFileObject> fileObjects) throws IOException {
        if (shouldStop(CompileState.PARSE))
            return List.nil();
 
@@ -981,14 +981,13 @@
      */
     public JavaCompiler processAnnotations(List<JCCompilationUnit> roots,
                                            List<String> classnames)
-        throws IOException  { // TODO: see TEMP note in JavacProcessingEnvironment
+            throws IOException  { // TODO: see TEMP note in JavacProcessingEnvironment
         if (shouldStop(CompileState.PROCESS)) {
-            // Errors were encountered.  If todo is empty, then the
-            // encountered errors were parse errors.  Otherwise, the
-            // errors were found during the enter phase which should
-            // be ignored when processing annotations.
-
-            if (todo.isEmpty())
+            // Errors were encountered.
+            // If log.unrecoverableError is set, the errors were parse errors
+            // or other errors during enter which cannot be fixed by running
+            // any annotation processors.
+            if (log.unrecoverableError)
                 return this;
         }
 
--- a/langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java	Mon Aug 23 17:00:07 2010 +0100
+++ b/langtools/src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java	Mon Aug 23 11:56:53 2010 -0700
@@ -795,6 +795,13 @@
         final JavaCompiler compiler;
         /** The log for the round. */
         final Log log;
+        /** The number of warnings in the previous round. */
+        final int priorWarnings;
+
+        /** The ASTs to be compiled. */
+        List<JCCompilationUnit> roots;
+        /** The classes to be compiler that have were generated. */
+        Map<String, JavaFileObject> genClassFiles;
 
         /** The set of annotations to be processed this round. */
         Set<TypeElement> annotationsPresent;
@@ -803,10 +810,12 @@
         /** The set of package-info files to be processed this round. */
         List<PackageSymbol> packageInfoFiles;
 
-        /** Create a round. */
-        Round(Context context, int number) {
+        /** Create a round (common code). */
+        private Round(Context context, int number, int priorWarnings) {
             this.context = context;
             this.number = number;
+            this.priorWarnings = priorWarnings;
+
             compiler = JavaCompiler.instance(context);
             log = Log.instance(context);
 
@@ -814,15 +823,86 @@
             JavacProcessingEnvironment.this.context = context;
 
             // the following will be populated as needed
-            annotationsPresent = new LinkedHashSet<TypeElement>();
             topLevelClasses  = List.nil();
             packageInfoFiles = List.nil();
         }
 
+        /** Create the first round. */
+        Round(Context context, List<JCCompilationUnit> roots, List<ClassSymbol> classSymbols) {
+            this(context, 1, 0);
+            this.roots = roots;
+            genClassFiles = new HashMap<String,JavaFileObject>();
+
+            compiler.todo.clear(); // free the compiler's resources
+
+            // The reverse() in the following line is to maintain behavioural
+            // compatibility with the previous revision of the code. Strictly speaking,
+            // it should not be necessary, but a javah golden file test fails without it.
+            topLevelClasses =
+                getTopLevelClasses(roots).prependList(classSymbols.reverse());
+
+            packageInfoFiles = getPackageInfoFiles(roots);
+
+            findAnnotationsPresent();
+        }
+
+        /** Create a new round. */
+        private Round(Round prev,
+                Set<JavaFileObject> newSourceFiles, Map<String,JavaFileObject> newClassFiles)
+                throws IOException {
+            this(prev.nextContext(), prev.number+1, prev.compiler.log.nwarnings);
+            this.genClassFiles = prev.genClassFiles;
+
+            updateProcessingState();
+
+            List<JCCompilationUnit> parsedFiles = compiler.parseFiles(newSourceFiles);
+            roots = cleanTrees(prev.roots).appendList(parsedFiles);
+
+            // Check for errors after parsing
+            if (unrecoverableError())
+                return;
+
+            enterClassFiles(genClassFiles);
+            List<ClassSymbol> newClasses = enterClassFiles(newClassFiles);
+            genClassFiles.putAll(newClassFiles);
+            enterTrees(roots);
+
+            if (unrecoverableError())
+                return;
+
+            topLevelClasses = join(
+                    getTopLevelClasses(parsedFiles),
+                    getTopLevelClassesFromClasses(newClasses));
+
+            packageInfoFiles = join(
+                    getPackageInfoFiles(parsedFiles),
+                    getPackageInfoFilesFromClasses(newClasses));
+
+            findAnnotationsPresent();
+        }
+
         /** Create the next round to be used. */
-        Round next() {
-            compiler.close(false);
-            return new Round(contextForNextRound(), number + 1);
+        Round next(Set<JavaFileObject> newSourceFiles, Map<String, JavaFileObject> newClassFiles)
+                throws IOException {
+            try {
+                return new Round(this, newSourceFiles, newClassFiles);
+            } finally {
+                compiler.close(false);
+            }
+        }
+
+        /** Create the compiler to be used for the final compilation. */
+        JavaCompiler finalCompiler(boolean errorStatus) {
+            try {
+                JavaCompiler c = JavaCompiler.instance(nextContext());
+                if (errorStatus) {
+                    c.log.nwarnings += priorWarnings + compiler.log.nwarnings;
+                    c.log.nerrors += compiler.log.nerrors;
+                }
+                return c;
+            } finally {
+                compiler.close(false);
+            }
         }
 
         /** Return the number of errors found so far in this round.
@@ -839,12 +919,16 @@
 
         /** Return whether or not an unrecoverable error has occurred. */
         boolean unrecoverableError() {
-            return log.unrecoverableError;
+            return log.unrecoverableError
+                    || messager.errorRaised()
+                    || (werror && log.nwarnings > 0)
+                    || (fatalErrors && log.nerrors > 0);
         }
 
         /** Find the set of annotations present in the set of top level
-         * classes and package info files to be processed this round. */
-        void findAnnotationsPresent(ComputeAnnotationSet annotationComputer) {
+         *  classes and package info files to be processed this round. */
+        void findAnnotationsPresent() {
+            ComputeAnnotationSet annotationComputer = new ComputeAnnotationSet(elementUtils);
             // Use annotation processing to compute the set of annotations present
             annotationsPresent = new LinkedHashSet<TypeElement>();
             for (ClassSymbol classSym : topLevelClasses)
@@ -853,26 +937,13 @@
                 annotationComputer.scan(pkgSym, annotationsPresent);
         }
 
-        /**
-         * Parse the latest set of generated source files created by the filer.
-         */
-        List<JCCompilationUnit> parseNewSourceFiles()
-            throws IOException {
-            List<JavaFileObject> fileObjects = List.nil();
-            for (JavaFileObject jfo : filer.getGeneratedSourceFileObjects() ) {
-                fileObjects = fileObjects.prepend(jfo);
-            }
-
-           return compiler.parseFiles(fileObjects);
-        }
-
-        /** Enter the latest set of generated class files created by the filer. */
-        List<ClassSymbol> enterNewClassFiles() {
+        /** Enter a set of generated class files. */
+        List<ClassSymbol> enterClassFiles(Map<String, JavaFileObject> classFiles) {
             ClassReader reader = ClassReader.instance(context);
             Names names = Names.instance(context);
             List<ClassSymbol> list = List.nil();
 
-            for (Map.Entry<String,JavaFileObject> entry : filer.getGeneratedClasses().entrySet()) {
+            for (Map.Entry<String,JavaFileObject> entry : classFiles.entrySet()) {
                 Name name = names.fromString(entry.getKey());
                 JavaFileObject file = entry.getValue();
                 if (file.getKind() != JavaFileObject.Kind.CLASS)
@@ -900,11 +971,7 @@
 
         /** Run a processing round. */
         void run(boolean lastRound, boolean errorStatus) {
-//            assert lastRound
-//                ? (topLevelClasses.size() == 0 && annotationsPresent.size() == 0)
-//                : (errorStatus == false);
-//
-//            printRoundInfo(topLevelClasses, annotationsPresent, lastRound);
+            printRoundInfo(lastRound);
 
             TaskListener taskListener = context.get(TaskListener.class);
             if (taskListener != null)
@@ -912,7 +979,6 @@
 
             try {
                 if (lastRound) {
-                    printRoundInfo(List.<ClassSymbol>nil(), Collections.<TypeElement>emptySet(), lastRound);
                     filer.setLastRound(true);
                     Set<Element> emptyRootElements = Collections.emptySet(); // immutable
                     RoundEnvironment renv = new JavacRoundEnvironment(true,
@@ -921,7 +987,6 @@
                             JavacProcessingEnvironment.this);
                     discoveredProcs.iterator().runContributingProcs(renv);
                 } else {
-                    printRoundInfo(topLevelClasses, annotationsPresent, lastRound);
                     discoverAndRunProcs(context, annotationsPresent, topLevelClasses, packageInfoFiles);
                 }
             } finally {
@@ -931,11 +996,7 @@
         }
 
         /** Update the processing state for the current context. */
-        // Question: should this not be part of next()?
-        // Note: Calling it from next() breaks some tests. There is an issue
-        // whether the annotationComputer is using elementUtils with the
-        // correct context.
-        void updateProcessingState() {
+        private void updateProcessingState() {
             filer.newRound(context);
             messager.newRound(context);
 
@@ -944,14 +1005,14 @@
         }
 
         /** Print info about this round. */
-        private void printRoundInfo(List<ClassSymbol> topLevelClasses,
-                Set<TypeElement> annotationsPresent,
-                boolean lastRound) {
+        private void printRoundInfo(boolean lastRound) {
             if (printRounds || verbose) {
+                List<ClassSymbol> tlc = lastRound ? List.<ClassSymbol>nil() : topLevelClasses;
+                Set<TypeElement> ap = lastRound ? Collections.<TypeElement>emptySet() : annotationsPresent;
                 log.printNoteLines("x.print.rounds",
-                        (!lastRound ? number : number + 1),
-                        "{" + topLevelClasses.toString(", ") + "}",
-                        annotationsPresent,
+                        number,
+                        "{" + tlc.toString(", ") + "}",
+                        ap,
                         lastRound);
             }
         }
@@ -960,7 +1021,7 @@
          * Important values are propogated from round to round;
          * other values are implicitly reset.
          */
-        private Context contextForNextRound() {
+        private Context nextContext() {
             Context next = new Context();
 
             Options options = Options.instance(context);
@@ -1025,138 +1086,90 @@
                                      Iterable<? extends PackageSymbol> pckSymbols)
         throws IOException {
 
+        TaskListener taskListener = context.get(TaskListener.class);
         log = Log.instance(context);
 
-        Round round = new Round(context, 1);
-        round.compiler.todo.clear(); // free the compiler's resources
-
-        // The reverse() in the following line is to maintain behavioural
-        // compatibility with the previous revision of the code. Strictly speaking,
-        // it should not be necessary, but a javah golden file test fails without it.
-        round.topLevelClasses =
-                getTopLevelClasses(roots).prependList(classSymbols.reverse());
-
-        round.packageInfoFiles = getPackageInfoFiles(roots);
-
         Set<PackageSymbol> specifiedPackages = new LinkedHashSet<PackageSymbol>();
         for (PackageSymbol psym : pckSymbols)
             specifiedPackages.add(psym);
         this.specifiedPackages = Collections.unmodifiableSet(specifiedPackages);
 
-        ComputeAnnotationSet annotationComputer = new ComputeAnnotationSet(elementUtils);
-        round.findAnnotationsPresent(annotationComputer);
-
-        boolean errorStatus = false;
+        Round round = new Round(context, roots, classSymbols);
 
-        runAround:
-        while (true) {
-            if ((fatalErrors && round.errorCount() != 0)
-                    || (werror && round.warningCount() != 0)) {
-                errorStatus = true;
-                break runAround;
-            }
-
+        boolean errorStatus;
+        boolean moreToDo;
+        do {
+            // Run processors for round n
             round.run(false, false);
 
-            /*
-             * Processors for round n have run to completion.  Prepare
-             * for round (n+1) by checked for errors raised by
-             * annotation processors and then checking for syntax
-             * errors on any generated source files.
-             */
-            if (messager.errorRaised()) {
-                errorStatus = true;
-                break runAround;
-            }
-
-            if (!moreToDo())
-                break runAround; // No new files
-
-            round = round.next();
-
-            List<JCCompilationUnit> parsedFiles = round.parseNewSourceFiles();
-            roots = cleanTrees(roots).appendList(parsedFiles);
+            // Processors for round n have run to completion.
+            // Check for errors and whether there is more work to do.
+            errorStatus = round.unrecoverableError();
+            moreToDo = moreToDo();
 
-            // Check for errors after parsing
-            if (round.unrecoverableError()) {
-                errorStatus = true;
-                break runAround;
-            }
-
-            List<ClassSymbol> newClasses = round.enterNewClassFiles();
-            round.enterTrees(roots);
+            // Set up next round.
+            // Copy mutable collections returned from filer.
+            round = round.next(
+                    new LinkedHashSet<JavaFileObject>(filer.getGeneratedSourceFileObjects()),
+                    new LinkedHashMap<String,JavaFileObject>(filer.getGeneratedClasses()));
 
-            round.topLevelClasses = join(
-                    getTopLevelClasses(parsedFiles),
-                    getTopLevelClassesFromClasses(newClasses));
+             // Check for errors during setup.
+            if (round.unrecoverableError())
+                errorStatus = true;
 
-            round.packageInfoFiles = join(
-                    getPackageInfoFiles(parsedFiles),
-                    getPackageInfoFilesFromClasses(newClasses));
-
-            round.findAnnotationsPresent(annotationComputer);
-            round.updateProcessingState();
-        }
+        } while (moreToDo && !errorStatus);
 
         // run last round
         round.run(true, errorStatus);
 
-        // Add any sources generated during the last round to the set
-        // of files to be compiled.
-        if (moreToDo()) {
-            List<JCCompilationUnit> parsedFiles = round.parseNewSourceFiles();
-            roots = cleanTrees(roots).appendList(parsedFiles);
-        }
-
-        // Set error status for any files compiled and generated in
-        // the last round
-        if (round.unrecoverableError() || (werror && round.warningCount() != 0))
-            errorStatus = true;
-
-        round = round.next();
-
         filer.warnIfUnclosedFiles();
         warnIfUnmatchedOptions();
 
-       /*
-        * If an annotation processor raises an error in a round,
-        * that round runs to completion and one last round occurs.
-        * The last round may also occur because no more source or
-        * class files have been generated.  Therefore, if an error
-        * was raised on either of the last *two* rounds, the compile
-        * should exit with a nonzero exit code.  The current value of
-        * errorStatus holds whether or not an error was raised on the
-        * second to last round; errorRaised() gives the error status
-        * of the last round.
-        */
-        errorStatus = errorStatus || messager.errorRaised();
+        /*
+         * If an annotation processor raises an error in a round,
+         * that round runs to completion and one last round occurs.
+         * The last round may also occur because no more source or
+         * class files have been generated.  Therefore, if an error
+         * was raised on either of the last *two* rounds, the compile
+         * should exit with a nonzero exit code.  The current value of
+         * errorStatus holds whether or not an error was raised on the
+         * second to last round; errorRaised() gives the error status
+         * of the last round.
+         */
+        if (messager.errorRaised()
+                || werror && round.warningCount() > 0 && round.errorCount() > 0)
+            errorStatus = true;
+
+        Set<JavaFileObject> newSourceFiles =
+                new LinkedHashSet<JavaFileObject>(filer.getGeneratedSourceFileObjects());
+        roots = cleanTrees(round.roots);
+
+        JavaCompiler compiler = round.finalCompiler(errorStatus);
+
+        if (newSourceFiles.size() > 0)
+            roots = roots.appendList(compiler.parseFiles(newSourceFiles));
+
+        errorStatus = errorStatus || (compiler.errorCount() > 0);
 
         // Free resources
         this.close();
 
-        TaskListener taskListener = this.context.get(TaskListener.class);
         if (taskListener != null)
             taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING));
 
-        JavaCompiler compiler;
-
         if (errorStatus) {
-            compiler = round.compiler;
-            compiler.log.nwarnings += messager.warningCount();
-            compiler.log.nerrors += messager.errorCount();
             if (compiler.errorCount() == 0)
                 compiler.log.nerrors++;
-        } else if (procOnly && !foundTypeProcessors) {
-            compiler = round.compiler;
+            return compiler;
+        }
+
+        if (procOnly && !foundTypeProcessors) {
             compiler.todo.clear();
-        } else { // Final compilation
-            round = round.next();
-            round.updateProcessingState();
-            compiler = round.compiler;
+        } else {
             if (procOnly && foundTypeProcessors)
                 compiler.shouldStopPolicy = CompileState.FLOW;
 
-            compiler.enterTrees(cleanTrees(roots));
+            compiler.enterTrees(roots);
         }
 
         return compiler;
@@ -1185,7 +1198,9 @@
         for (JCCompilationUnit unit : units) {
             for (JCTree node : unit.defs) {
                 if (node.getTag() == JCTree.CLASSDEF) {
-                    classes = classes.prepend(((JCClassDecl) node).sym);
+                    ClassSymbol sym = ((JCClassDecl) node).sym;
+                    assert sym != null;
+                    classes = classes.prepend(sym);
                 }
             }
         }
--- a/langtools/test/tools/javac/T6358024.java	Mon Aug 23 17:00:07 2010 +0100
+++ b/langtools/test/tools/javac/T6358024.java	Mon Aug 23 11:56:53 2010 -0700
@@ -60,7 +60,7 @@
              new Option[] { new XOption("-XprintRounds"),
                             new Option("-processorpath", "."),
                             new Option("-processor", self) },
-             11);
+             12);
     }
 
     static void test(JavacFileManager fm, JavaFileObject f, Option[] opts, int expect) throws Throwable {
--- a/langtools/test/tools/javac/T6403466.out	Mon Aug 23 17:00:07 2010 +0100
+++ b/langtools/test/tools/javac/T6403466.out	Mon Aug 23 11:56:53 2010 -0700
@@ -13,6 +13,10 @@
 Finished TaskEvent[ENTER,T6403466Wrapper.java,null]
 Started TaskEvent[ANNOTATION_PROCESSING_ROUND,null,null]
 Finished TaskEvent[ANNOTATION_PROCESSING_ROUND,null,null]
+Started TaskEvent[ENTER,T6403466.java,null]
+Started TaskEvent[ENTER,T6403466Wrapper.java,null]
+Finished TaskEvent[ENTER,T6403466.java,null]
+Finished TaskEvent[ENTER,T6403466Wrapper.java,null]
 Started TaskEvent[ANNOTATION_PROCESSING_ROUND,null,null]
 Finished TaskEvent[ANNOTATION_PROCESSING_ROUND,null,null]
 Finished TaskEvent[ANNOTATION_PROCESSING,null,null]
--- a/langtools/test/tools/javac/processing/filer/TestLastRound.out	Mon Aug 23 17:00:07 2010 +0100
+++ b/langtools/test/tools/javac/processing/filer/TestLastRound.out	Mon Aug 23 11:56:53 2010 -0700
@@ -1,3 +1,4 @@
 - compiler.warn.proc.file.create.last.round: LastRound.java
 - compiler.err.warnings.and.werror
 1 error
+1 warning