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