1/*
2 * Copyright (c) 2005, 2017, 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
26package com.sun.tools.javac.processing;
27
28import java.io.Closeable;
29import java.io.FileNotFoundException;
30import java.io.InputStream;
31import java.io.OutputStream;
32import java.io.FilterOutputStream;
33import java.io.Reader;
34import java.io.Writer;
35import java.io.FilterWriter;
36import java.io.PrintWriter;
37import java.io.IOException;
38import java.util.*;
39
40import static java.util.Collections.*;
41
42import javax.annotation.processing.*;
43import javax.lang.model.SourceVersion;
44import javax.lang.model.element.NestingKind;
45import javax.lang.model.element.Modifier;
46import javax.lang.model.element.Element;
47import javax.tools.*;
48import javax.tools.JavaFileManager.Location;
49
50import static javax.tools.StandardLocation.SOURCE_OUTPUT;
51import static javax.tools.StandardLocation.CLASS_OUTPUT;
52
53import com.sun.tools.javac.code.Lint;
54import com.sun.tools.javac.code.Symbol.ClassSymbol;
55import com.sun.tools.javac.code.Symbol.ModuleSymbol;
56import com.sun.tools.javac.code.Symtab;
57import com.sun.tools.javac.comp.Modules;
58import com.sun.tools.javac.model.JavacElements;
59import com.sun.tools.javac.resources.CompilerProperties.Warnings;
60import com.sun.tools.javac.util.*;
61import com.sun.tools.javac.util.DefinedBy.Api;
62
63import static com.sun.tools.javac.code.Lint.LintCategory.PROCESSING;
64import com.sun.tools.javac.code.Symbol.PackageSymbol;
65import 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 */
109public 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        // TODO: Check if type already exists on source or class path?
716        // If so, use warning message key proc.type.already.exists
717        checkName(typename, allowUnnamedPackageInfo);
718        ClassSymbol existing;
719        boolean alreadySeen = aggregateGeneratedSourceNames.contains(Pair.of(mod, typename)) ||
720                              aggregateGeneratedClassNames.contains(Pair.of(mod, typename)) ||
721                              initialClassNames.contains(typename) ||
722                              ((existing = elementUtils.getTypeElement(typename)) != null &&
723                               initialInputs.contains(existing.sourcefile));
724        if (alreadySeen) {
725            if (lint)
726                log.warning(Warnings.ProcTypeRecreate(typename));
727            throw new FilerException("Attempt to recreate a file for type " + 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}
889