JavacFileManager.java revision 2774:70d213c84585
1/*
2 * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javac.file;
27
28import java.io.File;
29import java.io.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 symbolFileEnabled;
105
106    protected enum SortFiles implements Comparator<Path> {
107        FORWARD {
108            @Override
109            public int compare(Path f1, Path f2) {
110                return f1.getFileName().compareTo(f2.getFileName());
111            }
112        },
113        REVERSE {
114            @Override
115            public int compare(Path f1, Path f2) {
116                return -f1.getFileName().compareTo(f2.getFileName());
117            }
118        }
119    }
120
121    protected SortFiles sortFiles;
122
123    /**
124     * Register a Context.Factory to create a JavacFileManager.
125     */
126    public static void preRegister(Context context) {
127        context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
128            @Override
129            public JavaFileManager make(Context c) {
130                return new JavacFileManager(c, true, null);
131            }
132        });
133    }
134
135    /**
136     * Create a JavacFileManager using a given context, optionally registering
137     * it as the JavaFileManager for that context.
138     */
139    public JavacFileManager(Context context, boolean register, Charset charset) {
140        super(charset);
141        if (register)
142            context.put(JavaFileManager.class, this);
143        setContext(context);
144    }
145
146    /**
147     * Set the context for JavacFileManager.
148     */
149    @Override
150    public void setContext(Context context) {
151        super.setContext(context);
152
153        fsInfo = FSInfo.instance(context);
154
155        contextUseOptimizedZip = options.getBoolean("useOptimizedZip", true);
156        if (contextUseOptimizedZip)
157            zipFileIndexCache = ZipFileIndexCache.getSharedInstance();
158
159        symbolFileEnabled = !options.isSet("ignore.symbol.file");
160
161        String sf = options.get("sortFiles");
162        if (sf != null) {
163            sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
164        }
165    }
166
167    /**
168     * Set whether or not to use ct.sym as an alternate to rt.jar.
169     */
170    public void setSymbolFileEnabled(boolean b) {
171        symbolFileEnabled = b;
172    }
173
174    public boolean isSymbolFileEnabled() {
175        return symbolFileEnabled;
176    }
177
178    // used by tests
179    public JavaFileObject getFileForInput(String name) {
180        return getRegularFile(Paths.get(name));
181    }
182
183    // used by tests
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<Path> paths = new ListBuffer<>();
199        for (String name : names)
200            paths.append(Paths.get(nullCheck(name)));
201        return getJavaFileObjectsFromPaths(paths.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> getJavaFileObjectsFromPaths(
877        Iterable<? extends Path> paths)
878    {
879        ArrayList<RegularFileObject> result;
880        if (paths instanceof Collection<?>)
881            result = new ArrayList<>(((Collection<?>)paths).size());
882        else
883            result = new ArrayList<>();
884        for (Path p: paths)
885            result.add(new RegularFileObject(this, nullCheck(p)));
886        return result;
887    }
888
889    @Override @DefinedBy(Api.COMPILER)
890    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
891        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
892    }
893
894    @Override @DefinedBy(Api.COMPILER)
895    public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
896        return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
897    }
898
899    @Override @DefinedBy(Api.COMPILER)
900    public void setLocation(Location location,
901                            Iterable<? extends File> searchpath)
902        throws IOException
903    {
904        nullCheck(location);
905        locations.setLocation(location, asPaths(searchpath));
906    }
907
908    @Override @DefinedBy(Api.COMPILER)
909    public void setLocationFromPaths(Location location,
910                            Iterable<? extends Path> searchpath)
911        throws IOException
912    {
913        nullCheck(location);
914        locations.setLocation(location, nullCheck(searchpath));
915    }
916
917    @Override @DefinedBy(Api.COMPILER)
918    public Iterable<? extends File> getLocation(Location location) {
919        nullCheck(location);
920        return asFiles(locations.getLocation(location));
921    }
922
923    @Override @DefinedBy(Api.COMPILER)
924    public Iterable<? extends Path> getLocationAsPaths(Location location) {
925        nullCheck(location);
926        return locations.getLocation(location);
927    }
928
929    private Path getClassOutDir() {
930        return locations.getOutputLocation(CLASS_OUTPUT);
931    }
932
933    private Path getSourceOutDir() {
934        return locations.getOutputLocation(SOURCE_OUTPUT);
935    }
936
937    @Override @DefinedBy(Api.COMPILER)
938    public Path asPath(FileObject file) {
939        if (file instanceof RegularFileObject) {
940            return ((RegularFileObject) file).file;
941        } else
942            throw new IllegalArgumentException(file.getName());
943    }
944
945    /**
946     * Enforces the specification of a "relative" name as used in
947     * {@linkplain #getFileForInput(Location,String,String)
948     * getFileForInput}.  This method must follow the rules defined in
949     * that method, do not make any changes without consulting the
950     * specification.
951     */
952    protected static boolean isRelativeUri(URI uri) {
953        if (uri.isAbsolute())
954            return false;
955        String path = uri.normalize().getPath();
956        if (path.length() == 0 /* isEmpty() is mustang API */)
957            return false;
958        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
959            return false;
960        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
961            return false;
962        return true;
963    }
964
965    // Convenience method
966    protected static boolean isRelativeUri(String u) {
967        try {
968            return isRelativeUri(new URI(u));
969        } catch (URISyntaxException e) {
970            return false;
971        }
972    }
973
974    /**
975     * Converts a relative file name to a relative URI.  This is
976     * different from File.toURI as this method does not canonicalize
977     * the file before creating the URI.  Furthermore, no schema is
978     * used.
979     * @param file a relative file name
980     * @return a relative URI
981     * @throws IllegalArgumentException if the file name is not
982     * relative according to the definition given in {@link
983     * javax.tools.JavaFileManager#getFileForInput}
984     */
985    public static String getRelativeName(File file) {
986        if (!file.isAbsolute()) {
987            String result = file.getPath().replace(File.separatorChar, '/');
988            if (isRelativeUri(result))
989                return result;
990        }
991        throw new IllegalArgumentException("Invalid relative path: " + file);
992    }
993
994    /**
995     * Get a detail message from an IOException.
996     * Most, but not all, instances of IOException provide a non-null result
997     * for getLocalizedMessage().  But some instances return null: in these
998     * cases, fallover to getMessage(), and if even that is null, return the
999     * name of the exception itself.
1000     * @param e an IOException
1001     * @return a string to include in a compiler diagnostic
1002     */
1003    public static String getMessage(IOException e) {
1004        String s = e.getLocalizedMessage();
1005        if (s != null)
1006            return s;
1007        s = e.getMessage();
1008        if (s != null)
1009            return s;
1010        return e.toString();
1011    }
1012
1013    /* Converters between files and paths.
1014     * These are temporary until we can update the StandardJavaFileManager API.
1015     */
1016
1017    private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1018        if (files == null)
1019            return null;
1020
1021        return () -> new Iterator<Path>() {
1022            Iterator<? extends File> iter = files.iterator();
1023
1024            @Override
1025            public boolean hasNext() {
1026                return iter.hasNext();
1027            }
1028
1029            @Override
1030            public Path next() {
1031                return iter.next().toPath();
1032            }
1033        };
1034    }
1035
1036    private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1037        if (paths == null)
1038            return null;
1039
1040        return () -> new Iterator<File>() {
1041            Iterator<? extends Path> iter = paths.iterator();
1042
1043            @Override
1044            public boolean hasNext() {
1045                return iter.hasNext();
1046            }
1047
1048            @Override
1049            public File next() {
1050                try {
1051                    return iter.next().toFile();
1052                } catch (UnsupportedOperationException e) {
1053                    throw new IllegalStateException(e);
1054                }
1055            }
1056        };
1057    }
1058}
1059