Thu, 24 Jul 2008 19:06:57 +0100
6717241: some diagnostic argument is prematurely converted into a String object
Summary: removed early toString() conversions applied to diagnostic arguments
Reviewed-by: jjg
1 /*
2 * Copyright 2005-2008 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
26 package com.sun.tools.javac.processing;
29 import java.lang.reflect.*;
30 import java.util.*;
31 import java.util.regex.*;
33 import java.net.URL;
34 import java.io.Closeable;
35 import java.io.File;
36 import java.io.PrintWriter;
37 import java.io.IOException;
38 import java.net.MalformedURLException;
39 import java.io.StringWriter;
41 import javax.annotation.processing.*;
42 import javax.lang.model.SourceVersion;
43 import javax.lang.model.element.AnnotationMirror;
44 import javax.lang.model.element.Element;
45 import javax.lang.model.element.TypeElement;
46 import javax.lang.model.element.PackageElement;
47 import javax.lang.model.util.*;
48 import javax.tools.JavaFileManager;
49 import javax.tools.StandardJavaFileManager;
50 import javax.tools.JavaFileObject;
51 import javax.tools.DiagnosticListener;
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.Paths;
59 import com.sun.tools.javac.file.JavacFileManager;
60 import com.sun.tools.javac.jvm.*;
61 import com.sun.tools.javac.main.JavaCompiler;
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.List;
70 import com.sun.tools.javac.util.ListBuffer;
71 import com.sun.tools.javac.util.Log;
72 import com.sun.tools.javac.util.Name;
73 import com.sun.tools.javac.util.Options;
75 import static javax.tools.StandardLocation.*;
77 /**
78 * Objects of this class hold and manage the state needed to support
79 * annotation processing.
80 *
81 * <p><b>This is NOT part of any API supported by Sun Microsystems.
82 * If you write code that depends on this, you do so at your own risk.
83 * This code and its internal interfaces are subject to change or
84 * deletion without notice.</b>
85 */
86 public class JavacProcessingEnvironment implements ProcessingEnvironment, Closeable {
87 Options options;
89 private final boolean printProcessorInfo;
90 private final boolean printRounds;
91 private final boolean verbose;
92 private final boolean lint;
93 private final boolean procOnly;
94 private final boolean fatalErrors;
96 private final JavacFiler filer;
97 private final JavacMessager messager;
98 private final JavacElements elementUtils;
99 private final JavacTypes typeUtils;
101 /**
102 * Holds relevant state history of which processors have been
103 * used.
104 */
105 private DiscoveredProcessors discoveredProcs;
107 /**
108 * Map of processor-specific options.
109 */
110 private final Map<String, String> processorOptions;
112 /**
113 */
114 private final Set<String> unmatchedProcessorOptions;
116 /**
117 * Annotations implicitly processed and claimed by javac.
118 */
119 private final Set<String> platformAnnotations;
121 /**
122 * Set of packages given on command line.
123 */
124 private Set<PackageSymbol> specifiedPackages = Collections.emptySet();
126 /** The log to be used for error reporting.
127 */
128 Log log;
130 /**
131 * Source level of the compile.
132 */
133 Source source;
135 private Context context;
137 public JavacProcessingEnvironment(Context context, Iterable<? extends Processor> processors) {
138 options = Options.instance(context);
139 this.context = context;
140 log = Log.instance(context);
141 source = Source.instance(context);
142 printProcessorInfo = options.get("-XprintProcessorInfo") != null;
143 printRounds = options.get("-XprintRounds") != null;
144 verbose = options.get("-verbose") != null;
145 lint = options.lint("processing");
146 procOnly = options.get("-proc:only") != null ||
147 options.get("-Xprint") != null;
148 fatalErrors = options.get("fatalEnterError") != null;
149 platformAnnotations = initPlatformAnnotations();
151 // Initialize services before any processors are initialzied
152 // in case processors use them.
153 filer = new JavacFiler(context);
154 messager = new JavacMessager(context, this);
155 elementUtils = new JavacElements(context);
156 typeUtils = new JavacTypes(context);
157 processorOptions = initProcessorOptions(context);
158 unmatchedProcessorOptions = initUnmatchedProcessorOptions();
159 initProcessorIterator(context, processors);
160 }
162 private Set<String> initPlatformAnnotations() {
163 Set<String> platformAnnotations = new HashSet<String>();
164 platformAnnotations.add("java.lang.Deprecated");
165 platformAnnotations.add("java.lang.Override");
166 platformAnnotations.add("java.lang.SuppressWarnings");
167 platformAnnotations.add("java.lang.annotation.Documented");
168 platformAnnotations.add("java.lang.annotation.Inherited");
169 platformAnnotations.add("java.lang.annotation.Retention");
170 platformAnnotations.add("java.lang.annotation.Target");
171 return Collections.unmodifiableSet(platformAnnotations);
172 }
174 private void initProcessorIterator(Context context, Iterable<? extends Processor> processors) {
175 Paths paths = Paths.instance(context);
176 Log log = Log.instance(context);
177 Iterator<? extends Processor> processorIterator;
179 if (options.get("-Xprint") != null) {
180 try {
181 Processor processor = PrintingProcessor.class.newInstance();
182 processorIterator = List.of(processor).iterator();
183 } catch (Throwable t) {
184 AssertionError assertError =
185 new AssertionError("Problem instantiating PrintingProcessor.");
186 assertError.initCause(t);
187 throw assertError;
188 }
189 } else if (processors != null) {
190 processorIterator = processors.iterator();
191 } else {
192 String processorNames = options.get("-processor");
193 JavaFileManager fileManager = context.get(JavaFileManager.class);
194 try {
195 // If processorpath is not explicitly set, use the classpath.
196 ClassLoader processorCL = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
197 ? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH)
198 : fileManager.getClassLoader(CLASS_PATH);
200 /*
201 * If the "-processor" option is used, search the appropriate
202 * path for the named class. Otherwise, use a service
203 * provider mechanism to create the processor iterator.
204 */
205 if (processorNames != null) {
206 processorIterator = new NameProcessIterator(processorNames, processorCL, log);
207 } else {
208 processorIterator = new ServiceIterator(processorCL, log);
209 }
210 } catch (SecurityException e) {
211 /*
212 * A security exception will occur if we can't create a classloader.
213 * Ignore the exception if, with hindsight, we didn't need it anyway
214 * (i.e. no processor was specified either explicitly, or implicitly,
215 * in service configuration file.) Otherwise, we cannot continue.
216 */
217 processorIterator = handleServiceLoaderUnavailability("proc.cant.create.loader", e);
218 }
219 }
220 discoveredProcs = new DiscoveredProcessors(processorIterator);
221 }
223 /**
224 * Returns an empty processor iterator if no processors are on the
225 * relevant path, otherwise if processors are present, logs an
226 * error. Called when a service loader is unavailable for some
227 * reason, either because a service loader class cannot be found
228 * or because a security policy prevents class loaders from being
229 * created.
230 *
231 * @param key The resource key to use to log an error message
232 * @param e If non-null, pass this exception to Abort
233 */
234 private Iterator<Processor> handleServiceLoaderUnavailability(String key, Exception e) {
235 JavaFileManager fileManager = context.get(JavaFileManager.class);
237 if (fileManager instanceof JavacFileManager) {
238 StandardJavaFileManager standardFileManager = (JavacFileManager) fileManager;
239 Iterable<? extends File> workingPath = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
240 ? standardFileManager.getLocation(ANNOTATION_PROCESSOR_PATH)
241 : standardFileManager.getLocation(CLASS_PATH);
243 if (needClassLoader(options.get("-processor"), workingPath) )
244 handleException(key, e);
246 } else {
247 handleException(key, e);
248 }
250 java.util.List<Processor> pl = Collections.emptyList();
251 return pl.iterator();
252 }
254 /**
255 * Handle a security exception thrown during initializing the
256 * Processor iterator.
257 */
258 private void handleException(String key, Exception e) {
259 if (e != null) {
260 log.error(key, e.getLocalizedMessage());
261 throw new Abort(e);
262 } else {
263 log.error(key);
264 throw new Abort();
265 }
266 }
268 /**
269 * Use a service loader appropriate for the platform to provide an
270 * iterator over annotations processors. If
271 * java.util.ServiceLoader is present use it, otherwise, use
272 * sun.misc.Service, otherwise fail if a loader is needed.
273 */
274 private class ServiceIterator implements Iterator<Processor> {
275 // The to-be-wrapped iterator.
276 private Iterator<?> iterator;
277 private Log log;
279 ServiceIterator(ClassLoader classLoader, Log log) {
280 Class<?> loaderClass;
281 String loadMethodName;
282 boolean jusl;
284 this.log = log;
285 try {
286 try {
287 loaderClass = Class.forName("java.util.ServiceLoader");
288 loadMethodName = "load";
289 jusl = true;
290 } catch (ClassNotFoundException cnfe) {
291 try {
292 loaderClass = Class.forName("sun.misc.Service");
293 loadMethodName = "providers";
294 jusl = false;
295 } catch (ClassNotFoundException cnfe2) {
296 // Fail softly if a loader is not actually needed.
297 this.iterator = handleServiceLoaderUnavailability("proc.no.service",
298 null);
299 return;
300 }
301 }
303 // java.util.ServiceLoader.load or sun.misc.Service.providers
304 Method loadMethod = loaderClass.getMethod(loadMethodName,
305 Class.class,
306 ClassLoader.class);
308 Object result = loadMethod.invoke(null,
309 Processor.class,
310 classLoader);
312 // For java.util.ServiceLoader, we have to call another
313 // method to get the iterator.
314 if (jusl) {
315 Method m = loaderClass.getMethod("iterator");
316 result = m.invoke(result); // serviceLoader.iterator();
317 }
319 // The result should now be an iterator.
320 this.iterator = (Iterator<?>) result;
321 } catch (Throwable t) {
322 log.error("proc.service.problem");
323 throw new Abort(t);
324 }
325 }
327 public boolean hasNext() {
328 try {
329 return iterator.hasNext();
330 } catch (Throwable t) {
331 if ("ServiceConfigurationError".
332 equals(t.getClass().getSimpleName())) {
333 log.error("proc.bad.config.file", t.getLocalizedMessage());
334 }
335 throw new Abort(t);
336 }
337 }
339 public Processor next() {
340 try {
341 return (Processor)(iterator.next());
342 } catch (Throwable t) {
343 if ("ServiceConfigurationError".
344 equals(t.getClass().getSimpleName())) {
345 log.error("proc.bad.config.file", t.getLocalizedMessage());
346 } else {
347 log.error("proc.processor.constructor.error", t.getLocalizedMessage());
348 }
349 throw new Abort(t);
350 }
351 }
353 public void remove() {
354 throw new UnsupportedOperationException();
355 }
356 }
359 private static class NameProcessIterator implements Iterator<Processor> {
360 Processor nextProc = null;
361 Iterator<String> names;
362 ClassLoader processorCL;
363 Log log;
365 NameProcessIterator(String names, ClassLoader processorCL, Log log) {
366 this.names = Arrays.asList(names.split(",")).iterator();
367 this.processorCL = processorCL;
368 this.log = log;
369 }
371 public boolean hasNext() {
372 if (nextProc != null)
373 return true;
374 else {
375 if (!names.hasNext())
376 return false;
377 else {
378 String processorName = names.next();
380 Processor processor;
381 try {
382 try {
383 processor =
384 (Processor) (processorCL.loadClass(processorName).newInstance());
385 } catch (ClassNotFoundException cnfe) {
386 log.error("proc.processor.not.found", processorName);
387 return false;
388 } catch (ClassCastException cce) {
389 log.error("proc.processor.wrong.type", processorName);
390 return false;
391 } catch (Exception e ) {
392 log.error("proc.processor.cant.instantiate", processorName);
393 return false;
394 }
395 } catch(Throwable t) {
396 throw new AnnotationProcessingError(t);
397 }
398 nextProc = processor;
399 return true;
400 }
402 }
403 }
405 public Processor next() {
406 if (hasNext()) {
407 Processor p = nextProc;
408 nextProc = null;
409 return p;
410 } else
411 throw new NoSuchElementException();
412 }
414 public void remove () {
415 throw new UnsupportedOperationException();
416 }
417 }
419 public boolean atLeastOneProcessor() {
420 return discoveredProcs.iterator().hasNext();
421 }
423 private Map<String, String> initProcessorOptions(Context context) {
424 Options options = Options.instance(context);
425 Set<String> keySet = options.keySet();
426 Map<String, String> tempOptions = new LinkedHashMap<String, String>();
428 for(String key : keySet) {
429 if (key.startsWith("-A") && key.length() > 2) {
430 int sepIndex = key.indexOf('=');
431 String candidateKey = null;
432 String candidateValue = null;
434 if (sepIndex == -1)
435 candidateKey = key.substring(2);
436 else if (sepIndex >= 3) {
437 candidateKey = key.substring(2, sepIndex);
438 candidateValue = (sepIndex < key.length()-1)?
439 key.substring(sepIndex+1) : null;
440 }
441 tempOptions.put(candidateKey, candidateValue);
442 }
443 }
445 return Collections.unmodifiableMap(tempOptions);
446 }
448 private Set<String> initUnmatchedProcessorOptions() {
449 Set<String> unmatchedProcessorOptions = new HashSet<String>();
450 unmatchedProcessorOptions.addAll(processorOptions.keySet());
451 return unmatchedProcessorOptions;
452 }
454 /**
455 * State about how a processor has been used by the tool. If a
456 * processor has been used on a prior round, its process method is
457 * called on all subsequent rounds, perhaps with an empty set of
458 * annotations to process. The {@code annotatedSupported} method
459 * caches the supported annotation information from the first (and
460 * only) getSupportedAnnotationTypes call to the processor.
461 */
462 static class ProcessorState {
463 public Processor processor;
464 public boolean contributed;
465 private ArrayList<Pattern> supportedAnnotationPatterns;
466 private ArrayList<String> supportedOptionNames;
468 ProcessorState(Processor p, Log log, Source source, ProcessingEnvironment env) {
469 processor = p;
470 contributed = false;
472 try {
473 processor.init(env);
475 checkSourceVersionCompatibility(source, log);
477 supportedAnnotationPatterns = new ArrayList<Pattern>();
478 for (String importString : processor.getSupportedAnnotationTypes()) {
479 supportedAnnotationPatterns.add(importStringToPattern(importString,
480 processor,
481 log));
482 }
484 supportedOptionNames = new ArrayList<String>();
485 for (String optionName : processor.getSupportedOptions() ) {
486 if (checkOptionName(optionName, log))
487 supportedOptionNames.add(optionName);
488 }
490 } catch (Throwable t) {
491 throw new AnnotationProcessingError(t);
492 }
493 }
495 /**
496 * Checks whether or not a processor's source version is
497 * compatible with the compilation source version. The
498 * processor's source version needs to be greater than or
499 * equal to the source version of the compile.
500 */
501 private void checkSourceVersionCompatibility(Source source, Log log) {
502 SourceVersion procSourceVersion = processor.getSupportedSourceVersion();
504 if (procSourceVersion.compareTo(Source.toSourceVersion(source)) < 0 ) {
505 log.warning("proc.processor.incompatible.source.version",
506 procSourceVersion,
507 processor.getClass().getName(),
508 source.name);
509 }
510 }
512 private boolean checkOptionName(String optionName, Log log) {
513 boolean valid = isValidOptionName(optionName);
514 if (!valid)
515 log.error("proc.processor.bad.option.name",
516 optionName,
517 processor.getClass().getName());
518 return valid;
519 }
521 public boolean annotationSupported(String annotationName) {
522 for(Pattern p: supportedAnnotationPatterns) {
523 if (p.matcher(annotationName).matches())
524 return true;
525 }
526 return false;
527 }
529 /**
530 * Remove options that are matched by this processor.
531 */
532 public void removeSupportedOptions(Set<String> unmatchedProcessorOptions) {
533 unmatchedProcessorOptions.removeAll(supportedOptionNames);
534 }
535 }
537 // TODO: These two classes can probably be rewritten better...
538 /**
539 * This class holds information about the processors that have
540 * been discoverd so far as well as the means to discover more, if
541 * necessary. A single iterator should be used per round of
542 * annotation processing. The iterator first visits already
543 * discovered processors then fails over to the service provided
544 * mechanism if additional queries are made.
545 */
546 class DiscoveredProcessors implements Iterable<ProcessorState> {
548 class ProcessorStateIterator implements Iterator<ProcessorState> {
549 DiscoveredProcessors psi;
550 Iterator<ProcessorState> innerIter;
551 boolean onProcInterator;
553 ProcessorStateIterator(DiscoveredProcessors psi) {
554 this.psi = psi;
555 this.innerIter = psi.procStateList.iterator();
556 this.onProcInterator = false;
557 }
559 public ProcessorState next() {
560 if (!onProcInterator) {
561 if (innerIter.hasNext())
562 return innerIter.next();
563 else
564 onProcInterator = true;
565 }
567 if (psi.processorIterator.hasNext()) {
568 ProcessorState ps = new ProcessorState(psi.processorIterator.next(),
569 log, source, JavacProcessingEnvironment.this);
570 psi.procStateList.add(ps);
571 return ps;
572 } else
573 throw new NoSuchElementException();
574 }
576 public boolean hasNext() {
577 if (onProcInterator)
578 return psi.processorIterator.hasNext();
579 else
580 return innerIter.hasNext() || psi.processorIterator.hasNext();
581 }
583 public void remove () {
584 throw new UnsupportedOperationException();
585 }
587 /**
588 * Run all remaining processors on the procStateList that
589 * have not already run this round with an empty set of
590 * annotations.
591 */
592 public void runContributingProcs(RoundEnvironment re) {
593 if (!onProcInterator) {
594 Set<TypeElement> emptyTypeElements = Collections.emptySet();
595 while(innerIter.hasNext()) {
596 ProcessorState ps = innerIter.next();
597 if (ps.contributed)
598 callProcessor(ps.processor, emptyTypeElements, re);
599 }
600 }
601 }
602 }
604 Iterator<? extends Processor> processorIterator;
605 ArrayList<ProcessorState> procStateList;
607 public ProcessorStateIterator iterator() {
608 return new ProcessorStateIterator(this);
609 }
611 DiscoveredProcessors(Iterator<? extends Processor> processorIterator) {
612 this.processorIterator = processorIterator;
613 this.procStateList = new ArrayList<ProcessorState>();
614 }
615 }
617 private void discoverAndRunProcs(Context context,
618 Set<TypeElement> annotationsPresent,
619 List<ClassSymbol> topLevelClasses,
620 List<PackageSymbol> packageInfoFiles) {
621 // Writer for -XprintRounds and -XprintProcessorInfo data
622 PrintWriter xout = context.get(Log.outKey);
624 Map<String, TypeElement> unmatchedAnnotations =
625 new HashMap<String, TypeElement>(annotationsPresent.size());
627 for(TypeElement a : annotationsPresent) {
628 unmatchedAnnotations.put(a.getQualifiedName().toString(),
629 a);
630 }
632 // Give "*" processors a chance to match
633 if (unmatchedAnnotations.size() == 0)
634 unmatchedAnnotations.put("", null);
636 DiscoveredProcessors.ProcessorStateIterator psi = discoveredProcs.iterator();
637 // TODO: Create proper argument values; need past round
638 // information to fill in this constructor. Note that the 1
639 // st round of processing could be the last round if there
640 // were parse errors on the initial source files; however, we
641 // are not doing processing in that case.
643 Set<Element> rootElements = new LinkedHashSet<Element>();
644 rootElements.addAll(topLevelClasses);
645 rootElements.addAll(packageInfoFiles);
646 rootElements = Collections.unmodifiableSet(rootElements);
648 RoundEnvironment renv = new JavacRoundEnvironment(false,
649 false,
650 rootElements,
651 JavacProcessingEnvironment.this);
653 while(unmatchedAnnotations.size() > 0 && psi.hasNext() ) {
654 ProcessorState ps = psi.next();
655 Set<String> matchedNames = new HashSet<String>();
656 Set<TypeElement> typeElements = new LinkedHashSet<TypeElement>();
657 for (String unmatchedAnnotationName : unmatchedAnnotations.keySet()) {
658 if (ps.annotationSupported(unmatchedAnnotationName) ) {
659 matchedNames.add(unmatchedAnnotationName);
660 TypeElement te = unmatchedAnnotations.get(unmatchedAnnotationName);
661 if (te != null)
662 typeElements.add(te);
663 }
664 }
666 if (matchedNames.size() > 0 || ps.contributed) {
667 boolean processingResult = callProcessor(ps.processor, typeElements, renv);
668 ps.contributed = true;
669 ps.removeSupportedOptions(unmatchedProcessorOptions);
671 if (printProcessorInfo || verbose) {
672 xout.println(Log.getLocalizedString("x.print.processor.info",
673 ps.processor.getClass().getName(),
674 matchedNames.toString(),
675 processingResult));
676 }
678 if (processingResult) {
679 unmatchedAnnotations.keySet().removeAll(matchedNames);
680 }
682 }
683 }
684 unmatchedAnnotations.remove("");
686 if (lint && unmatchedAnnotations.size() > 0) {
687 // Remove annotations processed by javac
688 unmatchedAnnotations.keySet().removeAll(platformAnnotations);
689 if (unmatchedAnnotations.size() > 0) {
690 log = Log.instance(context);
691 log.warning("proc.annotations.without.processors",
692 unmatchedAnnotations.keySet());
693 }
694 }
696 // Run contributing processors that haven't run yet
697 psi.runContributingProcs(renv);
699 // Debugging
700 if (options.get("displayFilerState") != null)
701 filer.displayState();
702 }
704 /**
705 * Computes the set of annotations on the symbol in question.
706 * Leave class public for external testing purposes.
707 */
708 public static class ComputeAnnotationSet extends
709 ElementScanner6<Set<TypeElement>, Set<TypeElement>> {
710 final Elements elements;
712 public ComputeAnnotationSet(Elements elements) {
713 super();
714 this.elements = elements;
715 }
717 @Override
718 public Set<TypeElement> visitPackage(PackageElement e, Set<TypeElement> p) {
719 // Don't scan enclosed elements of a package
720 return p;
721 }
723 @Override
724 public Set<TypeElement> scan(Element e, Set<TypeElement> p) {
725 for (AnnotationMirror annotationMirror :
726 elements.getAllAnnotationMirrors(e) ) {
727 Element e2 = annotationMirror.getAnnotationType().asElement();
728 p.add((TypeElement) e2);
729 }
730 return super.scan(e, p);
731 }
732 }
734 private boolean callProcessor(Processor proc,
735 Set<? extends TypeElement> tes,
736 RoundEnvironment renv) {
737 try {
738 return proc.process(tes, renv);
739 } catch (CompletionFailure ex) {
740 StringWriter out = new StringWriter();
741 ex.printStackTrace(new PrintWriter(out));
742 log.error("proc.cant.access", ex.sym, ex.getDetailValue(), out.toString());
743 return false;
744 } catch (Throwable t) {
745 throw new AnnotationProcessingError(t);
746 }
747 }
750 // TODO: internal catch clauses?; catch and rethrow an annotation
751 // processing error
752 public JavaCompiler doProcessing(Context context,
753 List<JCCompilationUnit> roots,
754 List<ClassSymbol> classSymbols,
755 Iterable<? extends PackageSymbol> pckSymbols)
756 throws IOException {
758 log = Log.instance(context);
759 // Writer for -XprintRounds and -XprintProcessorInfo data
760 PrintWriter xout = context.get(Log.outKey);
761 TaskListener taskListener = context.get(TaskListener.class);
764 AnnotationCollector collector = new AnnotationCollector();
766 JavaCompiler compiler = JavaCompiler.instance(context);
767 compiler.todo.clear(); // free the compiler's resources
769 int round = 0;
771 // List<JCAnnotation> annotationsPresentInSource = collector.findAnnotations(roots);
772 List<ClassSymbol> topLevelClasses = getTopLevelClasses(roots);
774 for (ClassSymbol classSym : classSymbols)
775 topLevelClasses = topLevelClasses.prepend(classSym);
776 List<PackageSymbol> packageInfoFiles =
777 getPackageInfoFiles(roots);
779 Set<PackageSymbol> specifiedPackages = new LinkedHashSet<PackageSymbol>();
780 for (PackageSymbol psym : pckSymbols)
781 specifiedPackages.add(psym);
782 this.specifiedPackages = Collections.unmodifiableSet(specifiedPackages);
784 // Use annotation processing to compute the set of annotations present
785 Set<TypeElement> annotationsPresent = new LinkedHashSet<TypeElement>();
786 ComputeAnnotationSet annotationComputer = new ComputeAnnotationSet(elementUtils);
787 for (ClassSymbol classSym : topLevelClasses)
788 annotationComputer.scan(classSym, annotationsPresent);
789 for (PackageSymbol pkgSym : packageInfoFiles)
790 annotationComputer.scan(pkgSym, annotationsPresent);
792 Context currentContext = context;
794 int roundNumber = 0;
795 boolean errorStatus = false;
797 runAround:
798 while(true) {
799 if (fatalErrors && compiler.errorCount() != 0) {
800 errorStatus = true;
801 break runAround;
802 }
804 this.context = currentContext;
805 roundNumber++;
806 printRoundInfo(xout, roundNumber, topLevelClasses, annotationsPresent, false);
808 if (taskListener != null)
809 taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
811 try {
812 discoverAndRunProcs(currentContext, annotationsPresent, topLevelClasses, packageInfoFiles);
813 } finally {
814 if (taskListener != null)
815 taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
816 }
818 /*
819 * Processors for round n have run to completion. Prepare
820 * for round (n+1) by checked for errors raised by
821 * annotation processors and then checking for syntax
822 * errors on any generated source files.
823 */
824 if (messager.errorRaised()) {
825 errorStatus = true;
826 break runAround;
827 } else {
828 if (moreToDo()) {
829 // annotationsPresentInSource = List.nil();
830 annotationsPresent = new LinkedHashSet<TypeElement>();
831 topLevelClasses = List.nil();
832 packageInfoFiles = List.nil();
834 compiler.close();
835 currentContext = contextForNextRound(currentContext, true);
837 JavaFileManager fileManager = currentContext.get(JavaFileManager.class);
839 List<JavaFileObject> fileObjects = List.nil();
840 for (JavaFileObject jfo : filer.getGeneratedSourceFileObjects() ) {
841 fileObjects = fileObjects.prepend(jfo);
842 }
845 compiler = JavaCompiler.instance(currentContext);
846 List<JCCompilationUnit> parsedFiles = compiler.parseFiles(fileObjects);
847 roots = cleanTrees(roots).reverse();
850 for (JCCompilationUnit unit : parsedFiles)
851 roots = roots.prepend(unit);
852 roots = roots.reverse();
854 // Check for errors after parsing
855 if (compiler.parseErrors()) {
856 errorStatus = true;
857 break runAround;
858 } else {
859 ListBuffer<ClassSymbol> classes = enterNewClassFiles(currentContext);
860 compiler.enterTrees(roots);
862 // annotationsPresentInSource =
863 // collector.findAnnotations(parsedFiles);
864 classes.appendList(getTopLevelClasses(parsedFiles));
865 topLevelClasses = classes.toList();
866 packageInfoFiles = getPackageInfoFiles(parsedFiles);
868 annotationsPresent = new LinkedHashSet<TypeElement>();
869 for (ClassSymbol classSym : topLevelClasses)
870 annotationComputer.scan(classSym, annotationsPresent);
871 for (PackageSymbol pkgSym : packageInfoFiles)
872 annotationComputer.scan(pkgSym, annotationsPresent);
874 updateProcessingState(currentContext, false);
875 }
876 } else
877 break runAround; // No new files
878 }
879 }
880 runLastRound(xout, roundNumber, errorStatus, taskListener);
882 compiler.close();
883 currentContext = contextForNextRound(currentContext, true);
884 compiler = JavaCompiler.instance(currentContext);
885 filer.newRound(currentContext, true);
886 filer.warnIfUnclosedFiles();
887 warnIfUnmatchedOptions();
889 /*
890 * If an annotation processor raises an error in a round,
891 * that round runs to completion and one last round occurs.
892 * The last round may also occur because no more source or
893 * class files have been generated. Therefore, if an error
894 * was raised on either of the last *two* rounds, the compile
895 * should exit with a nonzero exit code. The current value of
896 * errorStatus holds whether or not an error was raised on the
897 * second to last round; errorRaised() gives the error status
898 * of the last round.
899 */
900 errorStatus = errorStatus || messager.errorRaised();
903 // Free resources
904 this.close();
906 if (taskListener != null)
907 taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING));
909 if (errorStatus) {
910 compiler.log.nerrors += messager.errorCount();
911 if (compiler.errorCount() == 0)
912 compiler.log.nerrors++;
913 } else if (procOnly) {
914 compiler.todo.clear();
915 } else { // Final compilation
916 compiler.close();
917 currentContext = contextForNextRound(currentContext, true);
918 compiler = JavaCompiler.instance(currentContext);
920 if (true) {
921 compiler.enterTrees(cleanTrees(roots));
922 } else {
923 List<JavaFileObject> fileObjects = List.nil();
924 for (JCCompilationUnit unit : roots)
925 fileObjects = fileObjects.prepend(unit.getSourceFile());
926 roots = null;
927 compiler.enterTrees(compiler.parseFiles(fileObjects.reverse()));
928 }
929 }
931 return compiler;
932 }
934 // Call the last round of annotation processing
935 private void runLastRound(PrintWriter xout,
936 int roundNumber,
937 boolean errorStatus,
938 TaskListener taskListener) throws IOException {
939 roundNumber++;
940 List<ClassSymbol> noTopLevelClasses = List.nil();
941 Set<TypeElement> noAnnotations = Collections.emptySet();
942 printRoundInfo(xout, roundNumber, noTopLevelClasses, noAnnotations, true);
944 Set<Element> emptyRootElements = Collections.emptySet(); // immutable
945 RoundEnvironment renv = new JavacRoundEnvironment(true,
946 errorStatus,
947 emptyRootElements,
948 JavacProcessingEnvironment.this);
949 if (taskListener != null)
950 taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
952 try {
953 discoveredProcs.iterator().runContributingProcs(renv);
954 } finally {
955 if (taskListener != null)
956 taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND));
957 }
958 }
960 private void updateProcessingState(Context currentContext, boolean lastRound) {
961 filer.newRound(currentContext, lastRound);
962 messager.newRound(currentContext);
964 elementUtils.setContext(currentContext);
965 typeUtils.setContext(currentContext);
966 }
968 private void warnIfUnmatchedOptions() {
969 if (!unmatchedProcessorOptions.isEmpty()) {
970 log.warning("proc.unmatched.processor.options", unmatchedProcessorOptions.toString());
971 }
972 }
974 private void printRoundInfo(PrintWriter xout,
975 int roundNumber,
976 List<ClassSymbol> topLevelClasses,
977 Set<TypeElement> annotationsPresent,
978 boolean lastRound) {
979 if (printRounds || verbose) {
980 xout.println(Log.getLocalizedString("x.print.rounds",
981 roundNumber,
982 "{" + topLevelClasses.toString(", ") + "}",
983 annotationsPresent,
984 lastRound));
985 }
986 }
988 private ListBuffer<ClassSymbol> enterNewClassFiles(Context currentContext) {
989 ClassReader reader = ClassReader.instance(currentContext);
990 Name.Table names = Name.Table.instance(currentContext);
991 ListBuffer<ClassSymbol> list = new ListBuffer<ClassSymbol>();
993 for (Map.Entry<String,JavaFileObject> entry : filer.getGeneratedClasses().entrySet()) {
994 Name name = names.fromString(entry.getKey());
995 JavaFileObject file = entry.getValue();
996 if (file.getKind() != JavaFileObject.Kind.CLASS)
997 throw new AssertionError(file);
998 ClassSymbol cs = reader.enterClass(name, file);
999 list.append(cs);
1000 }
1001 return list;
1002 }
1004 /**
1005 * Free resources related to annotation processing.
1006 */
1007 public void close() {
1008 filer.close();
1009 discoveredProcs = null;
1010 }
1012 private List<ClassSymbol> getTopLevelClasses(List<? extends JCCompilationUnit> units) {
1013 List<ClassSymbol> classes = List.nil();
1014 for (JCCompilationUnit unit : units) {
1015 for (JCTree node : unit.defs) {
1016 if (node.getTag() == JCTree.CLASSDEF) {
1017 classes = classes.prepend(((JCClassDecl) node).sym);
1018 }
1019 }
1020 }
1021 return classes.reverse();
1022 }
1024 private List<PackageSymbol> getPackageInfoFiles(List<? extends JCCompilationUnit> units) {
1025 List<PackageSymbol> packages = List.nil();
1026 for (JCCompilationUnit unit : units) {
1027 boolean isPkgInfo = unit.sourcefile.isNameCompatible("package-info",
1028 JavaFileObject.Kind.SOURCE);
1029 if (isPkgInfo) {
1030 packages = packages.prepend(unit.packge);
1031 }
1032 }
1033 return packages.reverse();
1034 }
1036 private Context contextForNextRound(Context context, boolean shareNames)
1037 throws IOException
1038 {
1039 Context next = new Context();
1041 Options options = Options.instance(context);
1042 assert options != null;
1043 next.put(Options.optionsKey, options);
1045 PrintWriter out = context.get(Log.outKey);
1046 assert out != null;
1047 next.put(Log.outKey, out);
1049 if (shareNames) {
1050 Name.Table names = Name.Table.instance(context);
1051 assert names != null;
1052 next.put(Name.Table.namesKey, names);
1053 }
1055 DiagnosticListener dl = context.get(DiagnosticListener.class);
1056 if (dl != null)
1057 next.put(DiagnosticListener.class, dl);
1059 TaskListener tl = context.get(TaskListener.class);
1060 if (tl != null)
1061 next.put(TaskListener.class, tl);
1063 JavaFileManager jfm = context.get(JavaFileManager.class);
1064 assert jfm != null;
1065 next.put(JavaFileManager.class, jfm);
1066 if (jfm instanceof JavacFileManager) {
1067 ((JavacFileManager)jfm).setContext(next);
1068 }
1070 Name.Table names = Name.Table.instance(context);
1071 assert names != null;
1072 next.put(Name.Table.namesKey, names);
1074 Keywords keywords = Keywords.instance(context);
1075 assert(keywords != null);
1076 next.put(Keywords.keywordsKey, keywords);
1078 JavaCompiler oldCompiler = JavaCompiler.instance(context);
1079 JavaCompiler nextCompiler = JavaCompiler.instance(next);
1080 nextCompiler.initRound(oldCompiler);
1082 JavacTaskImpl task = context.get(JavacTaskImpl.class);
1083 if (task != null) {
1084 next.put(JavacTaskImpl.class, task);
1085 task.updateContext(next);
1086 }
1088 context.clear();
1089 return next;
1090 }
1092 /*
1093 * Called retroactively to determine if a class loader was required,
1094 * after we have failed to create one.
1095 */
1096 private boolean needClassLoader(String procNames, Iterable<? extends File> workingpath) {
1097 if (procNames != null)
1098 return true;
1100 String procPath;
1101 URL[] urls = new URL[1];
1102 for(File pathElement : workingpath) {
1103 try {
1104 urls[0] = pathElement.toURI().toURL();
1105 if (ServiceProxy.hasService(Processor.class, urls))
1106 return true;
1107 } catch (MalformedURLException ex) {
1108 throw new AssertionError(ex);
1109 }
1110 catch (ServiceProxy.ServiceConfigurationError e) {
1111 log.error("proc.bad.config.file", e.getLocalizedMessage());
1112 return true;
1113 }
1114 }
1116 return false;
1117 }
1119 private class AnnotationCollector extends TreeScanner {
1120 List<JCTree> path = List.nil();
1121 static final boolean verbose = false;
1122 List<JCAnnotation> annotations = List.nil();
1124 public List<JCAnnotation> findAnnotations(List<? extends JCTree> nodes) {
1125 annotations = List.nil();
1126 scan(nodes);
1127 List<JCAnnotation> found = annotations;
1128 annotations = List.nil();
1129 return found.reverse();
1130 }
1132 public void scan(JCTree node) {
1133 if (node == null)
1134 return;
1135 Symbol sym = TreeInfo.symbolFor(node);
1136 if (sym != null)
1137 path = path.prepend(node);
1138 super.scan(node);
1139 if (sym != null)
1140 path = path.tail;
1141 }
1143 public void visitAnnotation(JCAnnotation node) {
1144 annotations = annotations.prepend(node);
1145 if (verbose) {
1146 StringBuilder sb = new StringBuilder();
1147 for (JCTree tree : path.reverse()) {
1148 System.err.print(sb);
1149 System.err.println(TreeInfo.symbolFor(tree));
1150 sb.append(" ");
1151 }
1152 System.err.print(sb);
1153 System.err.println(node);
1154 }
1155 }
1156 }
1158 private static <T extends JCTree> List<T> cleanTrees(List<T> nodes) {
1159 for (T node : nodes)
1160 treeCleaner.scan(node);
1161 return nodes;
1162 }
1164 private static TreeScanner treeCleaner = new TreeScanner() {
1165 public void scan(JCTree node) {
1166 super.scan(node);
1167 if (node != null)
1168 node.type = null;
1169 }
1170 public void visitTopLevel(JCCompilationUnit node) {
1171 node.packge = null;
1172 super.visitTopLevel(node);
1173 }
1174 public void visitClassDef(JCClassDecl node) {
1175 node.sym = null;
1176 super.visitClassDef(node);
1177 }
1178 public void visitMethodDef(JCMethodDecl node) {
1179 node.sym = null;
1180 super.visitMethodDef(node);
1181 }
1182 public void visitVarDef(JCVariableDecl node) {
1183 node.sym = null;
1184 super.visitVarDef(node);
1185 }
1186 public void visitNewClass(JCNewClass node) {
1187 node.constructor = null;
1188 super.visitNewClass(node);
1189 }
1190 public void visitAssignop(JCAssignOp node) {
1191 node.operator = null;
1192 super.visitAssignop(node);
1193 }
1194 public void visitUnary(JCUnary node) {
1195 node.operator = null;
1196 super.visitUnary(node);
1197 }
1198 public void visitBinary(JCBinary node) {
1199 node.operator = null;
1200 super.visitBinary(node);
1201 }
1202 public void visitSelect(JCFieldAccess node) {
1203 node.sym = null;
1204 super.visitSelect(node);
1205 }
1206 public void visitIdent(JCIdent node) {
1207 node.sym = null;
1208 super.visitIdent(node);
1209 }
1210 };
1213 private boolean moreToDo() {
1214 return filer.newFiles();
1215 }
1217 /**
1218 * {@inheritdoc}
1219 *
1220 * Command line options suitable for presenting to annotation
1221 * processors. "-Afoo=bar" should be "-Afoo" => "bar".
1222 */
1223 public Map<String,String> getOptions() {
1224 return processorOptions;
1225 }
1227 public Messager getMessager() {
1228 return messager;
1229 }
1231 public Filer getFiler() {
1232 return filer;
1233 }
1235 public JavacElements getElementUtils() {
1236 return elementUtils;
1237 }
1239 public JavacTypes getTypeUtils() {
1240 return typeUtils;
1241 }
1243 public SourceVersion getSourceVersion() {
1244 return Source.toSourceVersion(source);
1245 }
1247 public Locale getLocale() {
1248 return Locale.getDefault();
1249 }
1251 public Set<Symbol.PackageSymbol> getSpecifiedPackages() {
1252 return specifiedPackages;
1253 }
1255 // Borrowed from DocletInvoker and apt
1256 // TODO: remove from apt's Main
1257 /**
1258 * Utility method for converting a search path string to an array
1259 * of directory and JAR file URLs.
1260 *
1261 * @param path the search path string
1262 * @return the resulting array of directory and JAR file URLs
1263 */
1264 public static URL[] pathToURLs(String path) {
1265 StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
1266 URL[] urls = new URL[st.countTokens()];
1267 int count = 0;
1268 while (st.hasMoreTokens()) {
1269 URL url = fileToURL(new File(st.nextToken()));
1270 if (url != null) {
1271 urls[count++] = url;
1272 }
1273 }
1274 if (urls.length != count) {
1275 URL[] tmp = new URL[count];
1276 System.arraycopy(urls, 0, tmp, 0, count);
1277 urls = tmp;
1278 }
1279 return urls;
1280 }
1282 /**
1283 * Returns the directory or JAR file URL corresponding to the specified
1284 * local file name.
1285 *
1286 * @param file the File object
1287 * @return the resulting directory or JAR file URL, or null if unknown
1288 */
1289 private static URL fileToURL(File file) {
1290 String name;
1291 try {
1292 name = file.getCanonicalPath();
1293 } catch (IOException e) {
1294 name = file.getAbsolutePath();
1295 }
1296 name = name.replace(File.separatorChar, '/');
1297 if (!name.startsWith("/")) {
1298 name = "/" + name;
1299 }
1300 // If the file does not exist, then assume that it's a directory
1301 if (!file.isFile()) {
1302 name = name + "/";
1303 }
1304 try {
1305 return new URL("file", "", name);
1306 } catch (MalformedURLException e) {
1307 throw new IllegalArgumentException("file");
1308 }
1309 }
1313 private static final Pattern allMatches = Pattern.compile(".*");
1315 private static final Pattern noMatches = Pattern.compile("(\\P{all})+");
1316 /**
1317 * Convert import-style string to regex matching that string. If
1318 * the string is a valid import-style string, return a regex that
1319 * won't match anything.
1320 */
1321 // TODO: remove version in Apt.java
1322 public static Pattern importStringToPattern(String s, Processor p, Log log) {
1323 if (s.equals("*")) {
1324 return allMatches;
1325 } else {
1326 String t = s;
1327 boolean star = false;
1329 /*
1330 * Validate string from factory is legal. If the string
1331 * has more than one asterisks or the asterisks does not
1332 * appear as the last character (preceded by a period),
1333 * the string is not legal.
1334 */
1336 boolean valid = true;
1337 int index = t.indexOf('*');
1338 if (index != -1) {
1339 // '*' must be last character...
1340 if (index == t.length() -1) {
1341 // ... and preceeding character must be '.'
1342 if ( index-1 >= 0 ) {
1343 valid = t.charAt(index-1) == '.';
1344 // Strip off ".*$" for identifier checks
1345 t = t.substring(0, t.length()-2);
1346 }
1347 } else
1348 valid = false;
1349 }
1351 // Verify string is off the form (javaId \.)+ or javaId
1352 if (valid) {
1353 String[] javaIds = t.split("\\.", t.length()+2);
1354 for(String javaId: javaIds)
1355 valid &= SourceVersion.isIdentifier(javaId);
1356 }
1358 if (!valid) {
1359 log.warning("proc.malformed.supported.string", s, p.getClass().getName());
1360 return noMatches; // won't match any valid identifier
1361 }
1363 String s_prime = s.replaceAll("\\.", "\\\\.");
1365 if (s_prime.endsWith("*")) {
1366 s_prime = s_prime.substring(0, s_prime.length() - 1) + ".+";
1367 }
1369 return Pattern.compile(s_prime);
1370 }
1371 }
1373 /**
1374 * For internal use by Sun Microsystems only. This method will be
1375 * removed without warning.
1376 */
1377 public Context getContext() {
1378 return context;
1379 }
1381 public String toString() {
1382 return "javac ProcessingEnvironment";
1383 }
1385 public static boolean isValidOptionName(String optionName) {
1386 for(String s : optionName.split("\\.", -1)) {
1387 if (!SourceVersion.isIdentifier(s))
1388 return false;
1389 }
1390 return true;
1391 }
1392 }