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