1 /*
   2  * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.javac.processing;
  27 
  28 import java.io.Closeable;
  29 import java.io.FileNotFoundException;
  30 import java.io.InputStream;
  31 import java.io.OutputStream;
  32 import java.io.FilterOutputStream;
  33 import java.io.Reader;
  34 import java.io.Writer;
  35 import java.io.FilterWriter;
  36 import java.io.PrintWriter;
  37 import java.io.IOException;
  38 import java.util.*;
  39 
  40 import static java.util.Collections.*;
  41 
  42 import javax.annotation.processing.*;
  43 import javax.lang.model.SourceVersion;
  44 import javax.lang.model.element.NestingKind;
  45 import javax.lang.model.element.Modifier;
  46 import javax.lang.model.element.Element;
  47 import javax.tools.*;
  48 import javax.tools.JavaFileManager.Location;
  49 
  50 import static javax.tools.StandardLocation.SOURCE_OUTPUT;
  51 import static javax.tools.StandardLocation.CLASS_OUTPUT;
  52 
  53 import com.sun.tools.javac.code.Lint;
  54 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  55 import com.sun.tools.javac.code.Symbol.ModuleSymbol;
  56 import com.sun.tools.javac.code.Symbol.TypeSymbol;
  57 import com.sun.tools.javac.code.Symtab;
  58 import com.sun.tools.javac.comp.Modules;
  59 import com.sun.tools.javac.model.JavacElements;
  60 import com.sun.tools.javac.resources.CompilerProperties.Warnings;
  61 import com.sun.tools.javac.util.*;
  62 import com.sun.tools.javac.util.DefinedBy.Api;
  63 
  64 import static com.sun.tools.javac.code.Lint.LintCategory.PROCESSING;
  65 import com.sun.tools.javac.code.Symbol.PackageSymbol;
  66 import com.sun.tools.javac.main.Option;
  67 
  68 /**
  69  * The FilerImplementation class must maintain a number of
  70  * constraints.  First, multiple attempts to open the same path within
  71  * the same invocation of the tool results in an IOException being
  72  * thrown.  For example, trying to open the same source file twice:
  73  *
  74  * <pre>
  75  * createSourceFile("foo.Bar")
  76  * ...
  77  * createSourceFile("foo.Bar")
  78  * </pre>
  79  *
  80  * is disallowed as is opening a text file that happens to have
  81  * the same name as a source file:
  82  *
  83  * <pre>
  84  * createSourceFile("foo.Bar")
  85  * ...
  86  * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null)
  87  * </pre>
  88  *
  89  * <p>Additionally, creating a source file that corresponds to an
  90  * already created class file (or vice versa) also results in an
  91  * IOException since each type can only be created once.  However, if
  92  * the Filer is used to create a text file named *.java that happens
  93  * to correspond to an existing class file, a warning is *not*
  94  * generated.  Similarly, a warning is not generated for a binary file
  95  * named *.class and an existing source file.
  96  *
  97  * <p>The reason for this difference is that source files and class
  98  * files are registered with the tool and can get passed on as
  99  * declarations to the next round of processing.  Files that are just
 100  * named *.java and *.class are not processed in that manner; although
 101  * having extra source files and class files on the source path and
 102  * class path can alter the behavior of the tool and any final
 103  * compile.
 104  *
 105  * <p><b>This is NOT part of any supported API.
 106  * If you write code that depends on this, you do so at your own risk.
 107  * This code and its internal interfaces are subject to change or
 108  * deletion without notice.</b>
 109  */
 110 public class JavacFiler implements Filer, Closeable {
 111     // TODO: Implement different transaction model for updating the
 112     // Filer's record keeping on file close.
 113 
 114     private static final String ALREADY_OPENED =
 115         "Output stream or writer has already been opened.";
 116     private static final String NOT_FOR_READING =
 117         "FileObject was not opened for reading.";
 118     private static final String NOT_FOR_WRITING =
 119         "FileObject was not opened for writing.";
 120 
 121     /**
 122      * Wrap a JavaFileObject to manage writing by the Filer.
 123      */
 124     private class FilerOutputFileObject extends ForwardingFileObject<FileObject> {
 125         private boolean opened = false;
 126         private ModuleSymbol mod;
 127         private String name;
 128 
 129         FilerOutputFileObject(ModuleSymbol mod, String name, FileObject fileObject) {
 130             super(fileObject);
 131             this.mod = mod;
 132             this.name = name;
 133         }
 134 
 135         @Override @DefinedBy(Api.COMPILER)
 136         public synchronized OutputStream openOutputStream() throws IOException {
 137             if (opened)
 138                 throw new IOException(ALREADY_OPENED);
 139             opened = true;
 140             return new FilerOutputStream(mod, name, fileObject);
 141         }
 142 
 143         @Override @DefinedBy(Api.COMPILER)
 144         public synchronized Writer openWriter() throws IOException {
 145             if (opened)
 146                 throw new IOException(ALREADY_OPENED);
 147             opened = true;
 148             return new FilerWriter(mod, name, fileObject);
 149         }
 150 
 151         // Three anti-literacy methods
 152         @Override @DefinedBy(Api.COMPILER)
 153         public InputStream openInputStream() throws IOException {
 154             throw new IllegalStateException(NOT_FOR_READING);
 155         }
 156 
 157         @Override @DefinedBy(Api.COMPILER)
 158         public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
 159             throw new IllegalStateException(NOT_FOR_READING);
 160         }
 161 
 162         @Override @DefinedBy(Api.COMPILER)
 163         public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
 164             throw new IllegalStateException(NOT_FOR_READING);
 165         }
 166 
 167         @Override @DefinedBy(Api.COMPILER)
 168         public boolean delete() {
 169             return false;
 170         }
 171     }
 172 
 173     private class FilerOutputJavaFileObject extends FilerOutputFileObject implements JavaFileObject {
 174         private final JavaFileObject javaFileObject;
 175         FilerOutputJavaFileObject(ModuleSymbol mod, String name, JavaFileObject javaFileObject) {
 176             super(mod, name, javaFileObject);
 177             this.javaFileObject = javaFileObject;
 178         }
 179 
 180         @DefinedBy(Api.COMPILER)
 181         public JavaFileObject.Kind getKind() {
 182             return javaFileObject.getKind();
 183         }
 184 
 185         @DefinedBy(Api.COMPILER)
 186         public boolean isNameCompatible(String simpleName,
 187                                         JavaFileObject.Kind kind) {
 188             return javaFileObject.isNameCompatible(simpleName, kind);
 189         }
 190 
 191         @DefinedBy(Api.COMPILER)
 192         public NestingKind getNestingKind() {
 193             return javaFileObject.getNestingKind();
 194         }
 195 
 196         @DefinedBy(Api.COMPILER)
 197         public Modifier getAccessLevel() {
 198             return javaFileObject.getAccessLevel();
 199         }
 200     }
 201 
 202     /**
 203      * Wrap a JavaFileObject to manage reading by the Filer.
 204      */
 205     private class FilerInputFileObject extends ForwardingFileObject<FileObject> {
 206         FilerInputFileObject(FileObject fileObject) {
 207             super(fileObject);
 208         }
 209 
 210         @Override @DefinedBy(Api.COMPILER)
 211         public OutputStream openOutputStream() throws IOException {
 212             throw new IllegalStateException(NOT_FOR_WRITING);
 213         }
 214 
 215         @Override @DefinedBy(Api.COMPILER)
 216         public Writer openWriter() throws IOException {
 217             throw new IllegalStateException(NOT_FOR_WRITING);
 218         }
 219 
 220         @Override @DefinedBy(Api.COMPILER)
 221         public boolean delete() {
 222             return false;
 223         }
 224     }
 225 
 226     private class FilerInputJavaFileObject extends FilerInputFileObject implements JavaFileObject {
 227         private final JavaFileObject javaFileObject;
 228         FilerInputJavaFileObject(JavaFileObject javaFileObject) {
 229             super(javaFileObject);
 230             this.javaFileObject = javaFileObject;
 231         }
 232 
 233         @DefinedBy(Api.COMPILER)
 234         public JavaFileObject.Kind getKind() {
 235             return javaFileObject.getKind();
 236         }
 237 
 238         @DefinedBy(Api.COMPILER)
 239         public boolean isNameCompatible(String simpleName,
 240                                         JavaFileObject.Kind kind) {
 241             return javaFileObject.isNameCompatible(simpleName, kind);
 242         }
 243 
 244         @DefinedBy(Api.COMPILER)
 245         public NestingKind getNestingKind() {
 246             return javaFileObject.getNestingKind();
 247         }
 248 
 249         @DefinedBy(Api.COMPILER)
 250         public Modifier getAccessLevel() {
 251             return javaFileObject.getAccessLevel();
 252         }
 253     }
 254 
 255 
 256     /**
 257      * Wrap a {@code OutputStream} returned from the {@code
 258      * JavaFileManager} to properly register source or class files
 259      * when they are closed.
 260      */
 261     private class FilerOutputStream extends FilterOutputStream {
 262         ModuleSymbol mod;
 263         String typeName;
 264         FileObject fileObject;
 265         boolean closed = false;
 266 
 267         /**
 268          * @param typeName name of class or {@code null} if just a
 269          * binary file
 270          */
 271         FilerOutputStream(ModuleSymbol mod, String typeName, FileObject fileObject) throws IOException {
 272             super(fileObject.openOutputStream());
 273             this.mod = mod;
 274             this.typeName = typeName;
 275             this.fileObject = fileObject;
 276         }
 277 
 278         public synchronized void close() throws IOException {
 279             if (!closed) {
 280                 closed = true;
 281                 /*
 282                  * If an IOException occurs when closing the underlying
 283                  * stream, still try to process the file.
 284                  */
 285 
 286                 closeFileObject(mod, typeName, fileObject);
 287                 out.close();
 288             }
 289         }
 290     }
 291 
 292     /**
 293      * Wrap a {@code Writer} returned from the {@code JavaFileManager}
 294      * to properly register source or class files when they are
 295      * closed.
 296      */
 297     private class FilerWriter extends FilterWriter {
 298         ModuleSymbol mod;
 299         String typeName;
 300         FileObject fileObject;
 301         boolean closed = false;
 302 
 303         /**
 304          * @param fileObject the fileObject to be written to
 305          * @param typeName name of source file or {@code null} if just a
 306          * text file
 307          */
 308         FilerWriter(ModuleSymbol mod, String typeName, FileObject fileObject) throws IOException {
 309             super(fileObject.openWriter());
 310             this.mod = mod;
 311             this.typeName = typeName;
 312             this.fileObject = fileObject;
 313         }
 314 
 315         public synchronized void close() throws IOException {
 316             if (!closed) {
 317                 closed = true;
 318                 /*
 319                  * If an IOException occurs when closing the underlying
 320                  * Writer, still try to process the file.
 321                  */
 322 
 323                 closeFileObject(mod, typeName, fileObject);
 324                 out.close();
 325             }
 326         }
 327     }
 328 
 329     JavaFileManager fileManager;
 330     JavacElements elementUtils;
 331     Log log;
 332     Modules modules;
 333     Names names;
 334     Symtab syms;
 335     Context context;
 336     boolean lastRound;
 337 
 338     private final boolean lint;
 339 
 340     /**
 341      * Initial inputs passed to the tool.  This set must be
 342      * synchronized.
 343      */
 344     private final Set<FileObject> initialInputs;
 345 
 346     /**
 347      * Logical names of all created files.  This set must be
 348      * synchronized.
 349      */
 350     private final Set<FileObject> fileObjectHistory;
 351 
 352     /**
 353      * Names of types that have had files created but not closed.
 354      */
 355     private final Set<String> openTypeNames;
 356 
 357     /**
 358      * Names of source files closed in this round.  This set must be
 359      * synchronized.  Its iterators should preserve insertion order.
 360      */
 361     private Set<String> generatedSourceNames;
 362 
 363     /**
 364      * Names and class files of the class files closed in this round.
 365      * This set must be synchronized.  Its iterators should preserve
 366      * insertion order.
 367      */
 368     private final Map<ModuleSymbol, Map<String, JavaFileObject>> generatedClasses;
 369 
 370     /**
 371      * JavaFileObjects for source files closed in this round.  This
 372      * set must be synchronized.  Its iterators should preserve
 373      * insertion order.
 374      */
 375     private Set<JavaFileObject> generatedSourceFileObjects;
 376 
 377     /**
 378      * Names of all created source files.  Its iterators should
 379      * preserve insertion order.
 380      */
 381     private final Set<Pair<ModuleSymbol, String>> aggregateGeneratedSourceNames;
 382 
 383     /**
 384      * Names of all created class files.  Its iterators should
 385      * preserve insertion order.
 386      */
 387     private final Set<Pair<ModuleSymbol, String>> aggregateGeneratedClassNames;
 388 
 389     private final Set<String> initialClassNames;
 390 
 391     private final String defaultTargetModule;
 392 
 393     JavacFiler(Context context) {
 394         this.context = context;
 395         fileManager = context.get(JavaFileManager.class);
 396         elementUtils = JavacElements.instance(context);
 397 
 398         log = Log.instance(context);
 399         modules = Modules.instance(context);
 400         names = Names.instance(context);
 401         syms = Symtab.instance(context);
 402 
 403         initialInputs = synchronizedSet(new LinkedHashSet<>());
 404         fileObjectHistory = synchronizedSet(new LinkedHashSet<>());
 405         generatedSourceNames = synchronizedSet(new LinkedHashSet<>());
 406         generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<>());
 407 
 408         generatedClasses = synchronizedMap(new LinkedHashMap<>());
 409 
 410         openTypeNames  = synchronizedSet(new LinkedHashSet<>());
 411 
 412         aggregateGeneratedSourceNames = new LinkedHashSet<>();
 413         aggregateGeneratedClassNames  = new LinkedHashSet<>();
 414         initialClassNames  = new LinkedHashSet<>();
 415 
 416         lint = (Lint.instance(context)).isEnabled(PROCESSING);
 417 
 418         Options options = Options.instance(context);
 419 
 420         defaultTargetModule = options.get(Option.DEFAULT_MODULE_FOR_CREATED_FILES);
 421     }
 422 
 423     @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
 424     public JavaFileObject createSourceFile(CharSequence nameAndModule,
 425                                            Element... originatingElements) throws IOException {
 426         Pair<ModuleSymbol, String> moduleAndClass = checkOrInferModule(nameAndModule);
 427         return createSourceOrClassFile(moduleAndClass.fst, true, moduleAndClass.snd);
 428     }
 429 
 430     @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
 431     public JavaFileObject createClassFile(CharSequence nameAndModule,
 432                                           Element... originatingElements) throws IOException {
 433         Pair<ModuleSymbol, String> moduleAndClass = checkOrInferModule(nameAndModule);
 434         return createSourceOrClassFile(moduleAndClass.fst, false, moduleAndClass.snd);
 435     }
 436 
 437     private Pair<ModuleSymbol, String> checkOrInferModule(CharSequence moduleAndPkg) throws FilerException {
 438         String moduleAndPkgString = moduleAndPkg.toString();
 439         int slash = moduleAndPkgString.indexOf('/');
 440         String module;
 441         String pkg;
 442 
 443         if (slash == (-1)) {
 444             //module name not specified:
 445             int lastDot = moduleAndPkgString.lastIndexOf('.');
 446             String pack = lastDot != (-1) ? moduleAndPkgString.substring(0, lastDot) : "";
 447             ModuleSymbol msym = inferModule(pack);
 448 
 449             if (msym != null) {
 450                 return Pair.of(msym, moduleAndPkgString);
 451             }
 452 
 453             if (defaultTargetModule == null) {
 454                 throw new FilerException("Cannot determine target module.");
 455             }
 456 
 457             module = defaultTargetModule;
 458             pkg = moduleAndPkgString;
 459         } else {
 460             //module name specified:
 461             module = moduleAndPkgString.substring(0, slash);
 462             pkg = moduleAndPkgString.substring(slash + 1);
 463         }
 464 
 465         ModuleSymbol explicitModule = syms.getModule(names.fromString(module));
 466 
 467         if (explicitModule == null) {
 468             throw new FilerException("Module: " + module + " does not exist.");
 469         }
 470 
 471         if (!modules.isRootModule(explicitModule)) {
 472             throw new FilerException("Cannot write to the given module.");
 473         }
 474 
 475         return Pair.of(explicitModule, pkg);
 476     }
 477 
 478     private JavaFileObject createSourceOrClassFile(ModuleSymbol mod, boolean isSourceFile, String name) throws IOException {
 479         Assert.checkNonNull(mod);
 480 
 481         if (lint) {
 482             int periodIndex = name.lastIndexOf(".");
 483             if (periodIndex != -1) {
 484                 String base = name.substring(periodIndex);
 485                 String extn = (isSourceFile ? ".java" : ".class");
 486                 if (base.equals(extn))
 487                     log.warning(Warnings.ProcSuspiciousClassName(name, extn));
 488             }
 489         }
 490         checkNameAndExistence(mod, name, isSourceFile);
 491         Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT);
 492 
 493         if (modules.multiModuleMode) {
 494             loc = this.fileManager.getLocationForModule(loc, mod.name.toString());
 495         }
 496         JavaFileObject.Kind kind = (isSourceFile ?
 497                                     JavaFileObject.Kind.SOURCE :
 498                                     JavaFileObject.Kind.CLASS);
 499 
 500         JavaFileObject fileObject =
 501             fileManager.getJavaFileForOutput(loc, name, kind, null);
 502         checkFileReopening(fileObject, true);
 503 
 504         if (lastRound)
 505             log.warning(Warnings.ProcFileCreateLastRound(name));
 506 
 507         if (isSourceFile)
 508             aggregateGeneratedSourceNames.add(Pair.of(mod, name));
 509         else
 510             aggregateGeneratedClassNames.add(Pair.of(mod, name));
 511         openTypeNames.add(name);
 512 
 513         return new FilerOutputJavaFileObject(mod, name, fileObject);
 514     }
 515 
 516     @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
 517     public FileObject createResource(JavaFileManager.Location location,
 518                                      CharSequence moduleAndPkg,
 519                                      CharSequence relativeName,
 520                                      Element... originatingElements) throws IOException {
 521         Tuple3<Location, ModuleSymbol, String> locationModuleAndPackage = checkOrInferModule(location, moduleAndPkg, true);
 522         location = locationModuleAndPackage.a;
 523         ModuleSymbol msym = locationModuleAndPackage.b;
 524         String pkg = locationModuleAndPackage.c;
 525 
 526         locationCheck(location);
 527 
 528         String strPkg = pkg.toString();
 529         if (strPkg.length() > 0)
 530             checkName(strPkg);
 531 
 532         FileObject fileObject =
 533             fileManager.getFileForOutput(location, strPkg,
 534                                          relativeName.toString(), null);
 535         checkFileReopening(fileObject, true);
 536 
 537         if (fileObject instanceof JavaFileObject)
 538             return new FilerOutputJavaFileObject(msym, null, (JavaFileObject)fileObject);
 539         else
 540             return new FilerOutputFileObject(msym, null, fileObject);
 541     }
 542 
 543     private void locationCheck(JavaFileManager.Location location) {
 544         if (location instanceof StandardLocation) {
 545             StandardLocation stdLoc = (StandardLocation) location;
 546             if (!stdLoc.isOutputLocation())
 547                 throw new IllegalArgumentException("Resource creation not supported in location " +
 548                                                    stdLoc);
 549         }
 550     }
 551 
 552     @Override @DefinedBy(Api.ANNOTATION_PROCESSING)
 553     public FileObject getResource(JavaFileManager.Location location,
 554                                   CharSequence moduleAndPkg,
 555                                   CharSequence relativeName) throws IOException {
 556         Tuple3<Location, ModuleSymbol, String> locationModuleAndPackage = checkOrInferModule(location, moduleAndPkg, false);
 557         location = locationModuleAndPackage.a;
 558         String pkg = locationModuleAndPackage.c;
 559 
 560         if (pkg.length() > 0)
 561             checkName(pkg);
 562 
 563         // TODO: Only support reading resources in selected output
 564         // locations?  Only allow reading of non-source, non-class
 565         // files from the supported input locations?
 566 
 567         // In the following, getFileForInput is the "obvious" method
 568         // to use, but it does not have the "obvious" semantics for
 569         // SOURCE_OUTPUT and CLASS_OUTPUT. Conversely, getFileForOutput
 570         // does not have the correct semantics for any "path" location
 571         // with more than one component. So, for now, we use a hybrid
 572         // invocation.
 573         FileObject fileObject;
 574         if (location.isOutputLocation()) {
 575             fileObject = fileManager.getFileForOutput(location,
 576                     pkg,
 577                     relativeName.toString(),
 578                     null);
 579         } else {
 580             fileObject = fileManager.getFileForInput(location,
 581                     pkg,
 582                     relativeName.toString());
 583         }
 584         if (fileObject == null) {
 585             String name = (pkg.length() == 0)
 586                     ? relativeName.toString() : (pkg + "/" + relativeName);
 587             throw new FileNotFoundException(name);
 588         }
 589 
 590         // If the path was already opened for writing, throw an exception.
 591         checkFileReopening(fileObject, false);
 592         return new FilerInputFileObject(fileObject);
 593     }
 594 
 595     private Tuple3<JavaFileManager.Location, ModuleSymbol, String> checkOrInferModule(JavaFileManager.Location location,
 596                                                            CharSequence moduleAndPkg,
 597                                                            boolean write) throws IOException {
 598         String moduleAndPkgString = moduleAndPkg.toString();
 599         int slash = moduleAndPkgString.indexOf('/');
 600         boolean multiModuleLocation = location.isModuleOrientedLocation() ||
 601                                       (modules.multiModuleMode && location.isOutputLocation());
 602         String module;
 603         String pkg;
 604 
 605         if (slash == (-1)) {
 606             //module name not specified:
 607             if (!multiModuleLocation) {
 608                 //package oriented location:
 609                 return new Tuple3<>(location, modules.getDefaultModule(), moduleAndPkgString);
 610             }
 611 
 612             if (location.isOutputLocation()) {
 613                 ModuleSymbol msym = inferModule(moduleAndPkgString);
 614 
 615                 if (msym != null) {
 616                     Location moduleLoc =
 617                             fileManager.getLocationForModule(location, msym.name.toString());
 618                     return new Tuple3<>(moduleLoc, msym, moduleAndPkgString);
 619                 }
 620             }
 621 
 622             if (defaultTargetModule == null) {
 623                 throw new FilerException("No module specified and the location is either " +
 624                                          "a module-oriented location, or a multi-module " +
 625                                          "output location.");
 626             }
 627 
 628             module = defaultTargetModule;
 629             pkg = moduleAndPkgString;
 630         } else {
 631             //module name specified:
 632             module = moduleAndPkgString.substring(0, slash);
 633             pkg = moduleAndPkgString.substring(slash + 1);
 634         }
 635 
 636         if (multiModuleLocation) {
 637             ModuleSymbol explicitModule = syms.getModule(names.fromString(module));
 638 
 639             if (explicitModule == null) {
 640                 throw new FilerException("Module: " + module + " does not exist.");
 641             }
 642 
 643             if (write && !modules.isRootModule(explicitModule)) {
 644                 throw new FilerException("Cannot write to the given module.");
 645             }
 646 
 647             Location moduleLoc = fileManager.getLocationForModule(location, module);
 648 
 649             return new Tuple3<>(moduleLoc, explicitModule, pkg);
 650         } else {
 651             throw new FilerException("Module specified but the location is neither " +
 652                                      "a module-oriented location, nor a multi-module " +
 653                                      "output location.");
 654         }
 655     }
 656 
 657     static final class Tuple3<A, B, C> {
 658         final A a;
 659         final B b;
 660         final C c;
 661 
 662         public Tuple3(A a, B b, C c) {
 663             this.a = a;
 664             this.b = b;
 665             this.c = c;
 666         }
 667     }
 668 
 669     private ModuleSymbol inferModule(String pkg) {
 670         if (modules.getDefaultModule() == syms.noModule)
 671             return modules.getDefaultModule();
 672 
 673         Set<ModuleSymbol> rootModules = modules.getRootModules();
 674 
 675         if (rootModules.size() == 1) {
 676             return rootModules.iterator().next();
 677         }
 678 
 679         PackageSymbol pack = elementUtils.getPackageElement(pkg);
 680 
 681         if (pack != null && pack.modle != syms.unnamedModule) {
 682             return pack.modle;
 683         }
 684 
 685         return null;
 686     }
 687 
 688     private void checkName(String name) throws FilerException {
 689         checkName(name, false);
 690     }
 691 
 692     private void checkName(String name, boolean allowUnnamedPackageInfo) throws FilerException {
 693         if (!SourceVersion.isName(name) && !isPackageInfo(name, allowUnnamedPackageInfo)) {
 694             if (lint)
 695                 log.warning(Warnings.ProcIllegalFileName(name));
 696             throw new FilerException("Illegal name " + name);
 697         }
 698     }
 699 
 700     private boolean isPackageInfo(String name, boolean allowUnnamedPackageInfo) {
 701         // Is the name of the form "package-info" or
 702         // "foo.bar.package-info"?
 703         final String PKG_INFO = "package-info";
 704         int periodIndex = name.lastIndexOf(".");
 705         if (periodIndex == -1) {
 706             return allowUnnamedPackageInfo ? name.equals(PKG_INFO) : false;
 707         } else {
 708             // "foo.bar.package-info." illegal
 709             String prefix = name.substring(0, periodIndex);
 710             String simple = name.substring(periodIndex+1);
 711             return SourceVersion.isName(prefix) && simple.equals(PKG_INFO);
 712         }
 713     }
 714 
 715     private void checkNameAndExistence(ModuleSymbol mod, String typename, boolean allowUnnamedPackageInfo) throws FilerException {
 716         checkName(typename, allowUnnamedPackageInfo);
 717         ClassSymbol existing = elementUtils.getTypeElement(typename);
 718         boolean alreadySeen = aggregateGeneratedSourceNames.contains(Pair.of(mod, typename)) ||
 719                               aggregateGeneratedClassNames.contains(Pair.of(mod, typename)) ||
 720                               initialClassNames.contains(typename) ||
 721                               containedInInitialInputs(typename);                          
 722         if (alreadySeen) {
 723             if (lint)
 724                 log.warning(Warnings.ProcTypeRecreate(typename));
 725             throw new FilerException("Attempt to recreate a file for type " + typename);
 726         }
 727         if (lint && existing != null) {
 728             log.warning(Warnings.ProcTypeAlreadyExists(typename));
 729         }
 730         if (!mod.isUnnamed() && !typename.contains(".")) {
 731             throw new FilerException("Attempt to create a type in unnamed package of a named module: " + typename);
 732         }
 733     }
 734 
 735     private boolean containedInInitialInputs(String typename) {
 736         // Name could be a type name or the name of a package-info file
 737         JavaFileObject sourceFile = null;
 738 
 739         ClassSymbol existingClass = elementUtils.getTypeElement(typename);
 740         if (existingClass != null) {
 741             sourceFile = existingClass.sourcefile;
 742         } else if (typename.endsWith(".package-info")) {
 743             String targetName = typename.substring(0, typename.length() - ".package-info".length());
 744             PackageSymbol existingPackage = elementUtils.getPackageElement(targetName);
 745             if (existingPackage != null)
 746                 sourceFile = existingPackage.sourcefile;
 747         }
 748         return (sourceFile == null) ? false : initialInputs.contains(sourceFile);
 749     }
 750 
 751     /**
 752      * Check to see if the file has already been opened; if so, throw
 753      * an exception, otherwise add it to the set of files.
 754      */
 755     private void checkFileReopening(FileObject fileObject, boolean forWriting) throws FilerException {
 756         if (isInFileObjectHistory(fileObject, forWriting)) {
 757             if (lint)
 758                 log.warning(Warnings.ProcFileReopening(fileObject.getName()));
 759             throw new FilerException("Attempt to reopen a file for path " + fileObject.getName());
 760         }
 761         if (forWriting)
 762             fileObjectHistory.add(fileObject);
 763     }
 764 
 765     private boolean isInFileObjectHistory(FileObject fileObject, boolean forWriting) {
 766         if (forWriting) {
 767             for(FileObject veteran : initialInputs) {
 768                 try {
 769                     if (fileManager.isSameFile(veteran, fileObject)) {
 770                         return true;
 771                     }
 772                 } catch (IllegalArgumentException e) {
 773                     //ignore...
 774                 }
 775             }
 776             for (String className : initialClassNames) {
 777                 try {
 778                     ClassSymbol existing = elementUtils.getTypeElement(className);
 779                     if (   existing != null
 780                         && (   (existing.sourcefile != null && fileManager.isSameFile(existing.sourcefile, fileObject))
 781                             || (existing.classfile != null && fileManager.isSameFile(existing.classfile, fileObject)))) {
 782                         return true;
 783                     }
 784                 } catch (IllegalArgumentException e) {
 785                     //ignore...
 786                 }
 787             }
 788         }
 789 
 790         for(FileObject veteran : fileObjectHistory) {
 791             if (fileManager.isSameFile(veteran, fileObject)) {
 792                 return true;
 793             }
 794         }
 795 
 796         return false;
 797     }
 798 
 799     public boolean newFiles() {
 800         return (!generatedSourceNames.isEmpty())
 801             || (!generatedClasses.isEmpty());
 802     }
 803 
 804     public Set<String> getGeneratedSourceNames() {
 805         return generatedSourceNames;
 806     }
 807 
 808     public Set<JavaFileObject> getGeneratedSourceFileObjects() {
 809         return generatedSourceFileObjects;
 810     }
 811 
 812     public Map<ModuleSymbol, Map<String, JavaFileObject>> getGeneratedClasses() {
 813         return generatedClasses;
 814     }
 815 
 816     public void warnIfUnclosedFiles() {
 817         if (!openTypeNames.isEmpty())
 818             log.warning(Warnings.ProcUnclosedTypeFiles(openTypeNames));
 819     }
 820 
 821     /**
 822      * Update internal state for a new round.
 823      */
 824     public void newRound() {
 825         clearRoundState();
 826     }
 827 
 828     void setLastRound(boolean lastRound) {
 829         this.lastRound = lastRound;
 830     }
 831 
 832     public void setInitialState(Collection<? extends JavaFileObject> initialInputs,
 833                                 Collection<String> initialClassNames) {
 834         this.initialInputs.addAll(initialInputs);
 835         this.initialClassNames.addAll(initialClassNames);
 836     }
 837 
 838     public void close() {
 839         clearRoundState();
 840         // Cross-round state
 841         initialClassNames.clear();
 842         initialInputs.clear();
 843         fileObjectHistory.clear();
 844         openTypeNames.clear();
 845         aggregateGeneratedSourceNames.clear();
 846         aggregateGeneratedClassNames.clear();
 847     }
 848 
 849     private void clearRoundState() {
 850         generatedSourceNames.clear();
 851         generatedSourceFileObjects.clear();
 852         generatedClasses.clear();
 853     }
 854 
 855     /**
 856      * Debugging function to display internal state.
 857      */
 858     public void displayState() {
 859         PrintWriter xout = context.get(Log.logKey).getWriter(Log.WriterKind.STDERR);
 860         xout.println("File Object History : " +  fileObjectHistory);
 861         xout.println("Open Type Names     : " +  openTypeNames);
 862         xout.println("Gen. Src Names      : " +  generatedSourceNames);
 863         xout.println("Gen. Cls Names      : " +  generatedClasses.keySet());
 864         xout.println("Agg. Gen. Src Names : " +  aggregateGeneratedSourceNames);
 865         xout.println("Agg. Gen. Cls Names : " +  aggregateGeneratedClassNames);
 866     }
 867 
 868     public String toString() {
 869         return "javac Filer";
 870     }
 871 
 872     /**
 873      * Upon close, register files opened by create{Source, Class}File
 874      * for annotation processing.
 875      */
 876     private void closeFileObject(ModuleSymbol mod, String typeName, FileObject fileObject) {
 877         /*
 878          * If typeName is non-null, the file object was opened as a
 879          * source or class file by the user.  If a file was opened as
 880          * a resource, typeName will be null and the file is *not*
 881          * subject to annotation processing.
 882          */
 883         if ((typeName != null)) {
 884             if (!(fileObject instanceof JavaFileObject))
 885                 throw new AssertionError("JavaFileOject not found for " + fileObject);
 886             JavaFileObject javaFileObject = (JavaFileObject)fileObject;
 887             switch(javaFileObject.getKind()) {
 888             case SOURCE:
 889                 generatedSourceNames.add(typeName);
 890                 generatedSourceFileObjects.add(javaFileObject);
 891                 openTypeNames.remove(typeName);
 892                 break;
 893 
 894             case CLASS:
 895                 generatedClasses.computeIfAbsent(mod, m -> Collections.synchronizedMap(new LinkedHashMap<>())).put(typeName, javaFileObject);
 896                 openTypeNames.remove(typeName);
 897                 break;
 898 
 899             default:
 900                 break;
 901             }
 902         }
 903     }
 904 
 905 }