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