diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,1713 @@ +/* + * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.processing; + +import java.io.Closeable; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.*; +import java.util.stream.Collectors; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.*; +import javax.lang.model.util.*; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardJavaFileManager; + +import static javax.tools.StandardLocation.*; + +import com.sun.source.util.TaskEvent; +import com.sun.tools.javac.api.MultiTaskListener; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Scope.WriteableScope; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Check; +import com.sun.tools.javac.comp.Enter; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.comp.Modules; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.model.JavacElements; +import com.sun.tools.javac.model.JavacTypes; +import com.sun.tools.javac.platform.PlatformDescription; +import com.sun.tools.javac.platform.PlatformDescription.PluginInfo; +import com.sun.tools.javac.resources.CompilerProperties.Errors; +import com.sun.tools.javac.resources.CompilerProperties.Warnings; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.util.Abort; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.ClientCodeException; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Convert; +import com.sun.tools.javac.util.DefinedBy; +import com.sun.tools.javac.util.DefinedBy.Api; +import com.sun.tools.javac.util.Iterators; +import com.sun.tools.javac.util.JCDiagnostic; +import com.sun.tools.javac.util.JDK9Wrappers.Module; +import com.sun.tools.javac.util.JavacMessages; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.MatchingUtils; +import com.sun.tools.javac.util.ModuleHelper; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; +import com.sun.tools.javac.util.Options; + +import static com.sun.tools.javac.code.Lint.LintCategory.PROCESSING; +import static com.sun.tools.javac.code.Kinds.Kind.*; +import com.sun.tools.javac.comp.Annotate; +import static com.sun.tools.javac.comp.CompileStates.CompileState; +import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.*; + +/** + * Objects of this class hold and manage the state needed to support + * annotation processing. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class JavacProcessingEnvironment implements ProcessingEnvironment, Closeable { + private final Options options; + + private final boolean printProcessorInfo; + private final boolean printRounds; + private final boolean verbose; + private final boolean lint; + private final boolean fatalErrors; + private final boolean werror; + private final boolean showResolveErrors; + private final boolean allowModules; + + private final JavacFiler filer; + private final JavacMessager messager; + private final JavacElements elementUtils; + private final JavacTypes typeUtils; + private final JavaCompiler compiler; + private final Modules modules; + private final Types types; + private final Annotate annotate; + + /** + * Holds relevant state history of which processors have been + * used. + */ + private DiscoveredProcessors discoveredProcs; + + /** + * Map of processor-specific options. + */ + private final Map processorOptions; + + /** + */ + private final Set unmatchedProcessorOptions; + + /** + * Annotations implicitly processed and claimed by javac. + */ + private final Set platformAnnotations; + + /** + * Set of packages given on command line. + */ + private Set specifiedPackages = Collections.emptySet(); + + /** The log to be used for error reporting. + */ + final Log log; + + /** Diagnostic factory. + */ + JCDiagnostic.Factory diags; + + /** + * Source level of the compile. + */ + Source source; + + private ClassLoader processorClassLoader; + private ServiceLoader serviceLoader; + private SecurityException processorLoaderException; + + private final JavaFileManager fileManager; + + /** + * JavacMessages object used for localization + */ + private JavacMessages messages; + + private MultiTaskListener taskListener; + private final Symtab symtab; + private final Names names; + private final Enter enter; + private final Completer initialCompleter; + private final Check chk; + + private final Context context; + + /** Get the JavacProcessingEnvironment instance for this context. */ + public static JavacProcessingEnvironment instance(Context context) { + JavacProcessingEnvironment instance = context.get(JavacProcessingEnvironment.class); + if (instance == null) + instance = new JavacProcessingEnvironment(context); + return instance; + } + + protected JavacProcessingEnvironment(Context context) { + this.context = context; + context.put(JavacProcessingEnvironment.class, this); + log = Log.instance(context); + source = Source.instance(context); + diags = JCDiagnostic.Factory.instance(context); + options = Options.instance(context); + printProcessorInfo = options.isSet(Option.XPRINTPROCESSORINFO); + printRounds = options.isSet(Option.XPRINTROUNDS); + verbose = options.isSet(Option.VERBOSE); + lint = Lint.instance(context).isEnabled(PROCESSING); + compiler = JavaCompiler.instance(context); + if (options.isSet(Option.PROC, "only") || options.isSet(Option.XPRINT)) { + compiler.shouldStopPolicyIfNoError = CompileState.PROCESS; + } + fatalErrors = options.isSet("fatalEnterError"); + showResolveErrors = options.isSet("showResolveErrors"); + werror = options.isSet(Option.WERROR); + fileManager = context.get(JavaFileManager.class); + platformAnnotations = initPlatformAnnotations(); + + // Initialize services before any processors are initialized + // in case processors use them. + filer = new JavacFiler(context); + messager = new JavacMessager(context, this); + elementUtils = JavacElements.instance(context); + typeUtils = JavacTypes.instance(context); + modules = Modules.instance(context); + types = Types.instance(context); + annotate = Annotate.instance(context); + processorOptions = initProcessorOptions(); + unmatchedProcessorOptions = initUnmatchedProcessorOptions(); + messages = JavacMessages.instance(context); + taskListener = MultiTaskListener.instance(context); + symtab = Symtab.instance(context); + names = Names.instance(context); + enter = Enter.instance(context); + initialCompleter = ClassFinder.instance(context).getCompleter(); + chk = Check.instance(context); + initProcessorLoader(); + + allowModules = source.allowModules(); + } + + public void setProcessors(Iterable processors) { + Assert.checkNull(discoveredProcs); + initProcessorIterator(processors); + } + + private Set initPlatformAnnotations() { + Set platformAnnotations = new HashSet<>(); + platformAnnotations.add("java.lang.Deprecated"); + platformAnnotations.add("java.lang.Override"); + platformAnnotations.add("java.lang.SuppressWarnings"); + platformAnnotations.add("java.lang.annotation.Documented"); + platformAnnotations.add("java.lang.annotation.Inherited"); + platformAnnotations.add("java.lang.annotation.Retention"); + platformAnnotations.add("java.lang.annotation.Target"); + return Collections.unmodifiableSet(platformAnnotations); + } + + private void initProcessorLoader() { + try { + if (fileManager.hasLocation(ANNOTATION_PROCESSOR_MODULE_PATH)) { + try { + serviceLoader = fileManager.getServiceLoader(ANNOTATION_PROCESSOR_MODULE_PATH, Processor.class); + } catch (IOException e) { + throw new Abort(e); + } + } else { + // If processorpath is not explicitly set, use the classpath. + processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH) + ? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH) + : fileManager.getClassLoader(CLASS_PATH); + + if (options.isSet("accessInternalAPI")) + ModuleHelper.addExports(Module.getModule(getClass()), Module.getUnnamedModule(processorClassLoader)); + + if (processorClassLoader != null && processorClassLoader instanceof Closeable) { + compiler.closeables = compiler.closeables.prepend((Closeable) processorClassLoader); + } + } + } catch (SecurityException e) { + processorLoaderException = e; + } + } + + private void initProcessorIterator(Iterable processors) { + Iterator processorIterator; + + if (options.isSet(Option.XPRINT)) { + try { + processorIterator = List.of(new PrintingProcessor()).iterator(); + } catch (Throwable t) { + AssertionError assertError = + new AssertionError("Problem instantiating PrintingProcessor."); + assertError.initCause(t); + throw assertError; + } + } else if (processors != null) { + processorIterator = processors.iterator(); + } else { + if (processorLoaderException == null) { + /* + * If the "-processor" option is used, search the appropriate + * path for the named class. Otherwise, use a service + * provider mechanism to create the processor iterator. + */ + String processorNames = options.get(Option.PROCESSOR); + if (fileManager.hasLocation(ANNOTATION_PROCESSOR_MODULE_PATH)) { + processorIterator = (processorNames == null) ? + new ServiceIterator(serviceLoader, log) : + new NameServiceIterator(serviceLoader, log, processorNames); + } else if (processorNames != null) { + processorIterator = new NameProcessIterator(processorNames, processorClassLoader, log); + } else { + processorIterator = new ServiceIterator(processorClassLoader, log); + } + } else { + /* + * A security exception will occur if we can't create a classloader. + * Ignore the exception if, with hindsight, we didn't need it anyway + * (i.e. no processor was specified either explicitly, or implicitly, + * in service configuration file.) Otherwise, we cannot continue. + */ + processorIterator = handleServiceLoaderUnavailability("proc.cant.create.loader", + processorLoaderException); + } + } + PlatformDescription platformProvider = context.get(PlatformDescription.class); + java.util.List platformProcessors = Collections.emptyList(); + if (platformProvider != null) { + platformProcessors = platformProvider.getAnnotationProcessors() + .stream() + .map(PluginInfo::getPlugin) + .collect(Collectors.toList()); + } + List> iterators = List.of(processorIterator, + platformProcessors.iterator()); + Iterator compoundIterator = + Iterators.createCompoundIterator(iterators, i -> i); + discoveredProcs = new DiscoveredProcessors(compoundIterator); + } + + public ServiceLoader getServiceLoader(Class service) { + if (fileManager.hasLocation(ANNOTATION_PROCESSOR_MODULE_PATH)) { + try { + return fileManager.getServiceLoader(ANNOTATION_PROCESSOR_MODULE_PATH, service); + } catch (IOException e) { + throw new Abort(e); + } + } else { + return ServiceLoader.load(service, getProcessorClassLoader()); + } + } + + /** + * Returns an empty processor iterator if no processors are on the + * relevant path, otherwise if processors are present, logs an + * error. Called when a service loader is unavailable for some + * reason, either because a service loader class cannot be found + * or because a security policy prevents class loaders from being + * created. + * + * @param key The resource key to use to log an error message + * @param e If non-null, pass this exception to Abort + */ + private Iterator handleServiceLoaderUnavailability(String key, Exception e) { + if (fileManager instanceof JavacFileManager) { + StandardJavaFileManager standardFileManager = (JavacFileManager) fileManager; + Iterable workingPath = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH) + ? standardFileManager.getLocationAsPaths(ANNOTATION_PROCESSOR_PATH) + : standardFileManager.getLocationAsPaths(CLASS_PATH); + + if (needClassLoader(options.get(Option.PROCESSOR), workingPath) ) + handleException(key, e); + + } else { + handleException(key, e); + } + + java.util.List pl = Collections.emptyList(); + return pl.iterator(); + } + + /** + * Handle a security exception thrown during initializing the + * Processor iterator. + */ + private void handleException(String key, Exception e) { + if (e != null) { + log.error(key, e.getLocalizedMessage()); + throw new Abort(e); + } else { + log.error(key); + throw new Abort(); + } + } + + /** + * Use a service loader appropriate for the platform to provide an + * iterator over annotations processors; fails if a loader is + * needed but unavailable. + */ + private class ServiceIterator implements Iterator { + Iterator iterator; + Log log; + ServiceLoader loader; + + ServiceIterator(ClassLoader classLoader, Log log) { + this.log = log; + try { + try { + loader = ServiceLoader.load(Processor.class, classLoader); + this.iterator = loader.iterator(); + } catch (Exception e) { + // Fail softly if a loader is not actually needed. + this.iterator = handleServiceLoaderUnavailability("proc.no.service", null); + } + } catch (Throwable t) { + log.error(Errors.ProcServiceProblem); + throw new Abort(t); + } + } + + ServiceIterator(ServiceLoader loader, Log log) { + this.log = log; + this.loader = loader; + this.iterator = loader.iterator(); + } + + @Override + public boolean hasNext() { + try { + return internalHasNext(); + } catch(ServiceConfigurationError sce) { + log.error(Errors.ProcBadConfigFile(sce.getLocalizedMessage())); + throw new Abort(sce); + } catch (Throwable t) { + throw new Abort(t); + } + } + + boolean internalHasNext() { + return iterator.hasNext(); + } + + @Override + public Processor next() { + try { + return internalNext(); + } catch (ServiceConfigurationError sce) { + log.error(Errors.ProcBadConfigFile(sce.getLocalizedMessage())); + throw new Abort(sce); + } catch (Throwable t) { + throw new Abort(t); + } + } + + Processor internalNext() { + return iterator.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + public void close() { + if (loader != null) { + try { + loader.reload(); + } catch(Exception e) { + // Ignore problems during a call to reload. + } + } + } + } + + private class NameServiceIterator extends ServiceIterator { + private Map namedProcessorsMap = new HashMap<>();; + private Iterator processorNames = null; + private Processor nextProc = null; + + public NameServiceIterator(ServiceLoader loader, Log log, String theNames) { + super(loader, log); + this.processorNames = Arrays.asList(theNames.split(",")).iterator(); + } + + @Override + boolean internalHasNext() { + if (nextProc != null) { + return true; + } + if (!processorNames.hasNext()) { + namedProcessorsMap = null; + return false; + } + String processorName = processorNames.next(); + Processor theProcessor = namedProcessorsMap.get(processorName); + if (theProcessor != null) { + namedProcessorsMap.remove(processorName); + nextProc = theProcessor; + return true; + } else { + while (iterator.hasNext()) { + theProcessor = iterator.next(); + String name = theProcessor.getClass().getName(); + if (name.equals(processorName)) { + nextProc = theProcessor; + return true; + } else { + namedProcessorsMap.put(name, theProcessor); + } + } + log.error(Errors.ProcProcessorNotFound(processorName)); + return false; + } + } + + @Override + Processor internalNext() { + if (hasNext()) { + Processor p = nextProc; + nextProc = null; + return p; + } else { + throw new NoSuchElementException(); + } + } + } + + private static class NameProcessIterator implements Iterator { + Processor nextProc = null; + Iterator names; + ClassLoader processorCL; + Log log; + + NameProcessIterator(String names, ClassLoader processorCL, Log log) { + this.names = Arrays.asList(names.split(",")).iterator(); + this.processorCL = processorCL; + this.log = log; + } + + public boolean hasNext() { + if (nextProc != null) + return true; + else { + if (!names.hasNext()) { + return false; + } else { + Processor processor = getNextProcessor(names.next()); + if (processor == null) { + return false; + } else { + nextProc = processor; + return true; + } + } + } + } + + private Processor getNextProcessor(String processorName) { + try { + try { + Class processorClass = processorCL.loadClass(processorName); + ensureReadable(processorClass); + return (Processor) processorClass.getConstructor().newInstance(); + } catch (ClassNotFoundException cnfe) { + log.error(Errors.ProcProcessorNotFound(processorName)); + return null; + } catch (ClassCastException cce) { + log.error(Errors.ProcProcessorWrongType(processorName)); + return null; + } catch (Exception e ) { + log.error(Errors.ProcProcessorCantInstantiate(processorName)); + return null; + } + } catch (ClientCodeException e) { + throw e; + } catch (Throwable t) { + throw new AnnotationProcessingError(t); + } + } + + public Processor next() { + if (hasNext()) { + Processor p = nextProc; + nextProc = null; + return p; + } else + throw new NoSuchElementException(); + } + + public void remove () { + throw new UnsupportedOperationException(); + } + + /** + * Ensures that the module of the given class is readable to this + * module. + */ + private void ensureReadable(Class targetClass) { + try { + Method getModuleMethod = Class.class.getMethod("getModule"); + Object thisModule = getModuleMethod.invoke(this.getClass()); + Object targetModule = getModuleMethod.invoke(targetClass); + + Class moduleClass = getModuleMethod.getReturnType(); + Method addReadsMethod = moduleClass.getMethod("addReads", moduleClass); + addReadsMethod.invoke(thisModule, targetModule); + } catch (NoSuchMethodException e) { + // ignore + } catch (Exception e) { + throw new InternalError(e); + } + } + } + + public boolean atLeastOneProcessor() { + return discoveredProcs.iterator().hasNext(); + } + + private Map initProcessorOptions() { + Set keySet = options.keySet(); + Map tempOptions = new LinkedHashMap<>(); + + for(String key : keySet) { + if (key.startsWith("-A") && key.length() > 2) { + int sepIndex = key.indexOf('='); + String candidateKey = null; + String candidateValue = null; + + if (sepIndex == -1) + candidateKey = key.substring(2); + else if (sepIndex >= 3) { + candidateKey = key.substring(2, sepIndex); + candidateValue = (sepIndex < key.length()-1)? + key.substring(sepIndex+1) : null; + } + tempOptions.put(candidateKey, candidateValue); + } + } + + PlatformDescription platformProvider = context.get(PlatformDescription.class); + + if (platformProvider != null) { + for (PluginInfo ap : platformProvider.getAnnotationProcessors()) { + tempOptions.putAll(ap.getOptions()); + } + } + + return Collections.unmodifiableMap(tempOptions); + } + + private Set initUnmatchedProcessorOptions() { + Set unmatchedProcessorOptions = new HashSet<>(); + unmatchedProcessorOptions.addAll(processorOptions.keySet()); + return unmatchedProcessorOptions; + } + + /** + * State about how a processor has been used by the tool. If a + * processor has been used on a prior round, its process method is + * called on all subsequent rounds, perhaps with an empty set of + * annotations to process. The {@code annotationSupported} method + * caches the supported annotation information from the first (and + * only) getSupportedAnnotationTypes call to the processor. + */ + static class ProcessorState { + public Processor processor; + public boolean contributed; + private ArrayList supportedAnnotationPatterns; + private ArrayList supportedOptionNames; + + ProcessorState(Processor p, Log log, Source source, boolean allowModules, ProcessingEnvironment env) { + processor = p; + contributed = false; + + try { + processor.init(env); + + checkSourceVersionCompatibility(source, log); + + supportedAnnotationPatterns = new ArrayList<>(); + for (String importString : processor.getSupportedAnnotationTypes()) { + supportedAnnotationPatterns.add(importStringToPattern(allowModules, + importString, + processor, + log)); + } + + supportedOptionNames = new ArrayList<>(); + for (String optionName : processor.getSupportedOptions() ) { + if (checkOptionName(optionName, log)) + supportedOptionNames.add(optionName); + } + + } catch (ClientCodeException e) { + throw e; + } catch (Throwable t) { + throw new AnnotationProcessingError(t); + } + } + + /** + * Checks whether or not a processor's source version is + * compatible with the compilation source version. The + * processor's source version needs to be greater than or + * equal to the source version of the compile. + */ + private void checkSourceVersionCompatibility(Source source, Log log) { + SourceVersion procSourceVersion = processor.getSupportedSourceVersion(); + + if (procSourceVersion.compareTo(Source.toSourceVersion(source)) < 0 ) { + log.warning(Warnings.ProcProcessorIncompatibleSourceVersion(procSourceVersion, + processor.getClass().getName(), + source.name)); + } + } + + private boolean checkOptionName(String optionName, Log log) { + boolean valid = isValidOptionName(optionName); + if (!valid) + log.error(Errors.ProcProcessorBadOptionName(optionName, + processor.getClass().getName())); + return valid; + } + + public boolean annotationSupported(String annotationName) { + for(Pattern p: supportedAnnotationPatterns) { + if (p.matcher(annotationName).matches()) + return true; + } + return false; + } + + /** + * Remove options that are matched by this processor. + */ + public void removeSupportedOptions(Set unmatchedProcessorOptions) { + unmatchedProcessorOptions.removeAll(supportedOptionNames); + } + } + + // TODO: These two classes can probably be rewritten better... + /** + * This class holds information about the processors that have + * been discovered so far as well as the means to discover more, if + * necessary. A single iterator should be used per round of + * annotation processing. The iterator first visits already + * discovered processors then fails over to the service provider + * mechanism if additional queries are made. + */ + class DiscoveredProcessors implements Iterable { + + class ProcessorStateIterator implements Iterator { + DiscoveredProcessors psi; + Iterator innerIter; + boolean onProcInterator; + + ProcessorStateIterator(DiscoveredProcessors psi) { + this.psi = psi; + this.innerIter = psi.procStateList.iterator(); + this.onProcInterator = false; + } + + public ProcessorState next() { + if (!onProcInterator) { + if (innerIter.hasNext()) + return innerIter.next(); + else + onProcInterator = true; + } + + if (psi.processorIterator.hasNext()) { + ProcessorState ps = new ProcessorState(psi.processorIterator.next(), + log, source, allowModules, + JavacProcessingEnvironment.this); + psi.procStateList.add(ps); + return ps; + } else + throw new NoSuchElementException(); + } + + public boolean hasNext() { + if (onProcInterator) + return psi.processorIterator.hasNext(); + else + return innerIter.hasNext() || psi.processorIterator.hasNext(); + } + + public void remove () { + throw new UnsupportedOperationException(); + } + + /** + * Run all remaining processors on the procStateList that + * have not already run this round with an empty set of + * annotations. + */ + public void runContributingProcs(RoundEnvironment re) { + if (!onProcInterator) { + Set emptyTypeElements = Collections.emptySet(); + while(innerIter.hasNext()) { + ProcessorState ps = innerIter.next(); + if (ps.contributed) + callProcessor(ps.processor, emptyTypeElements, re); + } + } + } + } + + Iterator processorIterator; + ArrayList procStateList; + + public ProcessorStateIterator iterator() { + return new ProcessorStateIterator(this); + } + + DiscoveredProcessors(Iterator processorIterator) { + this.processorIterator = processorIterator; + this.procStateList = new ArrayList<>(); + } + + /** + * Free jar files, etc. if using a service loader. + */ + public void close() { + if (processorIterator != null && + processorIterator instanceof ServiceIterator) { + ((ServiceIterator) processorIterator).close(); + } + } + } + + private void discoverAndRunProcs(Set annotationsPresent, + List topLevelClasses, + List packageInfoFiles, + List moduleInfoFiles) { + Map unmatchedAnnotations = new HashMap<>(annotationsPresent.size()); + + for(TypeElement a : annotationsPresent) { + ModuleElement mod = elementUtils.getModuleOf(a); + String moduleSpec = allowModules && mod != null ? mod.getQualifiedName() + "/" : ""; + unmatchedAnnotations.put(moduleSpec + a.getQualifiedName().toString(), + a); + } + + // Give "*" processors a chance to match + if (unmatchedAnnotations.size() == 0) + unmatchedAnnotations.put("", null); + + DiscoveredProcessors.ProcessorStateIterator psi = discoveredProcs.iterator(); + // TODO: Create proper argument values; need past round + // information to fill in this constructor. Note that the 1 + // st round of processing could be the last round if there + // were parse errors on the initial source files; however, we + // are not doing processing in that case. + + Set rootElements = new LinkedHashSet<>(); + rootElements.addAll(topLevelClasses); + rootElements.addAll(packageInfoFiles); + rootElements.addAll(moduleInfoFiles); + rootElements = Collections.unmodifiableSet(rootElements); + + RoundEnvironment renv = new JavacRoundEnvironment(false, + false, + rootElements, + JavacProcessingEnvironment.this); + + while(unmatchedAnnotations.size() > 0 && psi.hasNext() ) { + ProcessorState ps = psi.next(); + Set matchedNames = new HashSet<>(); + Set typeElements = new LinkedHashSet<>(); + + for (Map.Entry entry: unmatchedAnnotations.entrySet()) { + String unmatchedAnnotationName = entry.getKey(); + if (ps.annotationSupported(unmatchedAnnotationName) ) { + matchedNames.add(unmatchedAnnotationName); + TypeElement te = entry.getValue(); + if (te != null) + typeElements.add(te); + } + } + + if (matchedNames.size() > 0 || ps.contributed) { + boolean processingResult = callProcessor(ps.processor, typeElements, renv); + ps.contributed = true; + ps.removeSupportedOptions(unmatchedProcessorOptions); + + if (printProcessorInfo || verbose) { + log.printLines("x.print.processor.info", + ps.processor.getClass().getName(), + matchedNames.toString(), + processingResult); + } + + if (processingResult) { + unmatchedAnnotations.keySet().removeAll(matchedNames); + } + + } + } + unmatchedAnnotations.remove(""); + + if (lint && unmatchedAnnotations.size() > 0) { + // Remove annotations processed by javac + unmatchedAnnotations.keySet().removeAll(platformAnnotations); + if (unmatchedAnnotations.size() > 0) { + log.warning(Warnings.ProcAnnotationsWithoutProcessors(unmatchedAnnotations.keySet())); + } + } + + // Run contributing processors that haven't run yet + psi.runContributingProcs(renv); + } + + /** + * Computes the set of annotations on the symbol in question. + * Leave class public for external testing purposes. + */ + public static class ComputeAnnotationSet extends + ElementScanner9, Set> { + final Elements elements; + + public ComputeAnnotationSet(Elements elements) { + super(); + this.elements = elements; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Set visitPackage(PackageElement e, Set p) { + // Don't scan enclosed elements of a package + return p; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Set visitType(TypeElement e, Set p) { + // Type parameters are not considered to be enclosed by a type + scan(e.getTypeParameters(), p); + return super.visitType(e, p); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Set visitExecutable(ExecutableElement e, Set p) { + // Type parameters are not considered to be enclosed by an executable + scan(e.getTypeParameters(), p); + return super.visitExecutable(e, p); + } + + void addAnnotations(Element e, Set p) { + for (AnnotationMirror annotationMirror : + elements.getAllAnnotationMirrors(e) ) { + Element e2 = annotationMirror.getAnnotationType().asElement(); + p.add((TypeElement) e2); + } + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Set scan(Element e, Set p) { + addAnnotations(e, p); + return super.scan(e, p); + } + } + + private boolean callProcessor(Processor proc, + Set tes, + RoundEnvironment renv) { + try { + return proc.process(tes, renv); + } catch (ClassFinder.BadClassFile ex) { + log.error(Errors.ProcCantAccess1(ex.sym, ex.getDetailValue())); + return false; + } catch (CompletionFailure ex) { + StringWriter out = new StringWriter(); + ex.printStackTrace(new PrintWriter(out)); + log.error(Errors.ProcCantAccess(ex.sym, ex.getDetailValue(), out.toString())); + return false; + } catch (ClientCodeException e) { + throw e; + } catch (Throwable t) { + throw new AnnotationProcessingError(t); + } + } + + /** + * Helper object for a single round of annotation processing. + */ + class Round { + /** The round number. */ + final int number; + /** The diagnostic handler for the round. */ + final Log.DeferredDiagnosticHandler deferredDiagnosticHandler; + + /** The ASTs to be compiled. */ + List roots; + /** The trees that need to be cleaned - includes roots and implicitly parsed trees. */ + Set treesToClean; + /** The classes to be compiler that have were generated. */ + Map> genClassFiles; + + /** The set of annotations to be processed this round. */ + Set annotationsPresent; + /** The set of top level classes to be processed this round. */ + List topLevelClasses; + /** The set of package-info files to be processed this round. */ + List packageInfoFiles; + /** The set of module-info files to be processed this round. */ + List moduleInfoFiles; + + /** Create a round (common code). */ + private Round(int number, Set treesToClean, + Log.DeferredDiagnosticHandler deferredDiagnosticHandler) { + this.number = number; + + if (number == 1) { + Assert.checkNonNull(deferredDiagnosticHandler); + this.deferredDiagnosticHandler = deferredDiagnosticHandler; + } else { + this.deferredDiagnosticHandler = new Log.DeferredDiagnosticHandler(log); + compiler.setDeferredDiagnosticHandler(this.deferredDiagnosticHandler); + } + + // the following will be populated as needed + topLevelClasses = List.nil(); + packageInfoFiles = List.nil(); + moduleInfoFiles = List.nil(); + this.treesToClean = treesToClean; + } + + /** Create the first round. */ + Round(List roots, + List classSymbols, + Set treesToClean, + Log.DeferredDiagnosticHandler deferredDiagnosticHandler) { + this(1, treesToClean, deferredDiagnosticHandler); + this.roots = roots; + genClassFiles = new HashMap<>(); + + // 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); + + moduleInfoFiles = getModuleInfoFiles(roots); + + findAnnotationsPresent(); + } + + /** Create a new round. */ + private Round(Round prev, + Set newSourceFiles, Map> newClassFiles) { + this(prev.number+1, prev.treesToClean, null); + prev.newRound(); + this.genClassFiles = prev.genClassFiles; + + List parsedFiles = compiler.parseFiles(newSourceFiles); + roots = prev.roots.appendList(parsedFiles); + + // Check for errors after parsing + if (unrecoverableError()) { + compiler.initModules(List.nil()); + return; + } + + roots = compiler.initModules(roots); + + enterClassFiles(genClassFiles); + List newClasses = enterClassFiles(newClassFiles); + for (Entry> moduleAndClassFiles : newClassFiles.entrySet()) { + genClassFiles.computeIfAbsent(moduleAndClassFiles.getKey(), m -> new LinkedHashMap<>()).putAll(moduleAndClassFiles.getValue()); + } + enterTrees(roots); + + if (unrecoverableError()) + return; + + topLevelClasses = join( + getTopLevelClasses(parsedFiles), + getTopLevelClassesFromClasses(newClasses)); + + packageInfoFiles = join( + getPackageInfoFiles(parsedFiles), + getPackageInfoFilesFromClasses(newClasses)); + + moduleInfoFiles = List.nil(); //module-info cannot be generated + + findAnnotationsPresent(); + } + + /** Create the next round to be used. */ + Round next(Set newSourceFiles, Map> newClassFiles) { + return new Round(this, newSourceFiles, newClassFiles); + } + + /** Prepare the compiler for the final compilation. */ + void finalCompiler() { + newRound(); + } + + /** Return the number of errors found so far in this round. + * This may include uncoverable errors, such as parse errors, + * and transient errors, such as missing symbols. */ + int errorCount() { + return compiler.errorCount(); + } + + /** Return the number of warnings found so far in this round. */ + int warningCount() { + return compiler.warningCount(); + } + + /** Return whether or not an unrecoverable error has occurred. */ + boolean unrecoverableError() { + if (messager.errorRaised()) + return true; + + for (JCDiagnostic d: deferredDiagnosticHandler.getDiagnostics()) { + switch (d.getKind()) { + case WARNING: + if (werror) + return true; + break; + + case ERROR: + if (fatalErrors || !d.isFlagSet(RECOVERABLE)) + return true; + break; + } + } + + return false; + } + + /** 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 = new ComputeAnnotationSet(elementUtils); + // Use annotation processing to compute the set of annotations present + annotationsPresent = new LinkedHashSet<>(); + for (ClassSymbol classSym : topLevelClasses) + annotationComputer.scan(classSym, annotationsPresent); + for (PackageSymbol pkgSym : packageInfoFiles) + annotationComputer.scan(pkgSym, annotationsPresent); + for (ModuleSymbol mdlSym : moduleInfoFiles) + annotationComputer.scan(mdlSym, annotationsPresent); + } + + /** Enter a set of generated class files. */ + private List enterClassFiles(Map> modulesAndClassFiles) { + List list = List.nil(); + + for (Entry> moduleAndClassFiles : modulesAndClassFiles.entrySet()) { + for (Map.Entry entry : moduleAndClassFiles.getValue().entrySet()) { + Name name = names.fromString(entry.getKey()); + JavaFileObject file = entry.getValue(); + if (file.getKind() != JavaFileObject.Kind.CLASS) + throw new AssertionError(file); + ClassSymbol cs; + if (isPkgInfo(file, JavaFileObject.Kind.CLASS)) { + Name packageName = Convert.packagePart(name); + PackageSymbol p = symtab.enterPackage(moduleAndClassFiles.getKey(), packageName); + if (p.package_info == null) + p.package_info = symtab.enterClass(moduleAndClassFiles.getKey(), Convert.shortName(name), p); + cs = p.package_info; + cs.reset(); + if (cs.classfile == null) + cs.classfile = file; + cs.completer = initialCompleter; + } else { + cs = symtab.enterClass(moduleAndClassFiles.getKey(), name); + cs.reset(); + cs.classfile = file; + cs.completer = initialCompleter; + cs.owner.members().enter(cs); //XXX - OverwriteBetweenCompilations; syms.getClass is not sufficient anymore + } + list = list.prepend(cs); + } + } + return list.reverse(); + } + + /** Enter a set of syntax trees. */ + private void enterTrees(List roots) { + compiler.enterTrees(roots); + } + + /** Run a processing round. */ + void run(boolean lastRound, boolean errorStatus) { + printRoundInfo(lastRound); + + if (!taskListener.isEmpty()) + taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND)); + + try { + if (lastRound) { + filer.setLastRound(true); + Set emptyRootElements = Collections.emptySet(); // immutable + RoundEnvironment renv = new JavacRoundEnvironment(true, + errorStatus, + emptyRootElements, + JavacProcessingEnvironment.this); + discoveredProcs.iterator().runContributingProcs(renv); + } else { + discoverAndRunProcs(annotationsPresent, topLevelClasses, packageInfoFiles, moduleInfoFiles); + } + } catch (Throwable t) { + // we're specifically expecting Abort here, but if any Throwable + // comes by, we should flush all deferred diagnostics, rather than + // drop them on the ground. + deferredDiagnosticHandler.reportDeferredDiagnostics(); + log.popDiagnosticHandler(deferredDiagnosticHandler); + compiler.setDeferredDiagnosticHandler(null); + throw t; + } finally { + if (!taskListener.isEmpty()) + taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND)); + } + } + + void showDiagnostics(boolean showAll) { + Set kinds = EnumSet.allOf(JCDiagnostic.Kind.class); + if (!showAll) { + // suppress errors, which are all presumed to be transient resolve errors + kinds.remove(JCDiagnostic.Kind.ERROR); + } + deferredDiagnosticHandler.reportDeferredDiagnostics(kinds); + log.popDiagnosticHandler(deferredDiagnosticHandler); + compiler.setDeferredDiagnosticHandler(null); + } + + /** Print info about this round. */ + private void printRoundInfo(boolean lastRound) { + if (printRounds || verbose) { + List tlc = lastRound ? List.nil() : topLevelClasses; + Set ap = lastRound ? Collections.emptySet() : annotationsPresent; + log.printLines("x.print.rounds", + number, + "{" + tlc.toString(", ") + "}", + ap, + lastRound); + } + } + + /** Prepare for new round of annotation processing. Cleans trees, resets symbols, and + * asks selected services to prepare to a new round of annotation processing. + */ + private void newRound() { + //ensure treesToClean contains all trees, including implicitly parsed ones + for (Env env : enter.getEnvs()) { + treesToClean.add(env.toplevel); + } + for (JCCompilationUnit node : treesToClean) { + treeCleaner.scan(node); + } + chk.newRound(); + enter.newRound(); + filer.newRound(); + messager.newRound(); + compiler.newRound(); + modules.newRound(); + types.newRound(); + annotate.newRound(); + + boolean foundError = false; + + for (ClassSymbol cs : symtab.getAllClasses()) { + if (cs.kind == ERR) { + foundError = true; + break; + } + } + + if (foundError) { + for (ClassSymbol cs : symtab.getAllClasses()) { + if (cs.classfile != null || cs.kind == ERR) { + cs.reset(); + cs.type = new ClassType(cs.type.getEnclosingType(), null, cs); + if (cs.isCompleted()) { + cs.completer = initialCompleter; + } + } + } + } + } + } + + + // TODO: internal catch clauses?; catch and rethrow an annotation + // processing error + public boolean doProcessing(List roots, + List classSymbols, + Iterable pckSymbols, + Log.DeferredDiagnosticHandler deferredDiagnosticHandler) { + final Set treesToClean = + Collections.newSetFromMap(new IdentityHashMap()); + + //fill already attributed implicit trees: + for (Env env : enter.getEnvs()) { + treesToClean.add(env.toplevel); + } + + Set specifiedPackages = new LinkedHashSet<>(); + for (PackageSymbol psym : pckSymbols) + specifiedPackages.add(psym); + this.specifiedPackages = Collections.unmodifiableSet(specifiedPackages); + + Round round = new Round(roots, classSymbols, treesToClean, deferredDiagnosticHandler); + + boolean errorStatus; + boolean moreToDo; + do { + // Run processors for round n + round.run(false, false); + + // Processors for round n have run to completion. + // Check for errors and whether there is more work to do. + errorStatus = round.unrecoverableError(); + moreToDo = moreToDo(); + + round.showDiagnostics(errorStatus || showResolveErrors); + + // Set up next round. + // Copy mutable collections returned from filer. + round = round.next( + new LinkedHashSet<>(filer.getGeneratedSourceFileObjects()), + new LinkedHashMap<>(filer.getGeneratedClasses())); + + // Check for errors during setup. + if (round.unrecoverableError()) + errorStatus = true; + + } while (moreToDo && !errorStatus); + + // run last round + round.run(true, errorStatus); + round.showDiagnostics(true); + + 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. + */ + if (messager.errorRaised() + || werror && round.warningCount() > 0 && round.errorCount() > 0) + errorStatus = true; + + Set newSourceFiles = + new LinkedHashSet<>(filer.getGeneratedSourceFileObjects()); + roots = round.roots; + + errorStatus = errorStatus || (compiler.errorCount() > 0); + + round.finalCompiler(); + + if (newSourceFiles.size() > 0) + roots = roots.appendList(compiler.parseFiles(newSourceFiles)); + + errorStatus = errorStatus || (compiler.errorCount() > 0); + + // Free resources + this.close(); + + if (errorStatus && compiler.errorCount() == 0) { + compiler.log.nerrors++; + } + + compiler.enterTreesIfNeeded(roots); + + if (!taskListener.isEmpty()) + taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING)); + + return true; + } + + private void warnIfUnmatchedOptions() { + if (!unmatchedProcessorOptions.isEmpty()) { + log.warning(Warnings.ProcUnmatchedProcessorOptions(unmatchedProcessorOptions.toString())); + } + } + + /** + * Free resources related to annotation processing. + */ + public void close() { + filer.close(); + if (discoveredProcs != null) // Make calling close idempotent + discoveredProcs.close(); + discoveredProcs = null; + } + + private List getTopLevelClasses(List units) { + List classes = List.nil(); + for (JCCompilationUnit unit : units) { + for (JCTree node : unit.defs) { + if (node.hasTag(JCTree.Tag.CLASSDEF)) { + ClassSymbol sym = ((JCClassDecl) node).sym; + Assert.checkNonNull(sym); + classes = classes.prepend(sym); + } + } + } + return classes.reverse(); + } + + private List getTopLevelClassesFromClasses(List syms) { + List classes = List.nil(); + for (ClassSymbol sym : syms) { + if (!isPkgInfo(sym)) { + classes = classes.prepend(sym); + } + } + return classes.reverse(); + } + + private List getPackageInfoFiles(List units) { + List packages = List.nil(); + for (JCCompilationUnit unit : units) { + if (isPkgInfo(unit.sourcefile, JavaFileObject.Kind.SOURCE)) { + packages = packages.prepend(unit.packge); + } + } + return packages.reverse(); + } + + private List getPackageInfoFilesFromClasses(List syms) { + List packages = List.nil(); + for (ClassSymbol sym : syms) { + if (isPkgInfo(sym)) { + packages = packages.prepend((PackageSymbol) sym.owner); + } + } + return packages.reverse(); + } + + private List getModuleInfoFiles(List units) { + List modules = List.nil(); + for (JCCompilationUnit unit : units) { + if (isModuleInfo(unit.sourcefile, JavaFileObject.Kind.SOURCE) && + unit.defs.nonEmpty() && + unit.defs.head.hasTag(Tag.MODULEDEF)) { + modules = modules.prepend(unit.modle); + } + } + return modules.reverse(); + } + + // avoid unchecked warning from use of varargs + private static List join(List list1, List list2) { + return list1.appendList(list2); + } + + private boolean isPkgInfo(JavaFileObject fo, JavaFileObject.Kind kind) { + return fo.isNameCompatible("package-info", kind); + } + + private boolean isPkgInfo(ClassSymbol sym) { + return isPkgInfo(sym.classfile, JavaFileObject.Kind.CLASS) && (sym.packge().package_info == sym); + } + + private boolean isModuleInfo(JavaFileObject fo, JavaFileObject.Kind kind) { + return fo.isNameCompatible("module-info", kind); + } + + /* + * Called retroactively to determine if a class loader was required, + * after we have failed to create one. + */ + private boolean needClassLoader(String procNames, Iterable workingpath) { + if (procNames != null) + return true; + + URL[] urls = new URL[1]; + for(Path pathElement : workingpath) { + try { + urls[0] = pathElement.toUri().toURL(); + if (ServiceProxy.hasService(Processor.class, urls)) + return true; + } catch (MalformedURLException ex) { + throw new AssertionError(ex); + } + catch (ServiceProxy.ServiceConfigurationError e) { + log.error(Errors.ProcBadConfigFile(e.getLocalizedMessage())); + return true; + } + } + + return false; + } + + class ImplicitCompleter implements Completer { + + private final JCCompilationUnit topLevel; + + public ImplicitCompleter(JCCompilationUnit topLevel) { + this.topLevel = topLevel; + } + + @Override public void complete(Symbol sym) throws CompletionFailure { + compiler.readSourceFile(topLevel, (ClassSymbol) sym); + } + } + + private final TreeScanner treeCleaner = new TreeScanner() { + public void scan(JCTree node) { + super.scan(node); + if (node != null) + node.type = null; + } + JCCompilationUnit topLevel; + public void visitTopLevel(JCCompilationUnit node) { + if (node.packge != null) { + if (node.packge.package_info != null) { + node.packge.package_info.reset(); + } + node.packge.reset(); + } + boolean isModuleInfo = node.sourcefile.isNameCompatible("module-info", Kind.SOURCE); + if (isModuleInfo) { + node.modle.reset(); + node.modle.completer = sym -> modules.enter(List.of(node), node.modle.module_info); + node.modle.module_info.reset(); + node.modle.module_info.members_field = WriteableScope.create(node.modle.module_info); + } + node.packge = null; + topLevel = node; + try { + super.visitTopLevel(node); + } finally { + topLevel = null; + } + } + public void visitClassDef(JCClassDecl node) { + super.visitClassDef(node); + // remove generated constructor that may have been added during attribution: + List beforeConstructor = List.nil(); + List defs = node.defs; + while (defs.nonEmpty() && !defs.head.hasTag(Tag.METHODDEF)) { + beforeConstructor = beforeConstructor.prepend(defs.head); + defs = defs.tail; + } + if (defs.nonEmpty() && + (((JCMethodDecl) defs.head).mods.flags & Flags.GENERATEDCONSTR) != 0) { + defs = defs.tail; + while (beforeConstructor.nonEmpty()) { + defs = defs.prepend(beforeConstructor.head); + beforeConstructor = beforeConstructor.tail; + } + node.defs = defs; + } + if (node.sym != null) { + node.sym.completer = new ImplicitCompleter(topLevel); + } + node.sym = null; + } + public void visitMethodDef(JCMethodDecl node) { + // remove super constructor call that may have been added during attribution: + if (TreeInfo.isConstructor(node) && node.sym != null && node.sym.owner.isEnum() && + node.body.stats.nonEmpty() && TreeInfo.isSuperCall(node.body.stats.head) && + node.body.stats.head.pos == node.body.pos) { + node.body.stats = node.body.stats.tail; + } + node.sym = null; + super.visitMethodDef(node); + } + public void visitVarDef(JCVariableDecl node) { + node.sym = null; + super.visitVarDef(node); + } + public void visitNewClass(JCNewClass node) { + node.constructor = null; + super.visitNewClass(node); + } + public void visitAssignop(JCAssignOp node) { + node.operator = null; + super.visitAssignop(node); + } + public void visitUnary(JCUnary node) { + node.operator = null; + super.visitUnary(node); + } + public void visitBinary(JCBinary node) { + node.operator = null; + super.visitBinary(node); + } + public void visitSelect(JCFieldAccess node) { + node.sym = null; + super.visitSelect(node); + } + public void visitIdent(JCIdent node) { + node.sym = null; + super.visitIdent(node); + } + public void visitAnnotation(JCAnnotation node) { + node.attribute = null; + super.visitAnnotation(node); + } + }; + + + private boolean moreToDo() { + return filer.newFiles(); + } + + /** + * {@inheritDoc} + * + * Command line options suitable for presenting to annotation + * processors. + * {@literal "-Afoo=bar"} should be {@literal "-Afoo" => "bar"}. + */ + @DefinedBy(Api.ANNOTATION_PROCESSING) + public Map getOptions() { + return processorOptions; + } + + @DefinedBy(Api.ANNOTATION_PROCESSING) + public Messager getMessager() { + return messager; + } + + @DefinedBy(Api.ANNOTATION_PROCESSING) + public JavacFiler getFiler() { + return filer; + } + + @DefinedBy(Api.ANNOTATION_PROCESSING) + public JavacElements getElementUtils() { + return elementUtils; + } + + @DefinedBy(Api.ANNOTATION_PROCESSING) + public JavacTypes getTypeUtils() { + return typeUtils; + } + + @DefinedBy(Api.ANNOTATION_PROCESSING) + public SourceVersion getSourceVersion() { + return Source.toSourceVersion(source); + } + + @DefinedBy(Api.ANNOTATION_PROCESSING) + public Locale getLocale() { + return messages.getCurrentLocale(); + } + + public Set getSpecifiedPackages() { + return specifiedPackages; + } + + public static final Pattern noMatches = Pattern.compile("(\\P{all})+"); + + /** + * Convert import-style string for supported annotations into a + * regex matching that string. If the string is not a valid + * import-style string, return a regex that won't match anything. + */ + private static Pattern importStringToPattern(boolean allowModules, String s, Processor p, Log log) { + String module; + String pkg; + int slash = s.indexOf('/'); + if (slash == (-1)) { + if (s.equals("*")) { + return MatchingUtils.validImportStringToPattern(s); + } + module = allowModules ? ".*/" : ""; + pkg = s; + } else { + module = Pattern.quote(s.substring(0, slash + 1)); + pkg = s.substring(slash + 1); + } + if (MatchingUtils.isValidImportString(pkg)) { + return Pattern.compile(module + MatchingUtils.validImportStringToPatternString(pkg)); + } else { + log.warning(Warnings.ProcMalformedSupportedString(s, p.getClass().getName())); + return noMatches; // won't match any valid identifier + } + } + + /** + * For internal use only. This method may be removed without warning. + */ + public Context getContext() { + return context; + } + + /** + * For internal use only. This method may be removed without warning. + */ + public ClassLoader getProcessorClassLoader() { + return processorClassLoader; + } + + public String toString() { + return "javac ProcessingEnvironment"; + } + + public static boolean isValidOptionName(String optionName) { + for(String s : optionName.split("\\.", -1)) { + if (!SourceVersion.isIdentifier(s)) + return false; + } + return true; + } +}