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