JavacFileManager.java revision 3155:30e288cb2d22
1/*
2 * Copyright (c) 2005, 2015, 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.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        for (FileSystem fs: fileSystems.values())
529            fs.close();
530    }
531
532    @Override @DefinedBy(Api.COMPILER)
533    public ClassLoader getClassLoader(Location location) {
534        nullCheck(location);
535        Iterable<? extends File> path = getLocation(location);
536        if (path == null)
537            return null;
538        ListBuffer<URL> lb = new ListBuffer<>();
539        for (File f: path) {
540            try {
541                lb.append(f.toURI().toURL());
542            } catch (MalformedURLException e) {
543                throw new AssertionError(e);
544            }
545        }
546
547        return getClassLoader(lb.toArray(new URL[lb.size()]));
548    }
549
550    @Override @DefinedBy(Api.COMPILER)
551    public Iterable<JavaFileObject> list(Location location,
552                                         String packageName,
553                                         Set<JavaFileObject.Kind> kinds,
554                                         boolean recurse)
555        throws IOException
556    {
557        // validatePackageName(packageName);
558        nullCheck(packageName);
559        nullCheck(kinds);
560
561        Iterable<? extends Path> path = getLocationAsPaths(location);
562        if (path == null)
563            return List.nil();
564        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
565        ListBuffer<JavaFileObject> results = new ListBuffer<>();
566
567        for (Path directory : path)
568            listContainer(directory, subdirectory, kinds, recurse, results);
569        return results.toList();
570    }
571
572    @Override @DefinedBy(Api.COMPILER)
573    public String inferBinaryName(Location location, JavaFileObject file) {
574        Objects.requireNonNull(file);
575        Objects.requireNonNull(location);
576        // Need to match the path semantics of list(location, ...)
577        Iterable<? extends Path> path = getLocationAsPaths(location);
578        if (path == null) {
579            return null;
580        }
581
582        if (file instanceof PathFileObject) {
583            return ((PathFileObject) file).inferBinaryName(path);
584        } else
585            throw new IllegalArgumentException(file.getClass().getName());
586    }
587
588    @Override @DefinedBy(Api.COMPILER)
589    public boolean isSameFile(FileObject a, FileObject b) {
590        nullCheck(a);
591        nullCheck(b);
592        if (a instanceof PathFileObject && b instanceof PathFileObject)
593            return ((PathFileObject) a).isSameFile((PathFileObject) b);
594        return a.equals(b);
595    }
596
597    @Override @DefinedBy(Api.COMPILER)
598    public boolean hasLocation(Location location) {
599        return getLocation(location) != null;
600    }
601
602    @Override @DefinedBy(Api.COMPILER)
603    public JavaFileObject getJavaFileForInput(Location location,
604                                              String className,
605                                              JavaFileObject.Kind kind)
606        throws IOException
607    {
608        nullCheck(location);
609        // validateClassName(className);
610        nullCheck(className);
611        nullCheck(kind);
612        if (!sourceOrClass.contains(kind))
613            throw new IllegalArgumentException("Invalid kind: " + kind);
614        return getFileForInput(location, RelativeFile.forClass(className, kind));
615    }
616
617    @Override @DefinedBy(Api.COMPILER)
618    public FileObject getFileForInput(Location location,
619                                      String packageName,
620                                      String relativeName)
621        throws IOException
622    {
623        nullCheck(location);
624        // validatePackageName(packageName);
625        nullCheck(packageName);
626        if (!isRelativeUri(relativeName))
627            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
628        RelativeFile name = packageName.length() == 0
629            ? new RelativeFile(relativeName)
630            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
631        return getFileForInput(location, name);
632    }
633
634    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
635        Iterable<? extends Path> path = getLocationAsPaths(location);
636        if (path == null)
637            return null;
638
639        for (Path file: path) {
640            if (file == Locations.JRT_MARKER_FILE) {
641                JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
642                if (symbolFileEnabled && e.ctSym.hidden)
643                    continue;
644                Path p = e.files.get(name.basename());
645                if (p != null)
646                    return PathFileObject.forJRTPath(this, p);
647            } else if (fsInfo.isDirectory(file)) {
648                try {
649                    Path f = name.resolveAgainst(file);
650                    if (Files.exists(f))
651                        return PathFileObject.forSimplePath(this,
652                                fsInfo.getCanonicalFile(f), f);
653                } catch (InvalidPathException ignore) {
654                }
655            } else if (Files.exists(file)) {
656                FileSystem fs = getFileSystem(file);
657                if (fs != null) {
658                    Path fsRoot = fs.getRootDirectories().iterator().next();
659                    Path f = name.resolveAgainst(fsRoot);
660                    if (Files.exists(f))
661                        return PathFileObject.forJarPath(this, f, file);
662                }
663            }
664        }
665        return null;
666    }
667
668    @Override @DefinedBy(Api.COMPILER)
669    public JavaFileObject getJavaFileForOutput(Location location,
670                                               String className,
671                                               JavaFileObject.Kind kind,
672                                               FileObject sibling)
673        throws IOException
674    {
675        nullCheck(location);
676        // validateClassName(className);
677        nullCheck(className);
678        nullCheck(kind);
679        if (!sourceOrClass.contains(kind))
680            throw new IllegalArgumentException("Invalid kind: " + kind);
681        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
682    }
683
684    @Override @DefinedBy(Api.COMPILER)
685    public FileObject getFileForOutput(Location location,
686                                       String packageName,
687                                       String relativeName,
688                                       FileObject sibling)
689        throws IOException
690    {
691        nullCheck(location);
692        // validatePackageName(packageName);
693        nullCheck(packageName);
694        if (!isRelativeUri(relativeName))
695            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
696        RelativeFile name = packageName.length() == 0
697            ? new RelativeFile(relativeName)
698            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
699        return getFileForOutput(location, name, sibling);
700    }
701
702    private JavaFileObject getFileForOutput(Location location,
703                                            RelativeFile fileName,
704                                            FileObject sibling)
705        throws IOException
706    {
707        Path dir;
708        if (location == CLASS_OUTPUT) {
709            if (getClassOutDir() != null) {
710                dir = getClassOutDir();
711            } else {
712                String baseName = fileName.basename();
713                if (sibling != null && sibling instanceof PathFileObject) {
714                    return ((PathFileObject) sibling).getSibling(baseName);
715                } else {
716                    Path p = Paths.get(baseName);
717                    Path real = fsInfo.getCanonicalFile(p);
718                    return PathFileObject.forSimplePath(this, real, p);
719                }
720            }
721        } else if (location == SOURCE_OUTPUT) {
722            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
723        } else {
724            Iterable<? extends Path> path = locations.getLocation(location);
725            dir = null;
726            for (Path f: path) {
727                dir = f;
728                break;
729            }
730        }
731
732        try {
733            if (dir == null) {
734                dir = Paths.get(System.getProperty("user.dir"));
735            }
736            Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir));
737            return PathFileObject.forDirectoryPath(this, path, dir, fileName);
738        } catch (InvalidPathException e) {
739            throw new IOException("bad filename " + fileName, e);
740        }
741    }
742
743    @Override @DefinedBy(Api.COMPILER)
744    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
745        Iterable<? extends File> files)
746    {
747        ArrayList<PathFileObject> result;
748        if (files instanceof Collection<?>)
749            result = new ArrayList<>(((Collection<?>)files).size());
750        else
751            result = new ArrayList<>();
752        for (File f: files) {
753            Objects.requireNonNull(f);
754            Path p = f.toPath();
755            result.add(PathFileObject.forSimplePath(this,
756                    fsInfo.getCanonicalFile(p), p));
757        }
758        return result;
759    }
760
761    @Override @DefinedBy(Api.COMPILER)
762    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
763        Iterable<? extends Path> paths)
764    {
765        ArrayList<PathFileObject> result;
766        if (paths instanceof Collection<?>)
767            result = new ArrayList<>(((Collection<?>)paths).size());
768        else
769            result = new ArrayList<>();
770        for (Path p: paths)
771            result.add(PathFileObject.forSimplePath(this,
772                    fsInfo.getCanonicalFile(p), p));
773        return result;
774    }
775
776    @Override @DefinedBy(Api.COMPILER)
777    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
778        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
779    }
780
781    @Override @DefinedBy(Api.COMPILER)
782    public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
783        return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
784    }
785
786    @Override @DefinedBy(Api.COMPILER)
787    public void setLocation(Location location,
788                            Iterable<? extends File> searchpath)
789        throws IOException
790    {
791        nullCheck(location);
792        locations.setLocation(location, asPaths(searchpath));
793    }
794
795    @Override @DefinedBy(Api.COMPILER)
796    public void setLocationFromPaths(Location location,
797                            Iterable<? extends Path> searchpath)
798        throws IOException
799    {
800        nullCheck(location);
801        locations.setLocation(location, nullCheck(searchpath));
802    }
803
804    @Override @DefinedBy(Api.COMPILER)
805    public Iterable<? extends File> getLocation(Location location) {
806        nullCheck(location);
807        return asFiles(locations.getLocation(location));
808    }
809
810    @Override @DefinedBy(Api.COMPILER)
811    public Iterable<? extends Path> getLocationAsPaths(Location location) {
812        nullCheck(location);
813        return locations.getLocation(location);
814    }
815
816    private Path getClassOutDir() {
817        return locations.getOutputLocation(CLASS_OUTPUT);
818    }
819
820    private Path getSourceOutDir() {
821        return locations.getOutputLocation(SOURCE_OUTPUT);
822    }
823
824    @Override @DefinedBy(Api.COMPILER)
825    public Path asPath(FileObject file) {
826        if (file instanceof PathFileObject) {
827            return ((PathFileObject) file).path;
828        } else
829            throw new IllegalArgumentException(file.getName());
830    }
831
832    /**
833     * Enforces the specification of a "relative" name as used in
834     * {@linkplain #getFileForInput(Location,String,String)
835     * getFileForInput}.  This method must follow the rules defined in
836     * that method, do not make any changes without consulting the
837     * specification.
838     */
839    protected static boolean isRelativeUri(URI uri) {
840        if (uri.isAbsolute())
841            return false;
842        String path = uri.normalize().getPath();
843        if (path.length() == 0 /* isEmpty() is mustang API */)
844            return false;
845        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
846            return false;
847        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
848            return false;
849        return true;
850    }
851
852    // Convenience method
853    protected static boolean isRelativeUri(String u) {
854        try {
855            return isRelativeUri(new URI(u));
856        } catch (URISyntaxException e) {
857            return false;
858        }
859    }
860
861    /**
862     * Converts a relative file name to a relative URI.  This is
863     * different from File.toURI as this method does not canonicalize
864     * the file before creating the URI.  Furthermore, no schema is
865     * used.
866     * @param file a relative file name
867     * @return a relative URI
868     * @throws IllegalArgumentException if the file name is not
869     * relative according to the definition given in {@link
870     * javax.tools.JavaFileManager#getFileForInput}
871     */
872    public static String getRelativeName(File file) {
873        if (!file.isAbsolute()) {
874            String result = file.getPath().replace(File.separatorChar, '/');
875            if (isRelativeUri(result))
876                return result;
877        }
878        throw new IllegalArgumentException("Invalid relative path: " + file);
879    }
880
881    /**
882     * Get a detail message from an IOException.
883     * Most, but not all, instances of IOException provide a non-null result
884     * for getLocalizedMessage().  But some instances return null: in these
885     * cases, fallover to getMessage(), and if even that is null, return the
886     * name of the exception itself.
887     * @param e an IOException
888     * @return a string to include in a compiler diagnostic
889     */
890    public static String getMessage(IOException e) {
891        String s = e.getLocalizedMessage();
892        if (s != null)
893            return s;
894        s = e.getMessage();
895        if (s != null)
896            return s;
897        return e.toString();
898    }
899
900    /* Converters between files and paths.
901     * These are temporary until we can update the StandardJavaFileManager API.
902     */
903
904    private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
905        if (files == null)
906            return null;
907
908        return () -> new Iterator<Path>() {
909            Iterator<? extends File> iter = files.iterator();
910
911            @Override
912            public boolean hasNext() {
913                return iter.hasNext();
914            }
915
916            @Override
917            public Path next() {
918                return iter.next().toPath();
919            }
920        };
921    }
922
923    private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
924        if (paths == null)
925            return null;
926
927        return () -> new Iterator<File>() {
928            Iterator<? extends Path> iter = paths.iterator();
929
930            @Override
931            public boolean hasNext() {
932                return iter.hasNext();
933            }
934
935            @Override
936            public File next() {
937                try {
938                    return iter.next().toFile();
939                } catch (UnsupportedOperationException e) {
940                    throw new IllegalStateException(e);
941                }
942            }
943        };
944    }
945}
946