JavacFileManager.java revision 2694:fd59a2d43134
1146773Ssam/*
2146773Ssam * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
3146773Ssam * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4146773Ssam *
5251158Sdelphij * This code is free software; you can redistribute it and/or modify it
6146773Ssam * under the terms of the GNU General Public License version 2 only, as
7146773Ssam * published by the Free Software Foundation.  Oracle designates this
8146773Ssam * particular file as subject to the "Classpath" exception as provided
9146773Ssam * by Oracle in the LICENSE file that accompanied this code.
10146773Ssam *
11146773Ssam * This code is distributed in the hope that it will be useful, but WITHOUT
12146773Ssam * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13146773Ssam * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14146773Ssam * version 2 for more details (a copy is included in the LICENSE file that
15146773Ssam * accompanied this code).
16146773Ssam *
17146773Ssam * You should have received a copy of the GNU General Public License version
18146773Ssam * 2 along with this work; if not, write to the Free Software Foundation,
19146773Ssam * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20146773Ssam *
21146773Ssam * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22146773Ssam * or visit www.oracle.com if you need additional information or have any
23146773Ssam * questions.
24214478Srpaulo */
25146773Ssam
26146773Ssampackage com.sun.tools.javac.file;
27146773Ssam
28146773Ssamimport java.io.ByteArrayOutputStream;
29146773Ssamimport java.io.File;
30146773Ssamimport java.io.FileNotFoundException;
31146773Ssamimport java.io.IOException;
32146773Ssamimport java.io.OutputStreamWriter;
33146773Ssamimport java.net.MalformedURLException;
34146773Ssamimport java.net.URI;
35146773Ssamimport java.net.URISyntaxException;
36146773Ssamimport java.net.URL;
37146773Ssamimport java.nio.CharBuffer;
38146773Ssamimport java.nio.charset.Charset;
39146773Ssamimport java.util.ArrayList;
40146773Ssamimport java.util.Arrays;
41146773Ssamimport java.util.Collection;
42146773Ssamimport java.util.Collections;
43146773Ssamimport java.util.Comparator;
44146773Ssamimport java.util.EnumSet;
45146773Ssamimport java.util.HashMap;
46146773Ssamimport java.util.Iterator;
47146773Ssamimport java.util.Map;
48146773Ssamimport java.util.Set;
49146773Ssamimport java.util.zip.ZipFile;
50146773Ssam
51146773Ssamimport javax.lang.model.SourceVersion;
52146773Ssamimport javax.tools.FileObject;
53146773Ssamimport javax.tools.JavaFileManager;
54146773Ssamimport javax.tools.JavaFileObject;
55146773Ssamimport javax.tools.StandardJavaFileManager;
56146773Ssam
57146773Ssamimport com.sun.tools.javac.file.RelativePath.RelativeFile;
58146773Ssamimport com.sun.tools.javac.file.RelativePath.RelativeDirectory;
59146773Ssamimport com.sun.tools.javac.util.BaseFileManager;
60146773Ssamimport com.sun.tools.javac.util.Context;
61146773Ssamimport com.sun.tools.javac.util.DefinedBy;
62146773Ssamimport com.sun.tools.javac.util.DefinedBy.Api;
63146773Ssamimport com.sun.tools.javac.util.List;
64146773Ssamimport com.sun.tools.javac.util.ListBuffer;
65146773Ssam
66146773Ssamimport static javax.tools.StandardLocation.*;
67146773Ssam
68146773Ssam/**
69146773Ssam * This class provides access to the source, class and other files
70146773Ssam * used by the compiler and related tools.
71146773Ssam *
72146773Ssam * <p><b>This is NOT part of any supported API.
73146773Ssam * If you write code that depends on this, you do so at your own risk.
74146773Ssam * This code and its internal interfaces are subject to change or
75146773Ssam * deletion without notice.</b>
76146773Ssam */
77146773Ssampublic class JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
78146773Ssam
79146773Ssam    @SuppressWarnings("cast")
80190207Srpaulo    public static char[] toArray(CharBuffer buffer) {
81146773Ssam        if (buffer.hasArray())
82146773Ssam            return ((CharBuffer)buffer.compact().flip()).array();
83146773Ssam        else
84146773Ssam            return buffer.toString().toCharArray();
85146773Ssam    }
86146773Ssam
87214478Srpaulo    private FSInfo fsInfo;
88146773Ssam
89146773Ssam    private boolean contextUseOptimizedZip;
90190207Srpaulo    private ZipFileIndexCache zipFileIndexCache;
91146773Ssam
92146773Ssam    private final Set<JavaFileObject.Kind> sourceOrClass =
93146773Ssam        EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
94146773Ssam
95146773Ssam    protected boolean mmappedIO;
96146773Ssam    protected boolean symbolFileEnabled;
97146773Ssam
98146773Ssam    protected enum SortFiles implements Comparator<File> {
99146773Ssam        FORWARD {
100146773Ssam            public int compare(File f1, File f2) {
101146773Ssam                return f1.getName().compareTo(f2.getName());
102146773Ssam            }
103146773Ssam        },
104146773Ssam        REVERSE {
105146773Ssam            public int compare(File f1, File f2) {
106190207Srpaulo                return -f1.getName().compareTo(f2.getName());
107190207Srpaulo            }
108146773Ssam        }
109146773Ssam    }
110235530Sdelphij
111146773Ssam    protected SortFiles sortFiles;
112146773Ssam
113251158Sdelphij    /**
114146773Ssam     * Register a Context.Factory to create a JavacFileManager.
115190207Srpaulo     */
116190207Srpaulo    public static void preRegister(Context context) {
117190207Srpaulo        context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
118146773Ssam            public JavaFileManager make(Context c) {
119162017Ssam                return new JavacFileManager(c, true, null);
120235530Sdelphij            }
121162017Ssam        });
122146773Ssam    }
123146773Ssam
124146773Ssam    /**
125146773Ssam     * Create a JavacFileManager using a given context, optionally registering
126146773Ssam     * it as the JavaFileManager for that context.
127146773Ssam     */
128214478Srpaulo    public JavacFileManager(Context context, boolean register, Charset charset) {
129146773Ssam        super(charset);
130146773Ssam        if (register)
131146773Ssam            context.put(JavaFileManager.class, this);
132146773Ssam        setContext(context);
133146773Ssam    }
134146773Ssam
135146773Ssam    /**
136146773Ssam     * Set the context for JavacFileManager.
137146773Ssam     */
138146773Ssam    @Override
139146773Ssam    public void setContext(Context context) {
140146773Ssam        super.setContext(context);
141146773Ssam
142146773Ssam        fsInfo = FSInfo.instance(context);
143146773Ssam
144146773Ssam        contextUseOptimizedZip = options.getBoolean("useOptimizedZip", true);
145146773Ssam        if (contextUseOptimizedZip)
146146773Ssam            zipFileIndexCache = ZipFileIndexCache.getSharedInstance();
147146773Ssam
148146773Ssam        mmappedIO = options.isSet("mmappedIO");
149146773Ssam        symbolFileEnabled = !options.isSet("ignore.symbol.file");
150146773Ssam
151146773Ssam        String sf = options.get("sortFiles");
152146773Ssam        if (sf != null) {
153146773Ssam            sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
154214478Srpaulo        }
155214478Srpaulo    }
156214478Srpaulo
157214478Srpaulo    /**
158214478Srpaulo     * Set whether or not to use ct.sym as an alternate to rt.jar.
159146773Ssam     */
160214478Srpaulo    public void setSymbolFileEnabled(boolean b) {
161214478Srpaulo        symbolFileEnabled = b;
162235530Sdelphij    }
163214478Srpaulo
164214478Srpaulo    public JavaFileObject getFileForInput(String name) {
165146773Ssam        return getRegularFile(new File(name));
166214478Srpaulo    }
167214478Srpaulo
168214478Srpaulo    public JavaFileObject getRegularFile(File file) {
169214478Srpaulo        return new RegularFileObject(this, file);
170214478Srpaulo    }
171146773Ssam
172146773Ssam    public JavaFileObject getFileForOutput(String classname,
173146773Ssam                                           JavaFileObject.Kind kind,
174146773Ssam                                           JavaFileObject sibling)
175146773Ssam        throws IOException
176146773Ssam    {
177146773Ssam        return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
178146773Ssam    }
179146773Ssam
180251158Sdelphij    @DefinedBy(Api.COMPILER)
181251158Sdelphij    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
182251158Sdelphij        ListBuffer<File> files = new ListBuffer<>();
183251158Sdelphij        for (String name : names)
184251158Sdelphij            files.append(new File(nullCheck(name)));
185251158Sdelphij        return getJavaFileObjectsFromFiles(files.toList());
186146773Ssam    }
187146773Ssam
188146773Ssam    @DefinedBy(Api.COMPILER)
189146773Ssam    public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
190146773Ssam        return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
191146773Ssam    }
192146773Ssam
193146773Ssam    private static boolean isValidName(String name) {
194146773Ssam        // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
195214478Srpaulo        // but the set of keywords depends on the source level, and we don't want
196214478Srpaulo        // impls of JavaFileManager to have to be dependent on the source level.
197214478Srpaulo        // Therefore we simply check that the argument is a sequence of identifiers
198214478Srpaulo        // separated by ".".
199214478Srpaulo        for (String s : name.split("\\.", -1)) {
200214478Srpaulo            if (!SourceVersion.isIdentifier(s))
201214478Srpaulo                return false;
202146773Ssam        }
203214478Srpaulo        return true;
204146773Ssam    }
205214478Srpaulo
206214478Srpaulo    private static void validateClassName(String className) {
207214478Srpaulo        if (!isValidName(className))
208214478Srpaulo            throw new IllegalArgumentException("Invalid class name: " + className);
209146773Ssam    }
210146773Ssam
211146773Ssam    private static void validatePackageName(String packageName) {
212146773Ssam        if (packageName.length() > 0 && !isValidName(packageName))
213146773Ssam            throw new IllegalArgumentException("Invalid packageName name: " + packageName);
214146773Ssam    }
215146773Ssam
216146773Ssam    public static void testName(String name,
217146773Ssam                                boolean isValidPackageName,
218146773Ssam                                boolean isValidClassName)
219146773Ssam    {
220146773Ssam        try {
221146773Ssam            validatePackageName(name);
222146773Ssam            if (!isValidPackageName)
223146773Ssam                throw new AssertionError("Invalid package name accepted: " + name);
224146773Ssam            printAscii("Valid package name: \"%s\"", name);
225146773Ssam        } catch (IllegalArgumentException e) {
226146773Ssam            if (isValidPackageName)
227146773Ssam                throw new AssertionError("Valid package name rejected: " + name);
228146773Ssam            printAscii("Invalid package name: \"%s\"", name);
229146773Ssam        }
230146773Ssam        try {
231146773Ssam            validateClassName(name);
232146773Ssam            if (!isValidClassName)
233146773Ssam                throw new AssertionError("Invalid class name accepted: " + name);
234146773Ssam            printAscii("Valid class name: \"%s\"", name);
235146773Ssam        } catch (IllegalArgumentException e) {
236146773Ssam            if (isValidClassName)
237146773Ssam                throw new AssertionError("Valid class name rejected: " + name);
238146773Ssam            printAscii("Invalid class name: \"%s\"", name);
239146773Ssam        }
240146773Ssam    }
241146773Ssam
242146773Ssam    private static void printAscii(String format, Object... args) {
243251158Sdelphij        String message;
244146773Ssam        try {
245146773Ssam            final String ascii = "US-ASCII";
246146773Ssam            message = new String(String.format(null, format, args).getBytes(ascii), ascii);
247146773Ssam        } catch (java.io.UnsupportedEncodingException ex) {
248146773Ssam            throw new AssertionError(ex);
249146773Ssam        }
250146773Ssam        System.out.println(message);
251146773Ssam    }
252146773Ssam
253146773Ssam
254146773Ssam    /**
255146773Ssam     * Insert all files in subdirectory subdirectory of directory directory
256146773Ssam     * which match fileKinds into resultList
257146773Ssam     */
258146773Ssam    private void listDirectory(File directory,
259146773Ssam                               RelativeDirectory subdirectory,
260146773Ssam                               Set<JavaFileObject.Kind> fileKinds,
261172683Smlaier                               boolean recurse,
262146773Ssam                               ListBuffer<JavaFileObject> resultList) {
263235530Sdelphij        File d = subdirectory.getFile(directory);
264235530Sdelphij        if (!caseMapCheck(d, subdirectory))
265235530Sdelphij            return;
266146773Ssam
267146773Ssam        File[] files = d.listFiles();
268146773Ssam        if (files == null)
269146773Ssam            return;
270146773Ssam
271146773Ssam        if (sortFiles != null)
272146773Ssam            Arrays.sort(files, sortFiles);
273146773Ssam
274146773Ssam        for (File f: files) {
275146773Ssam            String fname = f.getName();
276146773Ssam            if (f.isDirectory()) {
277235530Sdelphij                if (recurse && SourceVersion.isIdentifier(fname)) {
278235530Sdelphij                    listDirectory(directory,
279235530Sdelphij                                  new RelativeDirectory(subdirectory, fname),
280146773Ssam                                  fileKinds,
281235530Sdelphij                                  recurse,
282235530Sdelphij                                  resultList);
283235530Sdelphij                }
284190207Srpaulo            } else {
285146773Ssam                if (isValidFile(fname, fileKinds)) {
286146773Ssam                    JavaFileObject fe =
287146773Ssam                        new RegularFileObject(this, fname, new File(d, fname));
288146773Ssam                    resultList.append(fe);
289241235Sdelphij                }
290251158Sdelphij            }
291214478Srpaulo        }
292214478Srpaulo    }
293146773Ssam
294146773Ssam    /**
295146773Ssam     * Insert all files in subdirectory subdirectory of archive archive
296146773Ssam     * which match fileKinds into resultList
297146773Ssam     */
298146773Ssam    private void listArchive(Archive archive,
299146773Ssam                               RelativeDirectory subdirectory,
300146773Ssam                               Set<JavaFileObject.Kind> fileKinds,
301190207Srpaulo                               boolean recurse,
302146773Ssam                               ListBuffer<JavaFileObject> resultList) {
303235530Sdelphij        // Get the files directly in the subdir
304235530Sdelphij        List<String> files = archive.getFiles(subdirectory);
305235530Sdelphij        if (files != null) {
306235530Sdelphij            for (; !files.isEmpty(); files = files.tail) {
307235530Sdelphij                String file = files.head;
308251158Sdelphij                if (isValidFile(file, fileKinds)) {
309235530Sdelphij                    resultList.append(archive.getFileObject(subdirectory, file));
310251158Sdelphij                }
311235530Sdelphij            }
312251158Sdelphij        }
313235530Sdelphij        if (recurse) {
314235530Sdelphij            for (RelativeDirectory s: archive.getSubdirectories()) {
315235530Sdelphij                if (subdirectory.contains(s)) {
316235530Sdelphij                    // Because the archive map is a flat list of directories,
317235530Sdelphij                    // the enclosing loop will pick up all child subdirectories.
318235530Sdelphij                    // Therefore, there is no need to recurse deeper.
319146773Ssam                    listArchive(archive, s, fileKinds, false, resultList);
320146773Ssam                }
321162017Ssam            }
322162017Ssam        }
323162017Ssam    }
324162017Ssam
325146773Ssam    /**
326162017Ssam     * container is a directory, a zip file, or a non-existant path.
327162017Ssam     * Insert all files in subdirectory subdirectory of container which
328162017Ssam     * match fileKinds into resultList
329146773Ssam     */
330146773Ssam    private void listContainer(File container,
331146773Ssam                               RelativeDirectory subdirectory,
332146773Ssam                               Set<JavaFileObject.Kind> fileKinds,
333146773Ssam                               boolean recurse,
334146773Ssam                               ListBuffer<JavaFileObject> resultList) {
335146773Ssam        Archive archive = archives.get(container);
336146773Ssam        if (archive == null) {
337146773Ssam            // archives are not created for directories.
338146773Ssam            if  (fsInfo.isDirectory(container)) {
339146773Ssam                listDirectory(container,
340146773Ssam                              subdirectory,
341146773Ssam                              fileKinds,
342146773Ssam                              recurse,
343146773Ssam                              resultList);
344146773Ssam                return;
345146773Ssam            }
346146773Ssam
347146773Ssam            // Not a directory; either a file or non-existant, create the archive
348146773Ssam            try {
349146773Ssam                archive = openArchive(container);
350146773Ssam            } catch (IOException ex) {
351146773Ssam                log.error("error.reading.file",
352146773Ssam                          container, getMessage(ex));
353146773Ssam                return;
354146773Ssam            }
355146773Ssam        }
356146773Ssam        listArchive(archive,
357146773Ssam                    subdirectory,
358146773Ssam                    fileKinds,
359146773Ssam                    recurse,
360146773Ssam                    resultList);
361146773Ssam    }
362146773Ssam
363146773Ssam    private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
364146773Ssam        JavaFileObject.Kind kind = getKind(s);
365146773Ssam        return fileKinds.contains(kind);
366146773Ssam    }
367146773Ssam
368235530Sdelphij    private static final boolean fileSystemIsCaseSensitive =
369146773Ssam        File.separatorChar == '/';
370146773Ssam
371146773Ssam    /** Hack to make Windows case sensitive. Test whether given path
372146773Ssam     *  ends in a string of characters with the same case as given name.
373146773Ssam     *  Ignore file separators in both path and name.
374146773Ssam     */
375146773Ssam    private boolean caseMapCheck(File f, RelativePath name) {
376146773Ssam        if (fileSystemIsCaseSensitive) return true;
377146773Ssam        // Note that getCanonicalPath() returns the case-sensitive
378146773Ssam        // spelled file name.
379146773Ssam        String path;
380146773Ssam        try {
381146773Ssam            path = f.getCanonicalPath();
382146773Ssam        } catch (IOException ex) {
383146773Ssam            return false;
384146773Ssam        }
385146773Ssam        char[] pcs = path.toCharArray();
386146773Ssam        char[] ncs = name.path.toCharArray();
387146773Ssam        int i = pcs.length - 1;
388146773Ssam        int j = ncs.length - 1;
389146773Ssam        while (i >= 0 && j >= 0) {
390146773Ssam            while (i >= 0 && pcs[i] == File.separatorChar) i--;
391146773Ssam            while (j >= 0 && ncs[j] == '/') j--;
392146773Ssam            if (i >= 0 && j >= 0) {
393146773Ssam                if (pcs[i] != ncs[j]) return false;
394146773Ssam                i--;
395214478Srpaulo                j--;
396146773Ssam            }
397146773Ssam        }
398146773Ssam        return j < 0;
399146773Ssam    }
400146773Ssam
401146773Ssam    /**
402146773Ssam     * An archive provides a flat directory structure of a ZipFile by
403146773Ssam     * mapping directory names to lists of files (basenames).
404146773Ssam     */
405146773Ssam    public interface Archive {
406146773Ssam        void close() throws IOException;
407146773Ssam
408146773Ssam        boolean contains(RelativePath name);
409146773Ssam
410146773Ssam        JavaFileObject getFileObject(RelativeDirectory subdirectory, String file);
411146773Ssam
412146773Ssam        List<String> getFiles(RelativeDirectory subdirectory);
413146773Ssam
414235530Sdelphij        Set<RelativeDirectory> getSubdirectories();
415146773Ssam    }
416146773Ssam
417251158Sdelphij    public class MissingArchive implements Archive {
418146773Ssam        final File zipFileName;
419146773Ssam        public MissingArchive(File name) {
420146773Ssam            zipFileName = name;
421146773Ssam        }
422146773Ssam        public boolean contains(RelativePath name) {
423146773Ssam            return false;
424146773Ssam        }
425146773Ssam
426146773Ssam        public void close() {
427146773Ssam        }
428146773Ssam
429146773Ssam        public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) {
430146773Ssam            return null;
431146773Ssam        }
432146773Ssam
433146773Ssam        public List<String> getFiles(RelativeDirectory subdirectory) {
434146773Ssam            return List.nil();
435146773Ssam        }
436146773Ssam
437146773Ssam        public Set<RelativeDirectory> getSubdirectories() {
438146773Ssam            return Collections.emptySet();
439146773Ssam        }
440146773Ssam
441146773Ssam        @Override
442146773Ssam        public String toString() {
443146773Ssam            return "MissingArchive[" + zipFileName + "]";
444146773Ssam        }
445146773Ssam    }
446146773Ssam
447146773Ssam    /** A directory of zip files already opened.
448146773Ssam     */
449146773Ssam    Map<File, Archive> archives = new HashMap<>();
450146773Ssam
451146773Ssam    private static final String[] symbolFileLocation = { "lib", "ct.sym" };
452146773Ssam    private static final RelativeDirectory symbolFilePrefix
453146773Ssam            = new RelativeDirectory("META-INF/sym/rt.jar/");
454146773Ssam
455146773Ssam    /*
456146773Ssam     * This method looks for a ZipFormatException and takes appropriate
457146773Ssam     * evasive action. If there is a failure in the fast mode then we
458146773Ssam     * fail over to the platform zip, and allow it to deal with a potentially
459146773Ssam     * non compliant zip file.
460146773Ssam     */
461235530Sdelphij    protected Archive openArchive(File zipFilename) throws IOException {
462235530Sdelphij        try {
463146773Ssam            return openArchive(zipFilename, contextUseOptimizedZip);
464146773Ssam        } catch (IOException ioe) {
465146773Ssam            if (ioe instanceof ZipFileIndex.ZipFormatException) {
466146773Ssam                return openArchive(zipFilename, false);
467146773Ssam            } else {
468146773Ssam                throw ioe;
469214478Srpaulo            }
470146773Ssam        }
471214478Srpaulo    }
472235530Sdelphij
473214478Srpaulo    /** Open a new zip file directory, and cache it.
474235530Sdelphij     */
475235530Sdelphij    private Archive openArchive(File zipFileName, boolean useOptimizedZip) throws IOException {
476146773Ssam        File origZipFileName = zipFileName;
477146773Ssam        if (symbolFileEnabled && locations.isDefaultBootClassPathRtJar(zipFileName)) {
478235530Sdelphij            File file = zipFileName.getParentFile().getParentFile(); // ${java.home}
479146773Ssam            if (new File(file.getName()).equals(new File("jre")))
480235530Sdelphij                file = file.getParentFile();
481146773Ssam            // file == ${jdk.home}
482146773Ssam            for (String name : symbolFileLocation)
483146773Ssam                file = new File(file, name);
484146773Ssam            // file == ${jdk.home}/lib/ct.sym
485146773Ssam            if (file.exists())
486146773Ssam                zipFileName = file;
487146773Ssam        }
488146773Ssam
489146773Ssam        Archive archive;
490146773Ssam        try {
491146773Ssam
492146773Ssam            ZipFile zdir = null;
493146773Ssam
494146773Ssam            boolean usePreindexedCache = false;
495235530Sdelphij            String preindexCacheLocation = null;
496146773Ssam
497146773Ssam            if (!useOptimizedZip) {
498146773Ssam                zdir = new ZipFile(zipFileName);
499235530Sdelphij            } else {
500235530Sdelphij                usePreindexedCache = options.isSet("usezipindex");
501235530Sdelphij                preindexCacheLocation = options.get("java.io.tmpdir");
502235530Sdelphij                String optCacheLoc = options.get("cachezipindexdir");
503235530Sdelphij
504235530Sdelphij                if (optCacheLoc != null && optCacheLoc.length() != 0) {
505235530Sdelphij                    if (optCacheLoc.startsWith("\"")) {
506146773Ssam                        if (optCacheLoc.endsWith("\"")) {
507146773Ssam                            optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1);
508214478Srpaulo                        }
509214478Srpaulo                        else {
510214478Srpaulo                            optCacheLoc = optCacheLoc.substring(1);
511214478Srpaulo                        }
512214478Srpaulo                    }
513214478Srpaulo
514214478Srpaulo                    File cacheDir = new File(optCacheLoc);
515146773Ssam                    if (cacheDir.exists() && cacheDir.canWrite()) {
516                        preindexCacheLocation = optCacheLoc;
517                        if (!preindexCacheLocation.endsWith("/") &&
518                            !preindexCacheLocation.endsWith(File.separator)) {
519                            preindexCacheLocation += File.separator;
520                        }
521                    }
522                }
523            }
524
525            if (origZipFileName == zipFileName) {
526                if (!useOptimizedZip) {
527                    archive = new ZipArchive(this, zdir);
528                } else {
529                    archive = new ZipFileIndexArchive(this,
530                                    zipFileIndexCache.getZipFileIndex(zipFileName,
531                                    null,
532                                    usePreindexedCache,
533                                    preindexCacheLocation,
534                                    options.isSet("writezipindexfiles")));
535                }
536            } else {
537                if (!useOptimizedZip) {
538                    archive = new SymbolArchive(this, origZipFileName, zdir, symbolFilePrefix);
539                } else {
540                    archive = new ZipFileIndexArchive(this,
541                                    zipFileIndexCache.getZipFileIndex(zipFileName,
542                                    symbolFilePrefix,
543                                    usePreindexedCache,
544                                    preindexCacheLocation,
545                                    options.isSet("writezipindexfiles")));
546                }
547            }
548        } catch (FileNotFoundException ex) {
549            archive = new MissingArchive(zipFileName);
550        } catch (ZipFileIndex.ZipFormatException zfe) {
551            throw zfe;
552        } catch (IOException ex) {
553            if (zipFileName.exists())
554                log.error("error.reading.file", zipFileName, getMessage(ex));
555            archive = new MissingArchive(zipFileName);
556        }
557
558        archives.put(origZipFileName, archive);
559        return archive;
560    }
561
562    /** Flush any output resources.
563     */
564    @DefinedBy(Api.COMPILER)
565    public void flush() {
566        contentCache.clear();
567    }
568
569    /**
570     * Close the JavaFileManager, releasing resources.
571     */
572    @DefinedBy(Api.COMPILER)
573    public void close() {
574        for (Iterator<Archive> i = archives.values().iterator(); i.hasNext(); ) {
575            Archive a = i.next();
576            i.remove();
577            try {
578                a.close();
579            } catch (IOException e) {
580            }
581        }
582    }
583
584    @DefinedBy(Api.COMPILER)
585    public ClassLoader getClassLoader(Location location) {
586        nullCheck(location);
587        Iterable<? extends File> path = getLocation(location);
588        if (path == null)
589            return null;
590        ListBuffer<URL> lb = new ListBuffer<>();
591        for (File f: path) {
592            try {
593                lb.append(f.toURI().toURL());
594            } catch (MalformedURLException e) {
595                throw new AssertionError(e);
596            }
597        }
598
599        return getClassLoader(lb.toArray(new URL[lb.size()]));
600    }
601
602    @DefinedBy(Api.COMPILER)
603    public Iterable<JavaFileObject> list(Location location,
604                                         String packageName,
605                                         Set<JavaFileObject.Kind> kinds,
606                                         boolean recurse)
607        throws IOException
608    {
609        // validatePackageName(packageName);
610        nullCheck(packageName);
611        nullCheck(kinds);
612
613        Iterable<? extends File> path = getLocation(location);
614        if (path == null)
615            return List.nil();
616        RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
617        ListBuffer<JavaFileObject> results = new ListBuffer<>();
618
619        for (File directory : path)
620            listContainer(directory, subdirectory, kinds, recurse, results);
621        return results.toList();
622    }
623
624    @DefinedBy(Api.COMPILER)
625    public String inferBinaryName(Location location, JavaFileObject file) {
626        file.getClass(); // null check
627        location.getClass(); // null check
628        // Need to match the path semantics of list(location, ...)
629        Iterable<? extends File> path = getLocation(location);
630        if (path == null) {
631            return null;
632        }
633
634        if (file instanceof BaseFileObject) {
635            return ((BaseFileObject) file).inferBinaryName(path);
636        } else
637            throw new IllegalArgumentException(file.getClass().getName());
638    }
639
640    @DefinedBy(Api.COMPILER)
641    public boolean isSameFile(FileObject a, FileObject b) {
642        nullCheck(a);
643        nullCheck(b);
644        if (!(a instanceof BaseFileObject))
645            throw new IllegalArgumentException("Not supported: " + a);
646        if (!(b instanceof BaseFileObject))
647            throw new IllegalArgumentException("Not supported: " + b);
648        return a.equals(b);
649    }
650
651    @DefinedBy(Api.COMPILER)
652    public boolean hasLocation(Location location) {
653        return getLocation(location) != null;
654    }
655
656    @DefinedBy(Api.COMPILER)
657    public JavaFileObject getJavaFileForInput(Location location,
658                                              String className,
659                                              JavaFileObject.Kind kind)
660        throws IOException
661    {
662        nullCheck(location);
663        // validateClassName(className);
664        nullCheck(className);
665        nullCheck(kind);
666        if (!sourceOrClass.contains(kind))
667            throw new IllegalArgumentException("Invalid kind: " + kind);
668        return getFileForInput(location, RelativeFile.forClass(className, kind));
669    }
670
671    @DefinedBy(Api.COMPILER)
672    public FileObject getFileForInput(Location location,
673                                      String packageName,
674                                      String relativeName)
675        throws IOException
676    {
677        nullCheck(location);
678        // validatePackageName(packageName);
679        nullCheck(packageName);
680        if (!isRelativeUri(relativeName))
681            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
682        RelativeFile name = packageName.length() == 0
683            ? new RelativeFile(relativeName)
684            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
685        return getFileForInput(location, name);
686    }
687
688    private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
689        Iterable<? extends File> path = getLocation(location);
690        if (path == null)
691            return null;
692
693        for (File dir: path) {
694            Archive a = archives.get(dir);
695            if (a == null) {
696                if (fsInfo.isDirectory(dir)) {
697                    File f = name.getFile(dir);
698                    if (f.exists())
699                        return new RegularFileObject(this, f);
700                    continue;
701                }
702                // Not a directory, create the archive
703                a = openArchive(dir);
704            }
705            // Process the archive
706            if (a.contains(name)) {
707                return a.getFileObject(name.dirname(), name.basename());
708            }
709        }
710        return null;
711    }
712
713    @DefinedBy(Api.COMPILER)
714    public JavaFileObject getJavaFileForOutput(Location location,
715                                               String className,
716                                               JavaFileObject.Kind kind,
717                                               FileObject sibling)
718        throws IOException
719    {
720        nullCheck(location);
721        // validateClassName(className);
722        nullCheck(className);
723        nullCheck(kind);
724        if (!sourceOrClass.contains(kind))
725            throw new IllegalArgumentException("Invalid kind: " + kind);
726        return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
727    }
728
729    @DefinedBy(Api.COMPILER)
730    public FileObject getFileForOutput(Location location,
731                                       String packageName,
732                                       String relativeName,
733                                       FileObject sibling)
734        throws IOException
735    {
736        nullCheck(location);
737        // validatePackageName(packageName);
738        nullCheck(packageName);
739        if (!isRelativeUri(relativeName))
740            throw new IllegalArgumentException("Invalid relative name: " + relativeName);
741        RelativeFile name = packageName.length() == 0
742            ? new RelativeFile(relativeName)
743            : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
744        return getFileForOutput(location, name, sibling);
745    }
746
747    private JavaFileObject getFileForOutput(Location location,
748                                            RelativeFile fileName,
749                                            FileObject sibling)
750        throws IOException
751    {
752        File dir;
753        if (location == CLASS_OUTPUT) {
754            if (getClassOutDir() != null) {
755                dir = getClassOutDir();
756            } else {
757                File siblingDir = null;
758                if (sibling != null && sibling instanceof RegularFileObject) {
759                    siblingDir = ((RegularFileObject)sibling).file.getParentFile();
760                }
761                return new RegularFileObject(this, new File(siblingDir, fileName.basename()));
762            }
763        } else if (location == SOURCE_OUTPUT) {
764            dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
765        } else {
766            Iterable<? extends File> path = locations.getLocation(location);
767            dir = null;
768            for (File f: path) {
769                dir = f;
770                break;
771            }
772        }
773
774        File file = fileName.getFile(dir); // null-safe
775        return new RegularFileObject(this, file);
776
777    }
778
779    @DefinedBy(Api.COMPILER)
780    public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
781        Iterable<? extends File> files)
782    {
783        ArrayList<RegularFileObject> result;
784        if (files instanceof Collection<?>)
785            result = new ArrayList<>(((Collection<?>)files).size());
786        else
787            result = new ArrayList<>();
788        for (File f: files)
789            result.add(new RegularFileObject(this, nullCheck(f)));
790        return result;
791    }
792
793    @DefinedBy(Api.COMPILER)
794    public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
795        return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
796    }
797
798    @DefinedBy(Api.COMPILER)
799    public void setLocation(Location location,
800                            Iterable<? extends File> path)
801        throws IOException
802    {
803        nullCheck(location);
804        locations.setLocation(location, path);
805    }
806
807    @DefinedBy(Api.COMPILER)
808    public Iterable<? extends File> getLocation(Location location) {
809        nullCheck(location);
810        return locations.getLocation(location);
811    }
812
813    private File getClassOutDir() {
814        return locations.getOutputLocation(CLASS_OUTPUT);
815    }
816
817    private File getSourceOutDir() {
818        return locations.getOutputLocation(SOURCE_OUTPUT);
819    }
820
821    /**
822     * Enforces the specification of a "relative" name as used in
823     * {@linkplain #getFileForInput(Location,String,String)
824     * getFileForInput}.  This method must follow the rules defined in
825     * that method, do not make any changes without consulting the
826     * specification.
827     */
828    protected static boolean isRelativeUri(URI uri) {
829        if (uri.isAbsolute())
830            return false;
831        String path = uri.normalize().getPath();
832        if (path.length() == 0 /* isEmpty() is mustang API */)
833            return false;
834        if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
835            return false;
836        if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
837            return false;
838        return true;
839    }
840
841    // Convenience method
842    protected static boolean isRelativeUri(String u) {
843        try {
844            return isRelativeUri(new URI(u));
845        } catch (URISyntaxException e) {
846            return false;
847        }
848    }
849
850    /**
851     * Converts a relative file name to a relative URI.  This is
852     * different from File.toURI as this method does not canonicalize
853     * the file before creating the URI.  Furthermore, no schema is
854     * used.
855     * @param file a relative file name
856     * @return a relative URI
857     * @throws IllegalArgumentException if the file name is not
858     * relative according to the definition given in {@link
859     * javax.tools.JavaFileManager#getFileForInput}
860     */
861    public static String getRelativeName(File file) {
862        if (!file.isAbsolute()) {
863            String result = file.getPath().replace(File.separatorChar, '/');
864            if (isRelativeUri(result))
865                return result;
866        }
867        throw new IllegalArgumentException("Invalid relative path: " + file);
868    }
869
870    /**
871     * Get a detail message from an IOException.
872     * Most, but not all, instances of IOException provide a non-null result
873     * for getLocalizedMessage().  But some instances return null: in these
874     * cases, fallover to getMessage(), and if even that is null, return the
875     * name of the exception itself.
876     * @param e an IOException
877     * @return a string to include in a compiler diagnostic
878     */
879    public static String getMessage(IOException e) {
880        String s = e.getLocalizedMessage();
881        if (s != null)
882            return s;
883        s = e.getMessage();
884        if (s != null)
885            return s;
886        return e.toString();
887    }
888}
889