/*
* Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javac.nio;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardLocation;
import static java.nio.file.FileVisitOption.*;
import static javax.tools.StandardLocation.*;
import com.sun.tools.javac.util.BaseFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import static com.sun.tools.javac.main.Option.*;
// NOTE the imports carefully for this compilation unit.
//
// Path: java.nio.file.Path -- the new NIO type for which this file manager exists
//
// Paths: com.sun.tools.javac.file.Paths -- legacy javac type for handling path options
// The other Paths (java.nio.file.Paths) is not used
// NOTE this and related classes depend on new API in JDK 7.
// This requires special handling while bootstrapping the JDK build,
// when these classes might not yet have been compiled. To workaround
// this, the build arranges to make stubs of these classes available
// when compiling this and related classes. The set of stub files
// is specified in make/build.properties.
/**
* Implementation of PathFileManager: a JavaFileManager based on the use
* of java.nio.file.Path.
*
* <p>Just as a Path is somewhat analagous to a File, so too is this
* JavacPathFileManager analogous to JavacFileManager, as it relates to the
* support of FileObjects based on File objects (i.e. just RegularFileObject,
* not ZipFileObject and its variants.)
*
* <p>The default values for the standard locations supported by this file
* manager are the same as the default values provided by JavacFileManager --
* i.e. as determined by the javac.file.Paths class. To override these values,
* call {@link #setLocation}.
*
* <p>To reduce confusion with Path objects, the locations such as "class path",
* "source path", etc, are generically referred to here as "search paths".
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class JavacPathFileManager extends BaseFileManager implements PathFileManager {
protected FileSystem defaultFileSystem;
/**
* Create a JavacPathFileManager using a given context, optionally registering
* it as the JavaFileManager for that context.
*/
public JavacPathFileManager(Context context, boolean register, Charset charset) {
super(charset);
if (register)
context.put(JavaFileManager.class, this);
pathsForLocation = new HashMap<Location, PathsForLocation>();
fileSystems = new HashMap<Path,FileSystem>();
setContext(context);
}
/**
* Set the context for JavacPathFileManager.
*/
@Override
public void setContext(Context context) {
super.setContext(context);
}
@Override
public FileSystem getDefaultFileSystem() {
if (defaultFileSystem == null)
defaultFileSystem = FileSystems.getDefault();
return defaultFileSystem;
}
@Override
public void setDefaultFileSystem(FileSystem fs) {
defaultFileSystem = fs;
}
@Override
public void flush() throws IOException {
contentCache.clear();
}
@Override
public void close() throws IOException {
for (FileSystem fs: fileSystems.values())
fs.close();
}
@Override
public ClassLoader getClassLoader(Location location) {
nullCheck(location);
Iterable<? extends Path> path = getLocation(location);
if (path == null)
return null;
ListBuffer<URL> lb = new ListBuffer<URL>();
for (Path p: path) {
try {
lb.append(p.toUri().toURL());
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
}
return getClassLoader(lb.toArray(new URL[lb.size()]));
}
@Override
public boolean isDefaultBootClassPath() {
return locations.isDefaultBootClassPath();
}
// <editor-fold defaultstate="collapsed" desc="Location handling">
public boolean hasLocation(Location location) {
return (getLocation(location) != null);
}
public Iterable<? extends Path> getLocation(Location location) {
nullCheck(location);
lazyInitSearchPaths();
PathsForLocation path = pathsForLocation.get(location);
if (path == null && !pathsForLocation.containsKey(location)) {
setDefaultForLocation(location);
path = pathsForLocation.get(location);
}
return path;
}
private Path getOutputLocation(Location location) {
Iterable<? extends Path> paths = getLocation(location);
return (paths == null ? null : paths.iterator().next());
}
public void setLocation(Location location, Iterable<? extends Path> searchPath)
throws IOException
{
nullCheck(location);
lazyInitSearchPaths();
if (searchPath == null) {
setDefaultForLocation(location);
} else {
if (location.isOutputLocation())
checkOutputPath(searchPath);
PathsForLocation pl = new PathsForLocation();
for (Path p: searchPath)
pl.add(p); // TODO -Xlint:path warn if path not found
pathsForLocation.put(location, pl);
}
}
private void checkOutputPath(Iterable<? extends Path> searchPath) throws IOException {
Iterator<? extends Path> pathIter = searchPath.iterator();
if (!pathIter.hasNext())
throw new IllegalArgumentException("empty path for directory");
Path path = pathIter.next();
if (pathIter.hasNext())
throw new IllegalArgumentException("path too long for directory");
if (!isDirectory(path))
throw new IOException(path + ": not a directory");
}
private void setDefaultForLocation(Location locn) {
Collection<File> files = null;
if (locn instanceof StandardLocation) {
switch ((StandardLocation) locn) {
case CLASS_PATH:
files = locations.userClassPath();
break;
case PLATFORM_CLASS_PATH:
files = locations.bootClassPath();
break;
case SOURCE_PATH:
files = locations.sourcePath();
break;
case CLASS_OUTPUT: {
String arg = options.get(D);
files = (arg == null ? null : Collections.singleton(new File(arg)));
break;
}
case SOURCE_OUTPUT: {
String arg = options.get(S);
files = (arg == null ? null : Collections.singleton(new File(arg)));
break;
}
}
}
PathsForLocation pl = new PathsForLocation();
if (files != null) {
for (File f: files)
pl.add(f.toPath());
}
if (!pl.isEmpty())
pathsForLocation.put(locn, pl);
}
private void lazyInitSearchPaths() {
if (!inited) {
setDefaultForLocation(PLATFORM_CLASS_PATH);
setDefaultForLocation(CLASS_PATH);
setDefaultForLocation(SOURCE_PATH);
inited = true;
}
}
// where
private boolean inited = false;
private Map<Location, PathsForLocation> pathsForLocation;
private static class PathsForLocation extends LinkedHashSet<Path> {
private static final long serialVersionUID = 6788510222394486733L;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="FileObject handling">
@Override
public Path getPath(FileObject fo) {
nullCheck(fo);
if (!(fo instanceof PathFileObject))
throw new IllegalArgumentException();
return ((PathFileObject) fo).getPath();
}
@Override
public boolean isSameFile(FileObject a, FileObject b) {
nullCheck(a);
nullCheck(b);
if (!(a instanceof PathFileObject))
throw new IllegalArgumentException("Not supported: " + a);
if (!(b instanceof PathFileObject))
throw new IllegalArgumentException("Not supported: " + b);
return ((PathFileObject) a).isSameFile((PathFileObject) b);
}
@Override
public Iterable<JavaFileObject> list(Location location,
String packageName, Set<Kind> kinds, boolean recurse)
throws IOException {
// validatePackageName(packageName);
nullCheck(packageName);
nullCheck(kinds);
Iterable<? extends Path> paths = getLocation(location);
if (paths == null)
return List.nil();
ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>();
for (Path path : paths)
list(path, packageName, kinds, recurse, results);
return results.toList();
}
private void list(Path path, String packageName, final Set<Kind> kinds,
boolean recurse, final ListBuffer<JavaFileObject> results)
throws IOException {
if (!Files.exists(path))
return;
final Path pathDir;
if (isDirectory(path))
pathDir = path;
else {
FileSystem fs = getFileSystem(path);
if (fs == null)
return;
pathDir = fs.getRootDirectories().iterator().next();
}
String sep = path.getFileSystem().getSeparator();
Path packageDir = packageName.isEmpty() ? pathDir
: pathDir.resolve(packageName.replace(".", sep));
if (!Files.exists(packageDir))
return;
/* Alternate impl of list, superceded by use of Files.walkFileTree */
// Deque<Path> queue = new LinkedList<Path>();
// queue.add(packageDir);
//
// Path dir;
// while ((dir = queue.poll()) != null) {
// DirectoryStream<Path> ds = dir.newDirectoryStream();
// try {
// for (Path p: ds) {
// String name = p.getFileName().toString();
// if (isDirectory(p)) {
// if (recurse && SourceVersion.isIdentifier(name)) {
// queue.add(p);
// }
// } else {
// if (kinds.contains(getKind(name))) {
// JavaFileObject fe =
// PathFileObject.createDirectoryPathFileObject(this, p, pathDir);
// results.append(fe);
// }
// }
// }
// } finally {
// ds.close();
// }
// }
int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
Files.walkFileTree(packageDir, opts, maxDepth,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
Path name = dir.getFileName();
if (name == null || SourceVersion.isIdentifier(name.toString())) // JSR 292?
return FileVisitResult.CONTINUE;
else
return FileVisitResult.SKIP_SUBTREE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (attrs.isRegularFile() && kinds.contains(getKind(file.getFileName().toString()))) {
JavaFileObject fe =
PathFileObject.createDirectoryPathFileObject(
JavacPathFileManager.this, file, pathDir);
results.append(fe);
}
return FileVisitResult.CONTINUE;
}
});
}
@Override
public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
Iterable<? extends Path> paths) {
ArrayList<PathFileObject> result;
if (paths instanceof Collection<?>)
result = new ArrayList<PathFileObject>(((Collection<?>)paths).size());
else
result = new ArrayList<PathFileObject>();
for (Path p: paths)
result.add(PathFileObject.createSimplePathFileObject(this, nullCheck(p)));
return result;
}
@Override
public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
}
@Override
public JavaFileObject getJavaFileForInput(Location location,
String className, Kind kind) throws IOException {
return getFileForInput(location, getRelativePath(className, kind));
}
@Override
public FileObject getFileForInput(Location location,
String packageName, String relativeName) throws IOException {
return getFileForInput(location, getRelativePath(packageName, relativeName));
}
private JavaFileObject getFileForInput(Location location, String relativePath)
throws IOException {
for (Path p: getLocation(location)) {
if (isDirectory(p)) {
Path f = resolve(p, relativePath);
if (Files.exists(f))
return PathFileObject.createDirectoryPathFileObject(this, f, p);
} else {
FileSystem fs = getFileSystem(p);
if (fs != null) {
Path file = getPath(fs, relativePath);
if (Files.exists(file))
return PathFileObject.createJarPathFileObject(this, file);
}
}
}
return null;
}
@Override
public JavaFileObject getJavaFileForOutput(Location location,
String className, Kind kind, FileObject sibling) throws IOException {
return getFileForOutput(location, getRelativePath(className, kind), sibling);
}
@Override
public FileObject getFileForOutput(Location location, String packageName,
String relativeName, FileObject sibling)
throws IOException {
return getFileForOutput(location, getRelativePath(packageName, relativeName), sibling);
}
private JavaFileObject getFileForOutput(Location location,
String relativePath, FileObject sibling) {
Path dir = getOutputLocation(location);
if (dir == null) {
if (location == CLASS_OUTPUT) {
Path siblingDir = null;
if (sibling != null && sibling instanceof PathFileObject) {
siblingDir = ((PathFileObject) sibling).getPath().getParent();
}
return PathFileObject.createSiblingPathFileObject(this,
siblingDir.resolve(getBaseName(relativePath)),
relativePath);
} else if (location == SOURCE_OUTPUT) {
dir = getOutputLocation(CLASS_OUTPUT);
}
}
Path file;
if (dir != null) {
file = resolve(dir, relativePath);
return PathFileObject.createDirectoryPathFileObject(this, file, dir);
} else {
file = getPath(getDefaultFileSystem(), relativePath);
return PathFileObject.createSimplePathFileObject(this, file);
}
}
@Override
public String inferBinaryName(Location location, JavaFileObject fo) {
nullCheck(fo);
// Need to match the path semantics of list(location, ...)
Iterable<? extends Path> paths = getLocation(location);
if (paths == null) {
return null;
}
if (!(fo instanceof PathFileObject))
throw new IllegalArgumentException(fo.getClass().getName());
return ((PathFileObject) fo).inferBinaryName(paths);
}
private FileSystem getFileSystem(Path p) throws IOException {
FileSystem fs = fileSystems.get(p);
if (fs == null) {
fs = FileSystems.newFileSystem(p, null);
fileSystems.put(p, fs);
}
return fs;
}
private Map<Path,FileSystem> fileSystems;
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Utility methods">
private static String getRelativePath(String className, Kind kind) {
return className.replace(".", "/") + kind.extension;
}
private static String getRelativePath(String packageName, String relativeName) {
return packageName.isEmpty()
? relativeName : packageName.replace(".", "/") + "/" + relativeName;
}
private static String getBaseName(String relativePath) {
int lastSep = relativePath.lastIndexOf("/");
return relativePath.substring(lastSep + 1); // safe if "/" not found
}
private static boolean isDirectory(Path path) throws IOException {
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
return attrs.isDirectory();
}
private static Path getPath(FileSystem fs, String relativePath) {
return fs.getPath(relativePath.replace("/", fs.getSeparator()));
}
private static Path resolve(Path base, String relativePath) {
FileSystem fs = base.getFileSystem();
Path rp = fs.getPath(relativePath.replace("/", fs.getSeparator()));
return base.resolve(rp);
}
// </editor-fold>
}