JavacFileManager.java revision 2674:e284f560acf6
1/*
2 * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.javac.file;
27
28import java.io.File;
29import java.io.FileNotFoundException;
30import java.io.IOException;
31import java.net.MalformedURLException;
32import java.net.URI;
33import java.net.URISyntaxException;
34import java.net.URL;
35import java.nio.CharBuffer;
36import java.nio.charset.Charset;
37import java.nio.file.Files;
38import java.nio.file.NoSuchFileException;
39import java.nio.file.Path;
40import java.nio.file.Paths;
41import java.util.ArrayList;
42import java.util.Arrays;
43import java.util.Collection;
44import java.util.Collections;
45import java.util.Comparator;
46import java.util.EnumSet;
47import java.util.HashMap;
48import java.util.Iterator;
49import java.util.Map;
50import java.util.Set;
51import java.util.stream.Collectors;
52import java.util.stream.Stream;
53import java.util.zip.ZipFile;
54
55import javax.lang.model.SourceVersion;
56import javax.tools.FileObject;
57import javax.tools.JavaFileManager;
58import javax.tools.JavaFileObject;
59import javax.tools.StandardJavaFileManager;
60
61import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
62import com.sun.tools.javac.file.RelativePath.RelativeFile;
63import com.sun.tools.javac.util.BaseFileManager;
64import com.sun.tools.javac.util.Context;
65import com.sun.tools.javac.util.DefinedBy;
66import com.sun.tools.javac.util.DefinedBy.Api;
67import com.sun.tools.javac.util.List;
68import com.sun.tools.javac.util.ListBuffer;
69
70import static javax.tools.StandardLocation.*;
71
72/**
73 * This class provides access to the source, class and other files
74 * used by the compiler and related tools.
75 *
76 * <p><b>This is NOT part of any supported API.
77 * If you write code that depends on this, you do so at your own risk.
78 * This code and its internal interfaces are subject to change or
79 * deletion without notice.</b>
80 */
81public class JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
82
83    public static char[] toArray(CharBuffer buffer) {
84        if (buffer.hasArray())
85            return ((CharBuffer)buffer.compact().flip()).array();
86        else
87            return buffer.toString().toCharArray();
88    }
89
90    private FSInfo fsInfo;
91
92    private boolean contextUseOptimizedZip;
93    private ZipFileIndexCache zipFileIndexCache;
94
95    private final Set<JavaFileObject.Kind> sourceOrClass =
96        EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
97
98    protected boolean mmappedIO;
99    protected boolean symbolFileEnabled;
100
101    protected enum SortFiles implements Comparator<Path> {
102        FORWARD {
103            public int compare(Path f1, Path f2) {
104                return f1.getFileName().compareTo(f2.getFileName());
105            }
106        },
107        REVERSE {
108            public int compare(Path f1, Path f2) {
109                return -f1.getFileName().compareTo(f2.getFileName());
110            }
111        }
112    }
113
114    protected SortFiles sortFiles;
115
116    /**
117     * Register a Context.Factory to create a JavacFileManager.
118     */
119    public static void preRegister(Context context) {
120        context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
121            public JavaFileManager make(Context c) {
122                return new JavacFileManager(c, true, null);
123            }
124        });
125    }
126
127    /**
128     * Create a JavacFileManager using a given context, optionally registering
129     * it as the JavaFileManager for that context.
130     */
131    public JavacFileManager(Context context, boolean register, Charset charset) {
132        super(charset);
133        if (register)
134            context.put(JavaFileManager.class, this);
135        setContext(context);
136    }
137
138    /**
139     * Set the context for JavacFileManager.
140     */
141    @Override
142    public void setContext(Context context) {
143        super.setContext(context);
144
145        fsInfo = FSInfo.instance(context);
146
147        contextUseOptimizedZip = options.getBoolean("useOptimizedZip", true);
148        if (contextUseOptimizedZip)
149            zipFileIndexCache = ZipFileIndexCache.getSharedInstance();
150
151        mmappedIO = options.isSet("mmappedIO");
152        symbolFileEnabled = !options.isSet("ignore.symbol.file");
153
154        String sf = options.get("sortFiles");
155        if (sf != null) {
156            sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
157        }
158    }
159
160    /**
161     * Set whether or not to use ct.sym as an alternate to rt.jar.
162     */
163    public void setSymbolFileEnabled(boolean b) {
164        symbolFileEnabled = b;
165    }
166
167    public JavaFileObject getFileForInput(String name) {
168        return getRegularFile(Paths.get(name));
169    }
170
171    public JavaFileObject getRegularFile(Path file) {
172        return new RegularFileObject(this, file);
173    }
174
175    public JavaFileObject getFileForOutput(String classname,
176                                           JavaFileObject.Kind kind,
177                                           JavaFileObject sibling)
178        throws IOException
179    {
180        return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
181    }
182
183    @DefinedBy(Api.COMPILER)
184    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
185        ListBuffer<File> files = new ListBuffer<>();
186        for (String name : names)
187            files.append(new File(nullCheck(name)));
188        return getJavaFileObjectsFromFiles(files.toList());
189    }
190
191    @DefinedBy(Api.COMPILER)
192    public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
193        return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
194    }
195
196    private static boolean isValidName(String name) {
197        // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
198        // but the set of keywords depends on the source level, and we don't want
199        // impls of JavaFileManager to have to be dependent on the source level.
200        // Therefore we simply check that the argument is a sequence of identifiers
201        // separated by ".".
202        for (String s : name.split("\\.", -1)) {
203            if (!SourceVersion.isIdentifier(s))
204                return false;
205        }
206        return true;
207    }
208
209    private static void validateClassName(String className) {
210        if (!isValidName(className))
211            throw new IllegalArgumentException("Invalid class name: " + className);
212    }
213
214    private static void validatePackageName(String packageName) {
215        if (packageName.length() > 0 && !isValidName(packageName))
216            throw new IllegalArgumentException("Invalid packageName name: " + packageName);
217    }
218
219    public static void testName(String name,
220                                boolean isValidPackageName,
221                                boolean isValidClassName)
222    {
223        try {
224            validatePackageName(name);
225            if (!isValidPackageName)
226                throw new AssertionError("Invalid package name accepted: " + name);
227            printAscii("Valid package name: \"%s\"", name);
228        } catch (IllegalArgumentException e) {
229            if (isValidPackageName)
230                throw new AssertionError("Valid package name rejected: " + name);
231            printAscii("Invalid package name: \"%s\"", name);
232        }
233        try {
234            validateClassName(name);
235            if (!isValidClassName)
236                throw new AssertionError("Invalid class name accepted: " + name);
237            printAscii("Valid class name: \"%s\"", name);
238        } catch (IllegalArgumentException e) {
239            if (isValidClassName)
240                throw new AssertionError("Valid class name rejected: " + name);
241            printAscii("Invalid class name: \"%s\"", name);
242        }
243    }
244
245    private static void printAscii(String format, Object... args) {
246        String message;
247        try {
248            final String ascii = "US-ASCII";
249            message = new String(String.format(null, format, args).getBytes(ascii), ascii);
250        } catch (java.io.UnsupportedEncodingException ex) {
251            throw new AssertionError(ex);
252        }
253        System.out.println(message);
254    }
255
256
257    /**
258     * Insert all files in subdirectory subdirectory of directory directory
259     * which match fileKinds into resultList
260     */
261    private void listDirectory(Path directory,
262                               RelativeDirectory subdirectory,
263                               Set<JavaFileObject.Kind> fileKinds,
264                               boolean recurse,
265                               ListBuffer<JavaFileObject> resultList) {
266        Path d = subdirectory.getFile(directory);
267        if (!caseMapCheck(d, subdirectory))
268            return;
269
270
271        java.util.List<Path> files;
272        try (Stream<Path> s = Files.list(d)) {
273            files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList());
274        } catch (IOException ignore) {
275            return;
276        }
277
278        for (Path f: files) {
279            String fname = f.getFileName().toString();
280            if (Files.isDirectory(f)) {
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, d.resolve(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(Path 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(Path 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.toRealPath().toString();
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 Path zipFileName;
423        public MissingArchive(Path 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<Path, 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(Path 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(Path zipFileName, boolean useOptimizedZip) throws IOException {
480        Path origZipFileName = zipFileName;
481        if (symbolFileEnabled && locations.isDefaultBootClassPathRtJar(zipFileName)) {
482            Path file = zipFileName.getParent().getParent(); // ${java.home}
483            if (file.getFileName().equals(Paths.get("jre")))
484                file = file.getParent();
485            // file == ${jdk.home}
486            for (String name : symbolFileLocation)
487                file = file.resolve(name);
488            // file == ${jdk.home}/lib/ct.sym
489            if (Files.exists(file))
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.toFile());
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 | NoSuchFileException ex) {
553            archive = new MissingArchive(zipFileName);
554        } catch (ZipFileIndex.ZipFormatException zfe) {
555            throw zfe;
556        } catch (IOException ex) {
557            if (Files.exists(zipFileName))
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        for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) {
579            Archive a = i.next();
580            i.remove();
581            try {
582                a.close();
583            } catch (IOException e) {
584            }
585        }
586    }
587
588    @DefinedBy(Api.COMPILER)
589    public ClassLoader getClassLoader(Location location) {
590        nullCheck(location);
591        Iterable<? extends File> path = getLocation(location);
592        if (path == null)
593            return null;
594        ListBuffer<URL> lb = new ListBuffer<>();
595        for (File f: path) {
596            try {
597                lb.append(f.toURI().toURL());
598            } catch (MalformedURLException e) {
599                throw new AssertionError(e);
600            }
601        }
602
603        return getClassLoader(lb.toArray(new URL[lb.size()]));
604    }
605
606    @DefinedBy(Api.COMPILER)
607    public Iterable<JavaFileObject> list(Location location,
608                                         String packageName,
609                                         Set<JavaFileObject.Kind> kinds,
610                                         boolean recurse)
611        throws IOException
612    {
613        // validatePackageName(packageName);
614        nullCheck(packageName);
615        nullCheck(kinds);
616
617        Iterable<? extends Path> path = asPaths(getLocation(location));
618        if (path == null)
619            return List.nil();
620        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
621        ListBuffer<JavaFileObject> results = new ListBuffer<>();
622
623        for (Path directory : path)
624            listContainer(directory, subdirectory, kinds, recurse, results);
625        return results.toList();
626    }
627
628    @DefinedBy(Api.COMPILER)
629    public String inferBinaryName(Location location, JavaFileObject file) {
630        file.getClass(); // null check
631        location.getClass(); // null check
632        // Need to match the path semantics of list(location, ...)
633        Iterable<? extends Path> path = getLocationAsPaths(location);
634        if (path == null) {
635            return null;
636        }
637
638        if (file instanceof BaseFileObject) {
639            return ((BaseFileObject) file).inferBinaryName(path);
640        } else
641            throw new IllegalArgumentException(file.getClass().getName());
642    }
643
644    @DefinedBy(Api.COMPILER)
645    public boolean isSameFile(FileObject a, FileObject b) {
646        nullCheck(a);
647        nullCheck(b);
648        if (!(a instanceof BaseFileObject))
649            throw new IllegalArgumentException("Not supported: " + a);
650        if (!(b instanceof BaseFileObject))
651            throw new IllegalArgumentException("Not supported: " + b);
652        return a.equals(b);
653    }
654
655    @DefinedBy(Api.COMPILER)
656    public boolean hasLocation(Location location) {
657        return getLocation(location) != null;
658    }
659
660    @DefinedBy(Api.COMPILER)
661    public JavaFileObject getJavaFileForInput(Location location,
662                                              String className,
663                                              JavaFileObject.Kind kind)
664        throws IOException
665    {
666        nullCheck(location);
667        // validateClassName(className);
668        nullCheck(className);
669        nullCheck(kind);
670        if (!sourceOrClass.contains(kind))
671            throw new IllegalArgumentException("Invalid kind: " + kind);
672        return getFileForInput(location, RelativeFile.forClass(className, kind));
673    }
674
675    @DefinedBy(Api.COMPILER)
676    public FileObject getFileForInput(Location location,
677                                      String packageName,
678                                      String relativeName)
679        throws IOException
680    {
681        nullCheck(location);
682        // validatePackageName(packageName);
683        nullCheck(packageName);
684        if (!isRelativeUri(relativeName))
685            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
686        RelativeFile name = packageName.length() == 0
687            ? new RelativeFile(relativeName)
688            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
689        return getFileForInput(location, name);
690    }
691
692    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
693        Iterable<? extends Path> path = asPaths(getLocation(location));
694        if (path == null)
695            return null;
696
697        for (Path dir: path) {
698            Archive a = archives.get(dir);
699            if (a == null) {
700                if (fsInfo.isDirectory(dir)) {
701                    Path f = name.getFile(dir);
702                    if (Files.exists(f))
703                        return new RegularFileObject(this, f);
704                    continue;
705                }
706                // Not a directory, create the archive
707                a = openArchive(dir);
708            }
709            // Process the archive
710            if (a.contains(name)) {
711                return a.getFileObject(name.dirname(), name.basename());
712            }
713        }
714        return null;
715    }
716
717    @DefinedBy(Api.COMPILER)
718    public JavaFileObject getJavaFileForOutput(Location location,
719                                               String className,
720                                               JavaFileObject.Kind kind,
721                                               FileObject sibling)
722        throws IOException
723    {
724        nullCheck(location);
725        // validateClassName(className);
726        nullCheck(className);
727        nullCheck(kind);
728        if (!sourceOrClass.contains(kind))
729            throw new IllegalArgumentException("Invalid kind: " + kind);
730        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
731    }
732
733    @DefinedBy(Api.COMPILER)
734    public FileObject getFileForOutput(Location location,
735                                       String packageName,
736                                       String relativeName,
737                                       FileObject sibling)
738        throws IOException
739    {
740        nullCheck(location);
741        // validatePackageName(packageName);
742        nullCheck(packageName);
743        if (!isRelativeUri(relativeName))
744            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
745        RelativeFile name = packageName.length() == 0
746            ? new RelativeFile(relativeName)
747            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
748        return getFileForOutput(location, name, sibling);
749    }
750
751    private JavaFileObject getFileForOutput(Location location,
752                                            RelativeFile fileName,
753                                            FileObject sibling)
754        throws IOException
755    {
756        Path dir;
757        if (location == CLASS_OUTPUT) {
758            if (getClassOutDir() != null) {
759                dir = getClassOutDir();
760            } else {
761                Path siblingDir = null;
762                if (sibling != null && sibling instanceof RegularFileObject) {
763                    siblingDir = ((RegularFileObject)sibling).file.getParent();
764                }
765                if (siblingDir == null)
766                    return new RegularFileObject(this, Paths.get(fileName.basename()));
767                else
768                    return new RegularFileObject(this, siblingDir.resolve(fileName.basename()));
769            }
770        } else if (location == SOURCE_OUTPUT) {
771            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
772        } else {
773            Iterable<? extends Path> path = locations.getLocation(location);
774            dir = null;
775            for (Path f: path) {
776                dir = f;
777                break;
778            }
779        }
780
781        Path file = fileName.getFile(dir); // null-safe
782        return new RegularFileObject(this, file);
783
784    }
785
786    @DefinedBy(Api.COMPILER)
787    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
788        Iterable<? extends File> files)
789    {
790        ArrayList<RegularFileObject> result;
791        if (files instanceof Collection<?>)
792            result = new ArrayList<>(((Collection<?>)files).size());
793        else
794            result = new ArrayList<>();
795        for (File f: files)
796            result.add(new RegularFileObject(this, nullCheck(f).toPath()));
797        return result;
798    }
799
800    @DefinedBy(Api.COMPILER)
801    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
802        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
803    }
804
805    @DefinedBy(Api.COMPILER)
806    public void setLocation(Location location,
807                            Iterable<? extends File> searchpath)
808        throws IOException
809    {
810        nullCheck(location);
811        locations.setLocation(location, asPaths(searchpath));
812    }
813
814    @DefinedBy(Api.COMPILER)
815    public Iterable<? extends File> getLocation(Location location) {
816        nullCheck(location);
817        return asFiles(locations.getLocation(location));
818    }
819
820    private Iterable<? extends Path> getLocationAsPaths(Location location) {
821        nullCheck(location);
822        return locations.getLocation(location);
823    }
824
825    private Path getClassOutDir() {
826        return locations.getOutputLocation(CLASS_OUTPUT);
827    }
828
829    private Path getSourceOutDir() {
830        return locations.getOutputLocation(SOURCE_OUTPUT);
831    }
832
833    /**
834     * Enforces the specification of a "relative" name as used in
835     * {@linkplain #getFileForInput(Location,String,String)
836     * getFileForInput}.  This method must follow the rules defined in
837     * that method, do not make any changes without consulting the
838     * specification.
839     */
840    protected static boolean isRelativeUri(URI uri) {
841        if (uri.isAbsolute())
842            return false;
843        String path = uri.normalize().getPath();
844        if (path.length() == 0 /* isEmpty() is mustang API */)
845            return false;
846        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
847            return false;
848        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
849            return false;
850        return true;
851    }
852
853    // Convenience method
854    protected static boolean isRelativeUri(String u) {
855        try {
856            return isRelativeUri(new URI(u));
857        } catch (URISyntaxException e) {
858            return false;
859        }
860    }
861
862    /**
863     * Converts a relative file name to a relative URI.  This is
864     * different from File.toURI as this method does not canonicalize
865     * the file before creating the URI.  Furthermore, no schema is
866     * used.
867     * @param file a relative file name
868     * @return a relative URI
869     * @throws IllegalArgumentException if the file name is not
870     * relative according to the definition given in {@link
871     * javax.tools.JavaFileManager#getFileForInput}
872     */
873    public static String getRelativeName(File file) {
874        if (!file.isAbsolute()) {
875            String result = file.getPath().replace(File.separatorChar, '/');
876            if (isRelativeUri(result))
877                return result;
878        }
879        throw new IllegalArgumentException("Invalid relative path: " + file);
880    }
881
882    /**
883     * Get a detail message from an IOException.
884     * Most, but not all, instances of IOException provide a non-null result
885     * for getLocalizedMessage().  But some instances return null: in these
886     * cases, fallover to getMessage(), and if even that is null, return the
887     * name of the exception itself.
888     * @param e an IOException
889     * @return a string to include in a compiler diagnostic
890     */
891    public static String getMessage(IOException e) {
892        String s = e.getLocalizedMessage();
893        if (s != null)
894            return s;
895        s = e.getMessage();
896        if (s != null)
897            return s;
898        return e.toString();
899    }
900
901    /* Converters between files and paths.
902     * These are temporary until we can update the StandardJavaFileManager API.
903     */
904
905    static Iterable<Path> asPaths(final Iterable<? extends File> files) {
906        if (files == null)
907            return null;
908
909        return () -> new Iterator<Path>() {
910            Iterator<? extends File> iter = files.iterator();
911
912            @Override
913            public boolean hasNext() {
914                return iter.hasNext();
915            }
916
917            @Override
918            public Path next() {
919                return iter.next().toPath();
920            }
921        };
922    }
923
924    static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
925        if (paths == null)
926            return null;
927
928        return () -> new Iterator<File>() {
929            Iterator<? extends Path> iter = paths.iterator();
930
931            @Override
932            public boolean hasNext() {
933                return iter.hasNext();
934            }
935
936            @Override
937            public File next() {
938                return iter.next().toFile();
939            }
940        };
941    }
942
943    static File asFile(Path path) {
944        return path == null ? null : path.toFile();
945    }
946}
947