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