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