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