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