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