aoqi@0: /* aoqi@0: * Copyright (c) 2009, 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.nio; aoqi@0: aoqi@0: import java.io.File; aoqi@0: import java.io.IOException; aoqi@0: import java.net.MalformedURLException; aoqi@0: import java.net.URL; aoqi@0: import java.nio.charset.Charset; aoqi@0: import java.nio.file.Files; aoqi@0: import java.nio.file.FileSystem; aoqi@0: import java.nio.file.FileSystems; aoqi@0: import java.nio.file.FileVisitOption; aoqi@0: import java.nio.file.FileVisitResult; aoqi@0: import java.nio.file.Path; aoqi@0: import java.nio.file.SimpleFileVisitor; aoqi@0: import java.nio.file.attribute.BasicFileAttributes; 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.EnumSet; aoqi@0: import java.util.HashMap; aoqi@0: import java.util.Iterator; aoqi@0: import java.util.LinkedHashSet; aoqi@0: import java.util.Map; aoqi@0: import java.util.Set; 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.JavaFileObject.Kind; aoqi@0: import javax.tools.StandardLocation; aoqi@0: aoqi@0: import static java.nio.file.FileVisitOption.*; aoqi@0: import static javax.tools.StandardLocation.*; aoqi@0: 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 com.sun.tools.javac.main.Option.*; aoqi@0: aoqi@0: aoqi@0: // NOTE the imports carefully for this compilation unit. aoqi@0: // aoqi@0: // Path: java.nio.file.Path -- the new NIO type for which this file manager exists aoqi@0: // aoqi@0: // Paths: com.sun.tools.javac.file.Paths -- legacy javac type for handling path options aoqi@0: // The other Paths (java.nio.file.Paths) is not used aoqi@0: aoqi@0: // NOTE this and related classes depend on new API in JDK 7. aoqi@0: // This requires special handling while bootstrapping the JDK build, aoqi@0: // when these classes might not yet have been compiled. To workaround aoqi@0: // this, the build arranges to make stubs of these classes available aoqi@0: // when compiling this and related classes. The set of stub files aoqi@0: // is specified in make/build.properties. aoqi@0: aoqi@0: /** aoqi@0: * Implementation of PathFileManager: a JavaFileManager based on the use aoqi@0: * of java.nio.file.Path. aoqi@0: * aoqi@0: *

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

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

To reduce confusion with Path objects, the locations such as "class path", aoqi@0: * "source path", etc, are generically referred to here as "search paths". 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 JavacPathFileManager extends BaseFileManager implements PathFileManager { aoqi@0: protected FileSystem defaultFileSystem; aoqi@0: aoqi@0: /** aoqi@0: * Create a JavacPathFileManager using a given context, optionally registering aoqi@0: * it as the JavaFileManager for that context. aoqi@0: */ aoqi@0: public JavacPathFileManager(Context context, boolean register, Charset charset) { aoqi@0: super(charset); aoqi@0: if (register) aoqi@0: context.put(JavaFileManager.class, this); aoqi@0: pathsForLocation = new HashMap(); aoqi@0: fileSystems = new HashMap(); aoqi@0: setContext(context); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Set the context for JavacPathFileManager. aoqi@0: */ aoqi@0: @Override aoqi@0: public void setContext(Context context) { aoqi@0: super.setContext(context); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public FileSystem getDefaultFileSystem() { aoqi@0: if (defaultFileSystem == null) aoqi@0: defaultFileSystem = FileSystems.getDefault(); aoqi@0: return defaultFileSystem; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public void setDefaultFileSystem(FileSystem fs) { aoqi@0: defaultFileSystem = fs; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public void flush() throws IOException { aoqi@0: contentCache.clear(); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public void close() throws IOException { aoqi@0: for (FileSystem fs: fileSystems.values()) aoqi@0: fs.close(); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public ClassLoader getClassLoader(Location location) { aoqi@0: nullCheck(location); aoqi@0: Iterable path = getLocation(location); aoqi@0: if (path == null) aoqi@0: return null; aoqi@0: ListBuffer lb = new ListBuffer(); aoqi@0: for (Path p: path) { aoqi@0: try { aoqi@0: lb.append(p.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: @Override aoqi@0: public boolean isDefaultBootClassPath() { aoqi@0: return locations.isDefaultBootClassPath(); aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: aoqi@0: public boolean hasLocation(Location location) { aoqi@0: return (getLocation(location) != null); aoqi@0: } aoqi@0: aoqi@0: public Iterable getLocation(Location location) { aoqi@0: nullCheck(location); aoqi@0: lazyInitSearchPaths(); aoqi@0: PathsForLocation path = pathsForLocation.get(location); aoqi@0: if (path == null && !pathsForLocation.containsKey(location)) { aoqi@0: setDefaultForLocation(location); aoqi@0: path = pathsForLocation.get(location); aoqi@0: } aoqi@0: return path; aoqi@0: } aoqi@0: aoqi@0: private Path getOutputLocation(Location location) { aoqi@0: Iterable paths = getLocation(location); aoqi@0: return (paths == null ? null : paths.iterator().next()); aoqi@0: } aoqi@0: aoqi@0: public void setLocation(Location location, Iterable searchPath) aoqi@0: throws IOException aoqi@0: { aoqi@0: nullCheck(location); aoqi@0: lazyInitSearchPaths(); aoqi@0: if (searchPath == null) { aoqi@0: setDefaultForLocation(location); aoqi@0: } else { aoqi@0: if (location.isOutputLocation()) aoqi@0: checkOutputPath(searchPath); aoqi@0: PathsForLocation pl = new PathsForLocation(); aoqi@0: for (Path p: searchPath) aoqi@0: pl.add(p); // TODO -Xlint:path warn if path not found aoqi@0: pathsForLocation.put(location, pl); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: private void checkOutputPath(Iterable searchPath) throws IOException { aoqi@0: Iterator pathIter = searchPath.iterator(); aoqi@0: if (!pathIter.hasNext()) aoqi@0: throw new IllegalArgumentException("empty path for directory"); aoqi@0: Path path = pathIter.next(); aoqi@0: if (pathIter.hasNext()) aoqi@0: throw new IllegalArgumentException("path too long for directory"); aoqi@0: if (!isDirectory(path)) aoqi@0: throw new IOException(path + ": not a directory"); aoqi@0: } aoqi@0: aoqi@0: private void setDefaultForLocation(Location locn) { aoqi@0: Collection files = null; aoqi@0: if (locn instanceof StandardLocation) { aoqi@0: switch ((StandardLocation) locn) { aoqi@0: case CLASS_PATH: aoqi@0: files = locations.userClassPath(); aoqi@0: break; aoqi@0: case PLATFORM_CLASS_PATH: aoqi@0: files = locations.bootClassPath(); aoqi@0: break; aoqi@0: case SOURCE_PATH: aoqi@0: files = locations.sourcePath(); aoqi@0: break; aoqi@0: case CLASS_OUTPUT: { aoqi@0: String arg = options.get(D); aoqi@0: files = (arg == null ? null : Collections.singleton(new File(arg))); aoqi@0: break; aoqi@0: } aoqi@0: case SOURCE_OUTPUT: { aoqi@0: String arg = options.get(S); aoqi@0: files = (arg == null ? null : Collections.singleton(new File(arg))); aoqi@0: break; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: PathsForLocation pl = new PathsForLocation(); aoqi@0: if (files != null) { aoqi@0: for (File f: files) aoqi@0: pl.add(f.toPath()); aoqi@0: } aoqi@0: if (!pl.isEmpty()) aoqi@0: pathsForLocation.put(locn, pl); aoqi@0: } aoqi@0: aoqi@0: private void lazyInitSearchPaths() { aoqi@0: if (!inited) { aoqi@0: setDefaultForLocation(PLATFORM_CLASS_PATH); aoqi@0: setDefaultForLocation(CLASS_PATH); aoqi@0: setDefaultForLocation(SOURCE_PATH); aoqi@0: inited = true; aoqi@0: } aoqi@0: } aoqi@0: // where aoqi@0: private boolean inited = false; aoqi@0: aoqi@0: private Map pathsForLocation; aoqi@0: aoqi@0: private static class PathsForLocation extends LinkedHashSet { aoqi@0: private static final long serialVersionUID = 6788510222394486733L; aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: aoqi@0: // aoqi@0: aoqi@0: @Override aoqi@0: public Path getPath(FileObject fo) { aoqi@0: nullCheck(fo); aoqi@0: if (!(fo instanceof PathFileObject)) aoqi@0: throw new IllegalArgumentException(); aoqi@0: return ((PathFileObject) fo).getPath(); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public boolean isSameFile(FileObject a, FileObject b) { aoqi@0: nullCheck(a); aoqi@0: nullCheck(b); aoqi@0: if (!(a instanceof PathFileObject)) aoqi@0: throw new IllegalArgumentException("Not supported: " + a); aoqi@0: if (!(b instanceof PathFileObject)) aoqi@0: throw new IllegalArgumentException("Not supported: " + b); aoqi@0: return ((PathFileObject) a).isSameFile((PathFileObject) b); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public Iterable list(Location location, aoqi@0: String packageName, Set kinds, boolean recurse) aoqi@0: throws IOException { aoqi@0: // validatePackageName(packageName); aoqi@0: nullCheck(packageName); aoqi@0: nullCheck(kinds); aoqi@0: aoqi@0: Iterable paths = getLocation(location); aoqi@0: if (paths == null) aoqi@0: return List.nil(); aoqi@0: ListBuffer results = new ListBuffer(); aoqi@0: aoqi@0: for (Path path : paths) aoqi@0: list(path, packageName, kinds, recurse, results); aoqi@0: aoqi@0: return results.toList(); aoqi@0: } aoqi@0: aoqi@0: private void list(Path path, String packageName, final Set kinds, aoqi@0: boolean recurse, final ListBuffer results) aoqi@0: throws IOException { aoqi@0: if (!Files.exists(path)) aoqi@0: return; aoqi@0: aoqi@0: final Path pathDir; aoqi@0: if (isDirectory(path)) aoqi@0: pathDir = path; aoqi@0: else { aoqi@0: FileSystem fs = getFileSystem(path); aoqi@0: if (fs == null) aoqi@0: return; aoqi@0: pathDir = fs.getRootDirectories().iterator().next(); aoqi@0: } aoqi@0: String sep = path.getFileSystem().getSeparator(); aoqi@0: Path packageDir = packageName.isEmpty() ? pathDir aoqi@0: : pathDir.resolve(packageName.replace(".", sep)); aoqi@0: if (!Files.exists(packageDir)) aoqi@0: return; aoqi@0: aoqi@0: /* Alternate impl of list, superceded by use of Files.walkFileTree */ aoqi@0: // Deque queue = new LinkedList(); aoqi@0: // queue.add(packageDir); aoqi@0: // aoqi@0: // Path dir; aoqi@0: // while ((dir = queue.poll()) != null) { aoqi@0: // DirectoryStream ds = dir.newDirectoryStream(); aoqi@0: // try { aoqi@0: // for (Path p: ds) { aoqi@0: // String name = p.getFileName().toString(); aoqi@0: // if (isDirectory(p)) { aoqi@0: // if (recurse && SourceVersion.isIdentifier(name)) { aoqi@0: // queue.add(p); aoqi@0: // } aoqi@0: // } else { aoqi@0: // if (kinds.contains(getKind(name))) { aoqi@0: // JavaFileObject fe = aoqi@0: // PathFileObject.createDirectoryPathFileObject(this, p, pathDir); aoqi@0: // results.append(fe); aoqi@0: // } aoqi@0: // } aoqi@0: // } aoqi@0: // } finally { aoqi@0: // ds.close(); aoqi@0: // } aoqi@0: // } aoqi@0: int maxDepth = (recurse ? Integer.MAX_VALUE : 1); aoqi@0: Set opts = EnumSet.of(FOLLOW_LINKS); aoqi@0: Files.walkFileTree(packageDir, opts, maxDepth, aoqi@0: new SimpleFileVisitor() { aoqi@0: @Override aoqi@0: public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { aoqi@0: Path name = dir.getFileName(); aoqi@0: if (name == null || SourceVersion.isIdentifier(name.toString())) // JSR 292? aoqi@0: return FileVisitResult.CONTINUE; aoqi@0: else aoqi@0: return FileVisitResult.SKIP_SUBTREE; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { aoqi@0: if (attrs.isRegularFile() && kinds.contains(getKind(file.getFileName().toString()))) { aoqi@0: JavaFileObject fe = aoqi@0: PathFileObject.createDirectoryPathFileObject( aoqi@0: JavacPathFileManager.this, file, pathDir); aoqi@0: results.append(fe); aoqi@0: } aoqi@0: return FileVisitResult.CONTINUE; aoqi@0: } aoqi@0: }); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public Iterable getJavaFileObjectsFromPaths( aoqi@0: Iterable paths) { aoqi@0: ArrayList result; aoqi@0: if (paths instanceof Collection) aoqi@0: result = new ArrayList(((Collection)paths).size()); aoqi@0: else aoqi@0: result = new ArrayList(); aoqi@0: for (Path p: paths) aoqi@0: result.add(PathFileObject.createSimplePathFileObject(this, nullCheck(p))); aoqi@0: return result; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public Iterable getJavaFileObjects(Path... paths) { aoqi@0: return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public JavaFileObject getJavaFileForInput(Location location, aoqi@0: String className, Kind kind) throws IOException { aoqi@0: return getFileForInput(location, getRelativePath(className, kind)); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public FileObject getFileForInput(Location location, aoqi@0: String packageName, String relativeName) throws IOException { aoqi@0: return getFileForInput(location, getRelativePath(packageName, relativeName)); aoqi@0: } aoqi@0: aoqi@0: private JavaFileObject getFileForInput(Location location, String relativePath) aoqi@0: throws IOException { aoqi@0: for (Path p: getLocation(location)) { aoqi@0: if (isDirectory(p)) { aoqi@0: Path f = resolve(p, relativePath); aoqi@0: if (Files.exists(f)) aoqi@0: return PathFileObject.createDirectoryPathFileObject(this, f, p); aoqi@0: } else { aoqi@0: FileSystem fs = getFileSystem(p); aoqi@0: if (fs != null) { aoqi@0: Path file = getPath(fs, relativePath); aoqi@0: if (Files.exists(file)) aoqi@0: return PathFileObject.createJarPathFileObject(this, file); aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: return null; aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public JavaFileObject getJavaFileForOutput(Location location, aoqi@0: String className, Kind kind, FileObject sibling) throws IOException { aoqi@0: return getFileForOutput(location, getRelativePath(className, kind), sibling); aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public FileObject getFileForOutput(Location location, String packageName, aoqi@0: String relativeName, FileObject sibling) aoqi@0: throws IOException { aoqi@0: return getFileForOutput(location, getRelativePath(packageName, relativeName), sibling); aoqi@0: } aoqi@0: aoqi@0: private JavaFileObject getFileForOutput(Location location, aoqi@0: String relativePath, FileObject sibling) { aoqi@0: Path dir = getOutputLocation(location); aoqi@0: if (dir == null) { aoqi@0: if (location == CLASS_OUTPUT) { aoqi@0: Path siblingDir = null; aoqi@0: if (sibling != null && sibling instanceof PathFileObject) { aoqi@0: siblingDir = ((PathFileObject) sibling).getPath().getParent(); aoqi@0: } aoqi@0: return PathFileObject.createSiblingPathFileObject(this, aoqi@0: siblingDir.resolve(getBaseName(relativePath)), aoqi@0: relativePath); aoqi@0: } else if (location == SOURCE_OUTPUT) { aoqi@0: dir = getOutputLocation(CLASS_OUTPUT); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: Path file; aoqi@0: if (dir != null) { aoqi@0: file = resolve(dir, relativePath); aoqi@0: return PathFileObject.createDirectoryPathFileObject(this, file, dir); aoqi@0: } else { aoqi@0: file = getPath(getDefaultFileSystem(), relativePath); aoqi@0: return PathFileObject.createSimplePathFileObject(this, file); aoqi@0: } aoqi@0: aoqi@0: } aoqi@0: aoqi@0: @Override aoqi@0: public String inferBinaryName(Location location, JavaFileObject fo) { aoqi@0: nullCheck(fo); aoqi@0: // Need to match the path semantics of list(location, ...) aoqi@0: Iterable paths = getLocation(location); aoqi@0: if (paths == null) { aoqi@0: return null; aoqi@0: } aoqi@0: aoqi@0: if (!(fo instanceof PathFileObject)) aoqi@0: throw new IllegalArgumentException(fo.getClass().getName()); aoqi@0: aoqi@0: return ((PathFileObject) fo).inferBinaryName(paths); aoqi@0: } aoqi@0: aoqi@0: private FileSystem getFileSystem(Path p) throws IOException { aoqi@0: FileSystem fs = fileSystems.get(p); aoqi@0: if (fs == null) { aoqi@0: fs = FileSystems.newFileSystem(p, null); aoqi@0: fileSystems.put(p, fs); aoqi@0: } aoqi@0: return fs; aoqi@0: } aoqi@0: aoqi@0: private Map fileSystems; aoqi@0: aoqi@0: // aoqi@0: aoqi@0: // aoqi@0: aoqi@0: private static String getRelativePath(String className, Kind kind) { aoqi@0: return className.replace(".", "/") + kind.extension; aoqi@0: } aoqi@0: aoqi@0: private static String getRelativePath(String packageName, String relativeName) { aoqi@0: return packageName.isEmpty() aoqi@0: ? relativeName : packageName.replace(".", "/") + "/" + relativeName; aoqi@0: } aoqi@0: aoqi@0: private static String getBaseName(String relativePath) { aoqi@0: int lastSep = relativePath.lastIndexOf("/"); aoqi@0: return relativePath.substring(lastSep + 1); // safe if "/" not found aoqi@0: } aoqi@0: aoqi@0: private static boolean isDirectory(Path path) throws IOException { aoqi@0: BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); aoqi@0: return attrs.isDirectory(); aoqi@0: } aoqi@0: aoqi@0: private static Path getPath(FileSystem fs, String relativePath) { aoqi@0: return fs.getPath(relativePath.replace("/", fs.getSeparator())); aoqi@0: } aoqi@0: aoqi@0: private static Path resolve(Path base, String relativePath) { aoqi@0: FileSystem fs = base.getFileSystem(); aoqi@0: Path rp = fs.getPath(relativePath.replace("/", fs.getSeparator())); aoqi@0: return base.resolve(rp); aoqi@0: } aoqi@0: aoqi@0: // aoqi@0: aoqi@0: }