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