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