Tue, 27 Jul 2010 11:52:11 -0700
6403456: -Werror should work with annotation processing
Reviewed-by: darcy
1 /*
2 * Copyright (c) 2005, 2009, 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 */
26 package com.sun.tools.javac.processing;
28 import java.lang.reflect.*;
29 import java.util.*;
30 import java.util.regex.*;
32 import java.net.URL;
33 import java.io.Closeable;
34 import java.io.File;
35 import java.io.PrintWriter;
36 import java.io.IOException;
37 import java.net.MalformedURLException;
38 import java.io.StringWriter;
40 import javax.annotation.processing.*;
41 import javax.lang.model.SourceVersion;
42 import javax.lang.model.element.AnnotationMirror;
43 import javax.lang.model.element.Element;
44 import javax.lang.model.element.TypeElement;
45 import javax.lang.model.element.PackageElement;
46 import javax.lang.model.util.*;
47 import javax.tools.JavaFileManager;
48 import javax.tools.StandardJavaFileManager;
49 import javax.tools.JavaFileObject;
50 import javax.tools.DiagnosticListener;
52 import com.sun.source.util.AbstractTypeProcessor;
53 import com.sun.source.util.TaskEvent;
54 import com.sun.source.util.TaskListener;
55 import com.sun.tools.javac.api.JavacTaskImpl;
56 import com.sun.tools.javac.code.*;
57 import com.sun.tools.javac.code.Symbol.*;
58 import com.sun.tools.javac.file.JavacFileManager;
59 import com.sun.tools.javac.jvm.*;
60 import com.sun.tools.javac.main.JavaCompiler;
61 import com.sun.tools.javac.main.JavaCompiler.CompileState;
62 import com.sun.tools.javac.model.JavacElements;
63 import com.sun.tools.javac.model.JavacTypes;
64 import com.sun.tools.javac.parser.*;
65 import com.sun.tools.javac.tree.*;
66 import com.sun.tools.javac.tree.JCTree.*;
67 import com.sun.tools.javac.util.Abort;
68 import com.sun.tools.javac.util.Context;
69 import com.sun.tools.javac.util.Convert;
70 import com.sun.tools.javac.util.List;
71 import com.sun.tools.javac.util.ListBuffer;
72 import com.sun.tools.javac.util.Log;
73 import com.sun.tools.javac.util.JavacMessages;
74 import com.sun.tools.javac.util.Name;
75 import com.sun.tools.javac.util.Names;
76 import com.sun.tools.javac.util.Options;
78 import static javax.tools.StandardLocation.*;
80 /**
81 * Objects of this class hold and manage the state needed to support
82 * annotation processing.
83 *
84 * <p><b>This is NOT part of any supported API.
85 * If you write code that depends on this, you do so at your own risk.
86 * This code and its internal interfaces are subject to change or
87 * deletion without notice.</b>
88 */
89 public class JavacProcessingEnvironment implements ProcessingEnvironment, Closeable {
90 Options options;
92 private final boolean printProcessorInfo;
93 private final boolean printRounds;
94 private final boolean verbose;
95 private final boolean lint;
96 private final boolean procOnly;
97 private final boolean fatalErrors;
98 private final boolean werror;
99 private boolean foundTypeProcessors;
101 private final JavacFiler filer;
102 private final JavacMessager messager;
103 private final JavacElements elementUtils;
104 private final JavacTypes typeUtils;
106 /**
107 * Holds relevant state history of which processors have been
108 * used.
109 */
110 private DiscoveredProcessors discoveredProcs;
112 /**
113 * Map of processor-specific options.
114 */
115 private final Map<String, String> processorOptions;
117 /**
118 */
119 private final Set<String> unmatchedProcessorOptions;
121 /**
122 * Annotations implicitly processed and claimed by javac.
123 */
124 private final Set<String> platformAnnotations;
126 /**
127 * Set of packages given on command line.
128 */
129 private Set<PackageSymbol> specifiedPackages = Collections.emptySet();
131 /** The log to be used for error reporting.
132 */
133 Log log;
135 /**
136 * Source level of the compile.
137 */
138 Source source;
140 private ClassLoader processorClassLoader;
142 /**
143 * JavacMessages object used for localization
144 */
145 private JavacMessages messages;
147 private Context context;
149 public JavacProcessingEnvironment(Context context, Iterable<? extends Processor> processors) {
150 options = Options.instance(context);
151 this.context = context;
152 log = Log.instance(context);
153 source = Source.instance(context);
154 printProcessorInfo = options.get("-XprintProcessorInfo") != null;
155 printRounds = options.get("-XprintRounds") != null;
156 verbose = options.get("-verbose") != null;
157 lint = options.lint("processing");
158 procOnly = options.get("-proc:only") != null ||
159 options.get("-Xprint") != null;
160 fatalErrors = options.get("fatalEnterError") != null;
161 werror = options.get("-Werror") != null;
162 platformAnnotations = initPlatformAnnotations();
163 foundTypeProcessors = false;
165 // Initialize services before any processors are initialzied
166 // in case processors use them.
167 filer = new JavacFiler(context);
168 messager = new JavacMessager(context, this);
169 elementUtils = new JavacElements(context);
170 typeUtils = new JavacTypes(context);
171 processorOptions = initProcessorOptions(context);
172 unmatchedProcessorOptions = initUnmatchedProcessorOptions();
173 messages = JavacMessages.instance(context);
174 initProcessorIterator(context, processors);
175 }
177 private Set<String> initPlatformAnnotations() {
178 Set<String> platformAnnotations = new HashSet<String>();
179 platformAnnotations.add("java.lang.Deprecated");
180 platformAnnotations.add("java.lang.Override");
181 platformAnnotations.add("java.lang.SuppressWarnings");
182 platformAnnotations.add("java.lang.annotation.Documented");
183 platformAnnotations.add("java.lang.annotation.Inherited");
184 platformAnnotations.add("java.lang.annotation.Retention");
185 platformAnnotations.add("java.lang.annotation.Target");
186 return Collections.unmodifiableSet(platformAnnotations);
187 }
189 private void initProcessorIterator(Context context, Iterable<? extends Processor> processors) {
190 Log log = Log.instance(context);
191 Iterator<? extends Processor> processorIterator;
193 if (options.get("-Xprint") != null) {
194 try {
195 Processor processor = PrintingProcessor.class.newInstance();
196 processorIterator = List.of(processor).iterator();
197 } catch (Throwable t) {
198 AssertionError assertError =
199 new AssertionError("Problem instantiating PrintingProcessor.");
200 assertError.initCause(t);
201 throw assertError;
202 }
203 } else if (processors != null) {
204 processorIterator = processors.iterator();
205 } else {
206 String processorNames = options.get("-processor");
207 JavaFileManager fileManager = context.get(JavaFileManager.class);
208 try {
209 // If processorpath is not explicitly set, use the classpath.
210 processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
211 ? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH)
212 : fileManager.getClassLoader(CLASS_PATH);
214 /*
215 * If the "-processor" option is used, search the appropriate
216 * path for the named class. Otherwise, use a service
217 * provider mechanism to create the processor iterator.
218 */
219 if (processorNames != null) {
220 processorIterator = new NameProcessIterator(processorNames, processorClassLoader, log);
221 } else {
222 processorIterator = new ServiceIterator(processorClassLoader, log);
223 }
224 } catch (SecurityException e) {
225 /*
226 * A security exception will occur if we can't create a classloader.
227 * Ignore the exception if, with hindsight, we didn't need it anyway
228 * (i.e. no processor was specified either explicitly, or implicitly,
229 * in service configuration file.) Otherwise, we cannot continue.
230 */
231 processorIterator = handleServiceLoaderUnavailability("proc.cant.create.loader", e);
232 }
233 }
234 discoveredProcs = new DiscoveredProcessors(processorIterator);
235 }
237 /**
238 * Returns an empty processor iterator if no processors are on the
239 * relevant path, otherwise if processors are present, logs an
240 * error. Called when a service loader is unavailable for some
241 * reason, either because a service loader class cannot be found
242 * or because a security policy prevents class loaders from being
243 * created.
244 *
245 * @param key The resource key to use to log an error message
246 * @param e If non-null, pass this exception to Abort
247 */
248 private Iterator<Processor> handleServiceLoaderUnavailability(String key, Exception e) {
249 JavaFileManager fileManager = context.get(JavaFileManager.class);
251 if (fileManager instanceof JavacFileManager) {
252 StandardJavaFileManager standardFileManager = (JavacFileManager) fileManager;
253 Iterable<? extends File> workingPath = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
254 ? standardFileManager.getLocation(ANNOTATION_PROCESSOR_PATH)
255 : standardFileManager.getLocation(CLASS_PATH);
257 if (needClassLoader(options.get("-processor"), workingPath) )
258 handleException(key, e);
260 } else {
261 handleException(key, e);
262 }
264 java.util.List<Processor> pl = Collections.emptyList();
265 return pl.iterator();
266 }
268 /**
269 * Handle a security exception thrown during initializing the
270 * Processor iterator.
271 */
272 private void handleException(String key, Exception e) {
273 if (e != null) {
274 log.error(key, e.getLocalizedMessage());
275 throw new Abort(e);
276 } else {
277 log.error(key);
278 throw new Abort();
279 }
280 }
282 /**
283 * Use a service loader appropriate for the platform to provide an
284 * iterator over annotations processors. If
285 * java.util.ServiceLoader is present use it, otherwise, use
286 * sun.misc.Service, otherwise fail if a loader is needed.
287 */
288 private class ServiceIterator implements Iterator<Processor> {
289 // The to-be-wrapped iterator.
290 private Iterator<?> iterator;
291 private Log log;
292 private Class<?> loaderClass;
293 private boolean jusl;
294 private Object loader;
296 ServiceIterator(ClassLoader classLoader, Log log) {
297 String loadMethodName;
299 this.log = log;
300 try {
301 try {
302 loaderClass = Class.forName("java.util.ServiceLoader");
303 loadMethodName = "load";
304 jusl = true;
305 } catch (ClassNotFoundException cnfe) {
306 try {
307 loaderClass = Class.forName("sun.misc.Service");
308 loadMethodName = "providers";
309 jusl = false;
310 } catch (ClassNotFoundException cnfe2) {
311 // Fail softly if a loader is not actually needed.
312 this.iterator = handleServiceLoaderUnavailability("proc.no.service",
313 null);
314 return;
315 }
316 }
318 // java.util.ServiceLoader.load or sun.misc.Service.providers
319 Method loadMethod = loaderClass.getMethod(loadMethodName,
320 Class.class,
321 ClassLoader.class);
323 Object result = loadMethod.invoke(null,
324 Processor.class,
325 classLoader);
327 // For java.util.ServiceLoader, we have to call another
328 // method to get the iterator.
329 if (jusl) {
330 loader = result; // Store ServiceLoader to call reload later
331 Method m = loaderClass.getMethod("iterator");
332 result = m.invoke(result); // serviceLoader.iterator();
333 }
335 // The result should now be an iterator.
336 this.iterator = (Iterator<?>) result;
337 } catch (Throwable t) {
338 log.error("proc.service.problem");
339 throw new Abort(t);
340 }
341 }
343 public boolean hasNext() {
344 try {
345 return iterator.hasNext();
346 } catch (Throwable t) {
347 if ("ServiceConfigurationError".
348 equals(t.getClass().getSimpleName())) {
349 log.error("proc.bad.config.file", t.getLocalizedMessage());
350 }
351 throw new Abort(t);
352 }
353 }
355 public Processor next() {
356 try {
357 return (Processor)(iterator.next());
358 } catch (Throwable t) {
359 if ("ServiceConfigurationError".
360 equals(t.getClass().getSimpleName())) {
361 log.error("proc.bad.config.file", t.getLocalizedMessage());
362 } else {
363 log.error("proc.processor.constructor.error", t.getLocalizedMessage());
364 }
365 throw new Abort(t);
366 }
367 }
369 public void remove() {
370 throw new UnsupportedOperationException();
371 }
373 public void close() {
374 if (jusl) {
375 try {
376 // Call java.util.ServiceLoader.reload
377 Method reloadMethod = loaderClass.getMethod("reload");
378 reloadMethod.invoke(loader);
379 } catch(Exception e) {
380 ; // Ignore problems during a call to reload.
381 }
382 }
383 }
384 }
387 private static class NameProcessIterator implements Iterator<Processor> {
388 Processor nextProc = null;
389 Iterator<String> names;
390 ClassLoader processorCL;
391 Log log;
393 NameProcessIterator(String names, ClassLoader processorCL, Log log) {
394 this.names = Arrays.asList(names.split(",")).iterator();
395 this.processorCL = processorCL;
396 this.log = log;
397 }
399 public boolean hasNext() {
400 if (nextProc != null)
401 return true;
402 else {
403 if (!names.hasNext())
404 return false;
405 else {
406 String processorName = names.next();
408 Processor processor;
409 try {
410 try {
411 processor =
412 (Processor) (processorCL.loadClass(processorName).newInstance());
413 } catch (ClassNotFoundException cnfe) {
414 log.error("proc.processor.not.found", processorName);
415 return false;
416 } catch (ClassCastException cce) {
417 log.error("proc.processor.wrong.type", processorName);
418 return false;
419 } catch (Exception e ) {
420 log.error("proc.processor.cant.instantiate", processorName);
421 return false;
422 }
423 } catch(Throwable t) {
424 throw new AnnotationProcessingError(t);
425 }
426 nextProc = processor;
427 return true;
428 }
430 }
431 }
433 public Processor next() {
434 if (hasNext()) {
435 Processor p = nextProc;
436 nextProc = null;
437 return p;
438 } else
439 throw new NoSuchElementException();
440 }
442 public void remove () {
443 throw new UnsupportedOperationException();
444 }
445 }
447 public boolean atLeastOneProcessor() {
448 return discoveredProcs.iterator().hasNext();
449 }
451 private Map<String, String> initProcessorOptions(Context context) {
452 Options options = Options.instance(context);
453 Set<String> keySet = options.keySet();
454 Map<String, String> tempOptions = new LinkedHashMap<String, String>();
456 for(String key : keySet) {
457 if (key.startsWith("-A") && key.length() > 2) {
458 int sepIndex = key.indexOf('=');
459 String candidateKey = null;
460 String candidateValue = null;
462 if (sepIndex == -1)
463 candidateKey = key.substring(2);
464 else if (sepIndex >= 3) {
465 candidateKey = key.substring(2, sepIndex);
466 candidateValue = (sepIndex < key.length()-1)?
467 key.substring(sepIndex+1) : null;
468 }
469 tempOptions.put(candidateKey, candidateValue);
470 }
471 }
473 return Collections.unmodifiableMap(tempOptions);
474 }
476 private Set<String> initUnmatchedProcessorOptions() {
477 Set<String> unmatchedProcessorOptions = new HashSet<String>();
478 unmatchedProcessorOptions.addAll(processorOptions.keySet());
479 return unmatchedProcessorOptions;
480 }
482 /**
483 * State about how a processor has been used by the tool. If a
484 * processor has been used on a prior round, its process method is
485 * called on all subsequent rounds, perhaps with an empty set of
486 * annotations to process. The {@code annotatedSupported} method
487 * caches the supported annotation information from the first (and
488 * only) getSupportedAnnotationTypes call to the processor.
489 */
490 static class ProcessorState {
491 public Processor processor;
492 public boolean contributed;
493 private ArrayList<Pattern> supportedAnnotationPatterns;
494 private ArrayList<String> supportedOptionNames;
496 ProcessorState(Processor p, Log log, Source source, ProcessingEnvironment env) {
497 processor = p;
498 contributed = false;
500 try {
501 processor.init(env);
503 checkSourceVersionCompatibility(source, log);
505 supportedAnnotationPatterns = new ArrayList<Pattern>();
506 for (String importString : processor.getSupportedAnnotationTypes()) {
507 supportedAnnotationPatterns.add(importStringToPattern(importString,
508 processor,
509 log));
510 }
512 supportedOptionNames = new ArrayList<String>();
513 for (String optionName : processor.getSupportedOptions() ) {
514 if (checkOptionName(optionName, log))
515 supportedOptionNames.add(optionName);
516 }
518 } catch (Throwable t) {
519 throw new AnnotationProcessingError(t);
520 }
521 }
523 /**
524 * Checks whether or not a processor's source version is
525 * compatible with the compilation source version. The
526 * processor's source version needs to be greater than or
527 * equal to the source version of the compile.
528 */
529 private void checkSourceVersionCompatibility(Source source, Log log) {
530 SourceVersion procSourceVersion = processor.getSupportedSourceVersion();
532 if (procSourceVersion.compareTo(Source.toSourceVersion(source)) < 0 ) {
533 log.warning("proc.processor.incompatible.source.version",
534 procSourceVersion,
535 processor.getClass().getName(),
536 source.name);
537 }
538 }
540 private boolean checkOptionName(String optionName, Log log) {
541 boolean valid = isValidOptionName(optionName);
542 if (!valid)
543 log.error("proc.processor.bad.option.name",
544 optionName,
545 processor.getClass().getName());
546 return valid;
547 }
549 public boolean annotationSupported(String annotationName) {
550 for(Pattern p: supportedAnnotationPatterns) {
551 if (p.matcher(annotationName).matches())
552 return true;
553 }
554 return false;
555 }
557 /**
558 * Remove options that are matched by this processor.
559 */
560 public void removeSupportedOptions(Set<String> unmatchedProcessorOptions) {
561 unmatchedProcessorOptions.removeAll(supportedOptionNames);
562 }
563 }
565 // TODO: These two classes can probably be rewritten better...
566 /**
567 * This class holds information about the processors that have
568 * been discoverd so far as well as the means to discover more, if
569 * necessary. A single iterator should be used per round of
570 * annotation processing. The iterator first visits already
571 * discovered processors then fails over to the service provider
572 * mechanism if additional queries are made.
573 */
574 class DiscoveredProcessors implements Iterable<ProcessorState> {
576 class ProcessorStateIterator implements Iterator<ProcessorState> {
577 DiscoveredProcessors psi;
578 Iterator<ProcessorState> innerIter;
579 boolean onProcInterator;
581 ProcessorStateIterator(DiscoveredProcessors psi) {
582 this.psi = psi;
583 this.innerIter = psi.procStateList.iterator();
584 this.onProcInterator = false;
585 }
587 public ProcessorState next() {
588 if (!onProcInterator) {
589 if (innerIter.hasNext())
590 return innerIter.next();
591 else
592 onProcInterator = true;
593 }
595 if (psi.processorIterator.hasNext()) {
596 ProcessorState ps = new ProcessorState(psi.processorIterator.next(),
597 log, source, JavacProcessingEnvironment.this);
598 psi.procStateList.add(ps);
599 return ps;
600 } else
601 throw new NoSuchElementException();
602 }
604 public boolean hasNext() {
605 if (onProcInterator)
606 return psi.processorIterator.hasNext();
607 else
608 return innerIter.hasNext() || psi.processorIterator.hasNext();
609 }
611 public void remove () {
612 throw new UnsupportedOperationException();
613 }
615 /**
616 * Run all remaining processors on the procStateList that
617 * have not already run this round with an empty set of
618 * annotations.
619 */
620 public void runContributingProcs(RoundEnvironment re) {
621 if (!onProcInterator) {
622 Set<TypeElement> emptyTypeElements = Collections.emptySet();
623 while(innerIter.hasNext()) {
624 ProcessorState ps = innerIter.next();
625 if (ps.contributed)
626 callProcessor(ps.processor, emptyTypeElements, re);
627 }
628 }
629 }
630 }
632 Iterator<? extends Processor> processorIterator;
633 ArrayList<ProcessorState> procStateList;
635 public ProcessorStateIterator iterator() {
636 return new ProcessorStateIterator(this);
637 }
639 DiscoveredProcessors(Iterator<? extends Processor> processorIterator) {
640 this.processorIterator = processorIterator;
641 this.procStateList = new ArrayList<ProcessorState>();
642 }
644 /**
645 * Free jar files, etc. if using a service loader.
646 */
647 public void close() {
648 if (processorIterator != null &&
649 processorIterator instanceof ServiceIterator) {
650 ((ServiceIterator) processorIterator).close();
651 }
652 }
653 }
655 private void discoverAndRunProcs(Context context,
656 Set<TypeElement> annotationsPresent,
657 List<ClassSymbol> topLevelClasses,
658 List<PackageSymbol> packageInfoFiles) {
659 Map<String, TypeElement> unmatchedAnnotations =
660 new HashMap<String, TypeElement>(annotationsPresent.size());
662 for(TypeElement a : annotationsPresent) {
663 unmatchedAnnotations.put(a.getQualifiedName().toString(),
664 a);
665 }
667 // Give "*" processors a chance to match
668 if (unmatchedAnnotations.size() == 0)
669 unmatchedAnnotations.put("", null);
671 DiscoveredProcessors.ProcessorStateIterator psi = discoveredProcs.iterator();
672 // TODO: Create proper argument values; need past round
673 // information to fill in this constructor. Note that the 1
674 // st round of processing could be the last round if there
675 // were parse errors on the initial source files; however, we
676 // are not doing processing in that case.
678 Set<Element> rootElements = new LinkedHashSet<Element>();
679 rootElements.addAll(topLevelClasses);
680 rootElements.addAll(packageInfoFiles);
681 rootElements = Collections.unmodifiableSet(rootElements);
683 RoundEnvironment renv = new JavacRoundEnvironment(false,
684 false,
685 rootElements,
686 JavacProcessingEnvironment.this);
688 while(unmatchedAnnotations.size() > 0 && psi.hasNext() ) {
689 ProcessorState ps = psi.next();
690 Set<String> matchedNames = new HashSet<String>();
691 Set<TypeElement> typeElements = new LinkedHashSet<TypeElement>();
693 for (Map.Entry<String, TypeElement> entry: unmatchedAnnotations.entrySet()) {
694 String unmatchedAnnotationName = entry.getKey();
695 if (ps.annotationSupported(unmatchedAnnotationName) ) {
696 matchedNames.add(unmatchedAnnotationName);
697 TypeElement te = entry.getValue();
698 if (te != null)
699 typeElements.add(te);
700 }
701 }
703 if (matchedNames.size() > 0 || ps.contributed) {
704 foundTypeProcessors = foundTypeProcessors || (ps.processor instanceof AbstractTypeProcessor);
705 boolean processingResult = callProcessor(ps.processor, typeElements, renv);
706 ps.contributed = true;
707 ps.removeSupportedOptions(unmatchedProcessorOptions);
709 if (printProcessorInfo || verbose) {
710 log.printNoteLines("x.print.processor.info",
711 ps.processor.getClass().getName(),
712 matchedNames.toString(),
713 processingResult);
714 }
716 if (processingResult) {
717 unmatchedAnnotations.keySet().removeAll(matchedNames);
718 }
720 }
721 }
722 unmatchedAnnotations.remove("");
724 if (lint && unmatchedAnnotations.size() > 0) {
725 // Remove annotations processed by javac
726 unmatchedAnnotations.keySet().removeAll(platformAnnotations);
727 if (unmatchedAnnotations.size() > 0) {
728 log = Log.instance(context);
729 log.warning("proc.annotations.without.processors",
730 unmatchedAnnotations.keySet());
731 }
732 }
734 // Run contributing processors that haven't run yet
735 psi.runContributingProcs(renv);
737 // Debugging
738 if (options.get("displayFilerState") != null)
739 filer.displayState();
740 }
742 /**
743 * Computes the set of annotations on the symbol in question.
744 * Leave class public for external testing purposes.
745 */
746 public static class ComputeAnnotationSet extends
747 ElementScanner7<Set<TypeElement>, Set<TypeElement>> {
748 final Elements elements;
750 public ComputeAnnotationSet(Elements elements) {
751 super();
752 this.elements = elements;
753 }
755 @Override
756 public Set<TypeElement> visitPackage(PackageElement e, Set<TypeElement> p) {
757 // Don't scan enclosed elements of a package
758 return p;
759 }
761 @Override
762 public Set<TypeElement> scan(Element e, Set<TypeElement> p) {
763 for (AnnotationMirror annotationMirror :
764 elements.getAllAnnotationMirrors(e) ) {
765 Element e2 = annotationMirror.getAnnotationType().asElement();
766 p.add((TypeElement) e2);
767 }
768 return super.scan(e, p);
769 }
770 }
772 private boolean callProcessor(Processor proc,
773 Set<? extends TypeElement> tes,
774 RoundEnvironment renv) {
775 try {
776 return proc.process(tes, renv);
777 } catch (CompletionFailure ex) {
778 StringWriter out = new StringWriter();
779 ex.printStackTrace(new PrintWriter(out));
780 log.error("proc.cant.access", ex.sym, ex.getDetailValue(), out.toString());
781 return false;
782 } catch (Throwable t) {
783 throw new AnnotationProcessingError(t);
784 }
785 }
788 // TODO: internal catch clauses?; catch and rethrow an annotation
789 // processing error
790 public JavaCompiler doProcessing(Context context,
791 List<JCCompilationUnit> roots,
792 List<ClassSymbol> classSymbols,
793 Iterable<? extends PackageSymbol> pckSymbols)
794 throws IOException {
796 log = Log.instance(context);
797 TaskListener taskListener = context.get(TaskListener.class);
799 JavaCompiler compiler = JavaCompiler.instance(context);
800 compiler.todo.clear(); // free the compiler's resources
802 int round = 0;
804 // List<JCAnnotation> annotationsPresentInSource = collector.findAnnotations(roots);
805 List<ClassSymbol> topLevelClasses = getTopLevelClasses(roots);
807 for (ClassSymbol classSym : classSymbols)
808 topLevelClasses = topLevelClasses.prepend(classSym);
809 List<PackageSymbol> packageInfoFiles =
810 getPackageInfoFiles(roots);
812 Set<PackageSymbol> specifiedPackages = new LinkedHashSet<PackageSymbol>();
813 for (PackageSymbol psym : pckSymbols)
814 specifiedPackages.add(psym);
815 this.specifiedPackages = Collections.unmodifiableSet(specifiedPackages);
817 // Use annotation processing to compute the set of annotations present
818 Set<TypeElement> annotationsPresent = new LinkedHashSet<TypeElement>();
819 ComputeAnnotationSet annotationComputer = new ComputeAnnotationSet(elementUtils);
820 for (ClassSymbol classSym : topLevelClasses)
821 annotationComputer.scan(classSym, annotationsPresent);
822 for (PackageSymbol pkgSym : packageInfoFiles)
823 annotationComputer.scan(pkgSym, annotationsPresent);
825 Context currentContext = context;
827 int roundNumber = 0;
828 boolean errorStatus = false;
830 runAround:
831 while(true) {
832 if ((fatalErrors && compiler.errorCount() != 0)
833 || (werror && compiler.warningCount() != 0)) {
834 errorStatus = true;
835 break runAround;
836 }
838 this.context = currentContext;
839 roundNumber++;
840 printRoundInfo(roundNumber, topLevelClasses, annotationsPresent, false);
842 if (taskListener != null)
843 taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
845 try {
846 discoverAndRunProcs(currentContext, annotationsPresent, topLevelClasses, packageInfoFiles);
847 } finally {
848 if (taskListener != null)
849 taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
850 }
852 /*
853 * Processors for round n have run to completion. Prepare
854 * for round (n+1) by checked for errors raised by
855 * annotation processors and then checking for syntax
856 * errors on any generated source files.
857 */
858 if (messager.errorRaised()) {
859 errorStatus = true;
860 break runAround;
861 } else {
862 if (moreToDo()) {
863 // annotationsPresentInSource = List.nil();
864 annotationsPresent = new LinkedHashSet<TypeElement>();
865 topLevelClasses = List.nil();
866 packageInfoFiles = List.nil();
868 compiler.close(false);
869 currentContext = contextForNextRound(currentContext, true);
871 JavaFileManager fileManager = currentContext.get(JavaFileManager.class);
873 compiler = JavaCompiler.instance(currentContext);
874 List<JCCompilationUnit> parsedFiles = sourcesToParsedFiles(compiler);
875 roots = cleanTrees(roots).appendList(parsedFiles);
877 // Check for errors after parsing
878 if (log.unrecoverableError) {
879 errorStatus = true;
880 break runAround;
881 } else {
882 List<ClassSymbol> newClasses = enterNewClassFiles(currentContext);
883 compiler.enterTrees(roots);
885 // annotationsPresentInSource =
886 // collector.findAnnotations(parsedFiles);
887 ListBuffer<ClassSymbol> tlc = new ListBuffer<ClassSymbol>();
888 tlc.appendList(getTopLevelClasses(parsedFiles));
889 tlc.appendList(getTopLevelClassesFromClasses(newClasses));
890 topLevelClasses = tlc.toList();
892 ListBuffer<PackageSymbol> pif = new ListBuffer<PackageSymbol>();
893 pif.appendList(getPackageInfoFiles(parsedFiles));
894 pif.appendList(getPackageInfoFilesFromClasses(newClasses));
895 packageInfoFiles = pif.toList();
897 annotationsPresent = new LinkedHashSet<TypeElement>();
898 for (ClassSymbol classSym : topLevelClasses)
899 annotationComputer.scan(classSym, annotationsPresent);
900 for (PackageSymbol pkgSym : packageInfoFiles)
901 annotationComputer.scan(pkgSym, annotationsPresent);
903 updateProcessingState(currentContext, false);
904 }
905 } else
906 break runAround; // No new files
907 }
908 }
909 roots = runLastRound(roundNumber, errorStatus, compiler, roots, taskListener);
910 // Set error status for any files compiled and generated in
911 // the last round
912 if (log.unrecoverableError || (werror && compiler.warningCount() != 0))
913 errorStatus = true;
915 compiler.close(false);
916 currentContext = contextForNextRound(currentContext, true);
917 compiler = JavaCompiler.instance(currentContext);
919 filer.newRound(currentContext, true);
920 filer.warnIfUnclosedFiles();
921 warnIfUnmatchedOptions();
923 /*
924 * If an annotation processor raises an error in a round,
925 * that round runs to completion and one last round occurs.
926 * The last round may also occur because no more source or
927 * class files have been generated. Therefore, if an error
928 * was raised on either of the last *two* rounds, the compile
929 * should exit with a nonzero exit code. The current value of
930 * errorStatus holds whether or not an error was raised on the
931 * second to last round; errorRaised() gives the error status
932 * of the last round.
933 */
934 errorStatus = errorStatus || messager.errorRaised();
937 // Free resources
938 this.close();
940 if (taskListener != null)
941 taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING));
943 if (errorStatus) {
944 compiler.log.nwarnings += messager.warningCount();
945 compiler.log.nerrors += messager.errorCount();
946 if (compiler.errorCount() == 0)
947 compiler.log.nerrors++;
948 } else if (procOnly && !foundTypeProcessors) {
949 compiler.todo.clear();
950 } else { // Final compilation
951 compiler.close(false);
952 currentContext = contextForNextRound(currentContext, true);
953 this.context = currentContext;
954 updateProcessingState(currentContext, true);
955 compiler = JavaCompiler.instance(currentContext);
956 if (procOnly && foundTypeProcessors)
957 compiler.shouldStopPolicy = CompileState.FLOW;
959 if (true) {
960 compiler.enterTrees(cleanTrees(roots));
961 } else {
962 List<JavaFileObject> fileObjects = List.nil();
963 for (JCCompilationUnit unit : roots)
964 fileObjects = fileObjects.prepend(unit.getSourceFile());
965 roots = null;
966 compiler.enterTrees(compiler.parseFiles(fileObjects.reverse()));
967 }
968 }
970 return compiler;
971 }
973 private List<JCCompilationUnit> sourcesToParsedFiles(JavaCompiler compiler)
974 throws IOException {
975 List<JavaFileObject> fileObjects = List.nil();
976 for (JavaFileObject jfo : filer.getGeneratedSourceFileObjects() ) {
977 fileObjects = fileObjects.prepend(jfo);
978 }
980 return compiler.parseFiles(fileObjects);
981 }
983 // Call the last round of annotation processing
984 private List<JCCompilationUnit> runLastRound(int roundNumber,
985 boolean errorStatus,
986 JavaCompiler compiler,
987 List<JCCompilationUnit> roots,
988 TaskListener taskListener) throws IOException {
989 roundNumber++;
990 List<ClassSymbol> noTopLevelClasses = List.nil();
991 Set<TypeElement> noAnnotations = Collections.emptySet();
992 printRoundInfo(roundNumber, noTopLevelClasses, noAnnotations, true);
994 Set<Element> emptyRootElements = Collections.emptySet(); // immutable
995 RoundEnvironment renv = new JavacRoundEnvironment(true,
996 errorStatus,
997 emptyRootElements,
998 JavacProcessingEnvironment.this);
999 if (taskListener != null)
1000 taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
1002 try {
1003 discoveredProcs.iterator().runContributingProcs(renv);
1004 } finally {
1005 if (taskListener != null)
1006 taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
1007 }
1009 // Add any sources generated during the last round to the set
1010 // of files to be compiled.
1011 if (moreToDo()) {
1012 List<JCCompilationUnit> parsedFiles = sourcesToParsedFiles(compiler);
1013 roots = cleanTrees(roots).appendList(parsedFiles);
1014 }
1016 return roots;
1017 }
1019 private void updateProcessingState(Context currentContext, boolean lastRound) {
1020 filer.newRound(currentContext, lastRound);
1021 messager.newRound(currentContext);
1023 elementUtils.setContext(currentContext);
1024 typeUtils.setContext(currentContext);
1025 }
1027 private void warnIfUnmatchedOptions() {
1028 if (!unmatchedProcessorOptions.isEmpty()) {
1029 log.warning("proc.unmatched.processor.options", unmatchedProcessorOptions.toString());
1030 }
1031 }
1033 private void printRoundInfo(int roundNumber,
1034 List<ClassSymbol> topLevelClasses,
1035 Set<TypeElement> annotationsPresent,
1036 boolean lastRound) {
1037 if (printRounds || verbose) {
1038 log.printNoteLines("x.print.rounds",
1039 roundNumber,
1040 "{" + topLevelClasses.toString(", ") + "}",
1041 annotationsPresent,
1042 lastRound);
1043 }
1044 }
1046 private List<ClassSymbol> enterNewClassFiles(Context currentContext) {
1047 ClassReader reader = ClassReader.instance(currentContext);
1048 Names names = Names.instance(currentContext);
1049 List<ClassSymbol> list = List.nil();
1051 for (Map.Entry<String,JavaFileObject> entry : filer.getGeneratedClasses().entrySet()) {
1052 Name name = names.fromString(entry.getKey());
1053 JavaFileObject file = entry.getValue();
1054 if (file.getKind() != JavaFileObject.Kind.CLASS)
1055 throw new AssertionError(file);
1056 ClassSymbol cs;
1057 if (isPkgInfo(file, JavaFileObject.Kind.CLASS)) {
1058 Name packageName = Convert.packagePart(name);
1059 PackageSymbol p = reader.enterPackage(packageName);
1060 if (p.package_info == null)
1061 p.package_info = reader.enterClass(Convert.shortName(name), p);
1062 cs = p.package_info;
1063 if (cs.classfile == null)
1064 cs.classfile = file;
1065 } else
1066 cs = reader.enterClass(name, file);
1067 list = list.prepend(cs);
1068 }
1069 return list.reverse();
1070 }
1072 /**
1073 * Free resources related to annotation processing.
1074 */
1075 public void close() throws IOException {
1076 filer.close();
1077 if (discoveredProcs != null) // Make calling close idempotent
1078 discoveredProcs.close();
1079 discoveredProcs = null;
1080 if (processorClassLoader != null && processorClassLoader instanceof Closeable)
1081 ((Closeable) processorClassLoader).close();
1082 }
1084 private List<ClassSymbol> getTopLevelClasses(List<? extends JCCompilationUnit> units) {
1085 List<ClassSymbol> classes = List.nil();
1086 for (JCCompilationUnit unit : units) {
1087 for (JCTree node : unit.defs) {
1088 if (node.getTag() == JCTree.CLASSDEF) {
1089 classes = classes.prepend(((JCClassDecl) node).sym);
1090 }
1091 }
1092 }
1093 return classes.reverse();
1094 }
1096 private List<ClassSymbol> getTopLevelClassesFromClasses(List<? extends ClassSymbol> syms) {
1097 List<ClassSymbol> classes = List.nil();
1098 for (ClassSymbol sym : syms) {
1099 if (!isPkgInfo(sym)) {
1100 classes = classes.prepend(sym);
1101 }
1102 }
1103 return classes.reverse();
1104 }
1106 private List<PackageSymbol> getPackageInfoFiles(List<? extends JCCompilationUnit> units) {
1107 List<PackageSymbol> packages = List.nil();
1108 for (JCCompilationUnit unit : units) {
1109 if (isPkgInfo(unit.sourcefile, JavaFileObject.Kind.SOURCE)) {
1110 packages = packages.prepend(unit.packge);
1111 }
1112 }
1113 return packages.reverse();
1114 }
1116 private List<PackageSymbol> getPackageInfoFilesFromClasses(List<? extends ClassSymbol> syms) {
1117 List<PackageSymbol> packages = List.nil();
1118 for (ClassSymbol sym : syms) {
1119 if (isPkgInfo(sym)) {
1120 packages = packages.prepend((PackageSymbol) sym.owner);
1121 }
1122 }
1123 return packages.reverse();
1124 }
1126 private boolean isPkgInfo(JavaFileObject fo, JavaFileObject.Kind kind) {
1127 return fo.isNameCompatible("package-info", kind);
1128 }
1130 private boolean isPkgInfo(ClassSymbol sym) {
1131 return isPkgInfo(sym.classfile, JavaFileObject.Kind.CLASS) && (sym.packge().package_info == sym);
1132 }
1134 private Context contextForNextRound(Context context, boolean shareNames)
1135 throws IOException
1136 {
1137 Context next = new Context();
1139 Options options = Options.instance(context);
1140 assert options != null;
1141 next.put(Options.optionsKey, options);
1143 PrintWriter out = context.get(Log.outKey);
1144 assert out != null;
1145 next.put(Log.outKey, out);
1147 if (shareNames) {
1148 Names names = Names.instance(context);
1149 assert names != null;
1150 next.put(Names.namesKey, names);
1151 }
1153 DiagnosticListener<?> dl = context.get(DiagnosticListener.class);
1154 if (dl != null)
1155 next.put(DiagnosticListener.class, dl);
1157 TaskListener tl = context.get(TaskListener.class);
1158 if (tl != null)
1159 next.put(TaskListener.class, tl);
1161 JavaFileManager jfm = context.get(JavaFileManager.class);
1162 assert jfm != null;
1163 next.put(JavaFileManager.class, jfm);
1164 if (jfm instanceof JavacFileManager) {
1165 ((JavacFileManager)jfm).setContext(next);
1166 }
1168 Names names = Names.instance(context);
1169 assert names != null;
1170 next.put(Names.namesKey, names);
1172 Keywords keywords = Keywords.instance(context);
1173 assert(keywords != null);
1174 next.put(Keywords.keywordsKey, keywords);
1176 JavaCompiler oldCompiler = JavaCompiler.instance(context);
1177 JavaCompiler nextCompiler = JavaCompiler.instance(next);
1178 nextCompiler.initRound(oldCompiler);
1180 JavacTaskImpl task = context.get(JavacTaskImpl.class);
1181 if (task != null) {
1182 next.put(JavacTaskImpl.class, task);
1183 task.updateContext(next);
1184 }
1186 context.clear();
1187 return next;
1188 }
1190 /*
1191 * Called retroactively to determine if a class loader was required,
1192 * after we have failed to create one.
1193 */
1194 private boolean needClassLoader(String procNames, Iterable<? extends File> workingpath) {
1195 if (procNames != null)
1196 return true;
1198 String procPath;
1199 URL[] urls = new URL[1];
1200 for(File pathElement : workingpath) {
1201 try {
1202 urls[0] = pathElement.toURI().toURL();
1203 if (ServiceProxy.hasService(Processor.class, urls))
1204 return true;
1205 } catch (MalformedURLException ex) {
1206 throw new AssertionError(ex);
1207 }
1208 catch (ServiceProxy.ServiceConfigurationError e) {
1209 log.error("proc.bad.config.file", e.getLocalizedMessage());
1210 return true;
1211 }
1212 }
1214 return false;
1215 }
1217 private static <T extends JCTree> List<T> cleanTrees(List<T> nodes) {
1218 for (T node : nodes)
1219 treeCleaner.scan(node);
1220 return nodes;
1221 }
1223 private static TreeScanner treeCleaner = new TreeScanner() {
1224 public void scan(JCTree node) {
1225 super.scan(node);
1226 if (node != null)
1227 node.type = null;
1228 }
1229 public void visitTopLevel(JCCompilationUnit node) {
1230 node.packge = null;
1231 super.visitTopLevel(node);
1232 }
1233 public void visitClassDef(JCClassDecl node) {
1234 node.sym = null;
1235 super.visitClassDef(node);
1236 }
1237 public void visitMethodDef(JCMethodDecl node) {
1238 node.sym = null;
1239 super.visitMethodDef(node);
1240 }
1241 public void visitVarDef(JCVariableDecl node) {
1242 node.sym = null;
1243 super.visitVarDef(node);
1244 }
1245 public void visitNewClass(JCNewClass node) {
1246 node.constructor = null;
1247 super.visitNewClass(node);
1248 }
1249 public void visitAssignop(JCAssignOp node) {
1250 node.operator = null;
1251 super.visitAssignop(node);
1252 }
1253 public void visitUnary(JCUnary node) {
1254 node.operator = null;
1255 super.visitUnary(node);
1256 }
1257 public void visitBinary(JCBinary node) {
1258 node.operator = null;
1259 super.visitBinary(node);
1260 }
1261 public void visitSelect(JCFieldAccess node) {
1262 node.sym = null;
1263 super.visitSelect(node);
1264 }
1265 public void visitIdent(JCIdent node) {
1266 node.sym = null;
1267 super.visitIdent(node);
1268 }
1269 };
1272 private boolean moreToDo() {
1273 return filer.newFiles();
1274 }
1276 /**
1277 * {@inheritdoc}
1278 *
1279 * Command line options suitable for presenting to annotation
1280 * processors. "-Afoo=bar" should be "-Afoo" => "bar".
1281 */
1282 public Map<String,String> getOptions() {
1283 return processorOptions;
1284 }
1286 public Messager getMessager() {
1287 return messager;
1288 }
1290 public Filer getFiler() {
1291 return filer;
1292 }
1294 public JavacElements getElementUtils() {
1295 return elementUtils;
1296 }
1298 public JavacTypes getTypeUtils() {
1299 return typeUtils;
1300 }
1302 public SourceVersion getSourceVersion() {
1303 return Source.toSourceVersion(source);
1304 }
1306 public Locale getLocale() {
1307 return messages.getCurrentLocale();
1308 }
1310 public Set<Symbol.PackageSymbol> getSpecifiedPackages() {
1311 return specifiedPackages;
1312 }
1314 private static final Pattern allMatches = Pattern.compile(".*");
1315 public static final Pattern noMatches = Pattern.compile("(\\P{all})+");
1317 /**
1318 * Convert import-style string for supported annotations into a
1319 * regex matching that string. If the string is a valid
1320 * import-style string, return a regex that won't match anything.
1321 */
1322 private static Pattern importStringToPattern(String s, Processor p, Log log) {
1323 if (isValidImportString(s)) {
1324 return validImportStringToPattern(s);
1325 } else {
1326 log.warning("proc.malformed.supported.string", s, p.getClass().getName());
1327 return noMatches; // won't match any valid identifier
1328 }
1329 }
1331 /**
1332 * Return true if the argument string is a valid import-style
1333 * string specifying claimed annotations; return false otherwise.
1334 */
1335 public static boolean isValidImportString(String s) {
1336 if (s.equals("*"))
1337 return true;
1339 boolean valid = true;
1340 String t = s;
1341 int index = t.indexOf('*');
1343 if (index != -1) {
1344 // '*' must be last character...
1345 if (index == t.length() -1) {
1346 // ... any and preceding character must be '.'
1347 if ( index-1 >= 0 ) {
1348 valid = t.charAt(index-1) == '.';
1349 // Strip off ".*$" for identifier checks
1350 t = t.substring(0, t.length()-2);
1351 }
1352 } else
1353 return false;
1354 }
1356 // Verify string is off the form (javaId \.)+ or javaId
1357 if (valid) {
1358 String[] javaIds = t.split("\\.", t.length()+2);
1359 for(String javaId: javaIds)
1360 valid &= SourceVersion.isIdentifier(javaId);
1361 }
1362 return valid;
1363 }
1365 public static Pattern validImportStringToPattern(String s) {
1366 if (s.equals("*")) {
1367 return allMatches;
1368 } else {
1369 String s_prime = s.replace(".", "\\.");
1371 if (s_prime.endsWith("*")) {
1372 s_prime = s_prime.substring(0, s_prime.length() - 1) + ".+";
1373 }
1375 return Pattern.compile(s_prime);
1376 }
1377 }
1379 /**
1380 * For internal use only. This method will be
1381 * removed without warning.
1382 */
1383 public Context getContext() {
1384 return context;
1385 }
1387 public String toString() {
1388 return "javac ProcessingEnvironment";
1389 }
1391 public static boolean isValidOptionName(String optionName) {
1392 for(String s : optionName.split("\\.", -1)) {
1393 if (!SourceVersion.isIdentifier(s))
1394 return false;
1395 }
1396 return true;
1397 }
1398 }