JavacFileManager.java revision 3981:8be741555fa6
1/*
2 * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javac.file;
27
28import java.io.File;
29import java.io.IOException;
30import java.net.MalformedURLException;
31import java.net.URI;
32import java.net.URISyntaxException;
33import java.net.URL;
34import java.nio.CharBuffer;
35import java.nio.charset.Charset;
36import java.nio.file.FileSystem;
37import java.nio.file.FileSystems;
38import java.nio.file.FileVisitOption;
39import java.nio.file.FileVisitResult;
40import java.nio.file.Files;
41import java.nio.file.InvalidPathException;
42import java.nio.file.LinkOption;
43import java.nio.file.Path;
44import java.nio.file.Paths;
45import java.nio.file.ProviderNotFoundException;
46import java.nio.file.SimpleFileVisitor;
47import java.nio.file.attribute.BasicFileAttributes;
48import java.nio.file.spi.FileSystemProvider;
49import java.util.ArrayList;
50import java.util.Arrays;
51import java.util.Collection;
52import java.util.Collections;
53import java.util.Comparator;
54import java.util.EnumSet;
55import java.util.HashMap;
56import java.util.Iterator;
57import java.util.Map;
58import java.util.Objects;
59import java.util.ServiceLoader;
60import java.util.Set;
61import java.util.stream.Collectors;
62import java.util.stream.Stream;
63
64import javax.lang.model.SourceVersion;
65import javax.tools.FileObject;
66import javax.tools.JavaFileManager;
67import javax.tools.JavaFileObject;
68import javax.tools.StandardJavaFileManager;
69
70import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
71import com.sun.tools.javac.file.RelativePath.RelativeFile;
72import com.sun.tools.javac.util.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                        RelativeFile file = new RelativeFile(subdirectory, fname);
477                        JavaFileObject fe = PathFileObject.forDirectoryPath(JavacFileManager.this,
478                                file.resolveAgainst(directory), userPath, file);
479                        resultList.append(fe);
480                    }
481                }
482            }
483        }
484
485        @Override
486        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
487            try {
488                Path f = name.resolveAgainst(userPath);
489                if (Files.exists(f))
490                    return PathFileObject.forSimplePath(JavacFileManager.this,
491                            fsInfo.getCanonicalFile(f), f);
492            } catch (InvalidPathException ignore) {
493            }
494            return null;
495        }
496
497        @Override
498        public void close() throws IOException {
499        }
500    }
501
502    private final class ArchiveContainer implements Container {
503        private final Path archivePath;
504        private final FileSystem fileSystem;
505        private final Map<RelativePath, Path> packages;
506
507        public ArchiveContainer(Path archivePath) throws IOException, ProviderNotFoundException, SecurityException {
508            this.archivePath = archivePath;
509            if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) {
510                Map<String,String> env = Collections.singletonMap("multi-release", multiReleaseValue);
511                FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
512                Assert.checkNonNull(jarFSProvider, "should have been caught before!");
513                this.fileSystem = jarFSProvider.newFileSystem(archivePath, env);
514            } else {
515                this.fileSystem = FileSystems.newFileSystem(archivePath, null);
516            }
517            packages = new HashMap<>();
518            for (Path root : fileSystem.getRootDirectories()) {
519                Files.walkFileTree(root, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE,
520                        new SimpleFileVisitor<Path>() {
521                            @Override
522                            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
523                                if (isValid(dir.getFileName())) {
524                                    packages.put(new RelativeDirectory(root.relativize(dir).toString()), dir);
525                                    return FileVisitResult.CONTINUE;
526                                } else {
527                                    return FileVisitResult.SKIP_SUBTREE;
528                                }
529                            }
530                        });
531            }
532        }
533
534        /**
535         * Insert all files in subdirectory subdirectory of this archive
536         * which match fileKinds into resultList
537         */
538        @Override
539        public void list(Path userPath,
540                         RelativeDirectory subdirectory,
541                         Set<JavaFileObject.Kind> fileKinds,
542                         boolean recurse,
543                         ListBuffer<JavaFileObject> resultList) throws IOException {
544            Path resolvedSubdirectory = packages.get(subdirectory);
545
546            if (resolvedSubdirectory == null)
547                return ;
548
549            int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
550            Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
551            Files.walkFileTree(resolvedSubdirectory, opts, maxDepth,
552                    new SimpleFileVisitor<Path>() {
553                        @Override
554                        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
555                            if (isValid(dir.getFileName())) {
556                                return FileVisitResult.CONTINUE;
557                            } else {
558                                return FileVisitResult.SKIP_SUBTREE;
559                            }
560                        }
561
562                        @Override
563                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
564                            if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) {
565                                JavaFileObject fe = PathFileObject.forJarPath(
566                                        JavacFileManager.this, file, archivePath);
567                                resultList.append(fe);
568                            }
569                            return FileVisitResult.CONTINUE;
570                        }
571                    });
572
573        }
574
575        private boolean isValid(Path fileName) {
576            if (fileName == null) {
577                return true;
578            } else {
579                String name = fileName.toString();
580                if (name.endsWith("/")) {
581                    name = name.substring(0, name.length() - 1);
582                }
583                return SourceVersion.isIdentifier(name);
584            }
585        }
586
587        @Override
588        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
589            RelativeDirectory root = name.dirname();
590            Path packagepath = packages.get(root);
591            if (packagepath != null) {
592                Path relpath = packagepath.resolve(name.basename());
593                if (Files.exists(relpath)) {
594                    return PathFileObject.forJarPath(JavacFileManager.this, relpath, userPath);
595                }
596            }
597            return null;
598        }
599
600        @Override
601        public void close() throws IOException {
602            fileSystem.close();
603        }
604    }
605
606    /**
607     * container is a directory, a zip file, or a non-existent path.
608     */
609    private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
610        JavaFileObject.Kind kind = getKind(s);
611        return fileKinds.contains(kind);
612    }
613
614    private static final boolean fileSystemIsCaseSensitive =
615        File.separatorChar == '/';
616
617    /** Hack to make Windows case sensitive. Test whether given path
618     *  ends in a string of characters with the same case as given name.
619     *  Ignore file separators in both path and name.
620     */
621    private boolean caseMapCheck(Path f, RelativePath name) {
622        if (fileSystemIsCaseSensitive) return true;
623        // Note that toRealPath() returns the case-sensitive
624        // spelled file name.
625        String path;
626        char sep;
627        try {
628            path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
629            sep = f.getFileSystem().getSeparator().charAt(0);
630        } catch (IOException ex) {
631            return false;
632        }
633        char[] pcs = path.toCharArray();
634        char[] ncs = name.path.toCharArray();
635        int i = pcs.length - 1;
636        int j = ncs.length - 1;
637        while (i >= 0 && j >= 0) {
638            while (i >= 0 && pcs[i] == sep) i--;
639            while (j >= 0 && ncs[j] == '/') j--;
640            if (i >= 0 && j >= 0) {
641                if (pcs[i] != ncs[j]) return false;
642                i--;
643                j--;
644            }
645        }
646        return j < 0;
647    }
648
649    /** Flush any output resources.
650     */
651    @Override @DefinedBy(Api.COMPILER)
652    public void flush() {
653        contentCache.clear();
654    }
655
656    /**
657     * Close the JavaFileManager, releasing resources.
658     */
659    @Override @DefinedBy(Api.COMPILER)
660    public void close() throws IOException {
661        if (deferredCloseTimeout > 0) {
662            deferredClose();
663            return;
664        }
665
666        locations.close();
667        for (Container container: containers.values()) {
668            container.close();
669        }
670        containers.clear();
671        contentCache.clear();
672    }
673
674    @Override @DefinedBy(Api.COMPILER)
675    public ClassLoader getClassLoader(Location location) {
676        checkNotModuleOrientedLocation(location);
677        Iterable<? extends File> path = getLocation(location);
678        if (path == null)
679            return null;
680        ListBuffer<URL> lb = new ListBuffer<>();
681        for (File f: path) {
682            try {
683                lb.append(f.toURI().toURL());
684            } catch (MalformedURLException e) {
685                throw new AssertionError(e);
686            }
687        }
688
689        return getClassLoader(lb.toArray(new URL[lb.size()]));
690    }
691
692    @Override @DefinedBy(Api.COMPILER)
693    public Iterable<JavaFileObject> list(Location location,
694                                         String packageName,
695                                         Set<JavaFileObject.Kind> kinds,
696                                         boolean recurse)
697        throws IOException
698    {
699        checkNotModuleOrientedLocation(location);
700        // validatePackageName(packageName);
701        nullCheck(packageName);
702        nullCheck(kinds);
703
704        Iterable<? extends Path> path = getLocationAsPaths(location);
705        if (path == null)
706            return List.nil();
707        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
708        ListBuffer<JavaFileObject> results = new ListBuffer<>();
709
710        for (Path directory : path) {
711            Container container = getContainer(directory);
712
713            container.list(directory, subdirectory, kinds, recurse, results);
714        }
715
716        return results.toList();
717    }
718
719    @Override @DefinedBy(Api.COMPILER)
720    public String inferBinaryName(Location location, JavaFileObject file) {
721        checkNotModuleOrientedLocation(location);
722        Objects.requireNonNull(file);
723        // Need to match the path semantics of list(location, ...)
724        Iterable<? extends Path> path = getLocationAsPaths(location);
725        if (path == null) {
726            return null;
727        }
728
729        if (file instanceof PathFileObject) {
730            return ((PathFileObject) file).inferBinaryName(path);
731        } else
732            throw new IllegalArgumentException(file.getClass().getName());
733    }
734
735    @Override @DefinedBy(Api.COMPILER)
736    public boolean isSameFile(FileObject a, FileObject b) {
737        nullCheck(a);
738        nullCheck(b);
739        if (a instanceof PathFileObject && b instanceof PathFileObject)
740            return ((PathFileObject) a).isSameFile((PathFileObject) b);
741        return a.equals(b);
742    }
743
744    @Override @DefinedBy(Api.COMPILER)
745    public boolean hasLocation(Location location) {
746        nullCheck(location);
747        return locations.hasLocation(location);
748    }
749
750    @Override @DefinedBy(Api.COMPILER)
751    public JavaFileObject getJavaFileForInput(Location location,
752                                              String className,
753                                              JavaFileObject.Kind kind)
754        throws IOException
755    {
756        checkNotModuleOrientedLocation(location);
757        // validateClassName(className);
758        nullCheck(className);
759        nullCheck(kind);
760        if (!sourceOrClass.contains(kind))
761            throw new IllegalArgumentException("Invalid kind: " + kind);
762        return getFileForInput(location, RelativeFile.forClass(className, kind));
763    }
764
765    @Override @DefinedBy(Api.COMPILER)
766    public FileObject getFileForInput(Location location,
767                                      String packageName,
768                                      String relativeName)
769        throws IOException
770    {
771        checkNotModuleOrientedLocation(location);
772        // validatePackageName(packageName);
773        nullCheck(packageName);
774        if (!isRelativeUri(relativeName))
775            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
776        RelativeFile name = packageName.length() == 0
777            ? new RelativeFile(relativeName)
778            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
779        return getFileForInput(location, name);
780    }
781
782    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
783        Iterable<? extends Path> path = getLocationAsPaths(location);
784        if (path == null)
785            return null;
786
787        for (Path file: path) {
788            JavaFileObject fo = getContainer(file).getFileObject(file, name);
789
790            if (fo != null) {
791                return fo;
792            }
793        }
794        return null;
795    }
796
797    @Override @DefinedBy(Api.COMPILER)
798    public JavaFileObject getJavaFileForOutput(Location location,
799                                               String className,
800                                               JavaFileObject.Kind kind,
801                                               FileObject sibling)
802        throws IOException
803    {
804        checkOutputLocation(location);
805        // validateClassName(className);
806        nullCheck(className);
807        nullCheck(kind);
808        if (!sourceOrClass.contains(kind))
809            throw new IllegalArgumentException("Invalid kind: " + kind);
810        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
811    }
812
813    @Override @DefinedBy(Api.COMPILER)
814    public FileObject getFileForOutput(Location location,
815                                       String packageName,
816                                       String relativeName,
817                                       FileObject sibling)
818        throws IOException
819    {
820        checkOutputLocation(location);
821        // validatePackageName(packageName);
822        nullCheck(packageName);
823        if (!isRelativeUri(relativeName))
824            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
825        RelativeFile name = packageName.length() == 0
826            ? new RelativeFile(relativeName)
827            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
828        return getFileForOutput(location, name, sibling);
829    }
830
831    private JavaFileObject getFileForOutput(Location location,
832                                            RelativeFile fileName,
833                                            FileObject sibling)
834        throws IOException
835    {
836        Path dir;
837        if (location == CLASS_OUTPUT) {
838            if (getClassOutDir() != null) {
839                dir = getClassOutDir();
840            } else {
841                String baseName = fileName.basename();
842                if (sibling != null && sibling instanceof PathFileObject) {
843                    return ((PathFileObject) sibling).getSibling(baseName);
844                } else {
845                    Path p = getPath(baseName);
846                    Path real = fsInfo.getCanonicalFile(p);
847                    return PathFileObject.forSimplePath(this, real, p);
848                }
849            }
850        } else if (location == SOURCE_OUTPUT) {
851            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
852        } else {
853            Iterable<? extends Path> path = locations.getLocation(location);
854            dir = null;
855            for (Path f: path) {
856                dir = f;
857                break;
858            }
859        }
860
861        try {
862            if (dir == null) {
863                dir = getPath(System.getProperty("user.dir"));
864            }
865            Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir));
866            return PathFileObject.forDirectoryPath(this, path, dir, fileName);
867        } catch (InvalidPathException e) {
868            throw new IOException("bad filename " + fileName, e);
869        }
870    }
871
872    @Override @DefinedBy(Api.COMPILER)
873    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
874        Iterable<? extends File> files)
875    {
876        ArrayList<PathFileObject> result;
877        if (files instanceof Collection<?>)
878            result = new ArrayList<>(((Collection<?>)files).size());
879        else
880            result = new ArrayList<>();
881        for (File f: files) {
882            Objects.requireNonNull(f);
883            Path p = f.toPath();
884            result.add(PathFileObject.forSimplePath(this,
885                    fsInfo.getCanonicalFile(p), p));
886        }
887        return result;
888    }
889
890    @Override @DefinedBy(Api.COMPILER)
891    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
892        Iterable<? extends Path> paths)
893    {
894        ArrayList<PathFileObject> result;
895        if (paths instanceof Collection<?>)
896            result = new ArrayList<>(((Collection<?>)paths).size());
897        else
898            result = new ArrayList<>();
899        for (Path p: paths)
900            result.add(PathFileObject.forSimplePath(this,
901                    fsInfo.getCanonicalFile(p), p));
902        return result;
903    }
904
905    @Override @DefinedBy(Api.COMPILER)
906    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
907        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
908    }
909
910    @Override @DefinedBy(Api.COMPILER)
911    public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
912        return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
913    }
914
915    @Override @DefinedBy(Api.COMPILER)
916    public void setLocation(Location location,
917                            Iterable<? extends File> searchpath)
918        throws IOException
919    {
920        nullCheck(location);
921        locations.setLocation(location, asPaths(searchpath));
922    }
923
924    @Override @DefinedBy(Api.COMPILER)
925    public void setLocationFromPaths(Location location,
926                            Collection<? extends Path> searchpath)
927        throws IOException
928    {
929        nullCheck(location);
930        locations.setLocation(location, nullCheck(searchpath));
931    }
932
933    @Override @DefinedBy(Api.COMPILER)
934    public Iterable<? extends File> getLocation(Location location) {
935        nullCheck(location);
936        return asFiles(locations.getLocation(location));
937    }
938
939    @Override @DefinedBy(Api.COMPILER)
940    public Iterable<? extends Path> getLocationAsPaths(Location location) {
941        nullCheck(location);
942        return locations.getLocation(location);
943    }
944
945    private Path getClassOutDir() {
946        return locations.getOutputLocation(CLASS_OUTPUT);
947    }
948
949    private Path getSourceOutDir() {
950        return locations.getOutputLocation(SOURCE_OUTPUT);
951    }
952
953    @Override @DefinedBy(Api.COMPILER)
954    public Location getLocationForModule(Location location, String moduleName) throws IOException {
955        checkModuleOrientedOrOutputLocation(location);
956        nullCheck(moduleName);
957        if (location == SOURCE_OUTPUT && getSourceOutDir() == null)
958            location = CLASS_OUTPUT;
959        return locations.getLocationForModule(location, moduleName);
960    }
961
962    @Override @DefinedBy(Api.COMPILER)
963    public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException {
964        nullCheck(location);
965        nullCheck(service);
966        Module.getModule(getClass()).addUses(service);
967        if (location.isModuleOrientedLocation()) {
968            Collection<Path> paths = locations.getLocation(location);
969            ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()]));
970            Layer bootLayer = Layer.boot();
971            Configuration cf = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), finder, Collections.emptySet());
972            Layer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader());
973            return ServiceLoaderHelper.load(layer, service);
974        } else {
975            return ServiceLoader.load(service, getClassLoader(location));
976        }
977    }
978
979    @Override @DefinedBy(Api.COMPILER)
980    public Location getLocationForModule(Location location, JavaFileObject fo, String pkgName) throws IOException {
981        checkModuleOrientedOrOutputLocation(location);
982        if (!(fo instanceof PathFileObject))
983            return null;
984        int depth = 1; // allow 1 for filename
985        if (pkgName != null && !pkgName.isEmpty()) {
986            depth += 1;
987            for (int i = 0; i < pkgName.length(); i++) {
988                switch (pkgName.charAt(i)) {
989                    case '/': case '.':
990                        depth++;
991                }
992            }
993        }
994        Path p = Locations.normalize(((PathFileObject) fo).path);
995        int fc = p.getNameCount();
996        if (depth < fc) {
997            Path root = p.getRoot();
998            Path subpath = p.subpath(0, fc - depth);
999            Path dir = (root == null) ? subpath : root.resolve(subpath);
1000            // need to find dir in location
1001            return locations.getLocationForModule(location, dir);
1002        } else {
1003            return null;
1004        }
1005    }
1006
1007    @Override @DefinedBy(Api.COMPILER)
1008    public String inferModuleName(Location location) {
1009        checkNotModuleOrientedLocation(location);
1010        return locations.inferModuleName(location);
1011    }
1012
1013    @Override @DefinedBy(Api.COMPILER)
1014    public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
1015        checkModuleOrientedOrOutputLocation(location);
1016        return locations.listLocationsForModules(location);
1017    }
1018
1019    @Override @DefinedBy(Api.COMPILER)
1020    public Path asPath(FileObject file) {
1021        if (file instanceof PathFileObject) {
1022            return ((PathFileObject) file).path;
1023        } else
1024            throw new IllegalArgumentException(file.getName());
1025    }
1026
1027    /**
1028     * Enforces the specification of a "relative" name as used in
1029     * {@linkplain #getFileForInput(Location,String,String)
1030     * getFileForInput}.  This method must follow the rules defined in
1031     * that method, do not make any changes without consulting the
1032     * specification.
1033     */
1034    protected static boolean isRelativeUri(URI uri) {
1035        if (uri.isAbsolute())
1036            return false;
1037        String path = uri.normalize().getPath();
1038        if (path.length() == 0 /* isEmpty() is mustang API */)
1039            return false;
1040        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
1041            return false;
1042        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
1043            return false;
1044        return true;
1045    }
1046
1047    // Convenience method
1048    protected static boolean isRelativeUri(String u) {
1049        try {
1050            return isRelativeUri(new URI(u));
1051        } catch (URISyntaxException e) {
1052            return false;
1053        }
1054    }
1055
1056    /**
1057     * Converts a relative file name to a relative URI.  This is
1058     * different from File.toURI as this method does not canonicalize
1059     * the file before creating the URI.  Furthermore, no schema is
1060     * used.
1061     * @param file a relative file name
1062     * @return a relative URI
1063     * @throws IllegalArgumentException if the file name is not
1064     * relative according to the definition given in {@link
1065     * javax.tools.JavaFileManager#getFileForInput}
1066     */
1067    public static String getRelativeName(File file) {
1068        if (!file.isAbsolute()) {
1069            String result = file.getPath().replace(File.separatorChar, '/');
1070            if (isRelativeUri(result))
1071                return result;
1072        }
1073        throw new IllegalArgumentException("Invalid relative path: " + file);
1074    }
1075
1076    /**
1077     * Get a detail message from an IOException.
1078     * Most, but not all, instances of IOException provide a non-null result
1079     * for getLocalizedMessage().  But some instances return null: in these
1080     * cases, fallover to getMessage(), and if even that is null, return the
1081     * name of the exception itself.
1082     * @param e an IOException
1083     * @return a string to include in a compiler diagnostic
1084     */
1085    public static String getMessage(IOException e) {
1086        String s = e.getLocalizedMessage();
1087        if (s != null)
1088            return s;
1089        s = e.getMessage();
1090        if (s != null)
1091            return s;
1092        return e.toString();
1093    }
1094
1095    private void checkOutputLocation(Location location) {
1096        Objects.requireNonNull(location);
1097        if (!location.isOutputLocation())
1098            throw new IllegalArgumentException("location is not an output location: " + location.getName());
1099    }
1100
1101    private void checkModuleOrientedOrOutputLocation(Location location) {
1102        Objects.requireNonNull(location);
1103        if (!location.isModuleOrientedLocation() && !location.isOutputLocation())
1104            throw new IllegalArgumentException(
1105                    "location is not an output location or a module-oriented location: "
1106                            + location.getName());
1107    }
1108
1109    private void checkNotModuleOrientedLocation(Location location) {
1110        Objects.requireNonNull(location);
1111        if (location.isModuleOrientedLocation())
1112            throw new IllegalArgumentException("location is module-oriented: " + location.getName());
1113    }
1114
1115    /* Converters between files and paths.
1116     * These are temporary until we can update the StandardJavaFileManager API.
1117     */
1118
1119    private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1120        if (files == null)
1121            return null;
1122
1123        return () -> new Iterator<Path>() {
1124            Iterator<? extends File> iter = files.iterator();
1125
1126            @Override
1127            public boolean hasNext() {
1128                return iter.hasNext();
1129            }
1130
1131            @Override
1132            public Path next() {
1133                return iter.next().toPath();
1134            }
1135        };
1136    }
1137
1138    private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1139        if (paths == null)
1140            return null;
1141
1142        return () -> new Iterator<File>() {
1143            Iterator<? extends Path> iter = paths.iterator();
1144
1145            @Override
1146            public boolean hasNext() {
1147                return iter.hasNext();
1148            }
1149
1150            @Override
1151            public File next() {
1152                try {
1153                    return iter.next().toFile();
1154                } catch (UnsupportedOperationException e) {
1155                    throw new IllegalStateException(e);
1156                }
1157            }
1158        };
1159    }
1160}
1161