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