JavacFileManager.java revision 2973:0e8fa3249327
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 (fname.endsWith("/"))
335                fname = fname.substring(0, fname.length() - 1);
336            if (Files.isDirectory(f)) {
337                if (recurse && SourceVersion.isIdentifier(fname)) {
338                    listDirectory(directory,
339                                  new RelativeDirectory(subdirectory, fname),
340                                  fileKinds,
341                                  recurse,
342                                  resultList);
343                }
344            } else {
345                if (isValidFile(fname, fileKinds)) {
346                    JavaFileObject fe =
347                        new RegularFileObject(this, fname, d.resolve(fname));
348                    resultList.append(fe);
349                }
350            }
351        }
352    }
353
354    /**
355     * Insert all files in subdirectory subdirectory of archive archive
356     * which match fileKinds into resultList
357     */
358    private void listArchive(Archive archive,
359                               RelativeDirectory subdirectory,
360                               Set<JavaFileObject.Kind> fileKinds,
361                               boolean recurse,
362                               ListBuffer<JavaFileObject> resultList) {
363        // Get the files directly in the subdir
364        List<String> files = archive.getFiles(subdirectory);
365        if (files != null) {
366            for (; !files.isEmpty(); files = files.tail) {
367                String file = files.head;
368                if (isValidFile(file, fileKinds)) {
369                    resultList.append(archive.getFileObject(subdirectory, file));
370                }
371            }
372        }
373        if (recurse) {
374            for (RelativeDirectory s: archive.getSubdirectories()) {
375                if (subdirectory.contains(s)) {
376                    // Because the archive map is a flat list of directories,
377                    // the enclosing loop will pick up all child subdirectories.
378                    // Therefore, there is no need to recurse deeper.
379                    listArchive(archive, s, fileKinds, false, resultList);
380                }
381            }
382        }
383    }
384
385    /**
386     * container is a directory, a zip file, or a non-existant path.
387     * Insert all files in subdirectory subdirectory of container which
388     * match fileKinds into resultList
389     */
390    private void listContainer(Path container,
391                               RelativeDirectory subdirectory,
392                               Set<JavaFileObject.Kind> fileKinds,
393                               boolean recurse,
394                               ListBuffer<JavaFileObject> resultList) {
395        Archive archive = archives.get(container);
396        if (archive == null) {
397            // Very temporary and obnoxious interim hack
398            if (container.endsWith("bootmodules.jimage")) {
399                System.err.println("Warning: reference to bootmodules.jimage replaced by jrt:");
400                container = Locations.JRT_MARKER_FILE;
401            } else if (container.getFileName().toString().endsWith(".jimage")) {
402                System.err.println("Warning: reference to " + container + " ignored");
403                return;
404            }
405
406            // archives are not created for directories or jrt: images
407            if (container == Locations.JRT_MARKER_FILE) {
408                try {
409                    listJRTImage(subdirectory,
410                            fileKinds,
411                            recurse,
412                            resultList);
413                } catch (IOException ex) {
414                    ex.printStackTrace(System.err);
415                    log.error("error.reading.file", container, getMessage(ex));
416                }
417                return;
418            }
419
420            if  (fsInfo.isDirectory(container)) {
421                listDirectory(container,
422                              subdirectory,
423                              fileKinds,
424                              recurse,
425                              resultList);
426                return;
427            }
428
429            // Not a directory; either a file or non-existant, create the archive
430            try {
431                archive = openArchive(container);
432            } catch (IOException ex) {
433                log.error("error.reading.file", container, getMessage(ex));
434                return;
435            }
436        }
437        listArchive(archive,
438                    subdirectory,
439                    fileKinds,
440                    recurse,
441                    resultList);
442    }
443
444    private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
445        JavaFileObject.Kind kind = getKind(s);
446        return fileKinds.contains(kind);
447    }
448
449    private static final boolean fileSystemIsCaseSensitive =
450        File.separatorChar == '/';
451
452    /** Hack to make Windows case sensitive. Test whether given path
453     *  ends in a string of characters with the same case as given name.
454     *  Ignore file separators in both path and name.
455     */
456    private boolean caseMapCheck(Path f, RelativePath name) {
457        if (fileSystemIsCaseSensitive) return true;
458        // Note that toRealPath() returns the case-sensitive
459        // spelled file name.
460        String path;
461        char sep;
462        try {
463            path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
464            sep = f.getFileSystem().getSeparator().charAt(0);
465        } catch (IOException ex) {
466            return false;
467        }
468        char[] pcs = path.toCharArray();
469        char[] ncs = name.path.toCharArray();
470        int i = pcs.length - 1;
471        int j = ncs.length - 1;
472        while (i >= 0 && j >= 0) {
473            while (i >= 0 && pcs[i] == sep) i--;
474            while (j >= 0 && ncs[j] == '/') j--;
475            if (i >= 0 && j >= 0) {
476                if (pcs[i] != ncs[j]) return false;
477                i--;
478                j--;
479            }
480        }
481        return j < 0;
482    }
483
484    /**
485     * An archive provides a flat directory structure of a ZipFile by
486     * mapping directory names to lists of files (basenames).
487     */
488    public interface Archive {
489        void close() throws IOException;
490
491        boolean contains(RelativePath name);
492
493        JavaFileObject getFileObject(RelativeDirectory subdirectory, String file);
494
495        List<String> getFiles(RelativeDirectory subdirectory);
496
497        Set<RelativeDirectory> getSubdirectories();
498    }
499
500    public class MissingArchive implements Archive {
501        final Path zipFileName;
502        public MissingArchive(Path name) {
503            zipFileName = name;
504        }
505        @Override
506        public boolean contains(RelativePath name) {
507            return false;
508        }
509
510        @Override
511        public void close() {
512        }
513
514        @Override
515        public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) {
516            return null;
517        }
518
519        @Override
520        public List<String> getFiles(RelativeDirectory subdirectory) {
521            return List.nil();
522        }
523
524        @Override
525        public Set<RelativeDirectory> getSubdirectories() {
526            return Collections.emptySet();
527        }
528
529        @Override
530        public String toString() {
531            return "MissingArchive[" + zipFileName + "]";
532        }
533    }
534
535    /** A directory of zip files already opened.
536     */
537    Map<Path, Archive> archives = new HashMap<>();
538
539    /*
540     * This method looks for a ZipFormatException and takes appropriate
541     * evasive action. If there is a failure in the fast mode then we
542     * fail over to the platform zip, and allow it to deal with a potentially
543     * non compliant zip file.
544     */
545    protected Archive openArchive(Path zipFilename) throws IOException {
546        try {
547            return openArchive(zipFilename, contextUseOptimizedZip);
548        } catch (IOException ioe) {
549            if (ioe instanceof ZipFileIndex.ZipFormatException) {
550                return openArchive(zipFilename, false);
551            } else {
552                throw ioe;
553            }
554        }
555    }
556
557    /** Open a new zip file directory, and cache it.
558     */
559    private Archive openArchive(Path zipFileName, boolean useOptimizedZip) throws IOException {
560        Archive archive;
561        try {
562
563            ZipFile zdir = null;
564
565            boolean usePreindexedCache = false;
566            String preindexCacheLocation = null;
567
568            if (!useOptimizedZip) {
569                zdir = new ZipFile(zipFileName.toFile());
570            } else {
571                usePreindexedCache = options.isSet("usezipindex");
572                preindexCacheLocation = options.get("java.io.tmpdir");
573                String optCacheLoc = options.get("cachezipindexdir");
574
575                if (optCacheLoc != null && optCacheLoc.length() != 0) {
576                    if (optCacheLoc.startsWith("\"")) {
577                        if (optCacheLoc.endsWith("\"")) {
578                            optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1);
579                        }
580                        else {
581                            optCacheLoc = optCacheLoc.substring(1);
582                        }
583                    }
584
585                    File cacheDir = new File(optCacheLoc);
586                    if (cacheDir.exists() && cacheDir.canWrite()) {
587                        preindexCacheLocation = optCacheLoc;
588                        if (!preindexCacheLocation.endsWith("/") &&
589                            !preindexCacheLocation.endsWith(File.separator)) {
590                            preindexCacheLocation += File.separator;
591                        }
592                    }
593                }
594            }
595
596                if (!useOptimizedZip) {
597                    archive = new ZipArchive(this, zdir);
598                } else {
599                    archive = new ZipFileIndexArchive(this,
600                                    zipFileIndexCache.getZipFileIndex(zipFileName,
601                                    null,
602                                    usePreindexedCache,
603                                    preindexCacheLocation,
604                                    options.isSet("writezipindexfiles")));
605                }
606        } catch (FileNotFoundException | NoSuchFileException ex) {
607            archive = new MissingArchive(zipFileName);
608        } catch (ZipFileIndex.ZipFormatException zfe) {
609            throw zfe;
610        } catch (IOException ex) {
611            if (Files.exists(zipFileName))
612                log.error("error.reading.file", zipFileName, getMessage(ex));
613            archive = new MissingArchive(zipFileName);
614        }
615
616        archives.put(zipFileName, archive);
617        return archive;
618    }
619
620    /** Flush any output resources.
621     */
622    @Override @DefinedBy(Api.COMPILER)
623    public void flush() {
624        contentCache.clear();
625    }
626
627    /**
628     * Close the JavaFileManager, releasing resources.
629     */
630    @Override @DefinedBy(Api.COMPILER)
631    public void close() {
632        for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) {
633            Archive a = i.next();
634            i.remove();
635            try {
636                a.close();
637            } catch (IOException ignore) {
638            }
639        }
640    }
641
642    @Override @DefinedBy(Api.COMPILER)
643    public ClassLoader getClassLoader(Location location) {
644        nullCheck(location);
645        Iterable<? extends File> path = getLocation(location);
646        if (path == null)
647            return null;
648        ListBuffer<URL> lb = new ListBuffer<>();
649        for (File f: path) {
650            try {
651                lb.append(f.toURI().toURL());
652            } catch (MalformedURLException e) {
653                throw new AssertionError(e);
654            }
655        }
656
657        return getClassLoader(lb.toArray(new URL[lb.size()]));
658    }
659
660    @Override @DefinedBy(Api.COMPILER)
661    public Iterable<JavaFileObject> list(Location location,
662                                         String packageName,
663                                         Set<JavaFileObject.Kind> kinds,
664                                         boolean recurse)
665        throws IOException
666    {
667        // validatePackageName(packageName);
668        nullCheck(packageName);
669        nullCheck(kinds);
670
671        Iterable<? extends Path> path = getLocationAsPaths(location);
672        if (path == null)
673            return List.nil();
674        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
675        ListBuffer<JavaFileObject> results = new ListBuffer<>();
676
677        for (Path directory : path)
678            listContainer(directory, subdirectory, kinds, recurse, results);
679        return results.toList();
680    }
681
682    @Override @DefinedBy(Api.COMPILER)
683    public String inferBinaryName(Location location, JavaFileObject file) {
684        Objects.requireNonNull(file);
685        Objects.requireNonNull(location);
686        // Need to match the path semantics of list(location, ...)
687        Iterable<? extends Path> path = getLocationAsPaths(location);
688        if (path == null) {
689            return null;
690        }
691
692        if (file instanceof BaseFileObject) {
693            return ((BaseFileObject) file).inferBinaryName(path);
694        } else if (file instanceof PathFileObject) {
695            return ((PathFileObject) file).inferBinaryName(null);
696        } else
697            throw new IllegalArgumentException(file.getClass().getName());
698    }
699
700    @Override @DefinedBy(Api.COMPILER)
701    public boolean isSameFile(FileObject a, FileObject b) {
702        nullCheck(a);
703        nullCheck(b);
704        if (a instanceof PathFileObject && b instanceof PathFileObject)
705            return ((PathFileObject) a).isSameFile((PathFileObject) b);
706        // In time, we should phase out BaseFileObject in favor of PathFileObject
707        if (!(a instanceof BaseFileObject  || a instanceof PathFileObject))
708            throw new IllegalArgumentException("Not supported: " + a);
709        if (!(b instanceof BaseFileObject || b instanceof PathFileObject))
710            throw new IllegalArgumentException("Not supported: " + b);
711        return a.equals(b);
712    }
713
714    @Override @DefinedBy(Api.COMPILER)
715    public boolean hasLocation(Location location) {
716        return getLocation(location) != null;
717    }
718
719    @Override @DefinedBy(Api.COMPILER)
720    public JavaFileObject getJavaFileForInput(Location location,
721                                              String className,
722                                              JavaFileObject.Kind kind)
723        throws IOException
724    {
725        nullCheck(location);
726        // validateClassName(className);
727        nullCheck(className);
728        nullCheck(kind);
729        if (!sourceOrClass.contains(kind))
730            throw new IllegalArgumentException("Invalid kind: " + kind);
731        return getFileForInput(location, RelativeFile.forClass(className, kind));
732    }
733
734    @Override @DefinedBy(Api.COMPILER)
735    public FileObject getFileForInput(Location location,
736                                      String packageName,
737                                      String relativeName)
738        throws IOException
739    {
740        nullCheck(location);
741        // validatePackageName(packageName);
742        nullCheck(packageName);
743        if (!isRelativeUri(relativeName))
744            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
745        RelativeFile name = packageName.length() == 0
746            ? new RelativeFile(relativeName)
747            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
748        return getFileForInput(location, name);
749    }
750
751    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
752        Iterable<? extends Path> path = getLocationAsPaths(location);
753        if (path == null)
754            return null;
755
756        for (Path file: path) {
757            Archive a = archives.get(file);
758            if (a == null) {
759                // archives are not created for directories or jrt: images
760                if (file == Locations.JRT_MARKER_FILE) {
761                    JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
762                    if (symbolFileEnabled && e.ctSym.hidden)
763                        continue;
764                    Path p = e.files.get(name.basename());
765                    if (p != null)
766                        return PathFileObject.createJRTPathFileObject(this, p);
767                    continue;
768                } else if (fsInfo.isDirectory(file)) {
769                    try {
770                        Path f = name.getFile(file);
771                        if (Files.exists(f))
772                            return new RegularFileObject(this, f);
773                    } catch (InvalidPathException ignore) {
774                    }
775                    continue;
776                }
777                // Not a directory, create the archive
778                a = openArchive(file);
779            }
780            // Process the archive
781            if (a.contains(name)) {
782                return a.getFileObject(name.dirname(), name.basename());
783            }
784        }
785        return null;
786    }
787
788    @Override @DefinedBy(Api.COMPILER)
789    public JavaFileObject getJavaFileForOutput(Location location,
790                                               String className,
791                                               JavaFileObject.Kind kind,
792                                               FileObject sibling)
793        throws IOException
794    {
795        nullCheck(location);
796        // validateClassName(className);
797        nullCheck(className);
798        nullCheck(kind);
799        if (!sourceOrClass.contains(kind))
800            throw new IllegalArgumentException("Invalid kind: " + kind);
801        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
802    }
803
804    @Override @DefinedBy(Api.COMPILER)
805    public FileObject getFileForOutput(Location location,
806                                       String packageName,
807                                       String relativeName,
808                                       FileObject sibling)
809        throws IOException
810    {
811        nullCheck(location);
812        // validatePackageName(packageName);
813        nullCheck(packageName);
814        if (!isRelativeUri(relativeName))
815            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
816        RelativeFile name = packageName.length() == 0
817            ? new RelativeFile(relativeName)
818            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
819        return getFileForOutput(location, name, sibling);
820    }
821
822    private JavaFileObject getFileForOutput(Location location,
823                                            RelativeFile fileName,
824                                            FileObject sibling)
825        throws IOException
826    {
827        Path dir;
828        if (location == CLASS_OUTPUT) {
829            if (getClassOutDir() != null) {
830                dir = getClassOutDir();
831            } else {
832                Path siblingDir = null;
833                if (sibling != null && sibling instanceof RegularFileObject) {
834                    siblingDir = ((RegularFileObject)sibling).file.getParent();
835                }
836                if (siblingDir == null)
837                    return new RegularFileObject(this, Paths.get(fileName.basename()));
838                else
839                    return new RegularFileObject(this, siblingDir.resolve(fileName.basename()));
840            }
841        } else if (location == SOURCE_OUTPUT) {
842            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
843        } else {
844            Iterable<? extends Path> path = locations.getLocation(location);
845            dir = null;
846            for (Path f: path) {
847                dir = f;
848                break;
849            }
850        }
851
852        try {
853            Path file = fileName.getFile(dir); // null-safe
854            return new RegularFileObject(this, file);
855        } catch (InvalidPathException e) {
856            throw new IOException("bad filename " + fileName, e);
857        }
858    }
859
860    @Override @DefinedBy(Api.COMPILER)
861    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
862        Iterable<? extends File> files)
863    {
864        ArrayList<RegularFileObject> result;
865        if (files instanceof Collection<?>)
866            result = new ArrayList<>(((Collection<?>)files).size());
867        else
868            result = new ArrayList<>();
869        for (File f: files)
870            result.add(new RegularFileObject(this, nullCheck(f).toPath()));
871        return result;
872    }
873
874    @Override @DefinedBy(Api.COMPILER)
875    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
876        Iterable<? extends Path> paths)
877    {
878        ArrayList<RegularFileObject> result;
879        if (paths instanceof Collection<?>)
880            result = new ArrayList<>(((Collection<?>)paths).size());
881        else
882            result = new ArrayList<>();
883        for (Path p: paths)
884            result.add(new RegularFileObject(this, nullCheck(p)));
885        return result;
886    }
887
888    @Override @DefinedBy(Api.COMPILER)
889    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
890        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
891    }
892
893    @Override @DefinedBy(Api.COMPILER)
894    public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
895        return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
896    }
897
898    @Override @DefinedBy(Api.COMPILER)
899    public void setLocation(Location location,
900                            Iterable<? extends File> searchpath)
901        throws IOException
902    {
903        nullCheck(location);
904        locations.setLocation(location, asPaths(searchpath));
905    }
906
907    @Override @DefinedBy(Api.COMPILER)
908    public void setLocationFromPaths(Location location,
909                            Iterable<? extends Path> searchpath)
910        throws IOException
911    {
912        nullCheck(location);
913        locations.setLocation(location, nullCheck(searchpath));
914    }
915
916    @Override @DefinedBy(Api.COMPILER)
917    public Iterable<? extends File> getLocation(Location location) {
918        nullCheck(location);
919        return asFiles(locations.getLocation(location));
920    }
921
922    @Override @DefinedBy(Api.COMPILER)
923    public Iterable<? extends Path> getLocationAsPaths(Location location) {
924        nullCheck(location);
925        return locations.getLocation(location);
926    }
927
928    private Path getClassOutDir() {
929        return locations.getOutputLocation(CLASS_OUTPUT);
930    }
931
932    private Path getSourceOutDir() {
933        return locations.getOutputLocation(SOURCE_OUTPUT);
934    }
935
936    @Override @DefinedBy(Api.COMPILER)
937    public Path asPath(FileObject file) {
938        if (file instanceof RegularFileObject) {
939            return ((RegularFileObject) file).file;
940        } else
941            throw new IllegalArgumentException(file.getName());
942    }
943
944    /**
945     * Enforces the specification of a "relative" name as used in
946     * {@linkplain #getFileForInput(Location,String,String)
947     * getFileForInput}.  This method must follow the rules defined in
948     * that method, do not make any changes without consulting the
949     * specification.
950     */
951    protected static boolean isRelativeUri(URI uri) {
952        if (uri.isAbsolute())
953            return false;
954        String path = uri.normalize().getPath();
955        if (path.length() == 0 /* isEmpty() is mustang API */)
956            return false;
957        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
958            return false;
959        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
960            return false;
961        return true;
962    }
963
964    // Convenience method
965    protected static boolean isRelativeUri(String u) {
966        try {
967            return isRelativeUri(new URI(u));
968        } catch (URISyntaxException e) {
969            return false;
970        }
971    }
972
973    /**
974     * Converts a relative file name to a relative URI.  This is
975     * different from File.toURI as this method does not canonicalize
976     * the file before creating the URI.  Furthermore, no schema is
977     * used.
978     * @param file a relative file name
979     * @return a relative URI
980     * @throws IllegalArgumentException if the file name is not
981     * relative according to the definition given in {@link
982     * javax.tools.JavaFileManager#getFileForInput}
983     */
984    public static String getRelativeName(File file) {
985        if (!file.isAbsolute()) {
986            String result = file.getPath().replace(File.separatorChar, '/');
987            if (isRelativeUri(result))
988                return result;
989        }
990        throw new IllegalArgumentException("Invalid relative path: " + file);
991    }
992
993    /**
994     * Get a detail message from an IOException.
995     * Most, but not all, instances of IOException provide a non-null result
996     * for getLocalizedMessage().  But some instances return null: in these
997     * cases, fallover to getMessage(), and if even that is null, return the
998     * name of the exception itself.
999     * @param e an IOException
1000     * @return a string to include in a compiler diagnostic
1001     */
1002    public static String getMessage(IOException e) {
1003        String s = e.getLocalizedMessage();
1004        if (s != null)
1005            return s;
1006        s = e.getMessage();
1007        if (s != null)
1008            return s;
1009        return e.toString();
1010    }
1011
1012    /* Converters between files and paths.
1013     * These are temporary until we can update the StandardJavaFileManager API.
1014     */
1015
1016    private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1017        if (files == null)
1018            return null;
1019
1020        return () -> new Iterator<Path>() {
1021            Iterator<? extends File> iter = files.iterator();
1022
1023            @Override
1024            public boolean hasNext() {
1025                return iter.hasNext();
1026            }
1027
1028            @Override
1029            public Path next() {
1030                return iter.next().toPath();
1031            }
1032        };
1033    }
1034
1035    private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1036        if (paths == null)
1037            return null;
1038
1039        return () -> new Iterator<File>() {
1040            Iterator<? extends Path> iter = paths.iterator();
1041
1042            @Override
1043            public boolean hasNext() {
1044                return iter.hasNext();
1045            }
1046
1047            @Override
1048            public File next() {
1049                try {
1050                    return iter.next().toFile();
1051                } catch (UnsupportedOperationException e) {
1052                    throw new IllegalStateException(e);
1053                }
1054            }
1055        };
1056    }
1057}
1058