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