duke@1: /* xdono@404: * Copyright 2005-2009 Sun Microsystems, Inc. All Rights Reserved. duke@1: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. duke@1: * duke@1: * This code is free software; you can redistribute it and/or modify it duke@1: * under the terms of the GNU General Public License version 2 only, as duke@1: * published by the Free Software Foundation. Sun designates this duke@1: * particular file as subject to the "Classpath" exception as provided duke@1: * by Sun in the LICENSE file that accompanied this code. duke@1: * duke@1: * This code is distributed in the hope that it will be useful, but WITHOUT duke@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or duke@1: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License duke@1: * version 2 for more details (a copy is included in the LICENSE file that duke@1: * accompanied this code). duke@1: * duke@1: * You should have received a copy of the GNU General Public License version duke@1: * 2 along with this work; if not, write to the Free Software Foundation, duke@1: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. duke@1: * duke@1: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, duke@1: * CA 95054 USA or visit www.sun.com if you need additional information or duke@1: * have any questions. duke@1: */ duke@1: jjg@50: package com.sun.tools.javac.file; duke@1: duke@1: import java.io.ByteArrayOutputStream; duke@1: import java.io.File; duke@1: import java.io.FileNotFoundException; duke@1: import java.io.IOException; duke@1: import java.io.OutputStreamWriter; duke@1: import java.net.MalformedURLException; duke@1: import java.net.URI; jjg@400: import java.net.URISyntaxException; duke@1: import java.net.URL; duke@1: import java.nio.CharBuffer; duke@1: import java.nio.charset.Charset; duke@1: import java.util.ArrayList; duke@1: import java.util.Arrays; duke@1: import java.util.Collection; duke@1: import java.util.Collections; duke@1: import java.util.EnumSet; duke@1: import java.util.HashMap; duke@1: import java.util.Iterator; duke@1: import java.util.Map; duke@1: import java.util.Set; duke@1: import java.util.zip.ZipFile; duke@1: duke@1: import javax.lang.model.SourceVersion; duke@1: import javax.tools.FileObject; duke@1: import javax.tools.JavaFileManager; duke@1: import javax.tools.JavaFileObject; jjg@50: import javax.tools.StandardJavaFileManager; duke@1: jjg@103: import com.sun.tools.javac.file.RelativePath.RelativeFile; jjg@103: import com.sun.tools.javac.file.RelativePath.RelativeDirectory; jjg@50: import com.sun.tools.javac.main.OptionName; jjg@450: import com.sun.tools.javac.util.BaseFileManager; jjg@50: import com.sun.tools.javac.util.Context; jjg@50: import com.sun.tools.javac.util.List; jjg@50: import com.sun.tools.javac.util.ListBuffer; duke@1: jjg@103: import static javax.tools.StandardLocation.*; duke@1: import static com.sun.tools.javac.main.OptionName.*; duke@1: duke@1: /** duke@1: * This class provides access to the source, class and other files duke@1: * used by the compiler and related tools. jjg@333: * jjg@333: *

This is NOT part of any API supported by Sun Microsystems. jjg@333: * If you write code that depends on this, you do so at your own risk. jjg@333: * This code and its internal interfaces are subject to change or jjg@333: * deletion without notice. duke@1: */ jjg@450: public class JavacFileManager extends BaseFileManager implements StandardJavaFileManager { duke@1: duke@1: boolean useZipFileIndex; duke@1: duke@1: public static char[] toArray(CharBuffer buffer) { duke@1: if (buffer.hasArray()) duke@1: return ((CharBuffer)buffer.compact().flip()).array(); duke@1: else duke@1: return buffer.toString().toCharArray(); duke@1: } duke@1: duke@1: /** Encapsulates knowledge of paths duke@1: */ duke@1: private Paths paths; duke@1: jjg@106: private FSInfo fsInfo; jjg@106: duke@1: private final File uninited = new File("U N I N I T E D"); duke@1: duke@1: private final Set sourceOrClass = duke@1: EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS); duke@1: duke@1: /** The standard output directory, primarily used for classes. duke@1: * Initialized by the "-d" option. duke@1: * If classOutDir = null, files are written into same directory as the sources duke@1: * they were generated from. duke@1: */ duke@1: private File classOutDir = uninited; duke@1: duke@1: /** The output directory, used when generating sources while processing annotations. duke@1: * Initialized by the "-s" option. duke@1: */ duke@1: private File sourceOutDir = uninited; duke@1: duke@1: protected boolean mmappedIO; duke@1: protected boolean ignoreSymbolFile; duke@1: duke@1: /** duke@1: * Register a Context.Factory to create a JavacFileManager. duke@1: */ duke@1: public static void preRegister(final Context context) { duke@1: context.put(JavaFileManager.class, new Context.Factory() { duke@1: public JavaFileManager make() { duke@1: return new JavacFileManager(context, true, null); duke@1: } duke@1: }); duke@1: } duke@1: duke@1: /** duke@1: * Create a JavacFileManager using a given context, optionally registering duke@1: * it as the JavaFileManager for that context. duke@1: */ duke@1: public JavacFileManager(Context context, boolean register, Charset charset) { jjg@450: super(charset); duke@1: if (register) duke@1: context.put(JavaFileManager.class, this); duke@1: setContext(context); duke@1: } duke@1: duke@1: /** duke@1: * Set the context for JavacFileManager. duke@1: */ jjg@450: @Override duke@1: public void setContext(Context context) { jjg@450: super.setContext(context); duke@1: if (paths == null) { duke@1: paths = Paths.instance(context); duke@1: } else { duke@1: // Reuse the Paths object as it stores the locations that duke@1: // have been set with setLocation, etc. duke@1: paths.setContext(context); duke@1: } duke@1: jjg@106: fsInfo = FSInfo.instance(context); duke@1: duke@1: useZipFileIndex = System.getProperty("useJavaUtilZip") == null;// TODO: options.get("useJavaUtilZip") == null; duke@1: duke@1: mmappedIO = options.get("mmappedIO") != null; duke@1: ignoreSymbolFile = options.get("ignore.symbol.file") != null; duke@1: } duke@1: duke@1: public JavaFileObject getFileForInput(String name) { duke@1: return getRegularFile(new File(name)); duke@1: } duke@1: duke@1: public JavaFileObject getRegularFile(File file) { jjg@57: return new RegularFileObject(this, file); duke@1: } duke@1: duke@1: public JavaFileObject getFileForOutput(String classname, duke@1: JavaFileObject.Kind kind, duke@1: JavaFileObject sibling) duke@1: throws IOException duke@1: { duke@1: return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling); duke@1: } duke@1: duke@1: public Iterable getJavaFileObjectsFromStrings(Iterable names) { duke@1: ListBuffer files = new ListBuffer(); duke@1: for (String name : names) duke@1: files.append(new File(nullCheck(name))); duke@1: return getJavaFileObjectsFromFiles(files.toList()); duke@1: } duke@1: duke@1: public Iterable getJavaFileObjects(String... names) { duke@1: return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names))); duke@1: } duke@1: duke@1: private static boolean isValidName(String name) { duke@1: // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ), duke@1: // but the set of keywords depends on the source level, and we don't want duke@1: // impls of JavaFileManager to have to be dependent on the source level. duke@1: // Therefore we simply check that the argument is a sequence of identifiers duke@1: // separated by ".". duke@1: for (String s : name.split("\\.", -1)) { duke@1: if (!SourceVersion.isIdentifier(s)) duke@1: return false; duke@1: } duke@1: return true; duke@1: } duke@1: duke@1: private static void validateClassName(String className) { duke@1: if (!isValidName(className)) duke@1: throw new IllegalArgumentException("Invalid class name: " + className); duke@1: } duke@1: duke@1: private static void validatePackageName(String packageName) { duke@1: if (packageName.length() > 0 && !isValidName(packageName)) duke@1: throw new IllegalArgumentException("Invalid packageName name: " + packageName); duke@1: } duke@1: duke@1: public static void testName(String name, duke@1: boolean isValidPackageName, duke@1: boolean isValidClassName) duke@1: { duke@1: try { duke@1: validatePackageName(name); duke@1: if (!isValidPackageName) duke@1: throw new AssertionError("Invalid package name accepted: " + name); duke@1: printAscii("Valid package name: \"%s\"", name); duke@1: } catch (IllegalArgumentException e) { duke@1: if (isValidPackageName) duke@1: throw new AssertionError("Valid package name rejected: " + name); duke@1: printAscii("Invalid package name: \"%s\"", name); duke@1: } duke@1: try { duke@1: validateClassName(name); duke@1: if (!isValidClassName) duke@1: throw new AssertionError("Invalid class name accepted: " + name); duke@1: printAscii("Valid class name: \"%s\"", name); duke@1: } catch (IllegalArgumentException e) { duke@1: if (isValidClassName) duke@1: throw new AssertionError("Valid class name rejected: " + name); duke@1: printAscii("Invalid class name: \"%s\"", name); duke@1: } duke@1: } jjg@103: duke@1: private static void printAscii(String format, Object... args) { duke@1: String message; duke@1: try { duke@1: final String ascii = "US-ASCII"; duke@1: message = new String(String.format(null, format, args).getBytes(ascii), ascii); duke@1: } catch (java.io.UnsupportedEncodingException ex) { duke@1: throw new AssertionError(ex); duke@1: } duke@1: System.out.println(message); duke@1: } duke@1: duke@1: /** duke@1: * Insert all files in subdirectory `subdirectory' of `directory' which end duke@1: * in one of the extensions in `extensions' into packageSym. duke@1: */ duke@1: private void listDirectory(File directory, jjg@103: RelativeDirectory subdirectory, duke@1: Set fileKinds, duke@1: boolean recurse, duke@1: ListBuffer l) { duke@1: Archive archive = archives.get(directory); duke@1: jjg@106: boolean isFile = fsInfo.isFile(directory); duke@1: duke@1: if (archive != null || isFile) { duke@1: if (archive == null) { duke@1: try { duke@1: archive = openArchive(directory); duke@1: } catch (IOException ex) { duke@1: log.error("error.reading.file", duke@1: directory, ex.getLocalizedMessage()); duke@1: return; duke@1: } duke@1: } duke@1: duke@1: List files = archive.getFiles(subdirectory); duke@1: if (files != null) { duke@1: for (String file; !files.isEmpty(); files = files.tail) { duke@1: file = files.head; duke@1: if (isValidFile(file, fileKinds)) { duke@1: l.append(archive.getFileObject(subdirectory, file)); duke@1: } duke@1: } duke@1: } duke@1: if (recurse) { jjg@103: for (RelativeDirectory s: archive.getSubdirectories()) { jjg@103: if (subdirectory.contains(s)) { duke@1: // Because the archive map is a flat list of directories, duke@1: // the enclosing loop will pick up all child subdirectories. duke@1: // Therefore, there is no need to recurse deeper. duke@1: listDirectory(directory, s, fileKinds, false, l); duke@1: } duke@1: } duke@1: } duke@1: } else { jjg@103: File d = subdirectory.getFile(directory); duke@1: if (!caseMapCheck(d, subdirectory)) duke@1: return; duke@1: duke@1: File[] files = d.listFiles(); duke@1: if (files == null) duke@1: return; duke@1: duke@1: for (File f: files) { duke@1: String fname = f.getName(); duke@1: if (f.isDirectory()) { duke@1: if (recurse && SourceVersion.isIdentifier(fname)) { duke@1: listDirectory(directory, jjg@103: new RelativeDirectory(subdirectory, fname), duke@1: fileKinds, duke@1: recurse, duke@1: l); duke@1: } duke@1: } else { duke@1: if (isValidFile(fname, fileKinds)) { duke@1: JavaFileObject fe = jjg@57: new RegularFileObject(this, fname, new File(d, fname)); duke@1: l.append(fe); duke@1: } duke@1: } duke@1: } duke@1: } duke@1: } duke@1: duke@1: private boolean isValidFile(String s, Set fileKinds) { jjg@450: JavaFileObject.Kind kind = getKind(s); duke@1: return fileKinds.contains(kind); duke@1: } duke@1: duke@1: private static final boolean fileSystemIsCaseSensitive = duke@1: File.separatorChar == '/'; duke@1: duke@1: /** Hack to make Windows case sensitive. Test whether given path duke@1: * ends in a string of characters with the same case as given name. duke@1: * Ignore file separators in both path and name. duke@1: */ jjg@103: private boolean caseMapCheck(File f, RelativePath name) { duke@1: if (fileSystemIsCaseSensitive) return true; duke@1: // Note that getCanonicalPath() returns the case-sensitive duke@1: // spelled file name. duke@1: String path; duke@1: try { duke@1: path = f.getCanonicalPath(); duke@1: } catch (IOException ex) { duke@1: return false; duke@1: } duke@1: char[] pcs = path.toCharArray(); jjg@103: char[] ncs = name.path.toCharArray(); duke@1: int i = pcs.length - 1; duke@1: int j = ncs.length - 1; duke@1: while (i >= 0 && j >= 0) { duke@1: while (i >= 0 && pcs[i] == File.separatorChar) i--; jjg@103: while (j >= 0 && ncs[j] == '/') j--; duke@1: if (i >= 0 && j >= 0) { duke@1: if (pcs[i] != ncs[j]) return false; duke@1: i--; duke@1: j--; duke@1: } duke@1: } duke@1: return j < 0; duke@1: } duke@1: duke@1: /** duke@1: * An archive provides a flat directory structure of a ZipFile by duke@1: * mapping directory names to lists of files (basenames). duke@1: */ duke@1: public interface Archive { duke@1: void close() throws IOException; duke@1: jjg@103: boolean contains(RelativePath name); duke@1: jjg@103: JavaFileObject getFileObject(RelativeDirectory subdirectory, String file); duke@1: jjg@103: List getFiles(RelativeDirectory subdirectory); duke@1: jjg@103: Set getSubdirectories(); duke@1: } duke@1: duke@1: public class MissingArchive implements Archive { duke@1: final File zipFileName; duke@1: public MissingArchive(File name) { duke@1: zipFileName = name; duke@1: } jjg@103: public boolean contains(RelativePath name) { jjg@57: return false; duke@1: } duke@1: duke@1: public void close() { duke@1: } duke@1: jjg@103: public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) { duke@1: return null; duke@1: } duke@1: jjg@103: public List getFiles(RelativeDirectory subdirectory) { duke@1: return List.nil(); duke@1: } duke@1: jjg@103: public Set getSubdirectories() { duke@1: return Collections.emptySet(); duke@1: } jjg@103: jjg@400: @Override jjg@103: public String toString() { jjg@103: return "MissingArchive[" + zipFileName + "]"; jjg@103: } duke@1: } duke@1: duke@1: /** A directory of zip files already opened. duke@1: */ duke@1: Map archives = new HashMap(); duke@1: jjg@103: private static final String[] symbolFileLocation = { "lib", "ct.sym" }; jjg@103: private static final RelativeDirectory symbolFilePrefix jjg@103: = new RelativeDirectory("META-INF/sym/rt.jar/"); jjg@103: duke@1: /** Open a new zip file directory. duke@1: */ duke@1: protected Archive openArchive(File zipFileName) throws IOException { duke@1: Archive archive = archives.get(zipFileName); duke@1: if (archive == null) { duke@1: File origZipFileName = zipFileName; duke@1: if (!ignoreSymbolFile && paths.isBootClassPathRtJar(zipFileName)) { duke@1: File file = zipFileName.getParentFile().getParentFile(); // ${java.home} duke@1: if (new File(file.getName()).equals(new File("jre"))) duke@1: file = file.getParentFile(); duke@1: // file == ${jdk.home} duke@1: for (String name : symbolFileLocation) duke@1: file = new File(file, name); duke@1: // file == ${jdk.home}/lib/ct.sym duke@1: if (file.exists()) duke@1: zipFileName = file; duke@1: } duke@1: duke@1: try { duke@1: duke@1: ZipFile zdir = null; duke@1: duke@1: boolean usePreindexedCache = false; duke@1: String preindexCacheLocation = null; duke@1: duke@1: if (!useZipFileIndex) { duke@1: zdir = new ZipFile(zipFileName); duke@1: } duke@1: else { duke@1: usePreindexedCache = options.get("usezipindex") != null; duke@1: preindexCacheLocation = options.get("java.io.tmpdir"); duke@1: String optCacheLoc = options.get("cachezipindexdir"); duke@1: duke@1: if (optCacheLoc != null && optCacheLoc.length() != 0) { duke@1: if (optCacheLoc.startsWith("\"")) { duke@1: if (optCacheLoc.endsWith("\"")) { duke@1: optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1); duke@1: } duke@1: else { duke@1: optCacheLoc = optCacheLoc.substring(1); duke@1: } duke@1: } duke@1: duke@1: File cacheDir = new File(optCacheLoc); duke@1: if (cacheDir.exists() && cacheDir.canWrite()) { duke@1: preindexCacheLocation = optCacheLoc; duke@1: if (!preindexCacheLocation.endsWith("/") && duke@1: !preindexCacheLocation.endsWith(File.separator)) { duke@1: preindexCacheLocation += File.separator; duke@1: } duke@1: } duke@1: } duke@1: } duke@1: duke@1: if (origZipFileName == zipFileName) { duke@1: if (!useZipFileIndex) { jjg@57: archive = new ZipArchive(this, zdir); duke@1: } else { jjg@103: archive = new ZipFileIndexArchive(this, jjg@103: ZipFileIndex.getZipFileIndex(zipFileName, jjg@103: null, jjg@103: usePreindexedCache, jjg@103: preindexCacheLocation, jjg@103: options.get("writezipindexfiles") != null)); duke@1: } duke@1: } duke@1: else { duke@1: if (!useZipFileIndex) { jjg@57: archive = new SymbolArchive(this, origZipFileName, zdir, symbolFilePrefix); duke@1: } duke@1: else { jjg@57: archive = new ZipFileIndexArchive(this, jjg@57: ZipFileIndex.getZipFileIndex(zipFileName, jjg@103: symbolFilePrefix, jjg@103: usePreindexedCache, jjg@103: preindexCacheLocation, jjg@103: options.get("writezipindexfiles") != null)); duke@1: } duke@1: } duke@1: } catch (FileNotFoundException ex) { duke@1: archive = new MissingArchive(zipFileName); duke@1: } catch (IOException ex) { jjg@56: if (zipFileName.exists()) jjg@56: log.error("error.reading.file", zipFileName, ex.getLocalizedMessage()); duke@1: archive = new MissingArchive(zipFileName); duke@1: } duke@1: duke@1: archives.put(origZipFileName, archive); duke@1: } duke@1: return archive; duke@1: } duke@1: duke@1: /** Flush any output resources. duke@1: */ duke@1: public void flush() { duke@1: contentCache.clear(); duke@1: } duke@1: duke@1: /** duke@1: * Close the JavaFileManager, releasing resources. duke@1: */ duke@1: public void close() { duke@1: for (Iterator i = archives.values().iterator(); i.hasNext(); ) { duke@1: Archive a = i.next(); duke@1: i.remove(); duke@1: try { duke@1: a.close(); duke@1: } catch (IOException e) { duke@1: } duke@1: } duke@1: } duke@1: duke@1: private String defaultEncodingName; duke@1: private String getDefaultEncodingName() { duke@1: if (defaultEncodingName == null) { duke@1: defaultEncodingName = duke@1: new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding(); duke@1: } duke@1: return defaultEncodingName; duke@1: } duke@1: duke@1: public ClassLoader getClassLoader(Location location) { duke@1: nullCheck(location); duke@1: Iterable path = getLocation(location); duke@1: if (path == null) duke@1: return null; duke@1: ListBuffer lb = new ListBuffer(); duke@1: for (File f: path) { duke@1: try { duke@1: lb.append(f.toURI().toURL()); duke@1: } catch (MalformedURLException e) { duke@1: throw new AssertionError(e); duke@1: } duke@1: } jjg@372: jjg@450: return getClassLoader(lb.toArray(new URL[lb.size()])); duke@1: } duke@1: duke@1: public Iterable list(Location location, duke@1: String packageName, duke@1: Set kinds, duke@1: boolean recurse) duke@1: throws IOException duke@1: { duke@1: // validatePackageName(packageName); duke@1: nullCheck(packageName); duke@1: nullCheck(kinds); duke@1: duke@1: Iterable path = getLocation(location); duke@1: if (path == null) duke@1: return List.nil(); jjg@103: RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); duke@1: ListBuffer results = new ListBuffer(); duke@1: duke@1: for (File directory : path) duke@1: listDirectory(directory, subdirectory, kinds, recurse, results); duke@1: duke@1: return results.toList(); duke@1: } duke@1: duke@1: public String inferBinaryName(Location location, JavaFileObject file) { duke@1: file.getClass(); // null check duke@1: location.getClass(); // null check duke@1: // Need to match the path semantics of list(location, ...) duke@1: Iterable path = getLocation(location); duke@1: if (path == null) { duke@1: return null; duke@1: } duke@1: jjg@57: if (file instanceof BaseFileObject) { jjg@57: return ((BaseFileObject) file).inferBinaryName(path); duke@1: } else duke@1: throw new IllegalArgumentException(file.getClass().getName()); duke@1: } duke@1: duke@1: public boolean isSameFile(FileObject a, FileObject b) { duke@1: nullCheck(a); duke@1: nullCheck(b); duke@1: if (!(a instanceof BaseFileObject)) duke@1: throw new IllegalArgumentException("Not supported: " + a); duke@1: if (!(b instanceof BaseFileObject)) duke@1: throw new IllegalArgumentException("Not supported: " + b); duke@1: return a.equals(b); duke@1: } duke@1: duke@1: public boolean hasLocation(Location location) { duke@1: return getLocation(location) != null; duke@1: } duke@1: duke@1: public JavaFileObject getJavaFileForInput(Location location, duke@1: String className, duke@1: JavaFileObject.Kind kind) duke@1: throws IOException duke@1: { duke@1: nullCheck(location); duke@1: // validateClassName(className); duke@1: nullCheck(className); duke@1: nullCheck(kind); duke@1: if (!sourceOrClass.contains(kind)) duke@1: throw new IllegalArgumentException("Invalid kind " + kind); jjg@103: return getFileForInput(location, RelativeFile.forClass(className, kind)); duke@1: } duke@1: duke@1: public FileObject getFileForInput(Location location, duke@1: String packageName, duke@1: String relativeName) duke@1: throws IOException duke@1: { duke@1: nullCheck(location); duke@1: // validatePackageName(packageName); duke@1: nullCheck(packageName); jjg@400: if (!isRelativeUri(relativeName)) duke@1: throw new IllegalArgumentException("Invalid relative name: " + relativeName); jjg@103: RelativeFile name = packageName.length() == 0 jjg@103: ? new RelativeFile(relativeName) jjg@103: : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); duke@1: return getFileForInput(location, name); duke@1: } duke@1: jjg@103: private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { duke@1: Iterable path = getLocation(location); duke@1: if (path == null) duke@1: return null; duke@1: duke@1: for (File dir: path) { duke@1: if (dir.isDirectory()) { jjg@103: File f = name.getFile(dir); duke@1: if (f.exists()) jjg@57: return new RegularFileObject(this, f); duke@1: } else { duke@1: Archive a = openArchive(dir); duke@1: if (a.contains(name)) { jjg@103: return a.getFileObject(name.dirname(), name.basename()); duke@1: } duke@1: duke@1: } duke@1: } jjg@103: duke@1: return null; duke@1: } duke@1: duke@1: public JavaFileObject getJavaFileForOutput(Location location, duke@1: String className, duke@1: JavaFileObject.Kind kind, duke@1: FileObject sibling) duke@1: throws IOException duke@1: { duke@1: nullCheck(location); duke@1: // validateClassName(className); duke@1: nullCheck(className); duke@1: nullCheck(kind); duke@1: if (!sourceOrClass.contains(kind)) duke@1: throw new IllegalArgumentException("Invalid kind " + kind); jjg@103: return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); duke@1: } duke@1: duke@1: public FileObject getFileForOutput(Location location, duke@1: String packageName, duke@1: String relativeName, duke@1: FileObject sibling) duke@1: throws IOException duke@1: { duke@1: nullCheck(location); duke@1: // validatePackageName(packageName); duke@1: nullCheck(packageName); jjg@400: if (!isRelativeUri(relativeName)) duke@1: throw new IllegalArgumentException("relativeName is invalid"); jjg@103: RelativeFile name = packageName.length() == 0 jjg@103: ? new RelativeFile(relativeName) jjg@103: : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); duke@1: return getFileForOutput(location, name, sibling); duke@1: } duke@1: duke@1: private JavaFileObject getFileForOutput(Location location, jjg@103: RelativeFile fileName, duke@1: FileObject sibling) duke@1: throws IOException duke@1: { duke@1: File dir; duke@1: if (location == CLASS_OUTPUT) { duke@1: if (getClassOutDir() != null) { duke@1: dir = getClassOutDir(); duke@1: } else { duke@1: File siblingDir = null; duke@1: if (sibling != null && sibling instanceof RegularFileObject) { jjg@424: siblingDir = ((RegularFileObject)sibling).file.getParentFile(); duke@1: } jjg@103: return new RegularFileObject(this, new File(siblingDir, fileName.basename())); duke@1: } duke@1: } else if (location == SOURCE_OUTPUT) { duke@1: dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); duke@1: } else { duke@1: Iterable path = paths.getPathForLocation(location); duke@1: dir = null; duke@1: for (File f: path) { duke@1: dir = f; duke@1: break; duke@1: } duke@1: } duke@1: jjg@103: File file = fileName.getFile(dir); // null-safe jjg@57: return new RegularFileObject(this, file); duke@1: duke@1: } duke@1: duke@1: public Iterable getJavaFileObjectsFromFiles( duke@1: Iterable files) duke@1: { duke@1: ArrayList result; mcimadamore@184: if (files instanceof Collection) mcimadamore@184: result = new ArrayList(((Collection)files).size()); duke@1: else duke@1: result = new ArrayList(); duke@1: for (File f: files) jjg@57: result.add(new RegularFileObject(this, nullCheck(f))); duke@1: return result; duke@1: } duke@1: duke@1: public Iterable getJavaFileObjects(File... files) { duke@1: return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); duke@1: } duke@1: duke@1: public void setLocation(Location location, duke@1: Iterable path) duke@1: throws IOException duke@1: { duke@1: nullCheck(location); duke@1: paths.lazy(); duke@1: duke@1: final File dir = location.isOutputLocation() ? getOutputDirectory(path) : null; duke@1: duke@1: if (location == CLASS_OUTPUT) duke@1: classOutDir = getOutputLocation(dir, D); duke@1: else if (location == SOURCE_OUTPUT) duke@1: sourceOutDir = getOutputLocation(dir, S); duke@1: else duke@1: paths.setPathForLocation(location, path); duke@1: } duke@1: // where duke@1: private File getOutputDirectory(Iterable path) throws IOException { duke@1: if (path == null) duke@1: return null; duke@1: Iterator pathIter = path.iterator(); duke@1: if (!pathIter.hasNext()) duke@1: throw new IllegalArgumentException("empty path for directory"); duke@1: File dir = pathIter.next(); duke@1: if (pathIter.hasNext()) duke@1: throw new IllegalArgumentException("path too long for directory"); duke@1: if (!dir.exists()) duke@1: throw new FileNotFoundException(dir + ": does not exist"); duke@1: else if (!dir.isDirectory()) duke@1: throw new IOException(dir + ": not a directory"); duke@1: return dir; duke@1: } duke@1: duke@1: private File getOutputLocation(File dir, OptionName defaultOptionName) { duke@1: if (dir != null) duke@1: return dir; duke@1: String arg = options.get(defaultOptionName); duke@1: if (arg == null) duke@1: return null; duke@1: return new File(arg); duke@1: } duke@1: duke@1: public Iterable getLocation(Location location) { duke@1: nullCheck(location); duke@1: paths.lazy(); duke@1: if (location == CLASS_OUTPUT) { duke@1: return (getClassOutDir() == null ? null : List.of(getClassOutDir())); duke@1: } else if (location == SOURCE_OUTPUT) { duke@1: return (getSourceOutDir() == null ? null : List.of(getSourceOutDir())); duke@1: } else duke@1: return paths.getPathForLocation(location); duke@1: } duke@1: duke@1: private File getClassOutDir() { duke@1: if (classOutDir == uninited) duke@1: classOutDir = getOutputLocation(null, D); duke@1: return classOutDir; duke@1: } duke@1: duke@1: private File getSourceOutDir() { duke@1: if (sourceOutDir == uninited) duke@1: sourceOutDir = getOutputLocation(null, S); duke@1: return sourceOutDir; duke@1: } duke@1: duke@1: /** duke@1: * Enforces the specification of a "relative" URI as used in duke@1: * {@linkplain #getFileForInput(Location,String,URI) duke@1: * getFileForInput}. This method must follow the rules defined in duke@1: * that method, do not make any changes without consulting the duke@1: * specification. duke@1: */ duke@1: protected static boolean isRelativeUri(URI uri) { duke@1: if (uri.isAbsolute()) duke@1: return false; duke@1: String path = uri.normalize().getPath(); duke@1: if (path.length() == 0 /* isEmpty() is mustang API */) duke@1: return false; duke@1: char first = path.charAt(0); duke@1: return first != '.' && first != '/'; duke@1: } duke@1: jjg@400: // Convenience method jjg@400: protected static boolean isRelativeUri(String u) { jjg@400: try { jjg@400: return isRelativeUri(new URI(u)); jjg@400: } catch (URISyntaxException e) { jjg@400: return false; jjg@400: } jjg@400: } jjg@400: duke@1: /** duke@1: * Converts a relative file name to a relative URI. This is duke@1: * different from File.toURI as this method does not canonicalize duke@1: * the file before creating the URI. Furthermore, no schema is duke@1: * used. duke@1: * @param file a relative file name duke@1: * @return a relative URI duke@1: * @throws IllegalArgumentException if the file name is not duke@1: * relative according to the definition given in {@link duke@1: * javax.tools.JavaFileManager#getFileForInput} duke@1: */ duke@1: public static String getRelativeName(File file) { duke@1: if (!file.isAbsolute()) { duke@1: String result = file.getPath().replace(File.separatorChar, '/'); jjg@400: if (isRelativeUri(result)) duke@1: return result; duke@1: } duke@1: throw new IllegalArgumentException("Invalid relative path: " + file); duke@1: } duke@1: }