aoqi@0: /*
aoqi@0: * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
aoqi@0: *
aoqi@0: * This code is free software; you can redistribute it and/or modify it
aoqi@0: * under the terms of the GNU General Public License version 2 only, as
aoqi@0: * published by the Free Software Foundation. Oracle designates this
aoqi@0: * particular file as subject to the "Classpath" exception as provided
aoqi@0: * by Oracle in the LICENSE file that accompanied this code.
aoqi@0: *
aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT
aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that
aoqi@0: * accompanied this code).
aoqi@0: *
aoqi@0: * You should have received a copy of the GNU General Public License version
aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation,
aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
aoqi@0: *
aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
aoqi@0: * or visit www.oracle.com if you need additional information or have any
aoqi@0: * questions.
aoqi@0: */
aoqi@0:
aoqi@0: package com.sun.tools.javac.file;
aoqi@0:
aoqi@0: import java.io.ByteArrayOutputStream;
aoqi@0: import java.io.File;
aoqi@0: import java.io.FileNotFoundException;
aoqi@0: import java.io.IOException;
aoqi@0: import java.io.OutputStreamWriter;
aoqi@0: import java.net.MalformedURLException;
aoqi@0: import java.net.URI;
aoqi@0: import java.net.URISyntaxException;
aoqi@0: import java.net.URL;
aoqi@0: import java.nio.CharBuffer;
aoqi@0: import java.nio.charset.Charset;
aoqi@0: import java.util.ArrayList;
aoqi@0: import java.util.Arrays;
aoqi@0: import java.util.Collection;
aoqi@0: import java.util.Collections;
aoqi@0: import java.util.Comparator;
aoqi@0: import java.util.EnumSet;
aoqi@0: import java.util.HashMap;
aoqi@0: import java.util.Iterator;
aoqi@0: import java.util.Map;
aoqi@0: import java.util.Set;
aoqi@0: import java.util.zip.ZipFile;
aoqi@0:
aoqi@0: import javax.lang.model.SourceVersion;
aoqi@0: import javax.tools.FileObject;
aoqi@0: import javax.tools.JavaFileManager;
aoqi@0: import javax.tools.JavaFileObject;
aoqi@0: import javax.tools.StandardJavaFileManager;
aoqi@0:
aoqi@0: import com.sun.tools.javac.file.RelativePath.RelativeFile;
aoqi@0: import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
aoqi@0: import com.sun.tools.javac.util.BaseFileManager;
aoqi@0: import com.sun.tools.javac.util.Context;
aoqi@0: import com.sun.tools.javac.util.List;
aoqi@0: import com.sun.tools.javac.util.ListBuffer;
aoqi@0:
aoqi@0: import static javax.tools.StandardLocation.*;
aoqi@0:
aoqi@0: /**
aoqi@0: * This class provides access to the source, class and other files
aoqi@0: * used by the compiler and related tools.
aoqi@0: *
aoqi@0: *
This is NOT part of any supported API.
aoqi@0: * If you write code that depends on this, you do so at your own risk.
aoqi@0: * This code and its internal interfaces are subject to change or
aoqi@0: * deletion without notice.
aoqi@0: */
aoqi@0: public class JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
aoqi@0:
aoqi@0: public static char[] toArray(CharBuffer buffer) {
aoqi@0: if (buffer.hasArray())
aoqi@0: return ((CharBuffer)buffer.compact().flip()).array();
aoqi@0: else
aoqi@0: return buffer.toString().toCharArray();
aoqi@0: }
aoqi@0:
aoqi@0: private FSInfo fsInfo;
aoqi@0:
aoqi@0: private boolean contextUseOptimizedZip;
aoqi@0: private ZipFileIndexCache zipFileIndexCache;
aoqi@0:
aoqi@0: private final Set sourceOrClass =
aoqi@0: EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
aoqi@0:
aoqi@0: protected boolean mmappedIO;
aoqi@0: protected boolean symbolFileEnabled;
aoqi@0:
aoqi@0: protected enum SortFiles implements Comparator {
aoqi@0: FORWARD {
aoqi@0: public int compare(File f1, File f2) {
aoqi@0: return f1.getName().compareTo(f2.getName());
aoqi@0: }
aoqi@0: },
aoqi@0: REVERSE {
aoqi@0: public int compare(File f1, File f2) {
aoqi@0: return -f1.getName().compareTo(f2.getName());
aoqi@0: }
aoqi@0: };
aoqi@0: };
aoqi@0: protected SortFiles sortFiles;
aoqi@0:
aoqi@0: /**
aoqi@0: * Register a Context.Factory to create a JavacFileManager.
aoqi@0: */
aoqi@0: public static void preRegister(Context context) {
aoqi@0: context.put(JavaFileManager.class, new Context.Factory() {
aoqi@0: public JavaFileManager make(Context c) {
aoqi@0: return new JavacFileManager(c, true, null);
aoqi@0: }
aoqi@0: });
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Create a JavacFileManager using a given context, optionally registering
aoqi@0: * it as the JavaFileManager for that context.
aoqi@0: */
aoqi@0: public JavacFileManager(Context context, boolean register, Charset charset) {
aoqi@0: super(charset);
aoqi@0: if (register)
aoqi@0: context.put(JavaFileManager.class, this);
aoqi@0: setContext(context);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Set the context for JavacFileManager.
aoqi@0: */
aoqi@0: @Override
aoqi@0: public void setContext(Context context) {
aoqi@0: super.setContext(context);
aoqi@0:
aoqi@0: fsInfo = FSInfo.instance(context);
aoqi@0:
aoqi@0: contextUseOptimizedZip = options.getBoolean("useOptimizedZip", true);
aoqi@0: if (contextUseOptimizedZip)
aoqi@0: zipFileIndexCache = ZipFileIndexCache.getSharedInstance();
aoqi@0:
aoqi@0: mmappedIO = options.isSet("mmappedIO");
aoqi@0: symbolFileEnabled = !options.isSet("ignore.symbol.file");
aoqi@0:
aoqi@0: String sf = options.get("sortFiles");
aoqi@0: if (sf != null) {
aoqi@0: sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Set whether or not to use ct.sym as an alternate to rt.jar.
aoqi@0: */
aoqi@0: public void setSymbolFileEnabled(boolean b) {
aoqi@0: symbolFileEnabled = b;
aoqi@0: }
aoqi@0:
aoqi@0: @Override
aoqi@0: public boolean isDefaultBootClassPath() {
aoqi@0: return locations.isDefaultBootClassPath();
aoqi@0: }
aoqi@0:
aoqi@0: public JavaFileObject getFileForInput(String name) {
aoqi@0: return getRegularFile(new File(name));
aoqi@0: }
aoqi@0:
aoqi@0: public JavaFileObject getRegularFile(File file) {
aoqi@0: return new RegularFileObject(this, file);
aoqi@0: }
aoqi@0:
aoqi@0: public JavaFileObject getFileForOutput(String classname,
aoqi@0: JavaFileObject.Kind kind,
aoqi@0: JavaFileObject sibling)
aoqi@0: throws IOException
aoqi@0: {
aoqi@0: return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
aoqi@0: }
aoqi@0:
aoqi@0: public Iterable extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable names) {
aoqi@0: ListBuffer files = new ListBuffer();
aoqi@0: for (String name : names)
aoqi@0: files.append(new File(nullCheck(name)));
aoqi@0: return getJavaFileObjectsFromFiles(files.toList());
aoqi@0: }
aoqi@0:
aoqi@0: public Iterable extends JavaFileObject> getJavaFileObjects(String... names) {
aoqi@0: return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
aoqi@0: }
aoqi@0:
aoqi@0: private static boolean isValidName(String name) {
aoqi@0: // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
aoqi@0: // but the set of keywords depends on the source level, and we don't want
aoqi@0: // impls of JavaFileManager to have to be dependent on the source level.
aoqi@0: // Therefore we simply check that the argument is a sequence of identifiers
aoqi@0: // separated by ".".
aoqi@0: for (String s : name.split("\\.", -1)) {
aoqi@0: if (!SourceVersion.isIdentifier(s))
aoqi@0: return false;
aoqi@0: }
aoqi@0: return true;
aoqi@0: }
aoqi@0:
aoqi@0: private static void validateClassName(String className) {
aoqi@0: if (!isValidName(className))
aoqi@0: throw new IllegalArgumentException("Invalid class name: " + className);
aoqi@0: }
aoqi@0:
aoqi@0: private static void validatePackageName(String packageName) {
aoqi@0: if (packageName.length() > 0 && !isValidName(packageName))
aoqi@0: throw new IllegalArgumentException("Invalid packageName name: " + packageName);
aoqi@0: }
aoqi@0:
aoqi@0: public static void testName(String name,
aoqi@0: boolean isValidPackageName,
aoqi@0: boolean isValidClassName)
aoqi@0: {
aoqi@0: try {
aoqi@0: validatePackageName(name);
aoqi@0: if (!isValidPackageName)
aoqi@0: throw new AssertionError("Invalid package name accepted: " + name);
aoqi@0: printAscii("Valid package name: \"%s\"", name);
aoqi@0: } catch (IllegalArgumentException e) {
aoqi@0: if (isValidPackageName)
aoqi@0: throw new AssertionError("Valid package name rejected: " + name);
aoqi@0: printAscii("Invalid package name: \"%s\"", name);
aoqi@0: }
aoqi@0: try {
aoqi@0: validateClassName(name);
aoqi@0: if (!isValidClassName)
aoqi@0: throw new AssertionError("Invalid class name accepted: " + name);
aoqi@0: printAscii("Valid class name: \"%s\"", name);
aoqi@0: } catch (IllegalArgumentException e) {
aoqi@0: if (isValidClassName)
aoqi@0: throw new AssertionError("Valid class name rejected: " + name);
aoqi@0: printAscii("Invalid class name: \"%s\"", name);
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: private static void printAscii(String format, Object... args) {
aoqi@0: String message;
aoqi@0: try {
aoqi@0: final String ascii = "US-ASCII";
aoqi@0: message = new String(String.format(null, format, args).getBytes(ascii), ascii);
aoqi@0: } catch (java.io.UnsupportedEncodingException ex) {
aoqi@0: throw new AssertionError(ex);
aoqi@0: }
aoqi@0: System.out.println(message);
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: /**
aoqi@0: * Insert all files in subdirectory subdirectory of directory directory
aoqi@0: * which match fileKinds into resultList
aoqi@0: */
aoqi@0: private void listDirectory(File directory,
aoqi@0: RelativeDirectory subdirectory,
aoqi@0: Set fileKinds,
aoqi@0: boolean recurse,
aoqi@0: ListBuffer resultList) {
aoqi@0: File d = subdirectory.getFile(directory);
aoqi@0: if (!caseMapCheck(d, subdirectory))
aoqi@0: return;
aoqi@0:
aoqi@0: File[] files = d.listFiles();
aoqi@0: if (files == null)
aoqi@0: return;
aoqi@0:
aoqi@0: if (sortFiles != null)
aoqi@0: Arrays.sort(files, sortFiles);
aoqi@0:
aoqi@0: for (File f: files) {
aoqi@0: String fname = f.getName();
aoqi@0: if (f.isDirectory()) {
aoqi@0: if (recurse && SourceVersion.isIdentifier(fname)) {
aoqi@0: listDirectory(directory,
aoqi@0: new RelativeDirectory(subdirectory, fname),
aoqi@0: fileKinds,
aoqi@0: recurse,
aoqi@0: resultList);
aoqi@0: }
aoqi@0: } else {
aoqi@0: if (isValidFile(fname, fileKinds)) {
aoqi@0: JavaFileObject fe =
aoqi@0: new RegularFileObject(this, fname, new File(d, fname));
aoqi@0: resultList.append(fe);
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Insert all files in subdirectory subdirectory of archive archive
aoqi@0: * which match fileKinds into resultList
aoqi@0: */
aoqi@0: private void listArchive(Archive archive,
aoqi@0: RelativeDirectory subdirectory,
aoqi@0: Set fileKinds,
aoqi@0: boolean recurse,
aoqi@0: ListBuffer resultList) {
aoqi@0: // Get the files directly in the subdir
aoqi@0: List files = archive.getFiles(subdirectory);
aoqi@0: if (files != null) {
aoqi@0: for (; !files.isEmpty(); files = files.tail) {
aoqi@0: String file = files.head;
aoqi@0: if (isValidFile(file, fileKinds)) {
aoqi@0: resultList.append(archive.getFileObject(subdirectory, file));
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0: if (recurse) {
aoqi@0: for (RelativeDirectory s: archive.getSubdirectories()) {
aoqi@0: if (subdirectory.contains(s)) {
aoqi@0: // Because the archive map is a flat list of directories,
aoqi@0: // the enclosing loop will pick up all child subdirectories.
aoqi@0: // Therefore, there is no need to recurse deeper.
aoqi@0: listArchive(archive, s, fileKinds, false, resultList);
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * container is a directory, a zip file, or a non-existant path.
aoqi@0: * Insert all files in subdirectory subdirectory of container which
aoqi@0: * match fileKinds into resultList
aoqi@0: */
aoqi@0: private void listContainer(File container,
aoqi@0: RelativeDirectory subdirectory,
aoqi@0: Set fileKinds,
aoqi@0: boolean recurse,
aoqi@0: ListBuffer resultList) {
aoqi@0: Archive archive = archives.get(container);
aoqi@0: if (archive == null) {
aoqi@0: // archives are not created for directories.
aoqi@0: if (fsInfo.isDirectory(container)) {
aoqi@0: listDirectory(container,
aoqi@0: subdirectory,
aoqi@0: fileKinds,
aoqi@0: recurse,
aoqi@0: resultList);
aoqi@0: return;
aoqi@0: }
aoqi@0:
aoqi@0: // Not a directory; either a file or non-existant, create the archive
aoqi@0: try {
aoqi@0: archive = openArchive(container);
aoqi@0: } catch (IOException ex) {
aoqi@0: log.error("error.reading.file",
aoqi@0: container, getMessage(ex));
aoqi@0: return;
aoqi@0: }
aoqi@0: }
aoqi@0: listArchive(archive,
aoqi@0: subdirectory,
aoqi@0: fileKinds,
aoqi@0: recurse,
aoqi@0: resultList);
aoqi@0: }
aoqi@0:
aoqi@0: private boolean isValidFile(String s, Set fileKinds) {
aoqi@0: JavaFileObject.Kind kind = getKind(s);
aoqi@0: return fileKinds.contains(kind);
aoqi@0: }
aoqi@0:
aoqi@0: private static final boolean fileSystemIsCaseSensitive =
aoqi@0: File.separatorChar == '/';
aoqi@0:
aoqi@0: /** Hack to make Windows case sensitive. Test whether given path
aoqi@0: * ends in a string of characters with the same case as given name.
aoqi@0: * Ignore file separators in both path and name.
aoqi@0: */
aoqi@0: private boolean caseMapCheck(File f, RelativePath name) {
aoqi@0: if (fileSystemIsCaseSensitive) return true;
aoqi@0: // Note that getCanonicalPath() returns the case-sensitive
aoqi@0: // spelled file name.
aoqi@0: String path;
aoqi@0: try {
aoqi@0: path = f.getCanonicalPath();
aoqi@0: } catch (IOException ex) {
aoqi@0: return false;
aoqi@0: }
aoqi@0: char[] pcs = path.toCharArray();
aoqi@0: char[] ncs = name.path.toCharArray();
aoqi@0: int i = pcs.length - 1;
aoqi@0: int j = ncs.length - 1;
aoqi@0: while (i >= 0 && j >= 0) {
aoqi@0: while (i >= 0 && pcs[i] == File.separatorChar) i--;
aoqi@0: while (j >= 0 && ncs[j] == '/') j--;
aoqi@0: if (i >= 0 && j >= 0) {
aoqi@0: if (pcs[i] != ncs[j]) return false;
aoqi@0: i--;
aoqi@0: j--;
aoqi@0: }
aoqi@0: }
aoqi@0: return j < 0;
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * An archive provides a flat directory structure of a ZipFile by
aoqi@0: * mapping directory names to lists of files (basenames).
aoqi@0: */
aoqi@0: public interface Archive {
aoqi@0: void close() throws IOException;
aoqi@0:
aoqi@0: boolean contains(RelativePath name);
aoqi@0:
aoqi@0: JavaFileObject getFileObject(RelativeDirectory subdirectory, String file);
aoqi@0:
aoqi@0: List getFiles(RelativeDirectory subdirectory);
aoqi@0:
aoqi@0: Set getSubdirectories();
aoqi@0: }
aoqi@0:
aoqi@0: public class MissingArchive implements Archive {
aoqi@0: final File zipFileName;
aoqi@0: public MissingArchive(File name) {
aoqi@0: zipFileName = name;
aoqi@0: }
aoqi@0: public boolean contains(RelativePath name) {
aoqi@0: return false;
aoqi@0: }
aoqi@0:
aoqi@0: public void close() {
aoqi@0: }
aoqi@0:
aoqi@0: public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) {
aoqi@0: return null;
aoqi@0: }
aoqi@0:
aoqi@0: public List getFiles(RelativeDirectory subdirectory) {
aoqi@0: return List.nil();
aoqi@0: }
aoqi@0:
aoqi@0: public Set getSubdirectories() {
aoqi@0: return Collections.emptySet();
aoqi@0: }
aoqi@0:
aoqi@0: @Override
aoqi@0: public String toString() {
aoqi@0: return "MissingArchive[" + zipFileName + "]";
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /** A directory of zip files already opened.
aoqi@0: */
aoqi@0: Map archives = new HashMap();
aoqi@0:
aoqi@0: private static final String[] symbolFileLocation = { "lib", "ct.sym" };
aoqi@0: private static final RelativeDirectory symbolFilePrefix
aoqi@0: = new RelativeDirectory("META-INF/sym/rt.jar/");
aoqi@0:
aoqi@0: /*
aoqi@0: * This method looks for a ZipFormatException and takes appropriate
aoqi@0: * evasive action. If there is a failure in the fast mode then we
aoqi@0: * fail over to the platform zip, and allow it to deal with a potentially
aoqi@0: * non compliant zip file.
aoqi@0: */
aoqi@0: protected Archive openArchive(File zipFilename) throws IOException {
aoqi@0: try {
aoqi@0: return openArchive(zipFilename, contextUseOptimizedZip);
aoqi@0: } catch (IOException ioe) {
aoqi@0: if (ioe instanceof ZipFileIndex.ZipFormatException) {
aoqi@0: return openArchive(zipFilename, false);
aoqi@0: } else {
aoqi@0: throw ioe;
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /** Open a new zip file directory, and cache it.
aoqi@0: */
aoqi@0: private Archive openArchive(File zipFileName, boolean useOptimizedZip) throws IOException {
aoqi@0: File origZipFileName = zipFileName;
aoqi@0: if (symbolFileEnabled && locations.isDefaultBootClassPathRtJar(zipFileName)) {
aoqi@0: File file = zipFileName.getParentFile().getParentFile(); // ${java.home}
aoqi@0: if (new File(file.getName()).equals(new File("jre")))
aoqi@0: file = file.getParentFile();
aoqi@0: // file == ${jdk.home}
aoqi@0: for (String name : symbolFileLocation)
aoqi@0: file = new File(file, name);
aoqi@0: // file == ${jdk.home}/lib/ct.sym
aoqi@0: if (file.exists())
aoqi@0: zipFileName = file;
aoqi@0: }
aoqi@0:
aoqi@0: Archive archive;
aoqi@0: try {
aoqi@0:
aoqi@0: ZipFile zdir = null;
aoqi@0:
aoqi@0: boolean usePreindexedCache = false;
aoqi@0: String preindexCacheLocation = null;
aoqi@0:
aoqi@0: if (!useOptimizedZip) {
aoqi@0: zdir = new ZipFile(zipFileName);
aoqi@0: } else {
aoqi@0: usePreindexedCache = options.isSet("usezipindex");
aoqi@0: preindexCacheLocation = options.get("java.io.tmpdir");
aoqi@0: String optCacheLoc = options.get("cachezipindexdir");
aoqi@0:
aoqi@0: if (optCacheLoc != null && optCacheLoc.length() != 0) {
aoqi@0: if (optCacheLoc.startsWith("\"")) {
aoqi@0: if (optCacheLoc.endsWith("\"")) {
aoqi@0: optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1);
aoqi@0: }
aoqi@0: else {
aoqi@0: optCacheLoc = optCacheLoc.substring(1);
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: File cacheDir = new File(optCacheLoc);
aoqi@0: if (cacheDir.exists() && cacheDir.canWrite()) {
aoqi@0: preindexCacheLocation = optCacheLoc;
aoqi@0: if (!preindexCacheLocation.endsWith("/") &&
aoqi@0: !preindexCacheLocation.endsWith(File.separator)) {
aoqi@0: preindexCacheLocation += File.separator;
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: if (origZipFileName == zipFileName) {
aoqi@0: if (!useOptimizedZip) {
aoqi@0: archive = new ZipArchive(this, zdir);
aoqi@0: } else {
aoqi@0: archive = new ZipFileIndexArchive(this,
aoqi@0: zipFileIndexCache.getZipFileIndex(zipFileName,
aoqi@0: null,
aoqi@0: usePreindexedCache,
aoqi@0: preindexCacheLocation,
aoqi@0: options.isSet("writezipindexfiles")));
aoqi@0: }
aoqi@0: } else {
aoqi@0: if (!useOptimizedZip) {
aoqi@0: archive = new SymbolArchive(this, origZipFileName, zdir, symbolFilePrefix);
aoqi@0: } else {
aoqi@0: archive = new ZipFileIndexArchive(this,
aoqi@0: zipFileIndexCache.getZipFileIndex(zipFileName,
aoqi@0: symbolFilePrefix,
aoqi@0: usePreindexedCache,
aoqi@0: preindexCacheLocation,
aoqi@0: options.isSet("writezipindexfiles")));
aoqi@0: }
aoqi@0: }
aoqi@0: } catch (FileNotFoundException ex) {
aoqi@0: archive = new MissingArchive(zipFileName);
aoqi@0: } catch (ZipFileIndex.ZipFormatException zfe) {
aoqi@0: throw zfe;
aoqi@0: } catch (IOException ex) {
aoqi@0: if (zipFileName.exists())
aoqi@0: log.error("error.reading.file", zipFileName, getMessage(ex));
aoqi@0: archive = new MissingArchive(zipFileName);
aoqi@0: }
aoqi@0:
aoqi@0: archives.put(origZipFileName, archive);
aoqi@0: return archive;
aoqi@0: }
aoqi@0:
aoqi@0: /** Flush any output resources.
aoqi@0: */
aoqi@0: public void flush() {
aoqi@0: contentCache.clear();
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Close the JavaFileManager, releasing resources.
aoqi@0: */
aoqi@0: public void close() {
aoqi@0: for (Iterator i = archives.values().iterator(); i.hasNext(); ) {
aoqi@0: Archive a = i.next();
aoqi@0: i.remove();
aoqi@0: try {
aoqi@0: a.close();
aoqi@0: } catch (IOException e) {
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: private String defaultEncodingName;
aoqi@0: private String getDefaultEncodingName() {
aoqi@0: if (defaultEncodingName == null) {
aoqi@0: defaultEncodingName =
aoqi@0: new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding();
aoqi@0: }
aoqi@0: return defaultEncodingName;
aoqi@0: }
aoqi@0:
aoqi@0: public ClassLoader getClassLoader(Location location) {
aoqi@0: nullCheck(location);
aoqi@0: Iterable extends File> path = getLocation(location);
aoqi@0: if (path == null)
aoqi@0: return null;
aoqi@0: ListBuffer lb = new ListBuffer();
aoqi@0: for (File f: path) {
aoqi@0: try {
aoqi@0: lb.append(f.toURI().toURL());
aoqi@0: } catch (MalformedURLException e) {
aoqi@0: throw new AssertionError(e);
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: return getClassLoader(lb.toArray(new URL[lb.size()]));
aoqi@0: }
aoqi@0:
aoqi@0: public Iterable list(Location location,
aoqi@0: String packageName,
aoqi@0: Set kinds,
aoqi@0: boolean recurse)
aoqi@0: throws IOException
aoqi@0: {
aoqi@0: // validatePackageName(packageName);
aoqi@0: nullCheck(packageName);
aoqi@0: nullCheck(kinds);
aoqi@0:
aoqi@0: Iterable extends File> path = getLocation(location);
aoqi@0: if (path == null)
aoqi@0: return List.nil();
aoqi@0: RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
aoqi@0: ListBuffer results = new ListBuffer();
aoqi@0:
aoqi@0: for (File directory : path)
aoqi@0: listContainer(directory, subdirectory, kinds, recurse, results);
aoqi@0: return results.toList();
aoqi@0: }
aoqi@0:
aoqi@0: public String inferBinaryName(Location location, JavaFileObject file) {
aoqi@0: file.getClass(); // null check
aoqi@0: location.getClass(); // null check
aoqi@0: // Need to match the path semantics of list(location, ...)
aoqi@0: Iterable extends File> path = getLocation(location);
aoqi@0: if (path == null) {
aoqi@0: return null;
aoqi@0: }
aoqi@0:
aoqi@0: if (file instanceof BaseFileObject) {
aoqi@0: return ((BaseFileObject) file).inferBinaryName(path);
aoqi@0: } else
aoqi@0: throw new IllegalArgumentException(file.getClass().getName());
aoqi@0: }
aoqi@0:
aoqi@0: public boolean isSameFile(FileObject a, FileObject b) {
aoqi@0: nullCheck(a);
aoqi@0: nullCheck(b);
aoqi@0: if (!(a instanceof BaseFileObject))
aoqi@0: throw new IllegalArgumentException("Not supported: " + a);
aoqi@0: if (!(b instanceof BaseFileObject))
aoqi@0: throw new IllegalArgumentException("Not supported: " + b);
aoqi@0: return a.equals(b);
aoqi@0: }
aoqi@0:
aoqi@0: public boolean hasLocation(Location location) {
aoqi@0: return getLocation(location) != null;
aoqi@0: }
aoqi@0:
aoqi@0: public JavaFileObject getJavaFileForInput(Location location,
aoqi@0: String className,
aoqi@0: JavaFileObject.Kind kind)
aoqi@0: throws IOException
aoqi@0: {
aoqi@0: nullCheck(location);
aoqi@0: // validateClassName(className);
aoqi@0: nullCheck(className);
aoqi@0: nullCheck(kind);
aoqi@0: if (!sourceOrClass.contains(kind))
aoqi@0: throw new IllegalArgumentException("Invalid kind: " + kind);
aoqi@0: return getFileForInput(location, RelativeFile.forClass(className, kind));
aoqi@0: }
aoqi@0:
aoqi@0: public FileObject getFileForInput(Location location,
aoqi@0: String packageName,
aoqi@0: String relativeName)
aoqi@0: throws IOException
aoqi@0: {
aoqi@0: nullCheck(location);
aoqi@0: // validatePackageName(packageName);
aoqi@0: nullCheck(packageName);
aoqi@0: if (!isRelativeUri(relativeName))
aoqi@0: throw new IllegalArgumentException("Invalid relative name: " + relativeName);
aoqi@0: RelativeFile name = packageName.length() == 0
aoqi@0: ? new RelativeFile(relativeName)
aoqi@0: : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
aoqi@0: return getFileForInput(location, name);
aoqi@0: }
aoqi@0:
aoqi@0: private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
aoqi@0: Iterable extends File> path = getLocation(location);
aoqi@0: if (path == null)
aoqi@0: return null;
aoqi@0:
aoqi@0: for (File dir: path) {
aoqi@0: Archive a = archives.get(dir);
aoqi@0: if (a == null) {
aoqi@0: if (fsInfo.isDirectory(dir)) {
aoqi@0: File f = name.getFile(dir);
aoqi@0: if (f.exists())
aoqi@0: return new RegularFileObject(this, f);
aoqi@0: continue;
aoqi@0: }
aoqi@0: // Not a directory, create the archive
aoqi@0: a = openArchive(dir);
aoqi@0: }
aoqi@0: // Process the archive
aoqi@0: if (a.contains(name)) {
aoqi@0: return a.getFileObject(name.dirname(), name.basename());
aoqi@0: }
aoqi@0: }
aoqi@0: return null;
aoqi@0: }
aoqi@0:
aoqi@0: public JavaFileObject getJavaFileForOutput(Location location,
aoqi@0: String className,
aoqi@0: JavaFileObject.Kind kind,
aoqi@0: FileObject sibling)
aoqi@0: throws IOException
aoqi@0: {
aoqi@0: nullCheck(location);
aoqi@0: // validateClassName(className);
aoqi@0: nullCheck(className);
aoqi@0: nullCheck(kind);
aoqi@0: if (!sourceOrClass.contains(kind))
aoqi@0: throw new IllegalArgumentException("Invalid kind: " + kind);
aoqi@0: return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
aoqi@0: }
aoqi@0:
aoqi@0: public FileObject getFileForOutput(Location location,
aoqi@0: String packageName,
aoqi@0: String relativeName,
aoqi@0: FileObject sibling)
aoqi@0: throws IOException
aoqi@0: {
aoqi@0: nullCheck(location);
aoqi@0: // validatePackageName(packageName);
aoqi@0: nullCheck(packageName);
aoqi@0: if (!isRelativeUri(relativeName))
aoqi@0: throw new IllegalArgumentException("Invalid relative name: " + relativeName);
aoqi@0: RelativeFile name = packageName.length() == 0
aoqi@0: ? new RelativeFile(relativeName)
aoqi@0: : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
aoqi@0: return getFileForOutput(location, name, sibling);
aoqi@0: }
aoqi@0:
aoqi@0: private JavaFileObject getFileForOutput(Location location,
aoqi@0: RelativeFile fileName,
aoqi@0: FileObject sibling)
aoqi@0: throws IOException
aoqi@0: {
aoqi@0: File dir;
aoqi@0: if (location == CLASS_OUTPUT) {
aoqi@0: if (getClassOutDir() != null) {
aoqi@0: dir = getClassOutDir();
aoqi@0: } else {
aoqi@0: File siblingDir = null;
aoqi@0: if (sibling != null && sibling instanceof RegularFileObject) {
aoqi@0: siblingDir = ((RegularFileObject)sibling).file.getParentFile();
aoqi@0: }
aoqi@0: return new RegularFileObject(this, new File(siblingDir, fileName.basename()));
aoqi@0: }
aoqi@0: } else if (location == SOURCE_OUTPUT) {
aoqi@0: dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
aoqi@0: } else {
aoqi@0: Iterable extends File> path = locations.getLocation(location);
aoqi@0: dir = null;
aoqi@0: for (File f: path) {
aoqi@0: dir = f;
aoqi@0: break;
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: File file = fileName.getFile(dir); // null-safe
aoqi@0: return new RegularFileObject(this, file);
aoqi@0:
aoqi@0: }
aoqi@0:
aoqi@0: public Iterable extends JavaFileObject> getJavaFileObjectsFromFiles(
aoqi@0: Iterable extends File> files)
aoqi@0: {
aoqi@0: ArrayList result;
aoqi@0: if (files instanceof Collection>)
aoqi@0: result = new ArrayList(((Collection>)files).size());
aoqi@0: else
aoqi@0: result = new ArrayList();
aoqi@0: for (File f: files)
aoqi@0: result.add(new RegularFileObject(this, nullCheck(f)));
aoqi@0: return result;
aoqi@0: }
aoqi@0:
aoqi@0: public Iterable extends JavaFileObject> getJavaFileObjects(File... files) {
aoqi@0: return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
aoqi@0: }
aoqi@0:
aoqi@0: public void setLocation(Location location,
aoqi@0: Iterable extends File> path)
aoqi@0: throws IOException
aoqi@0: {
aoqi@0: nullCheck(location);
aoqi@0: locations.setLocation(location, path);
aoqi@0: }
aoqi@0:
aoqi@0: public Iterable extends File> getLocation(Location location) {
aoqi@0: nullCheck(location);
aoqi@0: return locations.getLocation(location);
aoqi@0: }
aoqi@0:
aoqi@0: private File getClassOutDir() {
aoqi@0: return locations.getOutputLocation(CLASS_OUTPUT);
aoqi@0: }
aoqi@0:
aoqi@0: private File getSourceOutDir() {
aoqi@0: return locations.getOutputLocation(SOURCE_OUTPUT);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Enforces the specification of a "relative" name as used in
aoqi@0: * {@linkplain #getFileForInput(Location,String,String)
aoqi@0: * getFileForInput}. This method must follow the rules defined in
aoqi@0: * that method, do not make any changes without consulting the
aoqi@0: * specification.
aoqi@0: */
aoqi@0: protected static boolean isRelativeUri(URI uri) {
aoqi@0: if (uri.isAbsolute())
aoqi@0: return false;
aoqi@0: String path = uri.normalize().getPath();
aoqi@0: if (path.length() == 0 /* isEmpty() is mustang API */)
aoqi@0: return false;
aoqi@0: if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
aoqi@0: return false;
aoqi@0: if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
aoqi@0: return false;
aoqi@0: return true;
aoqi@0: }
aoqi@0:
aoqi@0: // Convenience method
aoqi@0: protected static boolean isRelativeUri(String u) {
aoqi@0: try {
aoqi@0: return isRelativeUri(new URI(u));
aoqi@0: } catch (URISyntaxException e) {
aoqi@0: return false;
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Converts a relative file name to a relative URI. This is
aoqi@0: * different from File.toURI as this method does not canonicalize
aoqi@0: * the file before creating the URI. Furthermore, no schema is
aoqi@0: * used.
aoqi@0: * @param file a relative file name
aoqi@0: * @return a relative URI
aoqi@0: * @throws IllegalArgumentException if the file name is not
aoqi@0: * relative according to the definition given in {@link
aoqi@0: * javax.tools.JavaFileManager#getFileForInput}
aoqi@0: */
aoqi@0: public static String getRelativeName(File file) {
aoqi@0: if (!file.isAbsolute()) {
aoqi@0: String result = file.getPath().replace(File.separatorChar, '/');
aoqi@0: if (isRelativeUri(result))
aoqi@0: return result;
aoqi@0: }
aoqi@0: throw new IllegalArgumentException("Invalid relative path: " + file);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Get a detail message from an IOException.
aoqi@0: * Most, but not all, instances of IOException provide a non-null result
aoqi@0: * for getLocalizedMessage(). But some instances return null: in these
aoqi@0: * cases, fallover to getMessage(), and if even that is null, return the
aoqi@0: * name of the exception itself.
aoqi@0: * @param e an IOException
aoqi@0: * @return a string to include in a compiler diagnostic
aoqi@0: */
aoqi@0: public static String getMessage(IOException e) {
aoqi@0: String s = e.getLocalizedMessage();
aoqi@0: if (s != null)
aoqi@0: return s;
aoqi@0: s = e.getMessage();
aoqi@0: if (s != null)
aoqi@0: return s;
aoqi@0: return e.toString();
aoqi@0: }
aoqi@0: }