JavacFileManager.java revision 3262:21d9e172e9f6
11195Srgrimes/*
250472Speter * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
337Srgrimes * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
473251Sgshapiro *
538103Speter * This code is free software; you can redistribute it and/or modify it
673251Sgshapiro * under the terms of the GNU General Public License version 2 only, as
738103Speter * published by the Free Software Foundation.  Oracle designates this
899451Sru * particular file as subject to the "Classpath" exception as provided
9114780Sdougb * by Oracle in the LICENSE file that accompanied this code.
10114780Sdougb *
1165532Snectar * This code is distributed in the hope that it will be useful, but WITHOUT
1255230Speter * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13113674Smtm * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14126756Smlaier * version 2 for more details (a copy is included in the LICENSE file that
15114555Sdougb * accompanied this code).
16114492Sdougb *
1798187Sgordon * You should have received a copy of the GNU General Public License version
1855230Speter * 2 along with this work; if not, write to the Free Software Foundation,
191734Sjkh * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2017639Swosch *
2117639Swosch * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22121911Smarkm * or visit www.oracle.com if you need additional information or have any
23121911Smarkm * questions.
24121911Smarkm */
2537Srgrimes
26121911Smarkmpackage com.sun.tools.javac.file;
2798548Sru
2857488Speterimport java.io.File;
2974837Sgreenimport java.io.IOException;
30124214Sdesimport java.net.MalformedURLException;
3157459Smarkmimport java.net.URI;
3260677Skrisimport java.net.URISyntaxException;
3360677Skrisimport java.net.URL;
3460677Skrisimport java.nio.CharBuffer;
3582521Saleximport java.nio.charset.Charset;
36108002Sgreenimport java.nio.file.FileSystem;
37147Srgrimesimport java.nio.file.FileSystems;
3827487Sasamiimport java.nio.file.FileVisitOption;
3965168Sasamiimport java.nio.file.FileVisitResult;
4095144Sgshapiroimport java.nio.file.Files;
4195144Sgshapiroimport java.nio.file.InvalidPathException;
4295144Sgshapiroimport java.nio.file.LinkOption;
43135851Sdougbimport java.nio.file.Path;
44135851Sdougbimport java.nio.file.Paths;
45135851Sdougbimport java.nio.file.SimpleFileVisitor;
46135851Sdougbimport java.nio.file.attribute.BasicFileAttributes;
47135851Sdougbimport java.util.ArrayList;
48135851Sdougbimport java.util.Arrays;
4999451Sruimport java.util.Collection;
50135851Sdougbimport java.util.Comparator;
5199451Sruimport java.util.EnumSet;
5290281Sumeimport java.util.HashMap;
53135851Sdougbimport java.util.Iterator;
5499451Sruimport java.util.Map;
5599451Sruimport java.util.Objects;
5699451Sruimport java.util.Set;
57117292Sgshapiroimport java.util.stream.Collectors;
58117292Sgshapiroimport java.util.stream.Stream;
59117292Sgshapiro
6064598Sgshapiroimport javax.lang.model.SourceVersion;
6164598Sgshapiroimport javax.tools.FileObject;
62117292Sgshapiroimport javax.tools.JavaFileManager;
6337Srgrimesimport javax.tools.JavaFileObject;
64263Srgrimesimport javax.tools.StandardJavaFileManager;
6599449Sru
66263Srgrimesimport com.sun.tools.javac.file.RelativePath.RelativeDirectory;
67124831Sruimport com.sun.tools.javac.file.RelativePath.RelativeFile;
68124831Sruimport com.sun.tools.javac.util.Context;
69124831Sruimport com.sun.tools.javac.util.DefinedBy;
70124831Sruimport com.sun.tools.javac.util.DefinedBy.Api;
71124831Sruimport com.sun.tools.javac.util.List;
724487Sphkimport com.sun.tools.javac.util.ListBuffer;
73124831Sru
7495327Sobrienimport static java.nio.file.FileVisitOption.FOLLOW_LINKS;
755948Sjkh
764487Sphkimport static javax.tools.StandardLocation.*;
7799449Sru
78100872Sru/**
7999451Sru * This class provides access to the source, class and other files
80111810Sru * used by the compiler and related tools.
81100872Sru *
8299451Sru * <p><b>This is NOT part of any supported API.
83100872Sru * If you write code that depends on this, you do so at your own risk.
8499451Sru * This code and its internal interfaces are subject to change or
8599451Sru * deletion without notice.</b>
86121580Semax */
87119385Smtmpublic class JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
8899449Sru
8999449Sru    @SuppressWarnings("cast")
9099449Sru    public static char[] toArray(CharBuffer buffer) {
9199449Sru        if (buffer.hasArray())
9299449Sru            return ((CharBuffer)buffer.compact().flip()).array();
9399449Sru        else
9477041Sru            return buffer.toString().toCharArray();
9599449Sru    }
9677041Sru
9773251Sgshapiro    private FSInfo fsInfo;
9899449Sru
9973251Sgshapiro    private final Set<JavaFileObject.Kind> sourceOrClass =
100120202Smarkm        EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
10198548Sru
102100872Sru    protected boolean symbolFileEnabled;
10399451Sru
10457488Speter    protected enum SortFiles implements Comparator<Path> {
105100872Sru        FORWARD {
10699451Sru            @Override
10760677Skris            public int compare(Path f1, Path f2) {
108120709Sphk                return f1.getFileName().compareTo(f2.getFileName());
10999449Sru            }
110100872Sru        },
111120709Sphk        REVERSE {
112120709Sphk            @Override
113120709Sphk            public int compare(Path f1, Path f2) {
114120709Sphk                return -f1.getFileName().compareTo(f2.getFileName());
11599451Sru            }
116100872Sru        }
11799451Sru    }
118100872Sru
11999451Sru    protected SortFiles sortFiles;
12099451Sru
12199451Sru    /**
12299451Sru     * Register a Context.Factory to create a JavacFileManager.
123100872Sru     */
1241731Sjkh    public static void preRegister(Context context) {
125135851Sdougb        context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
126100872Sru            @Override
1271731Sjkh            public JavaFileManager make(Context c) {
128135851Sdougb                return new JavacFileManager(c, true, null);
129119058Sobrien            }
1306177Samurai        });
131100872Sru    }
13264598Sgshapiro
13364629Sgshapiro    /**
13464629Sgshapiro     * Create a JavacFileManager using a given context, optionally registering
13564629Sgshapiro     * it as the JavaFileManager for that context.
13664629Sgshapiro     */
13764629Sgshapiro    public JavacFileManager(Context context, boolean register, Charset charset) {
138100872Sru        super(charset);
13937Srgrimes        if (register)
140100872Sru            context.put(JavaFileManager.class, this);
141147Srgrimes        setContext(context);
142100872Sru    }
14392100Srwatson
144100872Sru    /**
14599449Sru     * Set the context for JavacFileManager.
146103720Smarkm     */
147113259Sdes    @Override
148113259Sdes    public void setContext(Context context) {
149103738Smarkm        super.setContext(context);
150100872Sru
151147Srgrimes        fsInfo = FSInfo.instance(context);
152100872Sru
15337Srgrimes        symbolFileEnabled = !options.isSet("ignore.symbol.file");
154100872Sru
155288Srgrimes        String sf = options.get("sortFiles");
156100872Sru        if (sf != null) {
157147Srgrimes            sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
158100872Sru        }
15950126Sgreen    }
160100872Sru
16113378Sache    /**
162100872Sru     * Set whether or not to use ct.sym as an alternate to rt.jar.
16317104Spst     */
164100872Sru    public void setSymbolFileEnabled(boolean b) {
165147Srgrimes        symbolFileEnabled = b;
166100872Sru    }
16737Srgrimes
168100872Sru    public boolean isSymbolFileEnabled() {
1691759Sjkh        return symbolFileEnabled;
170100872Sru    }
17199451Sru
172126977Sru    // used by tests
173126977Sru    public JavaFileObject getJavaFileObject(String name) {
174126977Sru        return getJavaFileObjects(name).iterator().next();
17537Srgrimes    }
176147Srgrimes
177127339Sdes    // used by tests
178127339Sdes    public JavaFileObject getJavaFileObject(Path file) {
179127339Sdes        return getJavaFileObjects(file).iterator().next();
180127339Sdes    }
1817129Srgrimes
182135875Sdougb    public JavaFileObject getFileForOutput(String classname,
183135875Sdougb                                           JavaFileObject.Kind kind,
184135875Sdougb                                           JavaFileObject sibling)
185135875Sdougb        throws IOException
186135875Sdougb    {
187135875Sdougb        return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
188135875Sdougb    }
189135875Sdougb
190135875Sdougb    @Override @DefinedBy(Api.COMPILER)
19195144Sgshapiro    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
19295144Sgshapiro        ListBuffer<Path> paths = new ListBuffer<>();
19395144Sgshapiro        for (String name : names)
194410Srgrimes            paths.append(Paths.get(nullCheck(name)));
19577993Sache        return getJavaFileObjectsFromPaths(paths.toList());
19677993Sache    }
19777993Sache
19877993Sache    @Override @DefinedBy(Api.COMPILER)
19977993Sache    public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
20077993Sache        return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
20177993Sache    }
20277993Sache
20377993Sache    private static boolean isValidName(String name) {
204110663Sache        // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
205110663Sache        // but the set of keywords depends on the source level, and we don't want
206110663Sache        // impls of JavaFileManager to have to be dependent on the source level.
207110663Sache        // Therefore we simply check that the argument is a sequence of identifiers
208110663Sache        // separated by ".".
209110663Sache        for (String s : name.split("\\.", -1)) {
210110663Sache            if (!SourceVersion.isIdentifier(s))
211110663Sache                return false;
212110655Snectar        }
21311635Sache        return true;
21477999Sache    }
21511635Sache
21611635Sache    private static void validateClassName(String className) {
21711635Sache        if (!isValidName(className))
21811635Sache            throw new IllegalArgumentException("Invalid class name: " + className);
21911635Sache    }
22077999Sache
221147Srgrimes    private static void validatePackageName(String packageName) {
22248185Ssheldonh        if (packageName.length() > 0 && !isValidName(packageName))
223100872Sru            throw new IllegalArgumentException("Invalid packageName name: " + packageName);
22499451Sru    }
22599451Sru
226119385Smtm    public static void testName(String name,
227119385Smtm                                boolean isValidPackageName,
22848185Ssheldonh                                boolean isValidClassName)
22937Srgrimes    {
230        try {
231            validatePackageName(name);
232            if (!isValidPackageName)
233                throw new AssertionError("Invalid package name accepted: " + name);
234            printAscii("Valid package name: \"%s\"", name);
235        } catch (IllegalArgumentException e) {
236            if (isValidPackageName)
237                throw new AssertionError("Valid package name rejected: " + name);
238            printAscii("Invalid package name: \"%s\"", name);
239        }
240        try {
241            validateClassName(name);
242            if (!isValidClassName)
243                throw new AssertionError("Invalid class name accepted: " + name);
244            printAscii("Valid class name: \"%s\"", name);
245        } catch (IllegalArgumentException e) {
246            if (isValidClassName)
247                throw new AssertionError("Valid class name rejected: " + name);
248            printAscii("Invalid class name: \"%s\"", name);
249        }
250    }
251
252    private static void printAscii(String format, Object... args) {
253        String message;
254        try {
255            final String ascii = "US-ASCII";
256            message = new String(String.format(null, format, args).getBytes(ascii), ascii);
257        } catch (java.io.UnsupportedEncodingException ex) {
258            throw new AssertionError(ex);
259        }
260        System.out.println(message);
261    }
262
263    /**
264     * Insert all files in a subdirectory of the platform image
265     * which match fileKinds into resultList.
266     */
267    private void listJRTImage(RelativeDirectory subdirectory,
268                               Set<JavaFileObject.Kind> fileKinds,
269                               boolean recurse,
270                               ListBuffer<JavaFileObject> resultList) throws IOException {
271        JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory);
272        if (symbolFileEnabled && e.ctSym.hidden)
273            return;
274        for (Path file: e.files.values()) {
275            if (fileKinds.contains(getKind(file))) {
276                JavaFileObject fe
277                        = PathFileObject.forJRTPath(JavacFileManager.this, file);
278                resultList.append(fe);
279            }
280        }
281
282        if (recurse) {
283            for (RelativeDirectory rd: e.subdirs) {
284                listJRTImage(rd, fileKinds, recurse, resultList);
285            }
286        }
287    }
288
289    private synchronized JRTIndex getJRTIndex() {
290        if (jrtIndex == null)
291            jrtIndex = JRTIndex.getSharedInstance();
292        return jrtIndex;
293    }
294
295    private JRTIndex jrtIndex;
296
297
298    /**
299     * Insert all files in subdirectory subdirectory of directory directory
300     * which match fileKinds into resultList
301     */
302    private void listDirectory(Path directory, Path realDirectory,
303                               RelativeDirectory subdirectory,
304                               Set<JavaFileObject.Kind> fileKinds,
305                               boolean recurse,
306                               ListBuffer<JavaFileObject> resultList) {
307        Path d;
308        try {
309            d = subdirectory.resolveAgainst(directory);
310        } catch (InvalidPathException ignore) {
311            return;
312        }
313
314        if (!Files.exists(d)) {
315           return;
316        }
317
318        if (!caseMapCheck(d, subdirectory)) {
319            return;
320        }
321
322        java.util.List<Path> files;
323        try (Stream<Path> s = Files.list(d)) {
324            files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList());
325        } catch (IOException ignore) {
326            return;
327        }
328
329        if (realDirectory == null)
330            realDirectory = fsInfo.getCanonicalFile(directory);
331
332        for (Path f: files) {
333            String fname = f.getFileName().toString();
334            if (fname.endsWith("/"))
335                fname = fname.substring(0, fname.length() - 1);
336            if (Files.isDirectory(f)) {
337                if (recurse && SourceVersion.isIdentifier(fname)) {
338                    listDirectory(directory, realDirectory,
339                                  new RelativeDirectory(subdirectory, fname),
340                                  fileKinds,
341                                  recurse,
342                                  resultList);
343                }
344            } else {
345                if (isValidFile(fname, fileKinds)) {
346                    RelativeFile file = new RelativeFile(subdirectory, fname);
347                    JavaFileObject fe = PathFileObject.forDirectoryPath(this,
348                            file.resolveAgainst(realDirectory), directory, file);
349                    resultList.append(fe);
350                }
351            }
352        }
353    }
354
355    /**
356     * Insert all files in subdirectory subdirectory of archive archivePath
357     * which match fileKinds into resultList
358     */
359    private void listArchive(Path archivePath,
360            RelativeDirectory subdirectory,
361            Set<JavaFileObject.Kind> fileKinds,
362            boolean recurse,
363            ListBuffer<JavaFileObject> resultList)
364                throws IOException {
365        FileSystem fs = getFileSystem(archivePath);
366        if (fs == null) {
367            return;
368        }
369
370        Path containerSubdir = subdirectory.resolveAgainst(fs);
371        if (!Files.exists(containerSubdir)) {
372            return;
373        }
374
375        int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
376        Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
377        Files.walkFileTree(containerSubdir, opts, maxDepth,
378                new SimpleFileVisitor<Path>() {
379                    @Override
380                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
381                        if (isValid(dir.getFileName())) {
382                            return FileVisitResult.CONTINUE;
383                        } else {
384                            return FileVisitResult.SKIP_SUBTREE;
385                        }
386                    }
387
388                    boolean isValid(Path fileName) {
389                        if (fileName == null) {
390                            return true;
391                        } else {
392                            String name = fileName.toString();
393                            if (name.endsWith("/")) {
394                                name = name.substring(0, name.length() - 1);
395                            }
396                            return SourceVersion.isIdentifier(name);
397                        }
398                    }
399
400                    @Override
401                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
402                        if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) {
403                            JavaFileObject fe = PathFileObject.forJarPath(
404                                    JavacFileManager.this, file, archivePath);
405                            resultList.append(fe);
406                        }
407                        return FileVisitResult.CONTINUE;
408                    }
409                });
410
411    }
412
413    /**
414     * container is a directory, a zip file, or a non-existant path.
415     * Insert all files in subdirectory subdirectory of container which
416     * match fileKinds into resultList
417     */
418    private void listContainer(Path container,
419                               RelativeDirectory subdirectory,
420                               Set<JavaFileObject.Kind> fileKinds,
421                               boolean recurse,
422                               ListBuffer<JavaFileObject> resultList)
423            throws IOException {
424        // Very temporary and obnoxious interim hack
425        if (container.endsWith("bootmodules.jimage")) {
426            System.err.println("Warning: reference to bootmodules.jimage replaced by jrt:");
427            container = Locations.JRT_MARKER_FILE;
428        } else if (container.getNameCount() > 0 && container.getFileName().toString().endsWith(".jimage")) {
429            System.err.println("Warning: reference to " + container + " ignored");
430            return;
431        }
432
433        if (container == Locations.JRT_MARKER_FILE) {
434            try {
435                listJRTImage(subdirectory,
436                        fileKinds,
437                        recurse,
438                        resultList);
439            } catch (IOException ex) {
440                ex.printStackTrace(System.err);
441                log.error("error.reading.file", container, getMessage(ex));
442            }
443            return;
444        }
445
446        if  (fsInfo.isDirectory(container)) {
447            listDirectory(container, null,
448                          subdirectory,
449                          fileKinds,
450                          recurse,
451                          resultList);
452            return;
453        }
454
455        if (Files.exists(container)) {
456            listArchive(container,
457                    subdirectory,
458                    fileKinds,
459                    recurse,
460                    resultList);
461        }
462    }
463
464    private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
465        JavaFileObject.Kind kind = getKind(s);
466        return fileKinds.contains(kind);
467    }
468
469    private static final boolean fileSystemIsCaseSensitive =
470        File.separatorChar == '/';
471
472    /** Hack to make Windows case sensitive. Test whether given path
473     *  ends in a string of characters with the same case as given name.
474     *  Ignore file separators in both path and name.
475     */
476    private boolean caseMapCheck(Path f, RelativePath name) {
477        if (fileSystemIsCaseSensitive) return true;
478        // Note that toRealPath() returns the case-sensitive
479        // spelled file name.
480        String path;
481        char sep;
482        try {
483            path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
484            sep = f.getFileSystem().getSeparator().charAt(0);
485        } catch (IOException ex) {
486            return false;
487        }
488        char[] pcs = path.toCharArray();
489        char[] ncs = name.path.toCharArray();
490        int i = pcs.length - 1;
491        int j = ncs.length - 1;
492        while (i >= 0 && j >= 0) {
493            while (i >= 0 && pcs[i] == sep) i--;
494            while (j >= 0 && ncs[j] == '/') j--;
495            if (i >= 0 && j >= 0) {
496                if (pcs[i] != ncs[j]) return false;
497                i--;
498                j--;
499            }
500        }
501        return j < 0;
502    }
503
504    private FileSystem getFileSystem(Path path) throws IOException {
505        Path realPath = fsInfo.getCanonicalFile(path);
506        FileSystem fs = fileSystems.get(realPath);
507        if (fs == null) {
508            fileSystems.put(realPath, fs = FileSystems.newFileSystem(realPath, null));
509        }
510        return fs;
511    }
512
513    private final Map<Path,FileSystem> fileSystems = new HashMap<>();
514
515
516    /** Flush any output resources.
517     */
518    @Override @DefinedBy(Api.COMPILER)
519    public void flush() {
520        contentCache.clear();
521    }
522
523    /**
524     * Close the JavaFileManager, releasing resources.
525     */
526    @Override @DefinedBy(Api.COMPILER)
527    public void close() throws IOException {
528        if (deferredCloseTimeout > 0) {
529            deferredClose();
530            return;
531        }
532
533        for (FileSystem fs: fileSystems.values()) {
534            fs.close();
535        }
536        fileSystems.clear();
537        contentCache.clear();
538    }
539
540    @Override @DefinedBy(Api.COMPILER)
541    public ClassLoader getClassLoader(Location location) {
542        nullCheck(location);
543        Iterable<? extends File> path = getLocation(location);
544        if (path == null)
545            return null;
546        ListBuffer<URL> lb = new ListBuffer<>();
547        for (File f: path) {
548            try {
549                lb.append(f.toURI().toURL());
550            } catch (MalformedURLException e) {
551                throw new AssertionError(e);
552            }
553        }
554
555        return getClassLoader(lb.toArray(new URL[lb.size()]));
556    }
557
558    @Override @DefinedBy(Api.COMPILER)
559    public Iterable<JavaFileObject> list(Location location,
560                                         String packageName,
561                                         Set<JavaFileObject.Kind> kinds,
562                                         boolean recurse)
563        throws IOException
564    {
565        // validatePackageName(packageName);
566        nullCheck(packageName);
567        nullCheck(kinds);
568
569        Iterable<? extends Path> path = getLocationAsPaths(location);
570        if (path == null)
571            return List.nil();
572        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
573        ListBuffer<JavaFileObject> results = new ListBuffer<>();
574
575        for (Path directory : path)
576            listContainer(directory, subdirectory, kinds, recurse, results);
577        return results.toList();
578    }
579
580    @Override @DefinedBy(Api.COMPILER)
581    public String inferBinaryName(Location location, JavaFileObject file) {
582        Objects.requireNonNull(file);
583        Objects.requireNonNull(location);
584        // Need to match the path semantics of list(location, ...)
585        Iterable<? extends Path> path = getLocationAsPaths(location);
586        if (path == null) {
587            return null;
588        }
589
590        if (file instanceof PathFileObject) {
591            return ((PathFileObject) file).inferBinaryName(path);
592        } else
593            throw new IllegalArgumentException(file.getClass().getName());
594    }
595
596    @Override @DefinedBy(Api.COMPILER)
597    public boolean isSameFile(FileObject a, FileObject b) {
598        nullCheck(a);
599        nullCheck(b);
600        if (a instanceof PathFileObject && b instanceof PathFileObject)
601            return ((PathFileObject) a).isSameFile((PathFileObject) b);
602        return a.equals(b);
603    }
604
605    @Override @DefinedBy(Api.COMPILER)
606    public boolean hasLocation(Location location) {
607        return getLocation(location) != null;
608    }
609
610    @Override @DefinedBy(Api.COMPILER)
611    public JavaFileObject getJavaFileForInput(Location location,
612                                              String className,
613                                              JavaFileObject.Kind kind)
614        throws IOException
615    {
616        nullCheck(location);
617        // validateClassName(className);
618        nullCheck(className);
619        nullCheck(kind);
620        if (!sourceOrClass.contains(kind))
621            throw new IllegalArgumentException("Invalid kind: " + kind);
622        return getFileForInput(location, RelativeFile.forClass(className, kind));
623    }
624
625    @Override @DefinedBy(Api.COMPILER)
626    public FileObject getFileForInput(Location location,
627                                      String packageName,
628                                      String relativeName)
629        throws IOException
630    {
631        nullCheck(location);
632        // validatePackageName(packageName);
633        nullCheck(packageName);
634        if (!isRelativeUri(relativeName))
635            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
636        RelativeFile name = packageName.length() == 0
637            ? new RelativeFile(relativeName)
638            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
639        return getFileForInput(location, name);
640    }
641
642    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
643        Iterable<? extends Path> path = getLocationAsPaths(location);
644        if (path == null)
645            return null;
646
647        for (Path file: path) {
648            if (file == Locations.JRT_MARKER_FILE) {
649                JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
650                if (symbolFileEnabled && e.ctSym.hidden)
651                    continue;
652                Path p = e.files.get(name.basename());
653                if (p != null)
654                    return PathFileObject.forJRTPath(this, p);
655            } else if (fsInfo.isDirectory(file)) {
656                try {
657                    Path f = name.resolveAgainst(file);
658                    if (Files.exists(f))
659                        return PathFileObject.forSimplePath(this,
660                                fsInfo.getCanonicalFile(f), f);
661                } catch (InvalidPathException ignore) {
662                }
663            } else if (Files.exists(file)) {
664                FileSystem fs = getFileSystem(file);
665                if (fs != null) {
666                    Path fsRoot = fs.getRootDirectories().iterator().next();
667                    Path f = name.resolveAgainst(fsRoot);
668                    if (Files.exists(f))
669                        return PathFileObject.forJarPath(this, f, file);
670                }
671            }
672        }
673        return null;
674    }
675
676    @Override @DefinedBy(Api.COMPILER)
677    public JavaFileObject getJavaFileForOutput(Location location,
678                                               String className,
679                                               JavaFileObject.Kind kind,
680                                               FileObject sibling)
681        throws IOException
682    {
683        nullCheck(location);
684        // validateClassName(className);
685        nullCheck(className);
686        nullCheck(kind);
687        if (!sourceOrClass.contains(kind))
688            throw new IllegalArgumentException("Invalid kind: " + kind);
689        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
690    }
691
692    @Override @DefinedBy(Api.COMPILER)
693    public FileObject getFileForOutput(Location location,
694                                       String packageName,
695                                       String relativeName,
696                                       FileObject sibling)
697        throws IOException
698    {
699        nullCheck(location);
700        // validatePackageName(packageName);
701        nullCheck(packageName);
702        if (!isRelativeUri(relativeName))
703            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
704        RelativeFile name = packageName.length() == 0
705            ? new RelativeFile(relativeName)
706            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
707        return getFileForOutput(location, name, sibling);
708    }
709
710    private JavaFileObject getFileForOutput(Location location,
711                                            RelativeFile fileName,
712                                            FileObject sibling)
713        throws IOException
714    {
715        Path dir;
716        if (location == CLASS_OUTPUT) {
717            if (getClassOutDir() != null) {
718                dir = getClassOutDir();
719            } else {
720                String baseName = fileName.basename();
721                if (sibling != null && sibling instanceof PathFileObject) {
722                    return ((PathFileObject) sibling).getSibling(baseName);
723                } else {
724                    Path p = Paths.get(baseName);
725                    Path real = fsInfo.getCanonicalFile(p);
726                    return PathFileObject.forSimplePath(this, real, p);
727                }
728            }
729        } else if (location == SOURCE_OUTPUT) {
730            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
731        } else {
732            Iterable<? extends Path> path = locations.getLocation(location);
733            dir = null;
734            for (Path f: path) {
735                dir = f;
736                break;
737            }
738        }
739
740        try {
741            if (dir == null) {
742                dir = Paths.get(System.getProperty("user.dir"));
743            }
744            Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir));
745            return PathFileObject.forDirectoryPath(this, path, dir, fileName);
746        } catch (InvalidPathException e) {
747            throw new IOException("bad filename " + fileName, e);
748        }
749    }
750
751    @Override @DefinedBy(Api.COMPILER)
752    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
753        Iterable<? extends File> files)
754    {
755        ArrayList<PathFileObject> result;
756        if (files instanceof Collection<?>)
757            result = new ArrayList<>(((Collection<?>)files).size());
758        else
759            result = new ArrayList<>();
760        for (File f: files) {
761            Objects.requireNonNull(f);
762            Path p = f.toPath();
763            result.add(PathFileObject.forSimplePath(this,
764                    fsInfo.getCanonicalFile(p), p));
765        }
766        return result;
767    }
768
769    @Override @DefinedBy(Api.COMPILER)
770    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
771        Iterable<? extends Path> paths)
772    {
773        ArrayList<PathFileObject> result;
774        if (paths instanceof Collection<?>)
775            result = new ArrayList<>(((Collection<?>)paths).size());
776        else
777            result = new ArrayList<>();
778        for (Path p: paths)
779            result.add(PathFileObject.forSimplePath(this,
780                    fsInfo.getCanonicalFile(p), p));
781        return result;
782    }
783
784    @Override @DefinedBy(Api.COMPILER)
785    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
786        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
787    }
788
789    @Override @DefinedBy(Api.COMPILER)
790    public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
791        return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
792    }
793
794    @Override @DefinedBy(Api.COMPILER)
795    public void setLocation(Location location,
796                            Iterable<? extends File> searchpath)
797        throws IOException
798    {
799        nullCheck(location);
800        locations.setLocation(location, asPaths(searchpath));
801    }
802
803    @Override @DefinedBy(Api.COMPILER)
804    public void setLocationFromPaths(Location location,
805                            Iterable<? extends Path> searchpath)
806        throws IOException
807    {
808        nullCheck(location);
809        locations.setLocation(location, nullCheck(searchpath));
810    }
811
812    @Override @DefinedBy(Api.COMPILER)
813    public Iterable<? extends File> getLocation(Location location) {
814        nullCheck(location);
815        return asFiles(locations.getLocation(location));
816    }
817
818    @Override @DefinedBy(Api.COMPILER)
819    public Iterable<? extends Path> getLocationAsPaths(Location location) {
820        nullCheck(location);
821        return locations.getLocation(location);
822    }
823
824    private Path getClassOutDir() {
825        return locations.getOutputLocation(CLASS_OUTPUT);
826    }
827
828    private Path getSourceOutDir() {
829        return locations.getOutputLocation(SOURCE_OUTPUT);
830    }
831
832    @Override @DefinedBy(Api.COMPILER)
833    public Path asPath(FileObject file) {
834        if (file instanceof PathFileObject) {
835            return ((PathFileObject) file).path;
836        } else
837            throw new IllegalArgumentException(file.getName());
838    }
839
840    /**
841     * Enforces the specification of a "relative" name as used in
842     * {@linkplain #getFileForInput(Location,String,String)
843     * getFileForInput}.  This method must follow the rules defined in
844     * that method, do not make any changes without consulting the
845     * specification.
846     */
847    protected static boolean isRelativeUri(URI uri) {
848        if (uri.isAbsolute())
849            return false;
850        String path = uri.normalize().getPath();
851        if (path.length() == 0 /* isEmpty() is mustang API */)
852            return false;
853        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
854            return false;
855        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
856            return false;
857        return true;
858    }
859
860    // Convenience method
861    protected static boolean isRelativeUri(String u) {
862        try {
863            return isRelativeUri(new URI(u));
864        } catch (URISyntaxException e) {
865            return false;
866        }
867    }
868
869    /**
870     * Converts a relative file name to a relative URI.  This is
871     * different from File.toURI as this method does not canonicalize
872     * the file before creating the URI.  Furthermore, no schema is
873     * used.
874     * @param file a relative file name
875     * @return a relative URI
876     * @throws IllegalArgumentException if the file name is not
877     * relative according to the definition given in {@link
878     * javax.tools.JavaFileManager#getFileForInput}
879     */
880    public static String getRelativeName(File file) {
881        if (!file.isAbsolute()) {
882            String result = file.getPath().replace(File.separatorChar, '/');
883            if (isRelativeUri(result))
884                return result;
885        }
886        throw new IllegalArgumentException("Invalid relative path: " + file);
887    }
888
889    /**
890     * Get a detail message from an IOException.
891     * Most, but not all, instances of IOException provide a non-null result
892     * for getLocalizedMessage().  But some instances return null: in these
893     * cases, fallover to getMessage(), and if even that is null, return the
894     * name of the exception itself.
895     * @param e an IOException
896     * @return a string to include in a compiler diagnostic
897     */
898    public static String getMessage(IOException e) {
899        String s = e.getLocalizedMessage();
900        if (s != null)
901            return s;
902        s = e.getMessage();
903        if (s != null)
904            return s;
905        return e.toString();
906    }
907
908    /* Converters between files and paths.
909     * These are temporary until we can update the StandardJavaFileManager API.
910     */
911
912    private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
913        if (files == null)
914            return null;
915
916        return () -> new Iterator<Path>() {
917            Iterator<? extends File> iter = files.iterator();
918
919            @Override
920            public boolean hasNext() {
921                return iter.hasNext();
922            }
923
924            @Override
925            public Path next() {
926                return iter.next().toPath();
927            }
928        };
929    }
930
931    private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
932        if (paths == null)
933            return null;
934
935        return () -> new Iterator<File>() {
936            Iterator<? extends Path> iter = paths.iterator();
937
938            @Override
939            public boolean hasNext() {
940                return iter.hasNext();
941            }
942
943            @Override
944            public File next() {
945                try {
946                    return iter.next().toFile();
947                } catch (UnsupportedOperationException e) {
948                    throw new IllegalStateException(e);
949                }
950            }
951        };
952    }
953}
954