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