blob: 496c0397fb7c7e9795a0ac41facec20b844158f2 [file] [log] [blame]
/*
* Copyright (c) 2005, 2016, 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.file;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
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.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.SourceVersion;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
import com.sun.tools.javac.file.RelativePath.RelativeFile;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.ModuleWrappers.Configuration;
import com.sun.tools.javac.util.ModuleWrappers.Layer;
import com.sun.tools.javac.util.ModuleWrappers.ModuleFinder;
import com.sun.tools.javac.util.ModuleWrappers.ServiceLoaderHelper;
import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
import static javax.tools.StandardLocation.*;
/**
* This class provides access to the source, class and other files
* used by the compiler and related tools.
*
* <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 JavacFileManager extends BaseFileManager implements StandardJavaFileManager {
@SuppressWarnings("cast")
public static char[] toArray(CharBuffer buffer) {
if (buffer.hasArray())
return ((CharBuffer)buffer.compact().flip()).array();
else
return buffer.toString().toCharArray();
}
private FSInfo fsInfo;
private final Set<JavaFileObject.Kind> sourceOrClass =
EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS);
protected boolean symbolFileEnabled;
protected enum SortFiles implements Comparator<Path> {
FORWARD {
@Override
public int compare(Path f1, Path f2) {
return f1.getFileName().compareTo(f2.getFileName());
}
},
REVERSE {
@Override
public int compare(Path f1, Path f2) {
return -f1.getFileName().compareTo(f2.getFileName());
}
}
}
protected SortFiles sortFiles;
/**
* Register a Context.Factory to create a JavacFileManager.
*/
public static void preRegister(Context context) {
context.put(JavaFileManager.class, new Context.Factory<JavaFileManager>() {
@Override
public JavaFileManager make(Context c) {
return new JavacFileManager(c, true, null);
}
});
}
/**
* Create a JavacFileManager using a given context, optionally registering
* it as the JavaFileManager for that context.
*/
public JavacFileManager(Context context, boolean register, Charset charset) {
super(charset);
if (register)
context.put(JavaFileManager.class, this);
setContext(context);
}
/**
* Set the context for JavacFileManager.
*/
@Override
public void setContext(Context context) {
super.setContext(context);
fsInfo = FSInfo.instance(context);
symbolFileEnabled = !options.isSet("ignore.symbol.file");
String sf = options.get("sortFiles");
if (sf != null) {
sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD);
}
}
/**
* Set whether or not to use ct.sym as an alternate to rt.jar.
*/
public void setSymbolFileEnabled(boolean b) {
symbolFileEnabled = b;
}
public boolean isSymbolFileEnabled() {
return symbolFileEnabled;
}
// used by tests
public JavaFileObject getJavaFileObject(String name) {
return getJavaFileObjects(name).iterator().next();
}
// used by tests
public JavaFileObject getJavaFileObject(Path file) {
return getJavaFileObjects(file).iterator().next();
}
public JavaFileObject getFileForOutput(String classname,
JavaFileObject.Kind kind,
JavaFileObject sibling)
throws IOException
{
return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling);
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
ListBuffer<Path> paths = new ListBuffer<>();
for (String name : names)
paths.append(Paths.get(nullCheck(name)));
return getJavaFileObjectsFromPaths(paths.toList());
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names)));
}
private static boolean isValidName(String name) {
// Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ),
// but the set of keywords depends on the source level, and we don't want
// impls of JavaFileManager to have to be dependent on the source level.
// Therefore we simply check that the argument is a sequence of identifiers
// separated by ".".
for (String s : name.split("\\.", -1)) {
if (!SourceVersion.isIdentifier(s))
return false;
}
return true;
}
private static void validateClassName(String className) {
if (!isValidName(className))
throw new IllegalArgumentException("Invalid class name: " + className);
}
private static void validatePackageName(String packageName) {
if (packageName.length() > 0 && !isValidName(packageName))
throw new IllegalArgumentException("Invalid packageName name: " + packageName);
}
public static void testName(String name,
boolean isValidPackageName,
boolean isValidClassName)
{
try {
validatePackageName(name);
if (!isValidPackageName)
throw new AssertionError("Invalid package name accepted: " + name);
printAscii("Valid package name: \"%s\"", name);
} catch (IllegalArgumentException e) {
if (isValidPackageName)
throw new AssertionError("Valid package name rejected: " + name);
printAscii("Invalid package name: \"%s\"", name);
}
try {
validateClassName(name);
if (!isValidClassName)
throw new AssertionError("Invalid class name accepted: " + name);
printAscii("Valid class name: \"%s\"", name);
} catch (IllegalArgumentException e) {
if (isValidClassName)
throw new AssertionError("Valid class name rejected: " + name);
printAscii("Invalid class name: \"%s\"", name);
}
}
private static void printAscii(String format, Object... args) {
String message;
try {
final String ascii = "US-ASCII";
message = new String(String.format(null, format, args).getBytes(ascii), ascii);
} catch (java.io.UnsupportedEncodingException ex) {
throw new AssertionError(ex);
}
System.out.println(message);
}
/**
* Insert all files in a subdirectory of the platform image
* which match fileKinds into resultList.
*/
private void listJRTImage(RelativeDirectory subdirectory,
Set<JavaFileObject.Kind> fileKinds,
boolean recurse,
ListBuffer<JavaFileObject> resultList) throws IOException {
JRTIndex.Entry e = getJRTIndex().getEntry(subdirectory);
if (symbolFileEnabled && e.ctSym.hidden)
return;
for (Path file: e.files.values()) {
if (fileKinds.contains(getKind(file))) {
JavaFileObject fe
= PathFileObject.forJRTPath(JavacFileManager.this, file);
resultList.append(fe);
}
}
if (recurse) {
for (RelativeDirectory rd: e.subdirs) {
listJRTImage(rd, fileKinds, recurse, resultList);
}
}
}
private synchronized JRTIndex getJRTIndex() {
if (jrtIndex == null)
jrtIndex = JRTIndex.getSharedInstance();
return jrtIndex;
}
private JRTIndex jrtIndex;
/**
* Insert all files in subdirectory subdirectory of directory directory
* which match fileKinds into resultList
*/
private void listDirectory(Path directory, Path realDirectory,
RelativeDirectory subdirectory,
Set<JavaFileObject.Kind> fileKinds,
boolean recurse,
ListBuffer<JavaFileObject> resultList) {
Path d;
try {
d = subdirectory.resolveAgainst(directory);
} catch (InvalidPathException ignore) {
return;
}
if (!Files.exists(d)) {
return;
}
if (!caseMapCheck(d, subdirectory)) {
return;
}
java.util.List<Path> files;
try (Stream<Path> s = Files.list(d)) {
files = (sortFiles == null ? s : s.sorted(sortFiles)).collect(Collectors.toList());
} catch (IOException ignore) {
return;
}
if (realDirectory == null)
realDirectory = fsInfo.getCanonicalFile(directory);
for (Path f: files) {
String fname = f.getFileName().toString();
if (fname.endsWith("/"))
fname = fname.substring(0, fname.length() - 1);
if (Files.isDirectory(f)) {
if (recurse && SourceVersion.isIdentifier(fname)) {
listDirectory(directory, realDirectory,
new RelativeDirectory(subdirectory, fname),
fileKinds,
recurse,
resultList);
}
} else {
if (isValidFile(fname, fileKinds)) {
RelativeFile file = new RelativeFile(subdirectory, fname);
JavaFileObject fe = PathFileObject.forDirectoryPath(this,
file.resolveAgainst(realDirectory), directory, file);
resultList.append(fe);
}
}
}
}
/**
* Insert all files in subdirectory subdirectory of archive archivePath
* which match fileKinds into resultList
*/
private void listArchive(Path archivePath,
RelativeDirectory subdirectory,
Set<JavaFileObject.Kind> fileKinds,
boolean recurse,
ListBuffer<JavaFileObject> resultList)
throws IOException {
FileSystem fs = getFileSystem(archivePath);
if (fs == null) {
return;
}
Path containerSubdir = subdirectory.resolveAgainst(fs);
if (!Files.exists(containerSubdir)) {
return;
}
int maxDepth = (recurse ? Integer.MAX_VALUE : 1);
Set<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
Files.walkFileTree(containerSubdir, opts, maxDepth,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (isValid(dir.getFileName())) {
return FileVisitResult.CONTINUE;
} else {
return FileVisitResult.SKIP_SUBTREE;
}
}
boolean isValid(Path fileName) {
if (fileName == null) {
return true;
} else {
String name = fileName.toString();
if (name.endsWith("/")) {
name = name.substring(0, name.length() - 1);
}
return SourceVersion.isIdentifier(name);
}
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (attrs.isRegularFile() && fileKinds.contains(getKind(file.getFileName().toString()))) {
JavaFileObject fe = PathFileObject.forJarPath(
JavacFileManager.this, file, archivePath);
resultList.append(fe);
}
return FileVisitResult.CONTINUE;
}
});
}
/**
* container is a directory, a zip file, or a non-existant path.
* Insert all files in subdirectory subdirectory of container which
* match fileKinds into resultList
*/
private void listContainer(Path container,
RelativeDirectory subdirectory,
Set<JavaFileObject.Kind> fileKinds,
boolean recurse,
ListBuffer<JavaFileObject> resultList)
throws IOException {
if (Files.isRegularFile(container) && container.equals(Locations.thisSystemModules)) {
try {
listJRTImage(subdirectory,
fileKinds,
recurse,
resultList);
} catch (IOException ex) {
ex.printStackTrace(System.err);
log.error("error.reading.file", container, getMessage(ex));
}
return;
}
if (Files.isDirectory(container)) {
listDirectory(container, null,
subdirectory,
fileKinds,
recurse,
resultList);
return;
}
if (Files.isRegularFile(container)) {
listArchive(container,
subdirectory,
fileKinds,
recurse,
resultList);
}
}
private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
JavaFileObject.Kind kind = getKind(s);
return fileKinds.contains(kind);
}
private static final boolean fileSystemIsCaseSensitive =
File.separatorChar == '/';
/** Hack to make Windows case sensitive. Test whether given path
* ends in a string of characters with the same case as given name.
* Ignore file separators in both path and name.
*/
private boolean caseMapCheck(Path f, RelativePath name) {
if (fileSystemIsCaseSensitive) return true;
// Note that toRealPath() returns the case-sensitive
// spelled file name.
String path;
char sep;
try {
path = f.toRealPath(LinkOption.NOFOLLOW_LINKS).toString();
sep = f.getFileSystem().getSeparator().charAt(0);
} catch (IOException ex) {
return false;
}
char[] pcs = path.toCharArray();
char[] ncs = name.path.toCharArray();
int i = pcs.length - 1;
int j = ncs.length - 1;
while (i >= 0 && j >= 0) {
while (i >= 0 && pcs[i] == sep) i--;
while (j >= 0 && ncs[j] == '/') j--;
if (i >= 0 && j >= 0) {
if (pcs[i] != ncs[j]) return false;
i--;
j--;
}
}
return j < 0;
}
private FileSystem getFileSystem(Path path) throws IOException {
Path realPath = fsInfo.getCanonicalFile(path);
FileSystem fs = fileSystems.get(realPath);
if (fs == null) {
fileSystems.put(realPath, fs = FileSystems.newFileSystem(realPath, null));
}
return fs;
}
private final Map<Path,FileSystem> fileSystems = new HashMap<>();
/** Flush any output resources.
*/
@Override @DefinedBy(Api.COMPILER)
public void flush() {
contentCache.clear();
}
/**
* Close the JavaFileManager, releasing resources.
*/
@Override @DefinedBy(Api.COMPILER)
public void close() throws IOException {
if (deferredCloseTimeout > 0) {
deferredClose();
return;
}
locations.close();
for (FileSystem fs: fileSystems.values()) {
fs.close();
}
fileSystems.clear();
contentCache.clear();
}
@Override @DefinedBy(Api.COMPILER)
public ClassLoader getClassLoader(Location location) {
nullCheck(location);
Iterable<? extends File> path = getLocation(location);
if (path == null)
return null;
ListBuffer<URL> lb = new ListBuffer<>();
for (File f: path) {
try {
lb.append(f.toURI().toURL());
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
}
return getClassLoader(lb.toArray(new URL[lb.size()]));
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<JavaFileObject> list(Location location,
String packageName,
Set<JavaFileObject.Kind> kinds,
boolean recurse)
throws IOException
{
// validatePackageName(packageName);
nullCheck(packageName);
nullCheck(kinds);
Iterable<? extends Path> path = getLocationAsPaths(location);
if (path == null)
return List.nil();
RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName);
ListBuffer<JavaFileObject> results = new ListBuffer<>();
for (Path directory : path)
listContainer(directory, subdirectory, kinds, recurse, results);
return results.toList();
}
@Override @DefinedBy(Api.COMPILER)
public String inferBinaryName(Location location, JavaFileObject file) {
Objects.requireNonNull(file);
Objects.requireNonNull(location);
// Need to match the path semantics of list(location, ...)
Iterable<? extends Path> path = getLocationAsPaths(location);
if (path == null) {
return null;
}
if (file instanceof PathFileObject) {
return ((PathFileObject) file).inferBinaryName(path);
} else
throw new IllegalArgumentException(file.getClass().getName());
}
@Override @DefinedBy(Api.COMPILER)
public boolean isSameFile(FileObject a, FileObject b) {
nullCheck(a);
nullCheck(b);
if (a instanceof PathFileObject && b instanceof PathFileObject)
return ((PathFileObject) a).isSameFile((PathFileObject) b);
return a.equals(b);
}
@Override @DefinedBy(Api.COMPILER)
public boolean hasLocation(Location location) {
nullCheck(location);
return locations.hasLocation(location);
}
@Override @DefinedBy(Api.COMPILER)
public JavaFileObject getJavaFileForInput(Location location,
String className,
JavaFileObject.Kind kind)
throws IOException
{
nullCheck(location);
// validateClassName(className);
nullCheck(className);
nullCheck(kind);
if (!sourceOrClass.contains(kind))
throw new IllegalArgumentException("Invalid kind: " + kind);
return getFileForInput(location, RelativeFile.forClass(className, kind));
}
@Override @DefinedBy(Api.COMPILER)
public FileObject getFileForInput(Location location,
String packageName,
String relativeName)
throws IOException
{
nullCheck(location);
// validatePackageName(packageName);
nullCheck(packageName);
if (!isRelativeUri(relativeName))
throw new IllegalArgumentException("Invalid relative name: " + relativeName);
RelativeFile name = packageName.length() == 0
? new RelativeFile(relativeName)
: new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
return getFileForInput(location, name);
}
private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException {
Iterable<? extends Path> path = getLocationAsPaths(location);
if (path == null)
return null;
for (Path file: path) {
if (file.equals(Locations.thisSystemModules)) {
JRTIndex.Entry e = getJRTIndex().getEntry(name.dirname());
if (symbolFileEnabled && e.ctSym.hidden)
continue;
Path p = e.files.get(name.basename());
if (p != null)
return PathFileObject.forJRTPath(this, p);
} else if (Files.isDirectory(file)) {
try {
Path f = name.resolveAgainst(file);
if (Files.exists(f))
return PathFileObject.forSimplePath(this,
fsInfo.getCanonicalFile(f), f);
} catch (InvalidPathException ignore) {
}
} else if (Files.isRegularFile(file)) {
FileSystem fs = getFileSystem(file);
if (fs != null) {
Path fsRoot = fs.getRootDirectories().iterator().next();
Path f = name.resolveAgainst(fsRoot);
if (Files.exists(f))
return PathFileObject.forJarPath(this, f, file);
}
}
}
return null;
}
@Override @DefinedBy(Api.COMPILER)
public JavaFileObject getJavaFileForOutput(Location location,
String className,
JavaFileObject.Kind kind,
FileObject sibling)
throws IOException
{
nullCheck(location);
// validateClassName(className);
nullCheck(className);
nullCheck(kind);
if (!sourceOrClass.contains(kind))
throw new IllegalArgumentException("Invalid kind: " + kind);
return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling);
}
@Override @DefinedBy(Api.COMPILER)
public FileObject getFileForOutput(Location location,
String packageName,
String relativeName,
FileObject sibling)
throws IOException
{
nullCheck(location);
// validatePackageName(packageName);
nullCheck(packageName);
if (!isRelativeUri(relativeName))
throw new IllegalArgumentException("Invalid relative name: " + relativeName);
RelativeFile name = packageName.length() == 0
? new RelativeFile(relativeName)
: new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName);
return getFileForOutput(location, name, sibling);
}
private JavaFileObject getFileForOutput(Location location,
RelativeFile fileName,
FileObject sibling)
throws IOException
{
Path dir;
if (location == CLASS_OUTPUT) {
if (getClassOutDir() != null) {
dir = getClassOutDir();
} else {
String baseName = fileName.basename();
if (sibling != null && sibling instanceof PathFileObject) {
return ((PathFileObject) sibling).getSibling(baseName);
} else {
Path p = Paths.get(baseName);
Path real = fsInfo.getCanonicalFile(p);
return PathFileObject.forSimplePath(this, real, p);
}
}
} else if (location == SOURCE_OUTPUT) {
dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir());
} else {
Iterable<? extends Path> path = locations.getLocation(location);
dir = null;
for (Path f: path) {
dir = f;
break;
}
}
try {
if (dir == null) {
dir = Paths.get(System.getProperty("user.dir"));
}
Path path = fileName.resolveAgainst(fsInfo.getCanonicalFile(dir));
return PathFileObject.forDirectoryPath(this, path, dir, fileName);
} catch (InvalidPathException e) {
throw new IOException("bad filename " + fileName, e);
}
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
Iterable<? extends File> files)
{
ArrayList<PathFileObject> result;
if (files instanceof Collection<?>)
result = new ArrayList<>(((Collection<?>)files).size());
else
result = new ArrayList<>();
for (File f: files) {
Objects.requireNonNull(f);
Path p = f.toPath();
result.add(PathFileObject.forSimplePath(this,
fsInfo.getCanonicalFile(p), p));
}
return result;
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths(
Iterable<? extends Path> paths)
{
ArrayList<PathFileObject> result;
if (paths instanceof Collection<?>)
result = new ArrayList<>(((Collection<?>)paths).size());
else
result = new ArrayList<>();
for (Path p: paths)
result.add(PathFileObject.forSimplePath(this,
fsInfo.getCanonicalFile(p), p));
return result;
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files)));
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<? extends JavaFileObject> getJavaFileObjects(Path... paths) {
return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths)));
}
@Override @DefinedBy(Api.COMPILER)
public void setLocation(Location location,
Iterable<? extends File> searchpath)
throws IOException
{
nullCheck(location);
locations.setLocation(location, asPaths(searchpath));
}
@Override @DefinedBy(Api.COMPILER)
public void setLocationFromPaths(Location location,
Iterable<? extends Path> searchpath)
throws IOException
{
nullCheck(location);
locations.setLocation(location, nullCheck(searchpath));
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<? extends File> getLocation(Location location) {
nullCheck(location);
return asFiles(locations.getLocation(location));
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<? extends Path> getLocationAsPaths(Location location) {
nullCheck(location);
return locations.getLocation(location);
}
private Path getClassOutDir() {
return locations.getOutputLocation(CLASS_OUTPUT);
}
private Path getSourceOutDir() {
return locations.getOutputLocation(SOURCE_OUTPUT);
}
@Override @DefinedBy(Api.COMPILER)
public Location getModuleLocation(Location location, String moduleName) throws IOException {
nullCheck(location);
nullCheck(moduleName);
return locations.getModuleLocation(location, moduleName);
}
@Override @DefinedBy(Api.COMPILER)
public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws IOException {
nullCheck(location);
nullCheck(service);
if (location.isModuleLocation()) {
Collection<Path> paths = locations.getLocation(location);
ModuleFinder finder = ModuleFinder.of(paths.toArray(new Path[paths.size()]));
Layer bootLayer = Layer.boot();
Configuration cf = bootLayer.configuration().resolveRequiresAndUses(ModuleFinder.empty(), finder, Collections.emptySet());
Layer layer = bootLayer.defineModulesWithOneLoader(cf, ClassLoader.getSystemClassLoader());
return ServiceLoaderHelper.load(layer, service);
} else {
return ServiceLoader.load(service, getClassLoader(location));
}
}
@Override @DefinedBy(Api.COMPILER)
public Location getModuleLocation(Location location, JavaFileObject fo, String pkgName) throws IOException {
nullCheck(location);
if (!(fo instanceof PathFileObject))
throw new IllegalArgumentException(fo.getName());
int depth = 1; // allow 1 for filename
if (pkgName != null && !pkgName.isEmpty()) {
depth += 1;
for (int i = 0; i < pkgName.length(); i++) {
switch (pkgName.charAt(i)) {
case '/': case '.':
depth++;
}
}
}
Path p = Locations.normalize(((PathFileObject) fo).path);
int fc = p.getNameCount();
if (depth < fc) {
Path root = p.getRoot();
Path subpath = p.subpath(0, fc - depth);
Path dir = (root == null) ? subpath : root.resolve(subpath);
// need to find dir in location
return locations.getModuleLocation(location, dir);
} else {
return null;
}
}
@Override @DefinedBy(Api.COMPILER)
public String inferModuleName(Location location) {
nullCheck(location);
return locations.inferModuleName(location);
}
@Override @DefinedBy(Api.COMPILER)
public Iterable<Set<Location>> listModuleLocations(Location location) throws IOException {
nullCheck(location);
return locations.listModuleLocations(location);
}
@Override @DefinedBy(Api.COMPILER)
public Path asPath(FileObject file) {
if (file instanceof PathFileObject) {
return ((PathFileObject) file).path;
} else
throw new IllegalArgumentException(file.getName());
}
/**
* Enforces the specification of a "relative" name as used in
* {@linkplain #getFileForInput(Location,String,String)
* getFileForInput}. This method must follow the rules defined in
* that method, do not make any changes without consulting the
* specification.
*/
protected static boolean isRelativeUri(URI uri) {
if (uri.isAbsolute())
return false;
String path = uri.normalize().getPath();
if (path.length() == 0 /* isEmpty() is mustang API */)
return false;
if (!path.equals(uri.getPath())) // implicitly checks for embedded . and ..
return false;
if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../"))
return false;
return true;
}
// Convenience method
protected static boolean isRelativeUri(String u) {
try {
return isRelativeUri(new URI(u));
} catch (URISyntaxException e) {
return false;
}
}
/**
* Converts a relative file name to a relative URI. This is
* different from File.toURI as this method does not canonicalize
* the file before creating the URI. Furthermore, no schema is
* used.
* @param file a relative file name
* @return a relative URI
* @throws IllegalArgumentException if the file name is not
* relative according to the definition given in {@link
* javax.tools.JavaFileManager#getFileForInput}
*/
public static String getRelativeName(File file) {
if (!file.isAbsolute()) {
String result = file.getPath().replace(File.separatorChar, '/');
if (isRelativeUri(result))
return result;
}
throw new IllegalArgumentException("Invalid relative path: " + file);
}
/**
* Get a detail message from an IOException.
* Most, but not all, instances of IOException provide a non-null result
* for getLocalizedMessage(). But some instances return null: in these
* cases, fallover to getMessage(), and if even that is null, return the
* name of the exception itself.
* @param e an IOException
* @return a string to include in a compiler diagnostic
*/
public static String getMessage(IOException e) {
String s = e.getLocalizedMessage();
if (s != null)
return s;
s = e.getMessage();
if (s != null)
return s;
return e.toString();
}
/* Converters between files and paths.
* These are temporary until we can update the StandardJavaFileManager API.
*/
private static Iterable<Path> asPaths(final Iterable<? extends File> files) {
if (files == null)
return null;
return () -> new Iterator<Path>() {
Iterator<? extends File> iter = files.iterator();
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public Path next() {
return iter.next().toPath();
}
};
}
private static Iterable<File> asFiles(final Iterable<? extends Path> paths) {
if (paths == null)
return null;
return () -> new Iterator<File>() {
Iterator<? extends Path> iter = paths.iterator();
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public File next() {
try {
return iter.next().toFile();
} catch (UnsupportedOperationException e) {
throw new IllegalStateException(e);
}
}
};
}
}