JavacFileManager.java revision 3814:cea064fe9c1d
1189251Ssam/*
2214734Srpaulo * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
3252726Srpaulo * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4189251Ssam *
5252726Srpaulo * This code is free software; you can redistribute it and/or modify it
6252726Srpaulo * under the terms of the GNU General Public License version 2 only, as
7189251Ssam * published by the Free Software Foundation.  Oracle designates this
8189251Ssam * particular file as subject to the "Classpath" exception as provided
9189251Ssam * by Oracle in the LICENSE file that accompanied this code.
10189251Ssam *
11189251Ssam * This code is distributed in the hope that it will be useful, but WITHOUT
12189251Ssam * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13189251Ssam * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14189251Ssam * version 2 for more details (a copy is included in the LICENSE file that
15189251Ssam * accompanied this code).
16189251Ssam *
17189251Ssam * You should have received a copy of the GNU General Public License version
18189251Ssam * 2 along with this work; if not, write to the Free Software Foundation,
19189251Ssam * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20189251Ssam *
21189251Ssam * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22189251Ssam * or visit www.oracle.com if you need additional information or have any
23189251Ssam * questions.
24189251Ssam */
25252726Srpaulo
26252726Srpaulopackage com.sun.tools.javac.file;
27252726Srpaulo
28252726Srpauloimport java.io.File;
29252726Srpauloimport java.io.IOException;
30189251Ssamimport java.net.MalformedURLException;
31214734Srpauloimport java.net.URI;
32189251Ssamimport java.net.URISyntaxException;
33189251Ssamimport java.net.URL;
34189251Ssamimport java.nio.CharBuffer;
35189251Ssamimport java.nio.charset.Charset;
36189251Ssamimport java.nio.file.FileSystem;
37189251Ssamimport java.nio.file.FileSystems;
38189251Ssamimport java.nio.file.FileVisitOption;
39189251Ssamimport java.nio.file.FileVisitResult;
40189251Ssamimport java.nio.file.Files;
41189251Ssamimport java.nio.file.InvalidPathException;
42189251Ssamimport java.nio.file.LinkOption;
43189251Ssamimport java.nio.file.Path;
44189251Ssamimport java.nio.file.Paths;
45189251Ssamimport java.nio.file.ProviderNotFoundException;
46189251Ssamimport java.nio.file.SimpleFileVisitor;
47189251Ssamimport java.nio.file.attribute.BasicFileAttributes;
48189251Ssamimport java.nio.file.spi.FileSystemProvider;
49189251Ssamimport java.util.ArrayList;
50189251Ssamimport java.util.Arrays;
51189251Ssamimport java.util.Collection;
52214734Srpauloimport java.util.Collections;
53214734Srpauloimport java.util.Comparator;
54214734Srpauloimport java.util.EnumSet;
55214734Srpauloimport java.util.HashMap;
56252726Srpauloimport java.util.Iterator;
57214734Srpauloimport java.util.Map;
58214734Srpauloimport java.util.Objects;
59214734Srpauloimport java.util.ServiceLoader;
60214734Srpauloimport java.util.Set;
61214734Srpauloimport java.util.stream.Collectors;
62189251Ssamimport java.util.stream.Stream;
63189251Ssam
64189251Ssamimport javax.lang.model.SourceVersion;
65189251Ssamimport javax.tools.FileObject;
66189251Ssamimport javax.tools.JavaFileManager;
67189251Ssamimport javax.tools.JavaFileObject;
68189251Ssamimport javax.tools.StandardJavaFileManager;
69189251Ssam
70189251Ssamimport com.sun.tools.javac.file.RelativePath.RelativeDirectory;
71189251Ssamimport com.sun.tools.javac.file.RelativePath.RelativeFile;
72189251Ssamimport com.sun.tools.javac.util.Assert;
73189251Ssamimport com.sun.tools.javac.util.Context;
74189251Ssamimport com.sun.tools.javac.util.DefinedBy;
75189251Ssamimport com.sun.tools.javac.util.DefinedBy.Api;
76189251Ssamimport com.sun.tools.javac.util.List;
77189251Ssamimport com.sun.tools.javac.util.ListBuffer;
78214734Srpauloimport com.sun.tools.javac.util.JDK9Wrappers.Configuration;
79214734Srpauloimport com.sun.tools.javac.util.JDK9Wrappers.Layer;
80214734Srpauloimport com.sun.tools.javac.util.JDK9Wrappers.ModuleFinder;
81214734Srpauloimport com.sun.tools.javac.util.JDK9Wrappers.Module;
82214734Srpauloimport com.sun.tools.javac.util.JDK9Wrappers.ServiceLoaderHelper;
83214734Srpaulo
84252726Srpauloimport static java.nio.file.FileVisitOption.FOLLOW_LINKS;
85252726Srpaulo
86189251Ssamimport static javax.tools.StandardLocation.*;
87189251Ssam
88189251Ssam/**
89189251Ssam * This class provides access to the source, class and other files
90189251Ssam * used by the compiler and related tools.
91189251Ssam *
92189251Ssam * <p><b>This is NOT part of any supported API.
93189251Ssam * If you write code that depends on this, you do so at your own risk.
94189251Ssam * This code and its internal interfaces are subject to change or
95189251Ssam * deletion without notice.</b>
96189251Ssam */
97189251Ssampublic class JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
98189251Ssam
99189251Ssam    @SuppressWarnings("cast")
100189251Ssam    public static char[] toArray(CharBuffer buffer) {
101189251Ssam        if (buffer.hasArray())
102189251Ssam            return ((CharBuffer)buffer.compact().flip()).array();
103189251Ssam        else
104189251Ssam            return buffer.toString().toCharArray();
105189251Ssam    }
106189251Ssam
107189251Ssam    private FSInfo fsInfo;
108189251Ssam
109189251Ssam    private final Set<JavaFileObject.Kind> sourceOrClass =
110189251Ssam        EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
111189251Ssam
112189251Ssam    protected boolean symbolFileEnabled;
113189251Ssam
114189251Ssam    private PathFactory pathFactory = Paths::get;
115189251Ssam
116189251Ssam    protected enum SortFiles implements Comparator<Path> {
117189251Ssam        FORWARD {
118189251Ssam            @Override
119189251Ssam            public int compare(Path f1, Path f2) {
120189251Ssam                return f1.getFileName().compareTo(f2.getFileName());
121189251Ssam            }
122189251Ssam        },
123189251Ssam        REVERSE {
124189251Ssam            @Override
125189251Ssam            public int compare(Path f1, Path f2) {
126189251Ssam                return -f1.getFileName().compareTo(f2.getFileName());
127189251Ssam            }
128189251Ssam        }
129189251Ssam    }
130189251Ssam
131189251Ssam    protected SortFiles sortFiles;
132189251Ssam
133189251Ssam    /**
134189251Ssam     * Register a Context.Factory to create a JavacFileManager.
135189251Ssam     */
136189251Ssam    public static void preRegister(Context context) {
137189251Ssam        context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
138189251Ssam            @Override
139189251Ssam            public JavaFileManager make(Context c) {
140189251Ssam                return new JavacFileManager(c, true, null);
141189251Ssam            }
142189251Ssam        });
143189251Ssam    }
144189251Ssam
145189251Ssam    /**
146189251Ssam     * Create a JavacFileManager using a given context, optionally registering
147189251Ssam     * it as the JavaFileManager for that context.
148189251Ssam     */
149189251Ssam    public JavacFileManager(Context context, boolean register, Charset charset) {
150189251Ssam        super(charset);
151189251Ssam        if (register)
152189251Ssam            context.put(JavaFileManager.class, this);
153189251Ssam        setContext(context);
154189251Ssam    }
155189251Ssam
156189251Ssam    /**
157189251Ssam     * Set the context for JavacFileManager.
158189251Ssam     */
159189251Ssam    @Override
160189251Ssam    public void setContext(Context context) {
161189251Ssam        super.setContext(context);
162189251Ssam
163189251Ssam        fsInfo = FSInfo.instance(context);
164189251Ssam
165189251Ssam        symbolFileEnabled = !options.isSet("ignore.symbol.file");
166189251Ssam
167189251Ssam        String sf = options.get("sortFiles");
168189251Ssam        if (sf != null) {
169189251Ssam            sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
170189251Ssam        }
171189251Ssam    }
172189251Ssam
173189251Ssam    @Override @DefinedBy(DefinedBy.Api.COMPILER)
174189251Ssam    public void setPathFactory(PathFactory f) {
175189251Ssam        pathFactory = Objects.requireNonNull(f);
176189251Ssam        locations.setPathFactory(f);
177189251Ssam    }
178189251Ssam
179189251Ssam    private Path getPath(String first, String... more) {
180189251Ssam        return pathFactory.getPath(first, more);
181189251Ssam    }
182189251Ssam
183189251Ssam    /**
184189251Ssam     * Set whether or not to use ct.sym as an alternate to rt.jar.
185189251Ssam     */
186189251Ssam    public void setSymbolFileEnabled(boolean b) {
187189251Ssam        symbolFileEnabled = b;
188189251Ssam    }
189189251Ssam
190189251Ssam    public boolean isSymbolFileEnabled() {
191189251Ssam        return symbolFileEnabled;
192189251Ssam    }
193189251Ssam
194189251Ssam    // used by tests
195189251Ssam    public JavaFileObject getJavaFileObject(String name) {
196189251Ssam        return getJavaFileObjects(name).iterator().next();
197189251Ssam    }
198189251Ssam
199189251Ssam    // used by tests
200189251Ssam    public JavaFileObject getJavaFileObject(Path file) {
201189251Ssam        return getJavaFileObjects(file).iterator().next();
202189251Ssam    }
203189251Ssam
204189251Ssam    public JavaFileObject getFileForOutput(String classname,
205189251Ssam                                           JavaFileObject.Kind kind,
206189251Ssam                                           JavaFileObject sibling)
207189251Ssam        throws IOException
208189251Ssam    {
209189251Ssam        return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
210189251Ssam    }
211189251Ssam
212189251Ssam    @Override @DefinedBy(Api.COMPILER)
213189251Ssam    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
214189251Ssam        ListBuffer<Path> paths = new ListBuffer<>();
215189251Ssam        for (String name : names)
216189251Ssam            paths.append(getPath(nullCheck(name)));
217189251Ssam        return getJavaFileObjectsFromPaths(paths.toList());
218189251Ssam    }
219189251Ssam
220189251Ssam    @Override @DefinedBy(Api.COMPILER)
221189251Ssam    public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
222189251Ssam        return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
223189251Ssam    }
224189251Ssam
225189251Ssam    private static boolean isValidName(String name) {
226189251Ssam        // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
227189251Ssam        // but the set of keywords depends on the source level, and we don't want
228189251Ssam        // impls of JavaFileManager to have to be dependent on the source level.
229189251Ssam        // Therefore we simply check that the argument is a sequence of identifiers
230189251Ssam        // separated by ".".
231189251Ssam        for (String s : name.split("\\.", -1)) {
232189251Ssam            if (!SourceVersion.isIdentifier(s))
233189251Ssam                return false;
234189251Ssam        }
235189251Ssam        return true;
236189251Ssam    }
237189251Ssam
238189251Ssam    private static void validateClassName(String className) {
239189251Ssam        if (!isValidName(className))
240189251Ssam            throw new IllegalArgumentException("Invalid class name: " + className);
241189251Ssam    }
242189251Ssam
243189251Ssam    private static void validatePackageName(String packageName) {
244189251Ssam        if (packageName.length() > 0 && !isValidName(packageName))
245189251Ssam            throw new IllegalArgumentException("Invalid packageName name: " + packageName);
246189251Ssam    }
247189251Ssam
248189251Ssam    public static void testName(String name,
249189251Ssam                                boolean isValidPackageName,
250189251Ssam                                boolean isValidClassName)
251189251Ssam    {
252189251Ssam        try {
253189251Ssam            validatePackageName(name);
254189251Ssam            if (!isValidPackageName)
255189251Ssam                throw new AssertionError("Invalid package name accepted: " + name);
256189251Ssam            printAscii("Valid package name: \"%s\"", name);
257189251Ssam        } catch (IllegalArgumentException e) {
258189251Ssam            if (isValidPackageName)
259189251Ssam                throw new AssertionError("Valid package name rejected: " + name);
260189251Ssam            printAscii("Invalid package name: \"%s\"", name);
261189251Ssam        }
262189251Ssam        try {
263189251Ssam            validateClassName(name);
264189251Ssam            if (!isValidClassName)
265189251Ssam                throw new AssertionError("Invalid class name accepted: " + name);
266189251Ssam            printAscii("Valid class name: \"%s\"", name);
267189251Ssam        } catch (IllegalArgumentException e) {
268189251Ssam            if (isValidClassName)
269189251Ssam                throw new AssertionError("Valid class name rejected: " + name);
270189251Ssam            printAscii("Invalid class name: \"%s\"", name);
271189251Ssam        }
272189251Ssam    }
273189251Ssam
274189251Ssam    private static void printAscii(String format, Object... args) {
275189251Ssam        String message;
276189251Ssam        try {
277189251Ssam            final String ascii = "US-ASCII";
278189251Ssam            message = new String(String.format(null, format, args).getBytes(ascii), ascii);
279189251Ssam        } catch (java.io.UnsupportedEncodingException ex) {
280189251Ssam            throw new AssertionError(ex);
281189251Ssam        }
282189251Ssam        System.out.println(message);
283189251Ssam    }
284189251Ssam
285189251Ssam    private final Map<Path, Container> containers = new HashMap<>();
286189251Ssam
287189251Ssam    synchronized Container getContainer(Path path) throws IOException {
288189251Ssam        Container fs = containers.get(path);
289189251Ssam
290189251Ssam        if (fs != null) {
291189251Ssam            return fs;
292189251Ssam        }
293189251Ssam
294189251Ssam        if (fsInfo.isFile(path) && path.equals(Locations.thisSystemModules)) {
295189251Ssam            containers.put(path, fs = new JRTImageContainer());
296189251Ssam            return fs;
297189251Ssam        }
298189251Ssam
299189251Ssam        Path realPath = fsInfo.getCanonicalFile(path);
300189251Ssam
301189251Ssam        fs = containers.get(realPath);
302189251Ssam
303189251Ssam        if (fs != null) {
304189251Ssam            containers.put(path, fs);
305189251Ssam            return fs;
306189251Ssam        }
307189251Ssam
308189251Ssam        BasicFileAttributes attr = null;
309189251Ssam
310189251Ssam        try {
311189251Ssam            attr = Files.readAttributes(realPath, BasicFileAttributes.class);
312189251Ssam        } catch (IOException ex) {
313189251Ssam            //non-existing
314189251Ssam            fs = MISSING_CONTAINER;
315189251Ssam        }
316189251Ssam
317189251Ssam        if (attr != null) {
318189251Ssam            if (attr.isDirectory()) {
319189251Ssam                fs = new DirectoryContainer(realPath);
320189251Ssam            } else {
321189251Ssam                try {
322189251Ssam                    fs = new ArchiveContainer(realPath);
323189251Ssam                } catch (ProviderNotFoundException | SecurityException ex) {
324189251Ssam                    throw new IOException(ex);
325189251Ssam                }
326189251Ssam            }
327189251Ssam        }
328189251Ssam
329189251Ssam        containers.put(realPath, fs);
330189251Ssam        containers.put(path, fs);
331189251Ssam
332189251Ssam        return fs;
333189251Ssam    }
334189251Ssam
335189251Ssam    private interface Container {
336189251Ssam        /**
337189251Ssam         * Insert all files in subdirectory subdirectory of container which
338189251Ssam         * match fileKinds into resultList
339189251Ssam         */
340189251Ssam        public abstract void list(Path userPath,
341189251Ssam                                  RelativeDirectory subdirectory,
342189251Ssam                                  Set<JavaFileObject.Kind> fileKinds,
343189251Ssam                                  boolean recurse,
344189251Ssam                                  ListBuffer<JavaFileObject> resultList) throws IOException;
345189251Ssam        public abstract JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException;
346189251Ssam        public abstract void close() throws IOException;
347189251Ssam    }
348189251Ssam
349189251Ssam    private static final Container MISSING_CONTAINER =  new Container() {
350189251Ssam        @Override
351189251Ssam        public void list(Path userPath,
352189251Ssam                         RelativeDirectory subdirectory,
353189251Ssam                         Set<JavaFileObject.Kind> fileKinds,
354189251Ssam                         boolean recurse,
355189251Ssam                         ListBuffer<JavaFileObject> resultList) throws IOException {
356189251Ssam        }
357189251Ssam        @Override
358189251Ssam        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
359189251Ssam            return null;
360189251Ssam        }
361189251Ssam        @Override
362189251Ssam        public void close() throws IOException {}
363189251Ssam    };
364189251Ssam
365189251Ssam    private final class JRTImageContainer implements Container {
366189251Ssam
367189251Ssam        /**
368189251Ssam         * Insert all files in a subdirectory of the platform image
369189251Ssam         * which match fileKinds into resultList.
370189251Ssam         */
371189251Ssam        @Override
372189251Ssam        public void list(Path userPath,
373189251Ssam                         RelativeDirectory subdirectory,
374189251Ssam                         Set<JavaFileObject.Kind> fileKinds,
375189251Ssam                         boolean recurse,
376189251Ssam                         ListBuffer<JavaFileObject> resultList) throws IOException {
377189251Ssam            try {
378189251Ssam                JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory);
379189251Ssam                if (symbolFileEnabled && e.ctSym.hidden)
380189251Ssam                    return;
381189251Ssam                for (Path file: e.files.values()) {
382189251Ssam                    if (fileKinds.contains(getKind(file))) {
383189251Ssam                        JavaFileObject fe
384189251Ssam                                = PathFileObject.forJRTPath(JavacFileManager.this, file);
385189251Ssam                        resultList.append(fe);
386189251Ssam                    }
387189251Ssam                }
388189251Ssam
389189251Ssam                if (recurse) {
390189251Ssam                    for (RelativeDirectory rd: e.subdirs) {
391189251Ssam                        list(userPath, rd, fileKinds, recurse, resultList);
392189251Ssam                    }
393189251Ssam                }
394189251Ssam            } catch (IOException ex) {
395189251Ssam                ex.printStackTrace(System.err);
396189251Ssam                log.error("error.reading.file", userPath, getMessage(ex));
397189251Ssam            }
398189251Ssam        }
399189251Ssam
400189251Ssam        @Override
401189251Ssam        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
402189251Ssam            JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
403189251Ssam            if (symbolFileEnabled && e.ctSym.hidden)
404189251Ssam                return null;
405189251Ssam            Path p = e.files.get(name.basename());
406189251Ssam            if (p != null) {
407189251Ssam                return PathFileObject.forJRTPath(JavacFileManager.this, p);
408189251Ssam            } else {
409189251Ssam                return null;
410189251Ssam            }
411189251Ssam        }
412189251Ssam
413189251Ssam        @Override
414189251Ssam        public void close() throws IOException {
415189251Ssam        }
416189251Ssam    }
417189251Ssam
418189251Ssam    private synchronized JRTIndex getJRTIndex() {
419189251Ssam        if (jrtIndex == null)
420189251Ssam            jrtIndex = JRTIndex.getSharedInstance();
421189251Ssam        return jrtIndex;
422189251Ssam    }
423189251Ssam
424189251Ssam    private JRTIndex jrtIndex;
425189251Ssam
426189251Ssam    private final class DirectoryContainer implements Container {
427189251Ssam        private final Path directory;
428189251Ssam
429189251Ssam        public DirectoryContainer(Path directory) {
430189251Ssam            this.directory = directory;
431189251Ssam        }
432189251Ssam
433189251Ssam        /**
434189251Ssam         * Insert all files in subdirectory subdirectory of directory userPath
435189251Ssam         * which match fileKinds into resultList
436189251Ssam         */
437189251Ssam        @Override
438189251Ssam        public void list(Path userPath,
439189251Ssam                         RelativeDirectory subdirectory,
440189251Ssam                         Set<JavaFileObject.Kind> fileKinds,
441189251Ssam                         boolean recurse,
442189251Ssam                         ListBuffer<JavaFileObject> resultList) throws IOException {
443189251Ssam            Path d;
444189251Ssam            try {
445189251Ssam                d = subdirectory.resolveAgainst(userPath);
446189251Ssam            } catch (InvalidPathException ignore) {
447189251Ssam                return ;
448189251Ssam            }
449189251Ssam
450189251Ssam            if (!Files.exists(d)) {
451189251Ssam               return;
452189251Ssam            }
453189251Ssam
454189251Ssam            if (!caseMapCheck(d, subdirectory)) {
455189251Ssam                return;
456189251Ssam            }
457189251Ssam
458189251Ssam            java.util.List<Path> files;
459189251Ssam            try (Stream<Path> s = Files.list(d)) {
460189251Ssam                files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList());
461189251Ssam            } catch (IOException ignore) {
462189251Ssam                return;
463189251Ssam            }
464189251Ssam
465189251Ssam            for (Path f: files) {
466189251Ssam                String fname = f.getFileName().toString();
467189251Ssam                if (fname.endsWith("/"))
468189251Ssam                    fname = fname.substring(0, fname.length() - 1);
469189251Ssam                if (Files.isDirectory(f)) {
470189251Ssam                    if (recurse && SourceVersion.isIdentifier(fname)) {
471189251Ssam                        list(userPath,
472189251Ssam                             new RelativeDirectory(subdirectory, fname),
473189251Ssam                             fileKinds,
474189251Ssam                             recurse,
475189251Ssam                             resultList);
476189251Ssam                    }
477189251Ssam                } else {
478189251Ssam                    if (isValidFile(fname, fileKinds)) {
479189251Ssam                        RelativeFile file = new RelativeFile(subdirectory, fname);
480189251Ssam                        JavaFileObject fe = PathFileObject.forDirectoryPath(JavacFileManager.this,
481189251Ssam                                file.resolveAgainst(directory), userPath, file);
482189251Ssam                        resultList.append(fe);
483189251Ssam                    }
484189251Ssam                }
485189251Ssam            }
486189251Ssam        }
487189251Ssam
488189251Ssam        @Override
489189251Ssam        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
490189251Ssam            try {
491189251Ssam                Path f = name.resolveAgainst(userPath);
492189251Ssam                if (Files.exists(f))
493189251Ssam                    return PathFileObject.forSimplePath(JavacFileManager.this,
494189251Ssam                            fsInfo.getCanonicalFile(f), f);
495189251Ssam            } catch (InvalidPathException ignore) {
496189251Ssam            }
497189251Ssam            return null;
498189251Ssam        }
499189251Ssam
500189251Ssam        @Override
501189251Ssam        public void close() throws IOException {
502189251Ssam        }
503189251Ssam    }
504189251Ssam
505189251Ssam    private final class ArchiveContainer implements Container {
506189251Ssam        private final Path archivePath;
507189251Ssam        private final FileSystem fileSystem;
508189251Ssam        private final Map<RelativePath, Path> pathCache = new HashMap<>();
509189251Ssam
510189251Ssam        public ArchiveContainer(Path archivePath) throws IOException, ProviderNotFoundException, SecurityException {
511189251Ssam            this.archivePath = archivePath;
512189251Ssam            if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) {
513189251Ssam                Map<String,String> env = Collections.singletonMap("multi-release", multiReleaseValue);
514189251Ssam                FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
515189251Ssam                Assert.checkNonNull(jarFSProvider, "should have been caught before!");
516189251Ssam                this.fileSystem = jarFSProvider.newFileSystem(archivePath, env);
517189251Ssam            } else {
518189251Ssam                this.fileSystem = FileSystems.newFileSystem(archivePath, null);
519189251Ssam            }
520189251Ssam        }
521189251Ssam
522189251Ssam        /**
523189251Ssam         * Insert all files in subdirectory subdirectory of this archive
524189251Ssam         * which match fileKinds into resultList
525189251Ssam         */
526189251Ssam        @Override
527189251Ssam        public void list(Path userPath,
528252726Srpaulo                         RelativeDirectory subdirectory,
529252726Srpaulo                         Set<JavaFileObject.Kind> fileKinds,
530252726Srpaulo                         boolean recurse,
531252726Srpaulo                         ListBuffer<JavaFileObject> resultList) throws IOException {
532252726Srpaulo            Path resolvedSubdirectory = resolvePath(subdirectory);
533252726Srpaulo
534252726Srpaulo            if (resolvedSubdirectory == null)
535252726Srpaulo                return ;
536252726Srpaulo
537189251Ssam            int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
538189251Ssam            Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
539189251Ssam            Files.walkFileTree(resolvedSubdirectory, opts, maxDepth,
540189251Ssam                    new SimpleFileVisitor<Path>() {
541189251Ssam                        @Override
542189251Ssam                        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
543189251Ssam                            if (isValid(dir.getFileName())) {
544189251Ssam                                return FileVisitResult.CONTINUE;
545189251Ssam                            } else {
546189251Ssam                                return FileVisitResult.SKIP_SUBTREE;
547189251Ssam                            }
548189251Ssam                        }
549189251Ssam
550189251Ssam                        boolean isValid(Path fileName) {
551189251Ssam                            if (fileName == null) {
552189251Ssam                                return true;
553189251Ssam                            } else {
554189251Ssam                                String name = fileName.toString();
555189251Ssam                                if (name.endsWith("/")) {
556189251Ssam                                    name = name.substring(0, name.length() - 1);
557189251Ssam                                }
558189251Ssam                                return SourceVersion.isIdentifier(name);
559189251Ssam                            }
560189251Ssam                        }
561189251Ssam
562189251Ssam                        @Override
563189251Ssam                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
564189251Ssam                            if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) {
565189251Ssam                                JavaFileObject fe = PathFileObject.forJarPath(
566189251Ssam                                        JavacFileManager.this, file, archivePath);
567189251Ssam                                resultList.append(fe);
568189251Ssam                            }
569189251Ssam                            return FileVisitResult.CONTINUE;
570189251Ssam                        }
571189251Ssam                    });
572189251Ssam
573189251Ssam        }
574189251Ssam
575189251Ssam        @Override
576189251Ssam        public JavaFileObject getFileObject(Path userPath, RelativeFile name) throws IOException {
577189251Ssam            Path p = resolvePath(name);
578189251Ssam            if (p != null)
579189251Ssam                return PathFileObject.forJarPath(JavacFileManager.this, p, userPath);
580189251Ssam
581189251Ssam            return null;
582189251Ssam        }
583189251Ssam
584189251Ssam        private synchronized Path resolvePath(RelativePath path) {
585189251Ssam            if (!pathCache.containsKey(path)) {
586189251Ssam                Path relativePath = path.resolveAgainst(fileSystem);
587189251Ssam
588189251Ssam                if (!Files.exists(relativePath)) {
589189251Ssam                    relativePath = null;
590189251Ssam                }
591189251Ssam
592189251Ssam                pathCache.put(path, relativePath);
593189251Ssam                return relativePath;
594189251Ssam            }
595189251Ssam            return pathCache.get(path);
596189251Ssam        }
597189251Ssam
598189251Ssam        @Override
599189251Ssam        public void close() throws IOException {
600189251Ssam            fileSystem.close();
601189251Ssam        }
602189251Ssam    }
603189251Ssam
604189251Ssam    /**
605189251Ssam     * container is a directory, a zip file, or a non-existent path.
606189251Ssam     */
607189251Ssam    private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
608189251Ssam        JavaFileObject.Kind kind = getKind(s);
609189251Ssam        return fileKinds.contains(kind);
610189251Ssam    }
611189251Ssam
612189251Ssam    private static final boolean fileSystemIsCaseSensitive =
613189251Ssam        File.separatorChar == '/';
614189251Ssam
615189251Ssam    /** Hack to make Windows case sensitive. Test whether given path
616189251Ssam     *  ends in a string of characters with the same case as given name.
617189251Ssam     *  Ignore file separators in both path and name.
618189251Ssam     */
619189251Ssam    private boolean caseMapCheck(Path f, RelativePath name) {
620189251Ssam        if (fileSystemIsCaseSensitive) return true;
621189251Ssam        // Note that toRealPath() returns the case-sensitive
622189251Ssam        // spelled file name.
623189251Ssam        String path;
624189251Ssam        char sep;
625189251Ssam        try {
626189251Ssam            path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
627189251Ssam            sep = f.getFileSystem().getSeparator().charAt(0);
628189251Ssam        } catch (IOException ex) {
629189251Ssam            return false;
630189251Ssam        }
631189251Ssam        char[] pcs = path.toCharArray();
632189251Ssam        char[] ncs = name.path.toCharArray();
633189251Ssam        int i = pcs.length - 1;
634189251Ssam        int j = ncs.length - 1;
635189251Ssam        while (i >= 0 && j >= 0) {
636189251Ssam            while (i >= 0 && pcs[i] == sep) i--;
637189251Ssam            while (j >= 0 && ncs[j] == '/') j--;
638189251Ssam            if (i >= 0 && j >= 0) {
639189251Ssam                if (pcs[i] != ncs[j]) return false;
640189251Ssam                i--;
641189251Ssam                j--;
642189251Ssam            }
643189251Ssam        }
644189251Ssam        return j < 0;
645189251Ssam    }
646189251Ssam
647189251Ssam    /** Flush any output resources.
648189251Ssam     */
649189251Ssam    @Override @DefinedBy(Api.COMPILER)
650189251Ssam    public void flush() {
651189251Ssam        contentCache.clear();
652189251Ssam    }
653189251Ssam
654189251Ssam    /**
655189251Ssam     * Close the JavaFileManager, releasing resources.
656189251Ssam     */
657189251Ssam    @Override @DefinedBy(Api.COMPILER)
658189251Ssam    public void close() throws IOException {
659189251Ssam        if (deferredCloseTimeout > 0) {
660189251Ssam            deferredClose();
661189251Ssam            return;
662189251Ssam        }
663189251Ssam
664189251Ssam        locations.close();
665189251Ssam        for (Container container: containers.values()) {
666189251Ssam            container.close();
667189251Ssam        }
668189251Ssam        containers.clear();
669189251Ssam        contentCache.clear();
670189251Ssam    }
671189251Ssam
672189251Ssam    @Override @DefinedBy(Api.COMPILER)
673189251Ssam    public ClassLoader getClassLoader(Location location) {
674189251Ssam        checkNotModuleOrientedLocation(location);
675189251Ssam        Iterable<? extends File> path = getLocation(location);
676189251Ssam        if (path == null)
677189251Ssam            return null;
678189251Ssam        ListBuffer<URL> lb = new ListBuffer<>();
679189251Ssam        for (File f: path) {
680189251Ssam            try {
681189251Ssam                lb.append(f.toURI().toURL());
682189251Ssam            } catch (MalformedURLException e) {
683189251Ssam                throw new AssertionError(e);
684189251Ssam            }
685189251Ssam        }
686189251Ssam
687189251Ssam        return getClassLoader(lb.toArray(new URL[lb.size()]));
688189251Ssam    }
689189251Ssam
690189251Ssam    @Override @DefinedBy(Api.COMPILER)
691189251Ssam    public Iterable<JavaFileObject> list(Location location,
692189251Ssam                                         String packageName,
693189251Ssam                                         Set<JavaFileObject.Kind> kinds,
694189251Ssam                                         boolean recurse)
695214734Srpaulo        throws IOException
696214734Srpaulo    {
697214734Srpaulo        checkNotModuleOrientedLocation(location);
698214734Srpaulo        // validatePackageName(packageName);
699214734Srpaulo        nullCheck(packageName);
700214734Srpaulo        nullCheck(kinds);
701252726Srpaulo
702214734Srpaulo        Iterable<? extends Path> path = getLocationAsPaths(location);
703214734Srpaulo        if (path == null)
704214734Srpaulo            return List.nil();
705214734Srpaulo        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
706214734Srpaulo        ListBuffer<JavaFileObject> results = new ListBuffer<>();
707214734Srpaulo
708214734Srpaulo        for (Path directory : path) {
709214734Srpaulo            Container container = getContainer(directory);
710214734Srpaulo
711214734Srpaulo            container.list(directory, subdirectory, kinds, recurse, results);
712252726Srpaulo        }
713252726Srpaulo
714214734Srpaulo        return results.toList();
715214734Srpaulo    }
716214734Srpaulo
717214734Srpaulo    @Override @DefinedBy(Api.COMPILER)
718214734Srpaulo    public String inferBinaryName(Location location, JavaFileObject file) {
719214734Srpaulo        checkNotModuleOrientedLocation(location);
720214734Srpaulo        Objects.requireNonNull(file);
721214734Srpaulo        // Need to match the path semantics of list(location, ...)
722252726Srpaulo        Iterable<? extends Path> path = getLocationAsPaths(location);
723252726Srpaulo        if (path == null) {
724214734Srpaulo            return null;
725214734Srpaulo        }
726214734Srpaulo
727214734Srpaulo        if (file instanceof PathFileObject) {
728189251Ssam            return ((PathFileObject) file).inferBinaryName(path);
729189251Ssam        } else
730252726Srpaulo            throw new IllegalArgumentException(file.getClass().getName());
731209158Srpaulo    }
732209158Srpaulo
733189251Ssam    @Override @DefinedBy(Api.COMPILER)
734189251Ssam    public boolean isSameFile(FileObject a, FileObject b) {
735189251Ssam        nullCheck(a);
736189251Ssam        nullCheck(b);
737189251Ssam        if (a instanceof PathFileObject && b instanceof PathFileObject)
738209158Srpaulo            return ((PathFileObject) a).isSameFile((PathFileObject) b);
739209158Srpaulo        return a.equals(b);
740209158Srpaulo    }
741209158Srpaulo
742209158Srpaulo    @Override @DefinedBy(Api.COMPILER)
743209158Srpaulo    public boolean hasLocation(Location location) {
744209158Srpaulo        nullCheck(location);
745209158Srpaulo        return locations.hasLocation(location);
746209158Srpaulo    }
747189251Ssam
748189251Ssam    @Override @DefinedBy(Api.COMPILER)
749189251Ssam    public JavaFileObject getJavaFileForInput(Location location,
750189251Ssam                                              String className,
751189251Ssam                                              JavaFileObject.Kind kind)
752189251Ssam        throws IOException
753189251Ssam    {
754189251Ssam        checkNotModuleOrientedLocation(location);
755189251Ssam        // validateClassName(className);
756189251Ssam        nullCheck(className);
757189251Ssam        nullCheck(kind);
758189251Ssam        if (!sourceOrClass.contains(kind))
759189251Ssam            throw new IllegalArgumentException("Invalid kind: " + kind);
760189251Ssam        return getFileForInput(location, RelativeFile.forClass(className, kind));
761189251Ssam    }
762189251Ssam
763189251Ssam    @Override @DefinedBy(Api.COMPILER)
764189251Ssam    public FileObject getFileForInput(Location location,
765189251Ssam                                      String packageName,
766189251Ssam                                      String relativeName)
767189251Ssam        throws IOException
768189251Ssam    {
769189251Ssam        checkNotModuleOrientedLocation(location);
770189251Ssam        // validatePackageName(packageName);
771189251Ssam        nullCheck(packageName);
772189251Ssam        if (!isRelativeUri(relativeName))
773189251Ssam            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
774189251Ssam        RelativeFile name = packageName.length() == 0
775189251Ssam            ? new RelativeFile(relativeName)
776189251Ssam            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
777189251Ssam        return getFileForInput(location, name);
778189251Ssam    }
779189251Ssam
780189251Ssam    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
781189251Ssam        Iterable<? extends Path> path = getLocationAsPaths(location);
782189251Ssam        if (path == null)
783189251Ssam            return null;
784189251Ssam
785189251Ssam        for (Path file: path) {
786189251Ssam            JavaFileObject fo = getContainer(file).getFileObject(file, name);
787189251Ssam
788189251Ssam            if (fo != null) {
789189251Ssam                return fo;
790189251Ssam            }
791189251Ssam        }
792189251Ssam        return null;
793214734Srpaulo    }
794214734Srpaulo
795189251Ssam    @Override @DefinedBy(Api.COMPILER)
796189251Ssam    public JavaFileObject getJavaFileForOutput(Location location,
797189251Ssam                                               String className,
798189251Ssam                                               JavaFileObject.Kind kind,
799189251Ssam                                               FileObject sibling)
800189251Ssam        throws IOException
801189251Ssam    {
802189251Ssam        checkOutputLocation(location);
803189251Ssam        // validateClassName(className);
804189251Ssam        nullCheck(className);
805189251Ssam        nullCheck(kind);
806189251Ssam        if (!sourceOrClass.contains(kind))
807189251Ssam            throw new IllegalArgumentException("Invalid kind: " + kind);
808189251Ssam        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
809189251Ssam    }
810189251Ssam
811189251Ssam    @Override @DefinedBy(Api.COMPILER)
812189251Ssam    public FileObject getFileForOutput(Location location,
813189251Ssam                                       String packageName,
814189251Ssam                                       String relativeName,
815189251Ssam                                       FileObject sibling)
816189251Ssam        throws IOException
817189251Ssam    {
818189251Ssam        checkOutputLocation(location);
819189251Ssam        // validatePackageName(packageName);
820189251Ssam        nullCheck(packageName);
821189251Ssam        if (!isRelativeUri(relativeName))
822189251Ssam            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
823189251Ssam        RelativeFile name = packageName.length() == 0
824189251Ssam            ? new RelativeFile(relativeName)
825189251Ssam            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
826189251Ssam        return getFileForOutput(location, name, sibling);
827189251Ssam    }
828189251Ssam
829189251Ssam    private JavaFileObject getFileForOutput(Location location,
830189251Ssam                                            RelativeFile fileName,
831189251Ssam                                            FileObject sibling)
832189251Ssam        throws IOException
833189251Ssam    {
834189251Ssam        Path dir;
835189251Ssam        if (location == CLASS_OUTPUT) {
836189251Ssam            if (getClassOutDir() != null) {
837189251Ssam                dir = getClassOutDir();
838189251Ssam            } else {
839189251Ssam                String baseName = fileName.basename();
840189251Ssam                if (sibling != null && sibling instanceof PathFileObject) {
841189251Ssam                    return ((PathFileObject) sibling).getSibling(baseName);
842189251Ssam                } else {
843189251Ssam                    Path p = getPath(baseName);
844189251Ssam                    Path real = fsInfo.getCanonicalFile(p);
845189251Ssam                    return PathFileObject.forSimplePath(this, real, p);
846189251Ssam                }
847189251Ssam            }
848189251Ssam        } else if (location == SOURCE_OUTPUT) {
849189251Ssam            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
850189251Ssam        } else {
851189251Ssam            Iterable<? extends Path> path = locations.getLocation(location);
852189251Ssam            dir = null;
853189251Ssam            for (Path f: path) {
854189251Ssam                dir = f;
855189251Ssam                break;
856189251Ssam            }
857189251Ssam        }
858189251Ssam
859189251Ssam        try {
860189251Ssam            if (dir == null) {
861189251Ssam                dir = getPath(System.getProperty("user.dir"));
862189251Ssam            }
863189251Ssam            Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir));
864189251Ssam            return PathFileObject.forDirectoryPath(this, path, dir, fileName);
865189251Ssam        } catch (InvalidPathException e) {
866189251Ssam            throw new IOException("bad filename " + fileName, e);
867189251Ssam        }
868189251Ssam    }
869189251Ssam
870189251Ssam    @Override @DefinedBy(Api.COMPILER)
871189251Ssam    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
872189251Ssam        Iterable<? extends File> files)
873189251Ssam    {
874189251Ssam        ArrayList<PathFileObject> result;
875189251Ssam        if (files instanceof Collection<?>)
876189251Ssam            result = new ArrayList<>(((Collection<?>)files).size());
877189251Ssam        else
878189251Ssam            result = new ArrayList<>();
879189251Ssam        for (File f: files) {
880189251Ssam            Objects.requireNonNull(f);
881189251Ssam            Path p = f.toPath();
882189251Ssam            result.add(PathFileObject.forSimplePath(this,
883189251Ssam                    fsInfo.getCanonicalFile(p), p));
884189251Ssam        }
885189251Ssam        return result;
886189251Ssam    }
887189251Ssam
888189251Ssam    @Override @DefinedBy(Api.COMPILER)
889189251Ssam    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
890189251Ssam        Iterable<? extends Path> paths)
891189251Ssam    {
892189251Ssam        ArrayList<PathFileObject> result;
893189251Ssam        if (paths instanceof Collection<?>)
894189251Ssam            result = new ArrayList<>(((Collection<?>)paths).size());
895189251Ssam        else
896189251Ssam            result = new ArrayList<>();
897189251Ssam        for (Path p: paths)
898189251Ssam            result.add(PathFileObject.forSimplePath(this,
899189251Ssam                    fsInfo.getCanonicalFile(p), p));
900189251Ssam        return result;
901189251Ssam    }
902189251Ssam
903189251Ssam    @Override @DefinedBy(Api.COMPILER)
904189251Ssam    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
905189251Ssam        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
906189251Ssam    }
907189251Ssam
908189251Ssam    @Override @DefinedBy(Api.COMPILER)
909189251Ssam    public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
910189251Ssam        return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
911189251Ssam    }
912189251Ssam
913189251Ssam    @Override @DefinedBy(Api.COMPILER)
914189251Ssam    public void setLocation(Location location,
915189251Ssam                            Iterable<? extends File> searchpath)
916189251Ssam        throws IOException
917189251Ssam    {
918189251Ssam        nullCheck(location);
919189251Ssam        locations.setLocation(location, asPaths(searchpath));
920189251Ssam    }
921189251Ssam
922189251Ssam    @Override @DefinedBy(Api.COMPILER)
923189251Ssam    public void setLocationFromPaths(Location location,
924189251Ssam                            Collection<? extends Path> searchpath)
925189251Ssam        throws IOException
926189251Ssam    {
927189251Ssam        nullCheck(location);
928189251Ssam        locations.setLocation(location, nullCheck(searchpath));
929189251Ssam    }
930189251Ssam
931189251Ssam    @Override @DefinedBy(Api.COMPILER)
932189251Ssam    public Iterable<? extends File> getLocation(Location location) {
933189251Ssam        nullCheck(location);
934189251Ssam        return asFiles(locations.getLocation(location));
935189251Ssam    }
936189251Ssam
937189251Ssam    @Override @DefinedBy(Api.COMPILER)
938189251Ssam    public Iterable<? extends Path> getLocationAsPaths(Location location) {
939189251Ssam        nullCheck(location);
940189251Ssam        return locations.getLocation(location);
941189251Ssam    }
942189251Ssam
943189251Ssam    private Path getClassOutDir() {
944189251Ssam        return locations.getOutputLocation(CLASS_OUTPUT);
945189251Ssam    }
946189251Ssam
947189251Ssam    private Path getSourceOutDir() {
948189251Ssam        return locations.getOutputLocation(SOURCE_OUTPUT);
949189251Ssam    }
950189251Ssam
951189251Ssam    @Override @DefinedBy(Api.COMPILER)
952189251Ssam    public Location getLocationForModule(Location location, String moduleName) throws IOException {
953189251Ssam        checkModuleOrientedOrOutputLocation(location);
954189251Ssam        nullCheck(moduleName);
955189251Ssam        return locations.getLocationForModule(location, moduleName);
956189251Ssam    }
957189251Ssam
958189251Ssam    @Override @DefinedBy(Api.COMPILER)
959189251Ssam    public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException {
960189251Ssam        nullCheck(location);
961189251Ssam        nullCheck(service);
962189251Ssam        Module.getModule(getClass()).addUses(service);
963189251Ssam        if (location.isModuleOrientedLocation()) {
964189251Ssam            Collection<Path> paths = locations.getLocation(location);
965189251Ssam            ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()]));
966189251Ssam            Layer bootLayer = Layer.boot();
967189251Ssam            Configuration cf = bootLayer.configuration().resolveRequiresAndUses(ModuleFinder.of(), finder, Collections.emptySet());
968189251Ssam            Layer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader());
969189251Ssam            return ServiceLoaderHelper.load(layer, service);
970189251Ssam        } else {
971189251Ssam            return ServiceLoader.load(service, getClassLoader(location));
972189251Ssam        }
973189251Ssam    }
974189251Ssam
975189251Ssam    @Override @DefinedBy(Api.COMPILER)
976189251Ssam    public Location getLocationForModule(Location location, JavaFileObject fo, String pkgName) throws IOException {
977189251Ssam        checkModuleOrientedOrOutputLocation(location);
978189251Ssam        if (!(fo instanceof PathFileObject))
979189251Ssam            throw new IllegalArgumentException(fo.getName());
980189251Ssam        int depth = 1; // allow 1 for filename
981189251Ssam        if (pkgName != null && !pkgName.isEmpty()) {
982189251Ssam            depth += 1;
983189251Ssam            for (int i = 0; i < pkgName.length(); i++) {
984189251Ssam                switch (pkgName.charAt(i)) {
985189251Ssam                    case '/': case '.':
986189251Ssam                        depth++;
987189251Ssam                }
988189251Ssam            }
989189251Ssam        }
990189251Ssam        Path p = Locations.normalize(((PathFileObject) fo).path);
991189251Ssam        int fc = p.getNameCount();
992189251Ssam        if (depth < fc) {
993189251Ssam            Path root = p.getRoot();
994189251Ssam            Path subpath = p.subpath(0, fc - depth);
995189251Ssam            Path dir = (root == null) ? subpath : root.resolve(subpath);
996189251Ssam            // need to find dir in location
997189251Ssam            return locations.getLocationForModule(location, dir);
998189251Ssam        } else {
999189251Ssam            return null;
1000189251Ssam        }
1001189251Ssam    }
1002189251Ssam
1003189251Ssam    @Override @DefinedBy(Api.COMPILER)
1004189251Ssam    public String inferModuleName(Location location) {
1005189251Ssam        checkNotModuleOrientedLocation(location);
1006189251Ssam        return locations.inferModuleName(location);
1007189251Ssam    }
1008189251Ssam
1009189251Ssam    @Override @DefinedBy(Api.COMPILER)
1010189251Ssam    public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
1011189251Ssam        checkModuleOrientedOrOutputLocation(location);
1012189251Ssam        return locations.listLocationsForModules(location);
1013189251Ssam    }
1014189251Ssam
1015189251Ssam    @Override @DefinedBy(Api.COMPILER)
1016189251Ssam    public Path asPath(FileObject file) {
1017189251Ssam        if (file instanceof PathFileObject) {
1018189251Ssam            return ((PathFileObject) file).path;
1019189251Ssam        } else
1020189251Ssam            throw new IllegalArgumentException(file.getName());
1021189251Ssam    }
1022189251Ssam
1023189251Ssam    /**
1024189251Ssam     * Enforces the specification of a "relative" name as used in
1025189251Ssam     * {@linkplain #getFileForInput(Location,String,String)
1026189251Ssam     * getFileForInput}.  This method must follow the rules defined in
1027189251Ssam     * that method, do not make any changes without consulting the
1028189251Ssam     * specification.
1029189251Ssam     */
1030189251Ssam    protected static boolean isRelativeUri(URI uri) {
1031189251Ssam        if (uri.isAbsolute())
1032189251Ssam            return false;
1033189251Ssam        String path = uri.normalize().getPath();
1034189251Ssam        if (path.length() == 0 /* isEmpty() is mustang API */)
1035189251Ssam            return false;
1036189251Ssam        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
1037189251Ssam            return false;
1038189251Ssam        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
1039189251Ssam            return false;
1040189251Ssam        return true;
1041189251Ssam    }
1042189251Ssam
1043189251Ssam    // Convenience method
1044189251Ssam    protected static boolean isRelativeUri(String u) {
1045189251Ssam        try {
1046189251Ssam            return isRelativeUri(new URI(u));
1047189251Ssam        } catch (URISyntaxException e) {
1048189251Ssam            return false;
1049189251Ssam        }
1050189251Ssam    }
1051189251Ssam
1052189251Ssam    /**
1053189251Ssam     * Converts a relative file name to a relative URI.  This is
1054189251Ssam     * different from File.toURI as this method does not canonicalize
1055189251Ssam     * the file before creating the URI.  Furthermore, no schema is
1056189251Ssam     * used.
1057189251Ssam     * @param file a relative file name
1058189251Ssam     * @return a relative URI
1059189251Ssam     * @throws IllegalArgumentException if the file name is not
1060189251Ssam     * relative according to the definition given in {@link
1061214734Srpaulo     * javax.tools.JavaFileManager#getFileForInput}
1062214734Srpaulo     */
1063214734Srpaulo    public static String getRelativeName(File file) {
1064214734Srpaulo        if (!file.isAbsolute()) {
1065214734Srpaulo            String result = file.getPath().replace(File.separatorChar, '/');
1066214734Srpaulo            if (isRelativeUri(result))
1067214734Srpaulo                return result;
1068214734Srpaulo        }
1069214734Srpaulo        throw new IllegalArgumentException("Invalid relative path: " + file);
1070214734Srpaulo    }
1071214734Srpaulo
1072214734Srpaulo    /**
1073214734Srpaulo     * Get a detail message from an IOException.
1074214734Srpaulo     * Most, but not all, instances of IOException provide a non-null result
1075214734Srpaulo     * for getLocalizedMessage().  But some instances return null: in these
1076214734Srpaulo     * cases, fallover to getMessage(), and if even that is null, return the
1077214734Srpaulo     * name of the exception itself.
1078214734Srpaulo     * @param e an IOException
1079214734Srpaulo     * @return a string to include in a compiler diagnostic
1080214734Srpaulo     */
1081214734Srpaulo    public static String getMessage(IOException e) {
1082214734Srpaulo        String s = e.getLocalizedMessage();
1083214734Srpaulo        if (s != null)
1084214734Srpaulo            return s;
1085214734Srpaulo        s = e.getMessage();
1086214734Srpaulo        if (s != null)
1087214734Srpaulo            return s;
1088214734Srpaulo        return e.toString();
1089214734Srpaulo    }
1090214734Srpaulo
1091214734Srpaulo    private void checkOutputLocation(Location location) {
1092214734Srpaulo        Objects.requireNonNull(location);
1093214734Srpaulo        if (!location.isOutputLocation())
1094214734Srpaulo            throw new IllegalArgumentException("location is not an output location: " + location.getName());
1095214734Srpaulo    }
1096214734Srpaulo
1097214734Srpaulo    private void checkModuleOrientedOrOutputLocation(Location location) {
1098214734Srpaulo        Objects.requireNonNull(location);
1099214734Srpaulo        if (!location.isModuleOrientedLocation() && !location.isOutputLocation())
1100214734Srpaulo            throw new IllegalArgumentException(
1101214734Srpaulo                    "location is not an output location or a module-oriented location: "
1102214734Srpaulo                            + location.getName());
1103214734Srpaulo    }
1104214734Srpaulo
1105214734Srpaulo    private void checkNotModuleOrientedLocation(Location location) {
1106214734Srpaulo        Objects.requireNonNull(location);
1107214734Srpaulo        if (location.isModuleOrientedLocation())
1108214734Srpaulo            throw new IllegalArgumentException("location is module-oriented: " + location.getName());
1109214734Srpaulo    }
1110214734Srpaulo
1111214734Srpaulo    /* Converters between files and paths.
1112214734Srpaulo     * These are temporary until we can update the StandardJavaFileManager API.
1113214734Srpaulo     */
1114214734Srpaulo
1115214734Srpaulo    private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1116214734Srpaulo        if (files == null)
1117214734Srpaulo            return null;
1118214734Srpaulo
1119214734Srpaulo        return () -> new Iterator<Path>() {
1120214734Srpaulo            Iterator<? extends File> iter = files.iterator();
1121214734Srpaulo
1122214734Srpaulo            @Override
1123214734Srpaulo            public boolean hasNext() {
1124214734Srpaulo                return iter.hasNext();
1125214734Srpaulo            }
1126214734Srpaulo
1127214734Srpaulo            @Override
1128214734Srpaulo            public Path next() {
1129214734Srpaulo                return iter.next().toPath();
1130214734Srpaulo            }
1131214734Srpaulo        };
1132214734Srpaulo    }
1133214734Srpaulo
1134214734Srpaulo    private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1135214734Srpaulo        if (paths == null)
1136214734Srpaulo            return null;
1137214734Srpaulo
1138214734Srpaulo        return () -> new Iterator<File>() {
1139214734Srpaulo            Iterator<? extends Path> iter = paths.iterator();
1140214734Srpaulo
1141214734Srpaulo            @Override
1142214734Srpaulo            public boolean hasNext() {
1143214734Srpaulo                return iter.hasNext();
1144214734Srpaulo            }
1145214734Srpaulo
1146214734Srpaulo            @Override
1147214734Srpaulo            public File next() {
1148214734Srpaulo                try {
1149214734Srpaulo                    return iter.next().toFile();
1150214734Srpaulo                } catch (UnsupportedOperationException e) {
1151214734Srpaulo                    throw new IllegalStateException(e);
1152214734Srpaulo                }
1153214734Srpaulo            }
1154214734Srpaulo        };
1155214734Srpaulo    }
1156252726Srpaulo}
1157214734Srpaulo