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