jjg@450: /* ohair@962: * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. jjg@450: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@450: * jjg@450: * This code is free software; you can redistribute it and/or modify it jjg@450: * under the terms of the GNU General Public License version 2 only, as ohair@554: * published by the Free Software Foundation. Oracle designates this jjg@450: * particular file as subject to the "Classpath" exception as provided ohair@554: * by Oracle in the LICENSE file that accompanied this code. jjg@450: * jjg@450: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@450: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@450: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@450: * version 2 for more details (a copy is included in the LICENSE file that jjg@450: * accompanied this code). jjg@450: * jjg@450: * You should have received a copy of the GNU General Public License version jjg@450: * 2 along with this work; if not, write to the Free Software Foundation, jjg@450: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@450: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. jjg@450: */ jjg@450: jjg@450: package com.sun.tools.javac.nio; jjg@450: jjg@450: jjg@450: import java.io.File; jjg@450: import java.io.FileNotFoundException; jjg@450: import java.io.IOException; jjg@450: import java.net.MalformedURLException; jjg@450: import java.net.URL; jjg@450: import java.nio.charset.Charset; jjg@450: import java.nio.file.Files; jjg@450: import java.nio.file.FileSystem; jjg@450: import java.nio.file.FileSystems; jjg@450: import java.nio.file.FileVisitOption; jjg@450: import java.nio.file.FileVisitResult; jjg@450: import java.nio.file.Path; jjg@450: import java.nio.file.SimpleFileVisitor; jjg@450: import java.nio.file.attribute.BasicFileAttributes; jjg@450: import java.util.ArrayList; jjg@450: import java.util.Arrays; jjg@450: import java.util.Collection; jjg@450: import java.util.Collections; jjg@450: import java.util.EnumSet; jjg@450: import java.util.HashMap; jjg@450: import java.util.Iterator; jjg@450: import java.util.LinkedHashSet; jjg@450: import java.util.Map; jjg@450: import java.util.Set; jjg@450: import javax.lang.model.SourceVersion; jjg@450: import javax.tools.FileObject; jjg@450: import javax.tools.JavaFileManager; jjg@450: import javax.tools.JavaFileObject; jjg@450: import javax.tools.JavaFileObject.Kind; jjg@450: import javax.tools.StandardLocation; jjg@450: jjg@450: import static java.nio.file.FileVisitOption.*; jjg@450: import static javax.tools.StandardLocation.*; jjg@450: jjg@450: import com.sun.tools.javac.file.Paths; jjg@450: import com.sun.tools.javac.util.BaseFileManager; jjg@450: import com.sun.tools.javac.util.Context; jjg@450: import com.sun.tools.javac.util.List; jjg@450: import com.sun.tools.javac.util.ListBuffer; jjg@450: jjg@450: import static com.sun.tools.javac.main.OptionName.*; jjg@450: jjg@450: jjg@450: // NOTE the imports carefully for this compilation unit. jjg@450: // jjg@450: // Path: java.nio.file.Path -- the new NIO type for which this file manager exists jjg@450: // jjg@450: // Paths: com.sun.tools.javac.file.Paths -- legacy javac type for handling path options jjg@450: // The other Paths (java.nio.file.Paths) is not used jjg@450: jjg@450: // NOTE this and related classes depend on new API in JDK 7. jjg@450: // This requires special handling while bootstrapping the JDK build, jjg@450: // when these classes might not yet have been compiled. To workaround jjg@450: // this, the build arranges to make stubs of these classes available jjg@450: // when compiling this and related classes. The set of stub files jjg@450: // is specified in make/build.properties. jjg@450: jjg@450: /** jjg@450: * Implementation of PathFileManager: a JavaFileManager based on the use jjg@450: * of java.nio.file.Path. jjg@450: * jjg@450: *

Just as a Path is somewhat analagous to a File, so too is this jjg@450: * JavacPathFileManager analogous to JavacFileManager, as it relates to the jjg@450: * support of FileObjects based on File objects (i.e. just RegularFileObject, jjg@450: * not ZipFileObject and its variants.) jjg@450: * jjg@450: *

The default values for the standard locations supported by this file jjg@450: * manager are the same as the default values provided by JavacFileManager -- jjg@450: * i.e. as determined by the javac.file.Paths class. To override these values, jjg@450: * call {@link #setLocation}. jjg@450: * jjg@450: *

To reduce confusion with Path objects, the locations such as "class path", jjg@450: * "source path", etc, are generically referred to here as "search paths". jjg@450: * jjg@581: *

This is NOT part of any supported API. jjg@581: * If you write code that depends on this, you do so at your own risk. jjg@450: * This code and its internal interfaces are subject to change or jjg@450: * deletion without notice. jjg@450: */ jjg@450: public class JavacPathFileManager extends BaseFileManager implements PathFileManager { jjg@450: protected FileSystem defaultFileSystem; jjg@450: jjg@450: /** jjg@450: * Create a JavacPathFileManager using a given context, optionally registering jjg@450: * it as the JavaFileManager for that context. jjg@450: */ jjg@450: public JavacPathFileManager(Context context, boolean register, Charset charset) { jjg@450: super(charset); jjg@450: if (register) jjg@450: context.put(JavaFileManager.class, this); jjg@450: pathsForLocation = new HashMap(); jjg@450: fileSystems = new HashMap(); jjg@450: setContext(context); jjg@450: } jjg@450: jjg@450: /** jjg@450: * Set the context for JavacPathFileManager. jjg@450: */ jjg@450: @Override jjg@450: protected void setContext(Context context) { jjg@450: super.setContext(context); jjg@450: searchPaths = Paths.instance(context); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public FileSystem getDefaultFileSystem() { jjg@450: if (defaultFileSystem == null) jjg@450: defaultFileSystem = FileSystems.getDefault(); jjg@450: return defaultFileSystem; jjg@450: } jjg@450: jjg@450: @Override jjg@450: public void setDefaultFileSystem(FileSystem fs) { jjg@450: defaultFileSystem = fs; jjg@450: } jjg@450: jjg@450: @Override jjg@450: public void flush() throws IOException { jjg@450: contentCache.clear(); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public void close() throws IOException { jjg@450: for (FileSystem fs: fileSystems.values()) jjg@450: fs.close(); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public ClassLoader getClassLoader(Location location) { jjg@450: nullCheck(location); jjg@450: Iterable path = getLocation(location); jjg@450: if (path == null) jjg@450: return null; jjg@450: ListBuffer lb = new ListBuffer(); jjg@450: for (Path p: path) { jjg@450: try { jjg@450: lb.append(p.toUri().toURL()); jjg@450: } catch (MalformedURLException e) { jjg@450: throw new AssertionError(e); jjg@450: } jjg@450: } jjg@450: jjg@450: return getClassLoader(lb.toArray(new URL[lb.size()])); jjg@450: } jjg@450: jjg@757: @Override jjg@757: public boolean isDefaultBootClassPath() { jjg@757: return searchPaths.isDefaultBootClassPath(); jjg@757: } jjg@757: jjg@450: // jjg@450: jjg@450: public boolean hasLocation(Location location) { jjg@450: return (getLocation(location) != null); jjg@450: } jjg@450: jjg@450: public Iterable getLocation(Location location) { jjg@450: nullCheck(location); jjg@450: lazyInitSearchPaths(); jjg@450: PathsForLocation path = pathsForLocation.get(location); jjg@450: if (path == null && !pathsForLocation.containsKey(location)) { jjg@450: setDefaultForLocation(location); jjg@450: path = pathsForLocation.get(location); jjg@450: } jjg@450: return path; jjg@450: } jjg@450: jjg@450: private Path getOutputLocation(Location location) { jjg@450: Iterable paths = getLocation(location); jjg@450: return (paths == null ? null : paths.iterator().next()); jjg@450: } jjg@450: jjg@450: public void setLocation(Location location, Iterable searchPath) jjg@450: throws IOException jjg@450: { jjg@450: nullCheck(location); jjg@450: lazyInitSearchPaths(); jjg@450: if (searchPath == null) { jjg@450: setDefaultForLocation(location); jjg@450: } else { jjg@450: if (location.isOutputLocation()) jjg@450: checkOutputPath(searchPath); jjg@450: PathsForLocation pl = new PathsForLocation(); jjg@450: for (Path p: searchPath) jjg@450: pl.add(p); // TODO -Xlint:path warn if path not found jjg@450: pathsForLocation.put(location, pl); jjg@450: } jjg@450: } jjg@450: jjg@450: private void checkOutputPath(Iterable searchPath) throws IOException { jjg@450: Iterator pathIter = searchPath.iterator(); jjg@450: if (!pathIter.hasNext()) jjg@450: throw new IllegalArgumentException("empty path for directory"); jjg@450: Path path = pathIter.next(); jjg@450: if (pathIter.hasNext()) jjg@450: throw new IllegalArgumentException("path too long for directory"); alanb@847: if (!isDirectory(path)) jjg@450: throw new IOException(path + ": not a directory"); jjg@450: } jjg@450: jjg@450: private void setDefaultForLocation(Location locn) { jjg@450: Collection files = null; jjg@450: if (locn instanceof StandardLocation) { jjg@450: switch ((StandardLocation) locn) { jjg@450: case CLASS_PATH: jjg@450: files = searchPaths.userClassPath(); jjg@450: break; jjg@450: case PLATFORM_CLASS_PATH: jjg@450: files = searchPaths.bootClassPath(); jjg@450: break; jjg@450: case SOURCE_PATH: jjg@450: files = searchPaths.sourcePath(); jjg@450: break; jjg@450: case CLASS_OUTPUT: { jjg@450: String arg = options.get(D); jjg@450: files = (arg == null ? null : Collections.singleton(new File(arg))); jjg@450: break; jjg@450: } jjg@450: case SOURCE_OUTPUT: { jjg@450: String arg = options.get(S); jjg@450: files = (arg == null ? null : Collections.singleton(new File(arg))); jjg@450: break; jjg@450: } jjg@450: } jjg@450: } jjg@450: jjg@450: PathsForLocation pl = new PathsForLocation(); jjg@450: if (files != null) { jjg@450: for (File f: files) jjg@450: pl.add(f.toPath()); jjg@450: } jjg@450: pathsForLocation.put(locn, pl); jjg@450: } jjg@450: jjg@450: private void lazyInitSearchPaths() { jjg@450: if (!inited) { jjg@450: setDefaultForLocation(PLATFORM_CLASS_PATH); jjg@450: setDefaultForLocation(CLASS_PATH); jjg@450: setDefaultForLocation(SOURCE_PATH); jjg@450: inited = true; jjg@450: } jjg@450: } jjg@450: // where jjg@450: private boolean inited = false; jjg@450: jjg@450: private Map pathsForLocation; jjg@450: private Paths searchPaths; jjg@450: jjg@450: private static class PathsForLocation extends LinkedHashSet { jjg@450: private static final long serialVersionUID = 6788510222394486733L; jjg@450: } jjg@450: jjg@450: // jjg@450: jjg@450: // jjg@450: jjg@450: @Override jjg@450: public Path getPath(FileObject fo) { jjg@450: nullCheck(fo); jjg@450: if (!(fo instanceof PathFileObject)) jjg@450: throw new IllegalArgumentException(); jjg@450: return ((PathFileObject) fo).getPath(); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public boolean isSameFile(FileObject a, FileObject b) { jjg@450: nullCheck(a); jjg@450: nullCheck(b); jjg@450: if (!(a instanceof PathFileObject)) jjg@450: throw new IllegalArgumentException("Not supported: " + a); jjg@450: if (!(b instanceof PathFileObject)) jjg@450: throw new IllegalArgumentException("Not supported: " + b); jjg@450: return ((PathFileObject) a).isSameFile((PathFileObject) b); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public Iterable list(Location location, jjg@450: String packageName, Set kinds, boolean recurse) jjg@450: throws IOException { jjg@450: // validatePackageName(packageName); jjg@450: nullCheck(packageName); jjg@450: nullCheck(kinds); jjg@450: jjg@450: Iterable paths = getLocation(location); jjg@450: if (paths == null) jjg@450: return List.nil(); jjg@450: ListBuffer results = new ListBuffer(); jjg@450: jjg@450: for (Path path : paths) jjg@450: list(path, packageName, kinds, recurse, results); jjg@450: jjg@450: return results.toList(); jjg@450: } jjg@450: jjg@450: private void list(Path path, String packageName, final Set kinds, jjg@450: boolean recurse, final ListBuffer results) jjg@450: throws IOException { alanb@847: if (!Files.exists(path)) jjg@450: return; jjg@450: jjg@450: final Path pathDir; jjg@450: if (isDirectory(path)) jjg@450: pathDir = path; jjg@450: else { jjg@450: FileSystem fs = getFileSystem(path); jjg@450: if (fs == null) jjg@450: return; jjg@450: pathDir = fs.getRootDirectories().iterator().next(); jjg@450: } jjg@450: String sep = path.getFileSystem().getSeparator(); jjg@450: Path packageDir = packageName.isEmpty() ? pathDir jjg@450: : pathDir.resolve(packageName.replace(".", sep)); alanb@847: if (!Files.exists(packageDir)) jjg@450: return; jjg@450: jjg@450: /* Alternate impl of list, superceded by use of Files.walkFileTree */ jjg@450: // Deque queue = new LinkedList(); jjg@450: // queue.add(packageDir); jjg@450: // jjg@450: // Path dir; jjg@450: // while ((dir = queue.poll()) != null) { jjg@450: // DirectoryStream ds = dir.newDirectoryStream(); jjg@450: // try { jjg@450: // for (Path p: ds) { alanb@847: // String name = p.getFileName().toString(); jjg@450: // if (isDirectory(p)) { jjg@450: // if (recurse && SourceVersion.isIdentifier(name)) { jjg@450: // queue.add(p); jjg@450: // } jjg@450: // } else { jjg@450: // if (kinds.contains(getKind(name))) { jjg@450: // JavaFileObject fe = jjg@450: // PathFileObject.createDirectoryPathFileObject(this, p, pathDir); jjg@450: // results.append(fe); jjg@450: // } jjg@450: // } jjg@450: // } jjg@450: // } finally { jjg@450: // ds.close(); jjg@450: // } jjg@450: // } jjg@450: int maxDepth = (recurse ? Integer.MAX_VALUE : 1); alanb@701: Set opts = EnumSet.of(FOLLOW_LINKS); jjg@450: Files.walkFileTree(packageDir, opts, maxDepth, jjg@450: new SimpleFileVisitor() { jjg@450: @Override alanb@701: public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { alanb@847: Path name = dir.getFileName(); jjg@803: if (name == null || SourceVersion.isIdentifier(name.toString())) // JSR 292? jjg@450: return FileVisitResult.CONTINUE; jjg@450: else jjg@450: return FileVisitResult.SKIP_SUBTREE; jjg@450: } jjg@450: jjg@450: @Override jjg@450: public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { alanb@847: if (attrs.isRegularFile() && kinds.contains(getKind(file.getFileName().toString()))) { jjg@450: JavaFileObject fe = jjg@450: PathFileObject.createDirectoryPathFileObject( jjg@450: JavacPathFileManager.this, file, pathDir); jjg@450: results.append(fe); jjg@450: } jjg@450: return FileVisitResult.CONTINUE; jjg@450: } jjg@450: }); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public Iterable getJavaFileObjectsFromPaths( jjg@450: Iterable paths) { jjg@450: ArrayList result; jjg@450: if (paths instanceof Collection) jjg@450: result = new ArrayList(((Collection)paths).size()); jjg@450: else jjg@450: result = new ArrayList(); jjg@450: for (Path p: paths) jjg@450: result.add(PathFileObject.createSimplePathFileObject(this, nullCheck(p))); jjg@450: return result; jjg@450: } jjg@450: jjg@450: @Override jjg@450: public Iterable getJavaFileObjects(Path... paths) { jjg@450: return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public JavaFileObject getJavaFileForInput(Location location, jjg@450: String className, Kind kind) throws IOException { jjg@450: return getFileForInput(location, getRelativePath(className, kind)); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public FileObject getFileForInput(Location location, jjg@450: String packageName, String relativeName) throws IOException { jjg@450: return getFileForInput(location, getRelativePath(packageName, relativeName)); jjg@450: } jjg@450: jjg@450: private JavaFileObject getFileForInput(Location location, String relativePath) jjg@450: throws IOException { jjg@450: for (Path p: getLocation(location)) { jjg@450: if (isDirectory(p)) { jjg@450: Path f = resolve(p, relativePath); alanb@847: if (Files.exists(f)) jjg@450: return PathFileObject.createDirectoryPathFileObject(this, f, p); jjg@450: } else { jjg@450: FileSystem fs = getFileSystem(p); jjg@450: if (fs != null) { jjg@450: Path file = getPath(fs, relativePath); alanb@847: if (Files.exists(file)) jjg@450: return PathFileObject.createJarPathFileObject(this, file); jjg@450: } jjg@450: } jjg@450: } jjg@450: return null; jjg@450: } jjg@450: jjg@450: @Override jjg@450: public JavaFileObject getJavaFileForOutput(Location location, jjg@450: String className, Kind kind, FileObject sibling) throws IOException { jjg@450: return getFileForOutput(location, getRelativePath(className, kind), sibling); jjg@450: } jjg@450: jjg@450: @Override jjg@450: public FileObject getFileForOutput(Location location, String packageName, jjg@450: String relativeName, FileObject sibling) jjg@450: throws IOException { jjg@450: return getFileForOutput(location, getRelativePath(packageName, relativeName), sibling); jjg@450: } jjg@450: jjg@450: private JavaFileObject getFileForOutput(Location location, jjg@450: String relativePath, FileObject sibling) { jjg@450: Path dir = getOutputLocation(location); jjg@450: if (dir == null) { jjg@450: if (location == CLASS_OUTPUT) { jjg@450: Path siblingDir = null; jjg@450: if (sibling != null && sibling instanceof PathFileObject) { jjg@450: siblingDir = ((PathFileObject) sibling).getPath().getParent(); jjg@450: } jjg@450: return PathFileObject.createSiblingPathFileObject(this, jjg@450: siblingDir.resolve(getBaseName(relativePath)), jjg@450: relativePath); jjg@450: } else if (location == SOURCE_OUTPUT) { jjg@450: dir = getOutputLocation(CLASS_OUTPUT); jjg@450: } jjg@450: } jjg@450: jjg@450: Path file; jjg@450: if (dir != null) { jjg@450: file = resolve(dir, relativePath); jjg@450: return PathFileObject.createDirectoryPathFileObject(this, file, dir); jjg@450: } else { jjg@450: file = getPath(getDefaultFileSystem(), relativePath); jjg@450: return PathFileObject.createSimplePathFileObject(this, file); jjg@450: } jjg@450: jjg@450: } jjg@450: jjg@450: @Override jjg@450: public String inferBinaryName(Location location, JavaFileObject fo) { jjg@450: nullCheck(fo); jjg@450: // Need to match the path semantics of list(location, ...) jjg@450: Iterable paths = getLocation(location); jjg@450: if (paths == null) { jjg@450: return null; jjg@450: } jjg@450: jjg@450: if (!(fo instanceof PathFileObject)) jjg@450: throw new IllegalArgumentException(fo.getClass().getName()); jjg@450: jjg@450: return ((PathFileObject) fo).inferBinaryName(paths); jjg@450: } jjg@450: jjg@450: private FileSystem getFileSystem(Path p) throws IOException { jjg@450: FileSystem fs = fileSystems.get(p); jjg@450: if (fs == null) { alanb@847: fs = FileSystems.newFileSystem(p, null); jjg@450: fileSystems.put(p, fs); jjg@450: } jjg@450: return fs; jjg@450: } jjg@450: jjg@450: private Map fileSystems; jjg@450: jjg@450: // jjg@450: jjg@450: // jjg@450: jjg@450: private static String getRelativePath(String className, Kind kind) { jjg@450: return className.replace(".", "/") + kind.extension; jjg@450: } jjg@450: jjg@450: private static String getRelativePath(String packageName, String relativeName) { jjg@450: return packageName.replace(".", "/") + relativeName; jjg@450: } jjg@450: jjg@450: private static String getBaseName(String relativePath) { jjg@450: int lastSep = relativePath.lastIndexOf("/"); jjg@450: return relativePath.substring(lastSep + 1); // safe if "/" not found jjg@450: } jjg@450: jjg@450: private static boolean isDirectory(Path path) throws IOException { alanb@847: BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); jjg@450: return attrs.isDirectory(); jjg@450: } jjg@450: jjg@450: private static Path getPath(FileSystem fs, String relativePath) { jjg@450: return fs.getPath(relativePath.replace("/", fs.getSeparator())); jjg@450: } jjg@450: jjg@450: private static Path resolve(Path base, String relativePath) { jjg@450: FileSystem fs = base.getFileSystem(); jjg@450: Path rp = fs.getPath(relativePath.replace("/", fs.getSeparator())); jjg@450: return base.resolve(rp); jjg@450: } jjg@450: jjg@450: // jjg@450: jjg@450: }