JavacFileManager.java revision 3344:31c8b18fdc5b
1/*
2 * Copyright (c) 2005, 2016, 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.file;
27
28import java.io.File;
29import java.io.IOException;
30import java.net.MalformedURLException;
31import java.net.URI;
32import java.net.URISyntaxException;
33import java.net.URL;
34import java.nio.CharBuffer;
35import java.nio.charset.Charset;
36import java.nio.file.FileSystem;
37import java.nio.file.FileSystems;
38import java.nio.file.FileVisitOption;
39import java.nio.file.FileVisitResult;
40import java.nio.file.Files;
41import java.nio.file.InvalidPathException;
42import java.nio.file.LinkOption;
43import java.nio.file.Path;
44import java.nio.file.Paths;
45import java.nio.file.ProviderNotFoundException;
46import java.nio.file.SimpleFileVisitor;
47import java.nio.file.attribute.BasicFileAttributes;
48import java.nio.file.spi.FileSystemProvider;
49import java.util.ArrayList;
50import java.util.Arrays;
51import java.util.Collection;
52import java.util.Collections;
53import java.util.Comparator;
54import java.util.EnumSet;
55import java.util.HashMap;
56import java.util.Iterator;
57import java.util.Map;
58import java.util.Objects;
59import java.util.ServiceLoader;
60import java.util.Set;
61import java.util.stream.Collectors;
62import java.util.stream.Stream;
63
64import javax.lang.model.SourceVersion;
65import javax.tools.FileObject;
66import javax.tools.JavaFileManager;
67import javax.tools.JavaFileObject;
68import javax.tools.StandardJavaFileManager;
69
70import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
71import com.sun.tools.javac.file.RelativePath.RelativeFile;
72import com.sun.tools.javac.util.Context;
73import com.sun.tools.javac.util.DefinedBy;
74import com.sun.tools.javac.util.DefinedBy.Api;
75import com.sun.tools.javac.util.List;
76import com.sun.tools.javac.util.ListBuffer;
77import com.sun.tools.javac.util.ModuleWrappers.Configuration;
78import com.sun.tools.javac.util.ModuleWrappers.Layer;
79import com.sun.tools.javac.util.ModuleWrappers.ModuleFinder;
80import com.sun.tools.javac.util.ModuleWrappers.ServiceLoaderHelper;
81
82import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
83
84import static javax.tools.StandardLocation.*;
85
86/**
87 * This class provides access to the source, class and other files
88 * used by the compiler and related tools.
89 *
90 * <p><b>This is NOT part of any supported API.
91 * If you write code that depends on this, you do so at your own risk.
92 * This code and its internal interfaces are subject to change or
93 * deletion without notice.</b>
94 */
95public class JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
96
97    @SuppressWarnings("cast")
98    public static char[] toArray(CharBuffer buffer) {
99        if (buffer.hasArray())
100            return ((CharBuffer)buffer.compact().flip()).array();
101        else
102            return buffer.toString().toCharArray();
103    }
104
105    private FSInfo fsInfo;
106
107    private final Set<JavaFileObject.Kind> sourceOrClass =
108        EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
109
110    protected boolean symbolFileEnabled;
111
112    protected enum SortFiles implements Comparator<Path> {
113        FORWARD {
114            @Override
115            public int compare(Path f1, Path f2) {
116                return f1.getFileName().compareTo(f2.getFileName());
117            }
118        },
119        REVERSE {
120            @Override
121            public int compare(Path f1, Path f2) {
122                return -f1.getFileName().compareTo(f2.getFileName());
123            }
124        }
125    }
126
127    protected SortFiles sortFiles;
128
129    /**
130     * Register a Context.Factory to create a JavacFileManager.
131     */
132    public static void preRegister(Context context) {
133        context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
134            @Override
135            public JavaFileManager make(Context c) {
136                return new JavacFileManager(c, true, null);
137            }
138        });
139    }
140
141    /**
142     * Create a JavacFileManager using a given context, optionally registering
143     * it as the JavaFileManager for that context.
144     */
145    public JavacFileManager(Context context, boolean register, Charset charset) {
146        super(charset);
147        if (register)
148            context.put(JavaFileManager.class, this);
149        setContext(context);
150    }
151
152    /**
153     * Set the context for JavacFileManager.
154     */
155    @Override
156    public void setContext(Context context) {
157        super.setContext(context);
158
159        fsInfo = FSInfo.instance(context);
160
161        symbolFileEnabled = !options.isSet("ignore.symbol.file");
162
163        String sf = options.get("sortFiles");
164        if (sf != null) {
165            sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
166        }
167    }
168
169    /**
170     * Set whether or not to use ct.sym as an alternate to rt.jar.
171     */
172    public void setSymbolFileEnabled(boolean b) {
173        symbolFileEnabled = b;
174    }
175
176    public boolean isSymbolFileEnabled() {
177        return symbolFileEnabled;
178    }
179
180    // used by tests
181    public JavaFileObject getJavaFileObject(String name) {
182        return getJavaFileObjects(name).iterator().next();
183    }
184
185    // used by tests
186    public JavaFileObject getJavaFileObject(Path file) {
187        return getJavaFileObjects(file).iterator().next();
188    }
189
190    public JavaFileObject getFileForOutput(String classname,
191                                           JavaFileObject.Kind kind,
192                                           JavaFileObject sibling)
193        throws IOException
194    {
195        return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
196    }
197
198    @Override @DefinedBy(Api.COMPILER)
199    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
200        ListBuffer<Path> paths = new ListBuffer<>();
201        for (String name : names)
202            paths.append(Paths.get(nullCheck(name)));
203        return getJavaFileObjectsFromPaths(paths.toList());
204    }
205
206    @Override @DefinedBy(Api.COMPILER)
207    public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
208        return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
209    }
210
211    private static boolean isValidName(String name) {
212        // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
213        // but the set of keywords depends on the source level, and we don't want
214        // impls of JavaFileManager to have to be dependent on the source level.
215        // Therefore we simply check that the argument is a sequence of identifiers
216        // separated by ".".
217        for (String s : name.split("\\.", -1)) {
218            if (!SourceVersion.isIdentifier(s))
219                return false;
220        }
221        return true;
222    }
223
224    private static void validateClassName(String className) {
225        if (!isValidName(className))
226            throw new IllegalArgumentException("Invalid class name: " + className);
227    }
228
229    private static void validatePackageName(String packageName) {
230        if (packageName.length() > 0 && !isValidName(packageName))
231            throw new IllegalArgumentException("Invalid packageName name: " + packageName);
232    }
233
234    public static void testName(String name,
235                                boolean isValidPackageName,
236                                boolean isValidClassName)
237    {
238        try {
239            validatePackageName(name);
240            if (!isValidPackageName)
241                throw new AssertionError("Invalid package name accepted: " + name);
242            printAscii("Valid package name: \"%s\"", name);
243        } catch (IllegalArgumentException e) {
244            if (isValidPackageName)
245                throw new AssertionError("Valid package name rejected: " + name);
246            printAscii("Invalid package name: \"%s\"", name);
247        }
248        try {
249            validateClassName(name);
250            if (!isValidClassName)
251                throw new AssertionError("Invalid class name accepted: " + name);
252            printAscii("Valid class name: \"%s\"", name);
253        } catch (IllegalArgumentException e) {
254            if (isValidClassName)
255                throw new AssertionError("Valid class name rejected: " + name);
256            printAscii("Invalid class name: \"%s\"", name);
257        }
258    }
259
260    private static void printAscii(String format, Object... args) {
261        String message;
262        try {
263            final String ascii = "US-ASCII";
264            message = new String(String.format(null, format, args).getBytes(ascii), ascii);
265        } catch (java.io.UnsupportedEncodingException ex) {
266            throw new AssertionError(ex);
267        }
268        System.out.println(message);
269    }
270
271    private final Map<Path, Container> containers = new HashMap<>();
272
273    synchronized Container getContainer(Path path) throws IOException {
274        Container fs = containers.get(path);
275
276        if (fs != null) {
277            return fs;
278        }
279
280        if (fsInfo.isFile(path) && path.equals(Locations.thisSystemModules)) {
281            containers.put(path, fs = new JRTImageContainer());
282            return fs;
283        }
284
285        Path realPath = fsInfo.getCanonicalFile(path);
286
287        fs = containers.get(realPath);
288
289        if (fs != null) {
290            containers.put(path, fs);
291            return fs;
292        }
293
294        BasicFileAttributes attr = null;
295
296        try {
297            attr = Files.readAttributes(realPath, BasicFileAttributes.class);
298        } catch (IOException ex) {
299            //non-existing
300            fs = MISSING_CONTAINER;
301        }
302
303        if (attr != null) {
304            if (attr.isDirectory()) {
305                fs = new DirectoryContainer(realPath);
306            } else {
307                try {
308                    fs = new ArchiveContainer(realPath);
309                } catch (ProviderNotFoundException | SecurityException ex) {
310                    throw new IOException(ex);
311                }
312            }
313        }
314
315        containers.put(realPath, fs);
316        containers.put(path, fs);
317
318        return fs;
319    }
320
321    private interface Container {
322        /**
323         * Insert all files in subdirectory subdirectory of container which
324         * match fileKinds into resultList
325         */
326        public abstract void list(Path userPath,
327                                  RelativeDirectory subdirectory,
328                                  Set<JavaFileObject.Kind> fileKinds,
329                                  boolean recurse,
330                                  ListBuffer<JavaFileObject> resultList) throws IOException;
331        public abstract JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException;
332        public abstract void close() throws IOException;
333    }
334
335    private static final Container MISSING_CONTAINER =  new Container() {
336        @Override
337        public void list(Path userPath,
338                         RelativeDirectory subdirectory,
339                         Set<JavaFileObject.Kind> fileKinds,
340                         boolean recurse,
341                         ListBuffer<JavaFileObject> resultList) throws IOException {
342        }
343        @Override
344        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
345            return null;
346        }
347        @Override
348        public void close() throws IOException {}
349    };
350
351    private final class JRTImageContainer implements Container {
352
353        /**
354         * Insert all files in a subdirectory of the platform image
355         * which match fileKinds into resultList.
356         */
357        @Override
358        public void list(Path userPath,
359                         RelativeDirectory subdirectory,
360                         Set<JavaFileObject.Kind> fileKinds,
361                         boolean recurse,
362                         ListBuffer<JavaFileObject> resultList) throws IOException {
363            try {
364                JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory);
365                if (symbolFileEnabled && e.ctSym.hidden)
366                    return;
367                for (Path file: e.files.values()) {
368                    if (fileKinds.contains(getKind(file))) {
369                        JavaFileObject fe
370                                = PathFileObject.forJRTPath(JavacFileManager.this, file);
371                        resultList.append(fe);
372                    }
373                }
374
375                if (recurse) {
376                    for (RelativeDirectory rd: e.subdirs) {
377                        list(userPath, rd, fileKinds, recurse, resultList);
378                    }
379                }
380            } catch (IOException ex) {
381                ex.printStackTrace(System.err);
382                log.error("error.reading.file", userPath, getMessage(ex));
383            }
384        }
385
386        @Override
387        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
388            JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
389            if (symbolFileEnabled && e.ctSym.hidden)
390                return null;
391            Path p = e.files.get(name.basename());
392            if (p != null) {
393                return PathFileObject.forJRTPath(JavacFileManager.this, p);
394            } else {
395                return null;
396            }
397        }
398
399        @Override
400        public void close() throws IOException {
401        }
402    }
403
404    private synchronized JRTIndex getJRTIndex() {
405        if (jrtIndex == null)
406            jrtIndex = JRTIndex.getSharedInstance();
407        return jrtIndex;
408    }
409
410    private JRTIndex jrtIndex;
411
412    private final class DirectoryContainer implements Container {
413        private final Path directory;
414
415        public DirectoryContainer(Path directory) {
416            this.directory = directory;
417        }
418
419        /**
420         * Insert all files in subdirectory subdirectory of directory userPath
421         * which match fileKinds into resultList
422         */
423        @Override
424        public void list(Path userPath,
425                         RelativeDirectory subdirectory,
426                         Set<JavaFileObject.Kind> fileKinds,
427                         boolean recurse,
428                         ListBuffer<JavaFileObject> resultList) throws IOException {
429            Path d;
430            try {
431                d = subdirectory.resolveAgainst(userPath);
432            } catch (InvalidPathException ignore) {
433                return ;
434            }
435
436            if (!Files.exists(d)) {
437               return;
438            }
439
440            if (!caseMapCheck(d, subdirectory)) {
441                return;
442            }
443
444            java.util.List<Path> files;
445            try (Stream<Path> s = Files.list(d)) {
446                files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList());
447            } catch (IOException ignore) {
448                return;
449            }
450
451            for (Path f: files) {
452                String fname = f.getFileName().toString();
453                if (fname.endsWith("/"))
454                    fname = fname.substring(0, fname.length() - 1);
455                if (Files.isDirectory(f)) {
456                    if (recurse && SourceVersion.isIdentifier(fname)) {
457                        list(userPath,
458                             new RelativeDirectory(subdirectory, fname),
459                             fileKinds,
460                             recurse,
461                             resultList);
462                    }
463                } else {
464                    if (isValidFile(fname, fileKinds)) {
465                        RelativeFile file = new RelativeFile(subdirectory, fname);
466                        JavaFileObject fe = PathFileObject.forDirectoryPath(JavacFileManager.this,
467                                file.resolveAgainst(directory), userPath, file);
468                        resultList.append(fe);
469                    }
470                }
471            }
472        }
473
474        @Override
475        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
476            try {
477                Path f = name.resolveAgainst(userPath);
478                if (Files.exists(f))
479                    return PathFileObject.forSimplePath(JavacFileManager.this,
480                            fsInfo.getCanonicalFile(f), f);
481            } catch (InvalidPathException ignore) {
482            }
483            return null;
484        }
485
486        @Override
487        public void close() throws IOException {
488        }
489    }
490
491    private final class ArchiveContainer implements Container {
492        private final Path archivePath;
493        private final FileSystem fileSystem;
494        private final Map<RelativePath, Path> pathCache = new HashMap<>();
495
496        public ArchiveContainer(Path archivePath) throws IOException, ProviderNotFoundException, SecurityException {
497            this.archivePath = archivePath;
498            if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) {
499                Map<String,String> env = Collections.singletonMap("multi-release", multiReleaseValue);
500                this.fileSystem = getJarFSProvider().newFileSystem(archivePath, env);
501            } else {
502                this.fileSystem = FileSystems.newFileSystem(archivePath, null);
503            }
504        }
505
506        /**
507         * Insert all files in subdirectory subdirectory of this archive
508         * which match fileKinds into resultList
509         */
510        @Override
511        public void list(Path userPath,
512                         RelativeDirectory subdirectory,
513                         Set<JavaFileObject.Kind> fileKinds,
514                         boolean recurse,
515                         ListBuffer<JavaFileObject> resultList) throws IOException {
516            Path resolvedSubdirectory = resolvePath(subdirectory);
517
518            if (resolvedSubdirectory == null)
519                return ;
520
521            int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
522            Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
523            Files.walkFileTree(resolvedSubdirectory, opts, maxDepth,
524                    new SimpleFileVisitor<Path>() {
525                        @Override
526                        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
527                            if (isValid(dir.getFileName())) {
528                                return FileVisitResult.CONTINUE;
529                            } else {
530                                return FileVisitResult.SKIP_SUBTREE;
531                            }
532                        }
533
534                        boolean isValid(Path fileName) {
535                            if (fileName == null) {
536                                return true;
537                            } else {
538                                String name = fileName.toString();
539                                if (name.endsWith("/")) {
540                                    name = name.substring(0, name.length() - 1);
541                                }
542                                return SourceVersion.isIdentifier(name);
543                            }
544                        }
545
546                        @Override
547                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
548                            if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) {
549                                JavaFileObject fe = PathFileObject.forJarPath(
550                                        JavacFileManager.this, file, archivePath);
551                                resultList.append(fe);
552                            }
553                            return FileVisitResult.CONTINUE;
554                        }
555                    });
556
557        }
558
559        @Override
560        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
561            Path p = resolvePath(name);
562            if (p != null)
563                return PathFileObject.forJarPath(JavacFileManager.this, p, userPath);
564
565            return null;
566        }
567
568        private synchronized Path resolvePath(RelativePath path) {
569            if (!pathCache.containsKey(path)) {
570                Path relativePath = path.resolveAgainst(fileSystem);
571
572                if (!Files.exists(relativePath)) {
573                    relativePath = null;
574                }
575
576                pathCache.put(path, relativePath);
577                return relativePath;
578            }
579            return pathCache.get(path);
580        }
581
582        @Override
583        public void close() throws IOException {
584            fileSystem.close();
585        }
586    }
587
588    private FileSystemProvider jarFSProvider;
589
590    private FileSystemProvider getJarFSProvider() throws IOException {
591        if (jarFSProvider != null) {
592            return jarFSProvider;
593        }
594        for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
595            if (provider.getScheme().equals("jar")) {
596                return (jarFSProvider = provider);
597            }
598        }
599        throw new ProviderNotFoundException("no provider found for .jar files");
600    }
601
602    /**
603     * container is a directory, a zip file, or a non-existent path.
604     */
605    private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
606        JavaFileObject.Kind kind = getKind(s);
607        return fileKinds.contains(kind);
608    }
609
610    private static final boolean fileSystemIsCaseSensitive =
611        File.separatorChar == '/';
612
613    /** Hack to make Windows case sensitive. Test whether given path
614     *  ends in a string of characters with the same case as given name.
615     *  Ignore file separators in both path and name.
616     */
617    private boolean caseMapCheck(Path f, RelativePath name) {
618        if (fileSystemIsCaseSensitive) return true;
619        // Note that toRealPath() returns the case-sensitive
620        // spelled file name.
621        String path;
622        char sep;
623        try {
624            path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
625            sep = f.getFileSystem().getSeparator().charAt(0);
626        } catch (IOException ex) {
627            return false;
628        }
629        char[] pcs = path.toCharArray();
630        char[] ncs = name.path.toCharArray();
631        int i = pcs.length - 1;
632        int j = ncs.length - 1;
633        while (i >= 0 && j >= 0) {
634            while (i >= 0 && pcs[i] == sep) i--;
635            while (j >= 0 && ncs[j] == '/') j--;
636            if (i >= 0 && j >= 0) {
637                if (pcs[i] != ncs[j]) return false;
638                i--;
639                j--;
640            }
641        }
642        return j < 0;
643    }
644
645    /** Flush any output resources.
646     */
647    @Override @DefinedBy(Api.COMPILER)
648    public void flush() {
649        contentCache.clear();
650    }
651
652    /**
653     * Close the JavaFileManager, releasing resources.
654     */
655    @Override @DefinedBy(Api.COMPILER)
656    public void close() throws IOException {
657        if (deferredCloseTimeout > 0) {
658            deferredClose();
659            return;
660        }
661
662        locations.close();
663        for (Container container: containers.values()) {
664            container.close();
665        }
666        containers.clear();
667        contentCache.clear();
668    }
669
670    @Override @DefinedBy(Api.COMPILER)
671    public ClassLoader getClassLoader(Location location) {
672        nullCheck(location);
673        Iterable<? extends File> path = getLocation(location);
674        if (path == null)
675            return null;
676        ListBuffer<URL> lb = new ListBuffer<>();
677        for (File f: path) {
678            try {
679                lb.append(f.toURI().toURL());
680            } catch (MalformedURLException e) {
681                throw new AssertionError(e);
682            }
683        }
684
685        return getClassLoader(lb.toArray(new URL[lb.size()]));
686    }
687
688    @Override @DefinedBy(Api.COMPILER)
689    public Iterable<JavaFileObject> list(Location location,
690                                         String packageName,
691                                         Set<JavaFileObject.Kind> kinds,
692                                         boolean recurse)
693        throws IOException
694    {
695        // validatePackageName(packageName);
696        nullCheck(packageName);
697        nullCheck(kinds);
698
699        Iterable<? extends Path> path = getLocationAsPaths(location);
700        if (path == null)
701            return List.nil();
702        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
703        ListBuffer<JavaFileObject> results = new ListBuffer<>();
704
705        for (Path directory : path) {
706            Container container = getContainer(directory);
707
708            container.list(directory, subdirectory, kinds, recurse, results);
709        }
710
711        return results.toList();
712    }
713
714    @Override @DefinedBy(Api.COMPILER)
715    public String inferBinaryName(Location location, JavaFileObject file) {
716        Objects.requireNonNull(file);
717        Objects.requireNonNull(location);
718        // Need to match the path semantics of list(location, ...)
719        Iterable<? extends Path> path = getLocationAsPaths(location);
720        if (path == null) {
721            return null;
722        }
723
724        if (file instanceof PathFileObject) {
725            return ((PathFileObject) file).inferBinaryName(path);
726        } else
727            throw new IllegalArgumentException(file.getClass().getName());
728    }
729
730    @Override @DefinedBy(Api.COMPILER)
731    public boolean isSameFile(FileObject a, FileObject b) {
732        nullCheck(a);
733        nullCheck(b);
734        if (a instanceof PathFileObject && b instanceof PathFileObject)
735            return ((PathFileObject) a).isSameFile((PathFileObject) b);
736        return a.equals(b);
737    }
738
739    @Override @DefinedBy(Api.COMPILER)
740    public boolean hasLocation(Location location) {
741        nullCheck(location);
742        return locations.hasLocation(location);
743    }
744
745    @Override @DefinedBy(Api.COMPILER)
746    public JavaFileObject getJavaFileForInput(Location location,
747                                              String className,
748                                              JavaFileObject.Kind kind)
749        throws IOException
750    {
751        nullCheck(location);
752        // validateClassName(className);
753        nullCheck(className);
754        nullCheck(kind);
755        if (!sourceOrClass.contains(kind))
756            throw new IllegalArgumentException("Invalid kind: " + kind);
757        return getFileForInput(location, RelativeFile.forClass(className, kind));
758    }
759
760    @Override @DefinedBy(Api.COMPILER)
761    public FileObject getFileForInput(Location location,
762                                      String packageName,
763                                      String relativeName)
764        throws IOException
765    {
766        nullCheck(location);
767        // validatePackageName(packageName);
768        nullCheck(packageName);
769        if (!isRelativeUri(relativeName))
770            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
771        RelativeFile name = packageName.length() == 0
772            ? new RelativeFile(relativeName)
773            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
774        return getFileForInput(location, name);
775    }
776
777    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
778        Iterable<? extends Path> path = getLocationAsPaths(location);
779        if (path == null)
780            return null;
781
782        for (Path file: path) {
783            JavaFileObject fo = getContainer(file).getFileObject(file, name);
784
785            if (fo != null) {
786                return fo;
787            }
788        }
789        return null;
790    }
791
792    @Override @DefinedBy(Api.COMPILER)
793    public JavaFileObject getJavaFileForOutput(Location location,
794                                               String className,
795                                               JavaFileObject.Kind kind,
796                                               FileObject sibling)
797        throws IOException
798    {
799        nullCheck(location);
800        // validateClassName(className);
801        nullCheck(className);
802        nullCheck(kind);
803        if (!sourceOrClass.contains(kind))
804            throw new IllegalArgumentException("Invalid kind: " + kind);
805        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
806    }
807
808    @Override @DefinedBy(Api.COMPILER)
809    public FileObject getFileForOutput(Location location,
810                                       String packageName,
811                                       String relativeName,
812                                       FileObject sibling)
813        throws IOException
814    {
815        nullCheck(location);
816        // validatePackageName(packageName);
817        nullCheck(packageName);
818        if (!isRelativeUri(relativeName))
819            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
820        RelativeFile name = packageName.length() == 0
821            ? new RelativeFile(relativeName)
822            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
823        return getFileForOutput(location, name, sibling);
824    }
825
826    private JavaFileObject getFileForOutput(Location location,
827                                            RelativeFile fileName,
828                                            FileObject sibling)
829        throws IOException
830    {
831        Path dir;
832        if (location == CLASS_OUTPUT) {
833            if (getClassOutDir() != null) {
834                dir = getClassOutDir();
835            } else {
836                String baseName = fileName.basename();
837                if (sibling != null && sibling instanceof PathFileObject) {
838                    return ((PathFileObject) sibling).getSibling(baseName);
839                } else {
840                    Path p = Paths.get(baseName);
841                    Path real = fsInfo.getCanonicalFile(p);
842                    return PathFileObject.forSimplePath(this, real, p);
843                }
844            }
845        } else if (location == SOURCE_OUTPUT) {
846            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
847        } else {
848            Iterable<? extends Path> path = locations.getLocation(location);
849            dir = null;
850            for (Path f: path) {
851                dir = f;
852                break;
853            }
854        }
855
856        try {
857            if (dir == null) {
858                dir = Paths.get(System.getProperty("user.dir"));
859            }
860            Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir));
861            return PathFileObject.forDirectoryPath(this, path, dir, fileName);
862        } catch (InvalidPathException e) {
863            throw new IOException("bad filename " + fileName, e);
864        }
865    }
866
867    @Override @DefinedBy(Api.COMPILER)
868    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
869        Iterable<? extends File> files)
870    {
871        ArrayList<PathFileObject> result;
872        if (files instanceof Collection<?>)
873            result = new ArrayList<>(((Collection<?>)files).size());
874        else
875            result = new ArrayList<>();
876        for (File f: files) {
877            Objects.requireNonNull(f);
878            Path p = f.toPath();
879            result.add(PathFileObject.forSimplePath(this,
880                    fsInfo.getCanonicalFile(p), p));
881        }
882        return result;
883    }
884
885    @Override @DefinedBy(Api.COMPILER)
886    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
887        Iterable<? extends Path> paths)
888    {
889        ArrayList<PathFileObject> result;
890        if (paths instanceof Collection<?>)
891            result = new ArrayList<>(((Collection<?>)paths).size());
892        else
893            result = new ArrayList<>();
894        for (Path p: paths)
895            result.add(PathFileObject.forSimplePath(this,
896                    fsInfo.getCanonicalFile(p), p));
897        return result;
898    }
899
900    @Override @DefinedBy(Api.COMPILER)
901    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
902        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
903    }
904
905    @Override @DefinedBy(Api.COMPILER)
906    public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
907        return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
908    }
909
910    @Override @DefinedBy(Api.COMPILER)
911    public void setLocation(Location location,
912                            Iterable<? extends File> searchpath)
913        throws IOException
914    {
915        nullCheck(location);
916        locations.setLocation(location, asPaths(searchpath));
917    }
918
919    @Override @DefinedBy(Api.COMPILER)
920    public void setLocationFromPaths(Location location,
921                            Iterable<? extends Path> searchpath)
922        throws IOException
923    {
924        nullCheck(location);
925        locations.setLocation(location, nullCheck(searchpath));
926    }
927
928    @Override @DefinedBy(Api.COMPILER)
929    public Iterable<? extends File> getLocation(Location location) {
930        nullCheck(location);
931        return asFiles(locations.getLocation(location));
932    }
933
934    @Override @DefinedBy(Api.COMPILER)
935    public Iterable<? extends Path> getLocationAsPaths(Location location) {
936        nullCheck(location);
937        return locations.getLocation(location);
938    }
939
940    private Path getClassOutDir() {
941        return locations.getOutputLocation(CLASS_OUTPUT);
942    }
943
944    private Path getSourceOutDir() {
945        return locations.getOutputLocation(SOURCE_OUTPUT);
946    }
947
948    @Override @DefinedBy(Api.COMPILER)
949    public Location getModuleLocation(Location location, String moduleName) throws IOException {
950        nullCheck(location);
951        nullCheck(moduleName);
952        return locations.getModuleLocation(location, moduleName);
953    }
954
955    @Override @DefinedBy(Api.COMPILER)
956    public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException {
957        nullCheck(location);
958        nullCheck(service);
959        if (location.isModuleLocation()) {
960            Collection<Path> paths = locations.getLocation(location);
961            ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()]));
962            Layer bootLayer = Layer.boot();
963            Configuration cf = bootLayer.configuration().resolveRequiresAndUses(ModuleFinder.empty(), finder, Collections.emptySet());
964            Layer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader());
965            return ServiceLoaderHelper.load(layer, service);
966        } else {
967            return ServiceLoader.load(service, getClassLoader(location));
968        }
969    }
970
971    @Override @DefinedBy(Api.COMPILER)
972    public Location getModuleLocation(Location location, JavaFileObject fo, String pkgName) throws IOException {
973        nullCheck(location);
974        if (!(fo instanceof PathFileObject))
975            throw new IllegalArgumentException(fo.getName());
976        int depth = 1; // allow 1 for filename
977        if (pkgName != null && !pkgName.isEmpty()) {
978            depth += 1;
979            for (int i = 0; i < pkgName.length(); i++) {
980                switch (pkgName.charAt(i)) {
981                    case '/': case '.':
982                        depth++;
983                }
984            }
985        }
986        Path p = Locations.normalize(((PathFileObject) fo).path);
987        int fc = p.getNameCount();
988        if (depth < fc) {
989            Path root = p.getRoot();
990            Path subpath = p.subpath(0, fc - depth);
991            Path dir = (root == null) ? subpath : root.resolve(subpath);
992            // need to find dir in location
993            return locations.getModuleLocation(location, dir);
994        } else {
995            return null;
996        }
997    }
998
999    @Override @DefinedBy(Api.COMPILER)
1000    public String inferModuleName(Location location) {
1001        nullCheck(location);
1002        return locations.inferModuleName(location);
1003    }
1004
1005    @Override @DefinedBy(Api.COMPILER)
1006    public Iterable<Set<Location>> listModuleLocations(Location location) throws IOException {
1007        nullCheck(location);
1008        return locations.listModuleLocations(location);
1009    }
1010
1011    @Override @DefinedBy(Api.COMPILER)
1012    public Path asPath(FileObject file) {
1013        if (file instanceof PathFileObject) {
1014            return ((PathFileObject) file).path;
1015        } else
1016            throw new IllegalArgumentException(file.getName());
1017    }
1018
1019    /**
1020     * Enforces the specification of a "relative" name as used in
1021     * {@linkplain #getFileForInput(Location,String,String)
1022     * getFileForInput}.  This method must follow the rules defined in
1023     * that method, do not make any changes without consulting the
1024     * specification.
1025     */
1026    protected static boolean isRelativeUri(URI uri) {
1027        if (uri.isAbsolute())
1028            return false;
1029        String path = uri.normalize().getPath();
1030        if (path.length() == 0 /* isEmpty() is mustang API */)
1031            return false;
1032        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
1033            return false;
1034        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
1035            return false;
1036        return true;
1037    }
1038
1039    // Convenience method
1040    protected static boolean isRelativeUri(String u) {
1041        try {
1042            return isRelativeUri(new URI(u));
1043        } catch (URISyntaxException e) {
1044            return false;
1045        }
1046    }
1047
1048    /**
1049     * Converts a relative file name to a relative URI.  This is
1050     * different from File.toURI as this method does not canonicalize
1051     * the file before creating the URI.  Furthermore, no schema is
1052     * used.
1053     * @param file a relative file name
1054     * @return a relative URI
1055     * @throws IllegalArgumentException if the file name is not
1056     * relative according to the definition given in {@link
1057     * javax.tools.JavaFileManager#getFileForInput}
1058     */
1059    public static String getRelativeName(File file) {
1060        if (!file.isAbsolute()) {
1061            String result = file.getPath().replace(File.separatorChar, '/');
1062            if (isRelativeUri(result))
1063                return result;
1064        }
1065        throw new IllegalArgumentException("Invalid relative path: " + file);
1066    }
1067
1068    /**
1069     * Get a detail message from an IOException.
1070     * Most, but not all, instances of IOException provide a non-null result
1071     * for getLocalizedMessage().  But some instances return null: in these
1072     * cases, fallover to getMessage(), and if even that is null, return the
1073     * name of the exception itself.
1074     * @param e an IOException
1075     * @return a string to include in a compiler diagnostic
1076     */
1077    public static String getMessage(IOException e) {
1078        String s = e.getLocalizedMessage();
1079        if (s != null)
1080            return s;
1081        s = e.getMessage();
1082        if (s != null)
1083            return s;
1084        return e.toString();
1085    }
1086
1087    /* Converters between files and paths.
1088     * These are temporary until we can update the StandardJavaFileManager API.
1089     */
1090
1091    private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1092        if (files == null)
1093            return null;
1094
1095        return () -> new Iterator<Path>() {
1096            Iterator<? extends File> iter = files.iterator();
1097
1098            @Override
1099            public boolean hasNext() {
1100                return iter.hasNext();
1101            }
1102
1103            @Override
1104            public Path next() {
1105                return iter.next().toPath();
1106            }
1107        };
1108    }
1109
1110    private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1111        if (paths == null)
1112            return null;
1113
1114        return () -> new Iterator<File>() {
1115            Iterator<? extends Path> iter = paths.iterator();
1116
1117            @Override
1118            public boolean hasNext() {
1119                return iter.hasNext();
1120            }
1121
1122            @Override
1123            public File next() {
1124                try {
1125                    return iter.next().toFile();
1126                } catch (UnsupportedOperationException e) {
1127                    throw new IllegalStateException(e);
1128                }
1129            }
1130        };
1131    }
1132}
1133