JavacFileManager.java revision 2774:70d213c84585
1280297Sjkim/*
2280297Sjkim * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
3280297Sjkim * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4238384Sjkim *
5238384Sjkim * This code is free software; you can redistribute it and/or modify it
6348343Sjkim * under the terms of the GNU General Public License version 2 only, as
7238384Sjkim * published by the Free Software Foundation.  Oracle designates this
8238384Sjkim * particular file as subject to the "Classpath" exception as provided
9238384Sjkim * by Oracle in the LICENSE file that accompanied this code.
10238384Sjkim *
11238384Sjkim * This code is distributed in the hope that it will be useful, but WITHOUT
12238384Sjkim * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13280297Sjkim * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14238384Sjkim * version 2 for more details (a copy is included in the LICENSE file that
15238384Sjkim * accompanied this code).
16238384Sjkim *
17238384Sjkim * You should have received a copy of the GNU General Public License version
18238384Sjkim * 2 along with this work; if not, write to the Free Software Foundation,
19238384Sjkim * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20238384Sjkim *
21238384Sjkim * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22238384Sjkim * or visit www.oracle.com if you need additional information or have any
23238384Sjkim * questions.
24238384Sjkim */
25238384Sjkim
26238384Sjkimpackage com.sun.tools.javac.file;
27238384Sjkim
28238384Sjkimimport java.io.File;
29238384Sjkimimport java.io.FileNotFoundException;
30238384Sjkimimport java.io.IOException;
31238384Sjkimimport java.net.MalformedURLException;
32238384Sjkimimport java.net.URI;
33238384Sjkimimport java.net.URISyntaxException;
34238384Sjkimimport java.net.URL;
35238384Sjkimimport java.nio.CharBuffer;
36238384Sjkimimport java.nio.charset.Charset;
37238384Sjkimimport java.nio.file.Files;
38238384Sjkimimport java.nio.file.InvalidPathException;
39238384Sjkimimport java.nio.file.LinkOption;
40238384Sjkimimport java.nio.file.NoSuchFileException;
41238384Sjkimimport java.nio.file.Path;
42238384Sjkimimport java.nio.file.Paths;
43238384Sjkimimport java.util.ArrayList;
44238384Sjkimimport java.util.Arrays;
45238384Sjkimimport java.util.Collection;
46238384Sjkimimport java.util.Collections;
47238384Sjkimimport java.util.Comparator;
48238384Sjkimimport java.util.EnumSet;
49238384Sjkimimport java.util.HashMap;
50238384Sjkimimport java.util.Iterator;
51238384Sjkimimport java.util.Map;
52238384Sjkimimport java.util.Set;
53238384Sjkimimport java.util.stream.Collectors;
54238384Sjkimimport java.util.stream.Stream;
55238384Sjkimimport java.util.zip.ZipFile;
56238384Sjkim
57238384Sjkimimport javax.lang.model.SourceVersion;
58238384Sjkimimport javax.tools.FileObject;
59238384Sjkimimport javax.tools.JavaFileManager;
60238384Sjkimimport javax.tools.JavaFileObject;
61238384Sjkimimport javax.tools.StandardJavaFileManager;
62238384Sjkim
63238384Sjkimimport com.sun.tools.javac.file.RelativePath.RelativeDirectory;
64238384Sjkimimport com.sun.tools.javac.file.RelativePath.RelativeFile;
65238384Sjkimimport com.sun.tools.javac.nio.PathFileObject;
66290207Sjkimimport com.sun.tools.javac.util.BaseFileManager;
67290207Sjkimimport com.sun.tools.javac.util.Context;
68290207Sjkimimport com.sun.tools.javac.util.DefinedBy;
69290207Sjkimimport com.sun.tools.javac.util.DefinedBy.Api;
70238384Sjkimimport com.sun.tools.javac.util.List;
71238384Sjkimimport com.sun.tools.javac.util.ListBuffer;
72238384Sjkim
73238384Sjkimimport static javax.tools.StandardLocation.*;
74280297Sjkim
75280297Sjkimimport static com.sun.tools.javac.util.BaseFileManager.getKind;
76280297Sjkim
77280297Sjkim/**
78280297Sjkim * This class provides access to the source, class and other files
79290207Sjkim * used by the compiler and related tools.
80290207Sjkim *
81290207Sjkim * <p><b>This is NOT part of any supported API.
82290207Sjkim * If you write code that depends on this, you do so at your own risk.
83280297Sjkim * This code and its internal interfaces are subject to change or
84280297Sjkim * deletion without notice.</b>
85290207Sjkim */
86290207Sjkimpublic class JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
87290207Sjkim
88290207Sjkim    @SuppressWarnings("cast")
89290207Sjkim    public static char[] toArray(CharBuffer buffer) {
90290207Sjkim        if (buffer.hasArray())
91290207Sjkim            return ((CharBuffer)buffer.compact().flip()).array();
92290207Sjkim        else
93290207Sjkim            return buffer.toString().toCharArray();
94290207Sjkim    }
95290207Sjkim
96280297Sjkim    private FSInfo fsInfo;
97238384Sjkim
98238384Sjkim    private boolean contextUseOptimizedZip;
99280297Sjkim    private ZipFileIndexCache zipFileIndexCache;
100280297Sjkim
101280297Sjkim    private final Set<JavaFileObject.Kind> sourceOrClass =
102280297Sjkim        EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
103280297Sjkim
104348343Sjkim    protected boolean symbolFileEnabled;
105290207Sjkim
106280297Sjkim    protected enum SortFiles implements Comparator<Path> {
107280297Sjkim        FORWARD {
108290207Sjkim            @Override
109290207Sjkim            public int compare(Path f1, Path f2) {
110238384Sjkim                return f1.getFileName().compareTo(f2.getFileName());
111290207Sjkim            }
112290207Sjkim        },
113290207Sjkim        REVERSE {
114290207Sjkim            @Override
115290207Sjkim            public int compare(Path f1, Path f2) {
116290207Sjkim                return -f1.getFileName().compareTo(f2.getFileName());
117290207Sjkim            }
118280297Sjkim        }
119280297Sjkim    }
120280297Sjkim
121238384Sjkim    protected SortFiles sortFiles;
122280297Sjkim
123280297Sjkim    /**
124280297Sjkim     * Register a Context.Factory to create a JavacFileManager.
125238384Sjkim     */
126280297Sjkim    public static void preRegister(Context context) {
127280297Sjkim        context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
128280297Sjkim            @Override
129280297Sjkim            public JavaFileManager make(Context c) {
130280297Sjkim                return new JavacFileManager(c, true, null);
131280297Sjkim            }
132280297Sjkim        });
133290207Sjkim    }
134280297Sjkim
135280297Sjkim    /**
136290207Sjkim     * Create a JavacFileManager using a given context, optionally registering
137290207Sjkim     * it as the JavaFileManager for that context.
138290207Sjkim     */
139290207Sjkim    public JavacFileManager(Context context, boolean register, Charset charset) {
140290207Sjkim        super(charset);
141290207Sjkim        if (register)
142290207Sjkim            context.put(JavaFileManager.class, this);
143290207Sjkim        setContext(context);
144290207Sjkim    }
145290207Sjkim
146290207Sjkim    /**
147290207Sjkim     * Set the context for JavacFileManager.
148290207Sjkim     */
149280297Sjkim    @Override
150280297Sjkim    public void setContext(Context context) {
151238384Sjkim        super.setContext(context);
152238384Sjkim
153280297Sjkim        fsInfo = FSInfo.instance(context);
154280297Sjkim
155290207Sjkim        contextUseOptimizedZip = options.getBoolean("useOptimizedZip", true);
156290207Sjkim        if (contextUseOptimizedZip)
157290207Sjkim            zipFileIndexCache = ZipFileIndexCache.getSharedInstance();
158290207Sjkim
159290207Sjkim        symbolFileEnabled = !options.isSet("ignore.symbol.file");
160280297Sjkim
161290207Sjkim        String sf = options.get("sortFiles");
162280297Sjkim        if (sf != null) {
163238384Sjkim            sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
164238384Sjkim        }
165280297Sjkim    }
166280297Sjkim
167280297Sjkim    /**
168280297Sjkim     * Set whether or not to use ct.sym as an alternate to rt.jar.
169280297Sjkim     */
170280297Sjkim    public void setSymbolFileEnabled(boolean b) {
171280297Sjkim        symbolFileEnabled = b;
172280297Sjkim    }
173238384Sjkim
174290207Sjkim    public boolean isSymbolFileEnabled() {
175290207Sjkim        return symbolFileEnabled;
176290207Sjkim    }
177290207Sjkim
178290207Sjkim    // used by tests
179290207Sjkim    public JavaFileObject getFileForInput(String name) {
180280297Sjkim        return getRegularFile(Paths.get(name));
181290207Sjkim    }
182290207Sjkim
183280297Sjkim    // used by tests
184280297Sjkim    public JavaFileObject getRegularFile(Path file) {
185238384Sjkim        return new RegularFileObject(this, file);
186290207Sjkim    }
187290207Sjkim
188290207Sjkim    public JavaFileObject getFileForOutput(String classname,
189290207Sjkim                                           JavaFileObject.Kind kind,
190290207Sjkim                                           JavaFileObject sibling)
191290207Sjkim        throws IOException
192290207Sjkim    {
193290207Sjkim        return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
194290207Sjkim    }
195290207Sjkim
196290207Sjkim    @Override @DefinedBy(Api.COMPILER)
197290207Sjkim    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
198290207Sjkim        ListBuffer<Path> paths = new ListBuffer<>();
199290207Sjkim        for (String name : names)
200290207Sjkim            paths.append(Paths.get(nullCheck(name)));
201290207Sjkim        return getJavaFileObjectsFromPaths(paths.toList());
202290207Sjkim    }
203280297Sjkim
204280297Sjkim    @Override @DefinedBy(Api.COMPILER)
205280297Sjkim    public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
206238384Sjkim        return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
207290207Sjkim    }
208290207Sjkim
209290207Sjkim    private static boolean isValidName(String name) {
210325337Sjkim        // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
211325337Sjkim        // but the set of keywords depends on the source level, and we don't want
212325337Sjkim        // impls of JavaFileManager to have to be dependent on the source level.
213290207Sjkim        // Therefore we simply check that the argument is a sequence of identifiers
214325337Sjkim        // separated by ".".
215290207Sjkim        for (String s : name.split("\\.", -1)) {
216290207Sjkim            if (!SourceVersion.isIdentifier(s))
217290207Sjkim                return false;
218290207Sjkim        }
219290207Sjkim        return true;
220290207Sjkim    }
221290207Sjkim
222290207Sjkim    private static void validateClassName(String className) {
223290207Sjkim        if (!isValidName(className))
224290207Sjkim            throw new IllegalArgumentException("Invalid class name: " + className);
225290207Sjkim    }
226290207Sjkim
227290207Sjkim    private static void validatePackageName(String packageName) {
228290207Sjkim        if (packageName.length() > 0 && !isValidName(packageName))
229290207Sjkim            throw new IllegalArgumentException("Invalid packageName name: " + packageName);
230290207Sjkim    }
231290207Sjkim
232290207Sjkim    public static void testName(String name,
233290207Sjkim                                boolean isValidPackageName,
234290207Sjkim                                boolean isValidClassName)
235290207Sjkim    {
236290207Sjkim        try {
237290207Sjkim            validatePackageName(name);
238290207Sjkim            if (!isValidPackageName)
239290207Sjkim                throw new AssertionError("Invalid package name accepted: " + name);
240290207Sjkim            printAscii("Valid package name: \"%s\"", name);
241290207Sjkim        } catch (IllegalArgumentException e) {
242290207Sjkim            if (isValidPackageName)
243290207Sjkim                throw new AssertionError("Valid package name rejected: " + name);
244290207Sjkim            printAscii("Invalid package name: \"%s\"", name);
245290207Sjkim        }
246290207Sjkim        try {
247290207Sjkim            validateClassName(name);
248290207Sjkim            if (!isValidClassName)
249290207Sjkim                throw new AssertionError("Invalid class name accepted: " + name);
250290207Sjkim            printAscii("Valid class name: \"%s\"", name);
251290207Sjkim        } catch (IllegalArgumentException e) {
252290207Sjkim            if (isValidClassName)
253290207Sjkim                throw new AssertionError("Valid class name rejected: " + name);
254290207Sjkim            printAscii("Invalid class name: \"%s\"", name);
255290207Sjkim        }
256290207Sjkim    }
257290207Sjkim
258290207Sjkim    private static void printAscii(String format, Object... args) {
259290207Sjkim        String message;
260290207Sjkim        try {
261280297Sjkim            final String ascii = "US-ASCII";
262280297Sjkim            message = new String(String.format(null, format, args).getBytes(ascii), ascii);
263238384Sjkim        } catch (java.io.UnsupportedEncodingException ex) {
264280297Sjkim            throw new AssertionError(ex);
265280297Sjkim        }
266238384Sjkim        System.out.println(message);
267238384Sjkim    }
268280297Sjkim
269280297Sjkim    /**
270280297Sjkim     * Insert all files in a subdirectory of the platform image
271280297Sjkim     * which match fileKinds into resultList.
272280297Sjkim     */
273280297Sjkim    private void listJRTImage(RelativeDirectory subdirectory,
274280297Sjkim                               Set<JavaFileObject.Kind> fileKinds,
275290207Sjkim                               boolean recurse,
276290207Sjkim                               ListBuffer<JavaFileObject> resultList) throws IOException {
277290207Sjkim        JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory);
278290207Sjkim        if (symbolFileEnabled && e.ctSym.hidden)
279290207Sjkim            return;
280290207Sjkim        for (Path file: e.files.values()) {
281290207Sjkim            if (fileKinds.contains(getKind(file))) {
282290207Sjkim                JavaFileObject fe
283290207Sjkim                        = PathFileObject.createJRTPathFileObject(JavacFileManager.this, file);
284280297Sjkim                resultList.append(fe);
285280297Sjkim            }
286280297Sjkim        }
287280297Sjkim
288280297Sjkim        if (recurse) {
289290207Sjkim            for (RelativeDirectory rd: e.subdirs) {
290290207Sjkim                listJRTImage(rd, fileKinds, recurse, resultList);
291290207Sjkim            }
292290207Sjkim        }
293290207Sjkim    }
294290207Sjkim
295290207Sjkim    private synchronized JRTIndex getJRTIndex() {
296290207Sjkim        if (jrtIndex == null)
297290207Sjkim            jrtIndex = JRTIndex.getSharedInstance();
298290207Sjkim        return jrtIndex;
299280297Sjkim    }
300280297Sjkim
301238384Sjkim    private JRTIndex jrtIndex;
302290207Sjkim
303290207Sjkim
304290207Sjkim    /**
305290207Sjkim     * Insert all files in subdirectory subdirectory of directory directory
306290207Sjkim     * which match fileKinds into resultList
307290207Sjkim     */
308290207Sjkim    private void listDirectory(Path directory,
309290207Sjkim                               RelativeDirectory subdirectory,
310290207Sjkim                               Set<JavaFileObject.Kind> fileKinds,
311290207Sjkim                               boolean recurse,
312290207Sjkim                               ListBuffer<JavaFileObject> resultList) {
313290207Sjkim        Path d;
314290207Sjkim        try {
315290207Sjkim            d = subdirectory.getFile(directory);
316290207Sjkim        } catch (InvalidPathException ignore) {
317290207Sjkim            return;
318290207Sjkim        }
319290207Sjkim
320290207Sjkim        if (!Files.exists(d)) {
321290207Sjkim           return;
322290207Sjkim        }
323290207Sjkim
324290207Sjkim        if (!caseMapCheck(d, subdirectory)) {
325290207Sjkim            return;
326290207Sjkim        }
327290207Sjkim
328290207Sjkim        java.util.List<Path> files;
329290207Sjkim        try (Stream<Path> s = Files.list(d)) {
330290207Sjkim            files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList());
331290207Sjkim        } catch (IOException ignore) {
332290207Sjkim            return;
333290207Sjkim        }
334290207Sjkim
335290207Sjkim        for (Path f: files) {
336290207Sjkim            String fname = f.getFileName().toString();
337290207Sjkim            if (Files.isDirectory(f)) {
338290207Sjkim                if (recurse && SourceVersion.isIdentifier(fname)) {
339290207Sjkim                    listDirectory(directory,
340290207Sjkim                                  new RelativeDirectory(subdirectory, fname),
341290207Sjkim                                  fileKinds,
342290207Sjkim                                  recurse,
343290207Sjkim                                  resultList);
344290207Sjkim                }
345290207Sjkim            } else {
346290207Sjkim                if (isValidFile(fname, fileKinds)) {
347290207Sjkim                    JavaFileObject fe =
348290207Sjkim                        new RegularFileObject(this, fname, d.resolve(fname));
349290207Sjkim                    resultList.append(fe);
350290207Sjkim                }
351290207Sjkim            }
352290207Sjkim        }
353290207Sjkim    }
354290207Sjkim
355290207Sjkim    /**
356238384Sjkim     * Insert all files in subdirectory subdirectory of archive archive
357280297Sjkim     * which match fileKinds into resultList
358280297Sjkim     */
359280297Sjkim    private void listArchive(Archive archive,
360280297Sjkim                               RelativeDirectory subdirectory,
361280297Sjkim                               Set<JavaFileObject.Kind> fileKinds,
362290207Sjkim                               boolean recurse,
363290207Sjkim                               ListBuffer<JavaFileObject> resultList) {
364290207Sjkim        // Get the files directly in the subdir
365290207Sjkim        List<String> files = archive.getFiles(subdirectory);
366290207Sjkim        if (files != null) {
367290207Sjkim            for (; !files.isEmpty(); files = files.tail) {
368290207Sjkim                String file = files.head;
369290207Sjkim                if (isValidFile(file, fileKinds)) {
370290207Sjkim                    resultList.append(archive.getFileObject(subdirectory, file));
371290207Sjkim                }
372290207Sjkim            }
373290207Sjkim        }
374290207Sjkim        if (recurse) {
375290207Sjkim            for (RelativeDirectory s: archive.getSubdirectories()) {
376290207Sjkim                if (subdirectory.contains(s)) {
377290207Sjkim                    // Because the archive map is a flat list of directories,
378290207Sjkim                    // the enclosing loop will pick up all child subdirectories.
379290207Sjkim                    // Therefore, there is no need to recurse deeper.
380290207Sjkim                    listArchive(archive, s, fileKinds, false, resultList);
381290207Sjkim                }
382290207Sjkim            }
383280297Sjkim        }
384280297Sjkim    }
385280297Sjkim
386280297Sjkim    /**
387280297Sjkim     * container is a directory, a zip file, or a non-existant path.
388290207Sjkim     * Insert all files in subdirectory subdirectory of container which
389290207Sjkim     * match fileKinds into resultList
390290207Sjkim     */
391290207Sjkim    private void listContainer(Path container,
392290207Sjkim                               RelativeDirectory subdirectory,
393290207Sjkim                               Set<JavaFileObject.Kind> fileKinds,
394290207Sjkim                               boolean recurse,
395290207Sjkim                               ListBuffer<JavaFileObject> resultList) {
396290207Sjkim        Archive archive = archives.get(container);
397290207Sjkim        if (archive == null) {
398290207Sjkim            // Very temporary and obnoxious interim hack
399290207Sjkim            if (container.endsWith("bootmodules.jimage")) {
400290207Sjkim                System.err.println("Warning: reference to bootmodules.jimage replaced by jrt:");
401290207Sjkim                container = Locations.JRT_MARKER_FILE;
402280297Sjkim            } else if (container.getFileName().toString().endsWith(".jimage")) {
403280297Sjkim                System.err.println("Warning: reference to " + container + " ignored");
404280297Sjkim                return;
405280297Sjkim            }
406280297Sjkim
407290207Sjkim            // archives are not created for directories or jrt: images
408280297Sjkim            if (container == Locations.JRT_MARKER_FILE) {
409280297Sjkim                try {
410280297Sjkim                    listJRTImage(subdirectory,
411280297Sjkim                            fileKinds,
412280297Sjkim                            recurse,
413280297Sjkim                            resultList);
414238384Sjkim                } catch (IOException ex) {
415238384Sjkim                    ex.printStackTrace(System.err);
416280297Sjkim                    log.error("error.reading.file", container, getMessage(ex));
417280297Sjkim                }
418280297Sjkim                return;
419280297Sjkim            }
420280297Sjkim
421280297Sjkim            if  (fsInfo.isDirectory(container)) {
422280297Sjkim                listDirectory(container,
423280297Sjkim                              subdirectory,
424280297Sjkim                              fileKinds,
425290207Sjkim                              recurse,
426280297Sjkim                              resultList);
427280297Sjkim                return;
428280297Sjkim            }
429280297Sjkim
430280297Sjkim            // Not a directory; either a file or non-existant, create the archive
431238384Sjkim            try {
432280297Sjkim                archive = openArchive(container);
433280297Sjkim            } catch (IOException ex) {
434280297Sjkim                log.error("error.reading.file", container, getMessage(ex));
435280297Sjkim                return;
436290207Sjkim            }
437290207Sjkim        }
438290207Sjkim        listArchive(archive,
439280297Sjkim                    subdirectory,
440280297Sjkim                    fileKinds,
441280297Sjkim                    recurse,
442280297Sjkim                    resultList);
443290207Sjkim    }
444290207Sjkim
445290207Sjkim    private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
446290207Sjkim        JavaFileObject.Kind kind = getKind(s);
447290207Sjkim        return fileKinds.contains(kind);
448290207Sjkim    }
449290207Sjkim
450290207Sjkim    private static final boolean fileSystemIsCaseSensitive =
451290207Sjkim        File.separatorChar == '/';
452290207Sjkim
453290207Sjkim    /** Hack to make Windows case sensitive. Test whether given path
454290207Sjkim     *  ends in a string of characters with the same case as given name.
455325337Sjkim     *  Ignore file separators in both path and name.
456325337Sjkim     */
457325337Sjkim    private boolean caseMapCheck(Path f, RelativePath name) {
458290207Sjkim        if (fileSystemIsCaseSensitive) return true;
459290207Sjkim        // Note that toRealPath() returns the case-sensitive
460290207Sjkim        // spelled file name.
461290207Sjkim        String path;
462290207Sjkim        char sep;
463290207Sjkim        try {
464290207Sjkim            path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
465290207Sjkim            sep = f.getFileSystem().getSeparator().charAt(0);
466290207Sjkim        } catch (IOException ex) {
467290207Sjkim            return false;
468290207Sjkim        }
469290207Sjkim        char[] pcs = path.toCharArray();
470290207Sjkim        char[] ncs = name.path.toCharArray();
471290207Sjkim        int i = pcs.length - 1;
472290207Sjkim        int j = ncs.length - 1;
473290207Sjkim        while (i >= 0 && j >= 0) {
474290207Sjkim            while (i >= 0 && pcs[i] == sep) i--;
475290207Sjkim            while (j >= 0 && ncs[j] == '/') j--;
476290207Sjkim            if (i >= 0 && j >= 0) {
477290207Sjkim                if (pcs[i] != ncs[j]) return false;
478290207Sjkim                i--;
479290207Sjkim                j--;
480290207Sjkim            }
481290207Sjkim        }
482290207Sjkim        return j < 0;
483290207Sjkim    }
484290207Sjkim
485290207Sjkim    /**
486280297Sjkim     * An archive provides a flat directory structure of a ZipFile by
487290207Sjkim     * mapping directory names to lists of files (basenames).
488325337Sjkim     */
489337982Sjkim    public interface Archive {
490280297Sjkim        void close() throws IOException;
491238384Sjkim
492280297Sjkim        boolean contains(RelativePath name);
493280297Sjkim
494290207Sjkim        JavaFileObject getFileObject(RelativeDirectory subdirectory, String file);
495280297Sjkim
496280297Sjkim        List<String> getFiles(RelativeDirectory subdirectory);
497280297Sjkim
498238384Sjkim        Set<RelativeDirectory> getSubdirectories();
499280297Sjkim    }
500280297Sjkim
501238384Sjkim    public class MissingArchive implements Archive {
502280297Sjkim        final Path zipFileName;
503280297Sjkim        public MissingArchive(Path name) {
504238384Sjkim            zipFileName = name;
505280297Sjkim        }
506280297Sjkim        @Override
507238384Sjkim        public boolean contains(RelativePath name) {
508280297Sjkim            return false;
509280297Sjkim        }
510238384Sjkim
511280297Sjkim        @Override
512238384Sjkim        public void close() {
513280297Sjkim        }
514238384Sjkim
515280297Sjkim        @Override
516238384Sjkim        public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) {
517280297Sjkim            return null;
518238384Sjkim        }
519280297Sjkim
520280297Sjkim        @Override
521238384Sjkim        public List<String> getFiles(RelativeDirectory subdirectory) {
522280297Sjkim            return List.nil();
523280297Sjkim        }
524280297Sjkim
525290207Sjkim        @Override
526290207Sjkim        public Set<RelativeDirectory> getSubdirectories() {
527290207Sjkim            return Collections.emptySet();
528290207Sjkim        }
529290207Sjkim
530290207Sjkim        @Override
531290207Sjkim        public String toString() {
532290207Sjkim            return "MissingArchive[" + zipFileName + "]";
533290207Sjkim        }
534290207Sjkim    }
535290207Sjkim
536290207Sjkim    /** A directory of zip files already opened.
537290207Sjkim     */
538290207Sjkim    Map<Path, Archive> archives = new HashMap<>();
539290207Sjkim
540290207Sjkim    /*
541290207Sjkim     * This method looks for a ZipFormatException and takes appropriate
542290207Sjkim     * evasive action. If there is a failure in the fast mode then we
543290207Sjkim     * fail over to the platform zip, and allow it to deal with a potentially
544290207Sjkim     * non compliant zip file.
545290207Sjkim     */
546290207Sjkim    protected Archive openArchive(Path zipFilename) throws IOException {
547290207Sjkim        try {
548290207Sjkim            return openArchive(zipFilename, contextUseOptimizedZip);
549290207Sjkim        } catch (IOException ioe) {
550290207Sjkim            if (ioe instanceof ZipFileIndex.ZipFormatException) {
551290207Sjkim                return openArchive(zipFilename, false);
552290207Sjkim            } else {
553290207Sjkim                throw ioe;
554290207Sjkim            }
555290207Sjkim        }
556290207Sjkim    }
557290207Sjkim
558290207Sjkim    /** Open a new zip file directory, and cache it.
559     */
560    private Archive openArchive(Path zipFileName, boolean useOptimizedZip) throws IOException {
561        Archive archive;
562        try {
563
564            ZipFile zdir = null;
565
566            boolean usePreindexedCache = false;
567            String preindexCacheLocation = null;
568
569            if (!useOptimizedZip) {
570                zdir = new ZipFile(zipFileName.toFile());
571            } else {
572                usePreindexedCache = options.isSet("usezipindex");
573                preindexCacheLocation = options.get("java.io.tmpdir");
574                String optCacheLoc = options.get("cachezipindexdir");
575
576                if (optCacheLoc != null && optCacheLoc.length() != 0) {
577                    if (optCacheLoc.startsWith("\"")) {
578                        if (optCacheLoc.endsWith("\"")) {
579                            optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1);
580                        }
581                        else {
582                            optCacheLoc = optCacheLoc.substring(1);
583                        }
584                    }
585
586                    File cacheDir = new File(optCacheLoc);
587                    if (cacheDir.exists() && cacheDir.canWrite()) {
588                        preindexCacheLocation = optCacheLoc;
589                        if (!preindexCacheLocation.endsWith("/") &&
590                            !preindexCacheLocation.endsWith(File.separator)) {
591                            preindexCacheLocation += File.separator;
592                        }
593                    }
594                }
595            }
596
597                if (!useOptimizedZip) {
598                    archive = new ZipArchive(this, zdir);
599                } else {
600                    archive = new ZipFileIndexArchive(this,
601                                    zipFileIndexCache.getZipFileIndex(zipFileName,
602                                    null,
603                                    usePreindexedCache,
604                                    preindexCacheLocation,
605                                    options.isSet("writezipindexfiles")));
606                }
607        } catch (FileNotFoundException | NoSuchFileException ex) {
608            archive = new MissingArchive(zipFileName);
609        } catch (ZipFileIndex.ZipFormatException zfe) {
610            throw zfe;
611        } catch (IOException ex) {
612            if (Files.exists(zipFileName))
613                log.error("error.reading.file", zipFileName, getMessage(ex));
614            archive = new MissingArchive(zipFileName);
615        }
616
617        archives.put(zipFileName, archive);
618        return archive;
619    }
620
621    /** Flush any output resources.
622     */
623    @Override @DefinedBy(Api.COMPILER)
624    public void flush() {
625        contentCache.clear();
626    }
627
628    /**
629     * Close the JavaFileManager, releasing resources.
630     */
631    @Override @DefinedBy(Api.COMPILER)
632    public void close() {
633        for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) {
634            Archive a = i.next();
635            i.remove();
636            try {
637                a.close();
638            } catch (IOException ignore) {
639            }
640        }
641    }
642
643    @Override @DefinedBy(Api.COMPILER)
644    public ClassLoader getClassLoader(Location location) {
645        nullCheck(location);
646        Iterable<? extends File> path = getLocation(location);
647        if (path == null)
648            return null;
649        ListBuffer<URL> lb = new ListBuffer<>();
650        for (File f: path) {
651            try {
652                lb.append(f.toURI().toURL());
653            } catch (MalformedURLException e) {
654                throw new AssertionError(e);
655            }
656        }
657
658        return getClassLoader(lb.toArray(new URL[lb.size()]));
659    }
660
661    @Override @DefinedBy(Api.COMPILER)
662    public Iterable<JavaFileObject> list(Location location,
663                                         String packageName,
664                                         Set<JavaFileObject.Kind> kinds,
665                                         boolean recurse)
666        throws IOException
667    {
668        // validatePackageName(packageName);
669        nullCheck(packageName);
670        nullCheck(kinds);
671
672        Iterable<? extends Path> path = getLocationAsPaths(location);
673        if (path == null)
674            return List.nil();
675        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
676        ListBuffer<JavaFileObject> results = new ListBuffer<>();
677
678        for (Path directory : path)
679            listContainer(directory, subdirectory, kinds, recurse, results);
680        return results.toList();
681    }
682
683    @Override @DefinedBy(Api.COMPILER)
684    public String inferBinaryName(Location location, JavaFileObject file) {
685        file.getClass(); // null check
686        location.getClass(); // null check
687        // Need to match the path semantics of list(location, ...)
688        Iterable<? extends Path> path = getLocationAsPaths(location);
689        if (path == null) {
690            return null;
691        }
692
693        if (file instanceof BaseFileObject) {
694            return ((BaseFileObject) file).inferBinaryName(path);
695        } else if (file instanceof PathFileObject) {
696            return ((PathFileObject) file).inferBinaryName(null);
697        } else
698            throw new IllegalArgumentException(file.getClass().getName());
699    }
700
701    @Override @DefinedBy(Api.COMPILER)
702    public boolean isSameFile(FileObject a, FileObject b) {
703        nullCheck(a);
704        nullCheck(b);
705        if (a instanceof PathFileObject && b instanceof PathFileObject)
706            return ((PathFileObject) a).isSameFile((PathFileObject) b);
707        // In time, we should phase out BaseFileObject in favor of PathFileObject
708        if (!(a instanceof BaseFileObject  || a instanceof PathFileObject))
709            throw new IllegalArgumentException("Not supported: " + a);
710        if (!(b instanceof BaseFileObject || b instanceof PathFileObject))
711            throw new IllegalArgumentException("Not supported: " + b);
712        return a.equals(b);
713    }
714
715    @Override @DefinedBy(Api.COMPILER)
716    public boolean hasLocation(Location location) {
717        return getLocation(location) != null;
718    }
719
720    @Override @DefinedBy(Api.COMPILER)
721    public JavaFileObject getJavaFileForInput(Location location,
722                                              String className,
723                                              JavaFileObject.Kind kind)
724        throws IOException
725    {
726        nullCheck(location);
727        // validateClassName(className);
728        nullCheck(className);
729        nullCheck(kind);
730        if (!sourceOrClass.contains(kind))
731            throw new IllegalArgumentException("Invalid kind: " + kind);
732        return getFileForInput(location, RelativeFile.forClass(className, kind));
733    }
734
735    @Override @DefinedBy(Api.COMPILER)
736    public FileObject getFileForInput(Location location,
737                                      String packageName,
738                                      String relativeName)
739        throws IOException
740    {
741        nullCheck(location);
742        // validatePackageName(packageName);
743        nullCheck(packageName);
744        if (!isRelativeUri(relativeName))
745            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
746        RelativeFile name = packageName.length() == 0
747            ? new RelativeFile(relativeName)
748            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
749        return getFileForInput(location, name);
750    }
751
752    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
753        Iterable<? extends Path> path = getLocationAsPaths(location);
754        if (path == null)
755            return null;
756
757        for (Path file: path) {
758            Archive a = archives.get(file);
759            if (a == null) {
760                // archives are not created for directories or jrt: images
761                if (file == Locations.JRT_MARKER_FILE) {
762                    JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
763                    if (symbolFileEnabled && e.ctSym.hidden)
764                        continue;
765                    Path p = e.files.get(name.basename());
766                    if (p != null)
767                        return PathFileObject.createJRTPathFileObject(this, p);
768                    continue;
769                } else if (fsInfo.isDirectory(file)) {
770                    try {
771                        Path f = name.getFile(file);
772                        if (Files.exists(f))
773                            return new RegularFileObject(this, f);
774                    } catch (InvalidPathException ignore) {
775                    }
776                    continue;
777                }
778                // Not a directory, create the archive
779                a = openArchive(file);
780            }
781            // Process the archive
782            if (a.contains(name)) {
783                return a.getFileObject(name.dirname(), name.basename());
784            }
785        }
786        return null;
787    }
788
789    @Override @DefinedBy(Api.COMPILER)
790    public JavaFileObject getJavaFileForOutput(Location location,
791                                               String className,
792                                               JavaFileObject.Kind kind,
793                                               FileObject sibling)
794        throws IOException
795    {
796        nullCheck(location);
797        // validateClassName(className);
798        nullCheck(className);
799        nullCheck(kind);
800        if (!sourceOrClass.contains(kind))
801            throw new IllegalArgumentException("Invalid kind: " + kind);
802        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
803    }
804
805    @Override @DefinedBy(Api.COMPILER)
806    public FileObject getFileForOutput(Location location,
807                                       String packageName,
808                                       String relativeName,
809                                       FileObject sibling)
810        throws IOException
811    {
812        nullCheck(location);
813        // validatePackageName(packageName);
814        nullCheck(packageName);
815        if (!isRelativeUri(relativeName))
816            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
817        RelativeFile name = packageName.length() == 0
818            ? new RelativeFile(relativeName)
819            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
820        return getFileForOutput(location, name, sibling);
821    }
822
823    private JavaFileObject getFileForOutput(Location location,
824                                            RelativeFile fileName,
825                                            FileObject sibling)
826        throws IOException
827    {
828        Path dir;
829        if (location == CLASS_OUTPUT) {
830            if (getClassOutDir() != null) {
831                dir = getClassOutDir();
832            } else {
833                Path siblingDir = null;
834                if (sibling != null && sibling instanceof RegularFileObject) {
835                    siblingDir = ((RegularFileObject)sibling).file.getParent();
836                }
837                if (siblingDir == null)
838                    return new RegularFileObject(this, Paths.get(fileName.basename()));
839                else
840                    return new RegularFileObject(this, siblingDir.resolve(fileName.basename()));
841            }
842        } else if (location == SOURCE_OUTPUT) {
843            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
844        } else {
845            Iterable<? extends Path> path = locations.getLocation(location);
846            dir = null;
847            for (Path f: path) {
848                dir = f;
849                break;
850            }
851        }
852
853        try {
854            Path file = fileName.getFile(dir); // null-safe
855            return new RegularFileObject(this, file);
856        } catch (InvalidPathException e) {
857            throw new IOException("bad filename " + fileName, e);
858        }
859    }
860
861    @Override @DefinedBy(Api.COMPILER)
862    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
863        Iterable<? extends File> files)
864    {
865        ArrayList<RegularFileObject> result;
866        if (files instanceof Collection<?>)
867            result = new ArrayList<>(((Collection<?>)files).size());
868        else
869            result = new ArrayList<>();
870        for (File f: files)
871            result.add(new RegularFileObject(this, nullCheck(f).toPath()));
872        return result;
873    }
874
875    @Override @DefinedBy(Api.COMPILER)
876    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
877        Iterable<? extends Path> paths)
878    {
879        ArrayList<RegularFileObject> result;
880        if (paths instanceof Collection<?>)
881            result = new ArrayList<>(((Collection<?>)paths).size());
882        else
883            result = new ArrayList<>();
884        for (Path p: paths)
885            result.add(new RegularFileObject(this, nullCheck(p)));
886        return result;
887    }
888
889    @Override @DefinedBy(Api.COMPILER)
890    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
891        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
892    }
893
894    @Override @DefinedBy(Api.COMPILER)
895    public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
896        return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
897    }
898
899    @Override @DefinedBy(Api.COMPILER)
900    public void setLocation(Location location,
901                            Iterable<? extends File> searchpath)
902        throws IOException
903    {
904        nullCheck(location);
905        locations.setLocation(location, asPaths(searchpath));
906    }
907
908    @Override @DefinedBy(Api.COMPILER)
909    public void setLocationFromPaths(Location location,
910                            Iterable<? extends Path> searchpath)
911        throws IOException
912    {
913        nullCheck(location);
914        locations.setLocation(location, nullCheck(searchpath));
915    }
916
917    @Override @DefinedBy(Api.COMPILER)
918    public Iterable<? extends File> getLocation(Location location) {
919        nullCheck(location);
920        return asFiles(locations.getLocation(location));
921    }
922
923    @Override @DefinedBy(Api.COMPILER)
924    public Iterable<? extends Path> getLocationAsPaths(Location location) {
925        nullCheck(location);
926        return locations.getLocation(location);
927    }
928
929    private Path getClassOutDir() {
930        return locations.getOutputLocation(CLASS_OUTPUT);
931    }
932
933    private Path getSourceOutDir() {
934        return locations.getOutputLocation(SOURCE_OUTPUT);
935    }
936
937    @Override @DefinedBy(Api.COMPILER)
938    public Path asPath(FileObject file) {
939        if (file instanceof RegularFileObject) {
940            return ((RegularFileObject) file).file;
941        } else
942            throw new IllegalArgumentException(file.getName());
943    }
944
945    /**
946     * Enforces the specification of a "relative" name as used in
947     * {@linkplain #getFileForInput(Location,String,String)
948     * getFileForInput}.  This method must follow the rules defined in
949     * that method, do not make any changes without consulting the
950     * specification.
951     */
952    protected static boolean isRelativeUri(URI uri) {
953        if (uri.isAbsolute())
954            return false;
955        String path = uri.normalize().getPath();
956        if (path.length() == 0 /* isEmpty() is mustang API */)
957            return false;
958        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
959            return false;
960        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
961            return false;
962        return true;
963    }
964
965    // Convenience method
966    protected static boolean isRelativeUri(String u) {
967        try {
968            return isRelativeUri(new URI(u));
969        } catch (URISyntaxException e) {
970            return false;
971        }
972    }
973
974    /**
975     * Converts a relative file name to a relative URI.  This is
976     * different from File.toURI as this method does not canonicalize
977     * the file before creating the URI.  Furthermore, no schema is
978     * used.
979     * @param file a relative file name
980     * @return a relative URI
981     * @throws IllegalArgumentException if the file name is not
982     * relative according to the definition given in {@link
983     * javax.tools.JavaFileManager#getFileForInput}
984     */
985    public static String getRelativeName(File file) {
986        if (!file.isAbsolute()) {
987            String result = file.getPath().replace(File.separatorChar, '/');
988            if (isRelativeUri(result))
989                return result;
990        }
991        throw new IllegalArgumentException("Invalid relative path: " + file);
992    }
993
994    /**
995     * Get a detail message from an IOException.
996     * Most, but not all, instances of IOException provide a non-null result
997     * for getLocalizedMessage().  But some instances return null: in these
998     * cases, fallover to getMessage(), and if even that is null, return the
999     * name of the exception itself.
1000     * @param e an IOException
1001     * @return a string to include in a compiler diagnostic
1002     */
1003    public static String getMessage(IOException e) {
1004        String s = e.getLocalizedMessage();
1005        if (s != null)
1006            return s;
1007        s = e.getMessage();
1008        if (s != null)
1009            return s;
1010        return e.toString();
1011    }
1012
1013    /* Converters between files and paths.
1014     * These are temporary until we can update the StandardJavaFileManager API.
1015     */
1016
1017    private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
1018        if (files == null)
1019            return null;
1020
1021        return () -> new Iterator<Path>() {
1022            Iterator<? extends File> iter = files.iterator();
1023
1024            @Override
1025            public boolean hasNext() {
1026                return iter.hasNext();
1027            }
1028
1029            @Override
1030            public Path next() {
1031                return iter.next().toPath();
1032            }
1033        };
1034    }
1035
1036    private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
1037        if (paths == null)
1038            return null;
1039
1040        return () -> new Iterator<File>() {
1041            Iterator<? extends Path> iter = paths.iterator();
1042
1043            @Override
1044            public boolean hasNext() {
1045                return iter.hasNext();
1046            }
1047
1048            @Override
1049            public File next() {
1050                try {
1051                    return iter.next().toFile();
1052                } catch (UnsupportedOperationException e) {
1053                    throw new IllegalStateException(e);
1054                }
1055            }
1056        };
1057    }
1058}
1059