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