src/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java

Tue, 24 Feb 2009 17:48:53 -0800

author
darcy
date
Tue, 24 Feb 2009 17:48:53 -0800
changeset 232
1fbc1cc6e260
parent 184
905e151a185a
child 285
4ce1c1400334
permissions
-rw-r--r--

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

mercurial