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