package com.sun.tools.javac.file;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.ProviderNotFoundException;
import java.nio.file.spi.FileSystemProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import javax.lang.model.SourceVersion;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardJavaFileManager.PathFactory;
import javax.tools.StandardLocation;
import com.sun.tools.javac.code.Lint;
import com.sun.tools.javac.code.Lint.LintCategory;
import com.sun.tools.javac.main.Option;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.JDK9Wrappers;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.jvm.ModuleNameReader;
import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.Pair;
import com.sun.tools.javac.util.StringUtils;
import static javax.tools.StandardLocation.PLATFORM_CLASS_PATH;
import static com.sun.tools.javac.main.Option.BOOT_CLASS_PATH;
import static com.sun.tools.javac.main.Option.DJAVA_ENDORSED_DIRS;
import static com.sun.tools.javac.main.Option.DJAVA_EXT_DIRS;
import static com.sun.tools.javac.main.Option.ENDORSEDDIRS;
import static com.sun.tools.javac.main.Option.EXTDIRS;
import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH;
import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_APPEND;
import static com.sun.tools.javac.main.Option.XBOOTCLASSPATH_PREPEND;
public class Locations {
private Log log;
private FSInfo fsInfo;
private boolean warn;
private ModuleNameReader moduleNameReader;
private PathFactory pathFactory = Paths::get;
static final Path javaHome = FileSystems.getDefault().getPath(System.getProperty("java.home"));
static final Path thisSystemModules = javaHome.resolve("lib").resolve("modules");
Map<Path, FileSystem> fileSystems = new LinkedHashMap<>();
List<Closeable> closeables = new ArrayList<>();
private Map<String,String> fsEnv = Collections.emptyMap();
Locations() {
initHandlers();
}
Path getPath(String first, String... more) {
try {
return pathFactory.getPath(first, more);
} catch (InvalidPathException ipe) {
throw new IllegalArgumentException(ipe);
}
}
public void close() throws IOException {
ListBuffer<IOException> list = new ListBuffer<>();
closeables.forEach(closeable -> {
try {
closeable.close();
} catch (IOException ex) {
list.add(ex);
}
});
if (list.nonEmpty()) {
IOException ex = new IOException();
for (IOException e: list)
ex.addSuppressed(e);
throw ex;
}
}
void update(Log log, boolean warn, FSInfo fsInfo) {
this.log = log;
this.warn = warn;
this.fsInfo = fsInfo;
}
void setPathFactory(PathFactory f) {
pathFactory = f;
}
boolean isDefaultBootClassPath() {
BootClassPathLocationHandler h
= (BootClassPathLocationHandler) getHandler(PLATFORM_CLASS_PATH);
return h.isDefault();
}
private Iterable<Path> getPathEntries(String searchPath) {
return getPathEntries(searchPath, null);
}
private Iterable<Path> getPathEntries(String searchPath, Path emptyPathDefault) {
ListBuffer<Path> entries = new ListBuffer<>();
for (String s: searchPath.split(Pattern.quote(File.pathSeparator), -1)) {
if (s.isEmpty()) {
if (emptyPathDefault != null) {
entries.add(emptyPathDefault);
}
} else {
try {
entries.add(getPath(s));
} catch (IllegalArgumentException e) {
if (warn) {
log.warning(LintCategory.PATH, "invalid.path", s);
}
}
}
}
return entries;
}
public void setMultiReleaseValue(String multiReleaseValue) {
fsEnv = Collections.singletonMap("multi-release", multiReleaseValue);
}
private boolean contains(Collection<Path> searchPath, Path file) throws IOException {
if (searchPath == null) {
return false;
}
Path enclosingJar = null;
if (file.getFileSystem().provider() == fsInfo.getJarFSProvider()) {
URI uri = file.toUri();
if (uri.getScheme().equals("jar")) {
String ssp = uri.getSchemeSpecificPart();
int sep = ssp.lastIndexOf("!");
if (ssp.startsWith("file:") && sep > 0) {
enclosingJar = Paths.get(URI.create(ssp.substring(0, sep)));
}
}
}
Path nf = normalize(file);
for (Path p : searchPath) {
Path np = normalize(p);
if (np.getFileSystem() == nf.getFileSystem()
&& Files.isDirectory(np)
&& nf.startsWith(np)) {
return true;
}
if (enclosingJar != null
&& Files.isSameFile(enclosingJar, np)) {
return true;
}
}
return false;
}
private class SearchPath extends LinkedHashSet<Path> {
private static final long serialVersionUID = 0;
private boolean expandJarClassPaths = false;
private final Set<Path> canonicalValues = new HashSet<>();
public SearchPath expandJarClassPaths(boolean x) {
expandJarClassPaths = x;
return this;
}
private Path emptyPathDefault = null;
public SearchPath emptyPathDefault(Path x) {
emptyPathDefault = x;
return this;
}
public SearchPath addDirectories(String dirs, boolean warn) {
boolean prev = expandJarClassPaths;
expandJarClassPaths = true;
try {
if (dirs != null) {
for (Path dir : getPathEntries(dirs)) {
addDirectory(dir, warn);
}
}
return this;
} finally {
expandJarClassPaths = prev;
}
}
public SearchPath addDirectories(String dirs) {
return addDirectories(dirs, warn);
}
private void addDirectory(Path dir, boolean warn) {
if (!Files.isDirectory(dir)) {
if (warn) {
log.warning(Lint.LintCategory.PATH,
"dir.path.element.not.found", dir);
}
return;
}
try (Stream<Path> s = Files.list(dir)) {
s.filter(Locations.this::isArchive)
.forEach(dirEntry -> addFile(dirEntry, warn));
} catch (IOException ignore) {
}
}
public SearchPath addFiles(String files, boolean warn) {
if (files != null) {
addFiles(getPathEntries(files, emptyPathDefault), warn);
}
return this;
}
public SearchPath addFiles(String files) {
return addFiles(files, warn);
}
public SearchPath addFiles(Iterable<? extends Path> files, boolean warn) {
if (files != null) {
for (Path file : files) {
addFile(file, warn);
}
}
return this;
}
public SearchPath addFiles(Iterable<? extends Path> files) {
return addFiles(files, warn);
}
public void addFile(Path file, boolean warn) {
if (contains(file)) {
return;
}
if (!fsInfo.exists(file)) {
if (warn) {
log.warning(Lint.LintCategory.PATH,
"path.element.not.found", file);
}
super.add(file);
return;
}
Path canonFile = fsInfo.getCanonicalFile(file);
if (canonicalValues.contains(canonFile)) {
return;
}
if (fsInfo.isFile(file)) {
if ( !file.getFileName().toString().endsWith(".jmod")
&& !file.endsWith("modules")) {
if (!isArchive(file)) {
try {
FileSystems.newFileSystem(file, null).close();
if (warn) {
log.warning(Lint.LintCategory.PATH,
"unexpected.archive.file", file);
}
} catch (IOException | ProviderNotFoundException e) {
if (warn) {
log.warning(Lint.LintCategory.PATH,
"invalid.archive.file", file);
}
return;
}
} else {
if (fsInfo.getJarFSProvider() == null) {
log.error(Errors.NoZipfsForArchive(file));
return ;
}
}
}
}
super.add(file);
canonicalValues.add(canonFile);
if (expandJarClassPaths && fsInfo.isFile(file) && !file.endsWith("modules")) {
addJarClassPath(file, warn);
}
}
private void addJarClassPath(Path jarFile, boolean warn) {
try {
for (Path f : fsInfo.getJarClassPath(jarFile)) {
addFile(f, warn);
}
} catch (IOException e) {
log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e));
}
}
}
protected static abstract class LocationHandler {
abstract boolean handleOption(Option option, String value);
boolean isSet() {
return (getPaths() != null);
}
abstract Collection<Path> getPaths();
abstract void setPaths(Iterable<? extends Path> paths) throws IOException;
abstract void setPathsForModule(String moduleName, Iterable<? extends Path> paths)
throws IOException;
Location getLocationForModule(String moduleName) throws IOException {
return null;
}
Location getLocationForModule(Path file) throws IOException {
return null;
}
String inferModuleName() {
return null;
}
Iterable<Set<Location>> listLocationsForModules() throws IOException {
return null;
}
abstract boolean contains(Path file) throws IOException;
}
private static abstract class BasicLocationHandler extends LocationHandler {
final Location location;
final Set<Option> options;
protected BasicLocationHandler(Location location, Option... options) {
this.location = location;
this.options = options.length == 0
? EnumSet.noneOf(Option.class)
: EnumSet.copyOf(Arrays.asList(options));
}
@Override
void setPathsForModule(String moduleName, Iterable<? extends Path> files) throws IOException {
throw new UnsupportedOperationException("not supported for " + location);
}
protected Path checkSingletonDirectory(Iterable<? extends Path> paths) throws IOException {
Iterator<? extends Path> pathIter = paths.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");
}
checkDirectory(path);
return path;
}
protected Path checkDirectory(Path path) throws IOException {
Objects.requireNonNull(path);
if (!Files.exists(path)) {
throw new FileNotFoundException(path + ": does not exist");
}
if (!Files.isDirectory(path)) {
throw new IOException(path + ": not a directory");
}
return path;
}
}
private class OutputLocationHandler extends BasicLocationHandler {
private Path outputDir;
private ModuleTable moduleTable;
OutputLocationHandler(Location location, Option... options) {
super(location, options);
}
@Override
boolean handleOption(Option option, String value) {
if (!options.contains(option)) {
return false;
}
outputDir = (value == null) ? null : getPath(value);
return true;
}
@Override
Collection<Path> getPaths() {
return (outputDir == null) ? null : Collections.singleton(outputDir);
}
@Override
void setPaths(Iterable<? extends Path> paths) throws IOException {
if (paths == null) {
outputDir = null;
} else {
outputDir = checkSingletonDirectory(paths);
}
moduleTable = null;
listed = false;
}
@Override
Location getLocationForModule(String name) {
if (moduleTable == null) {
moduleTable = new ModuleTable();
}
ModuleLocationHandler l = moduleTable.get(name);
if (l == null) {
Path out = outputDir.resolve(name);
l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
name, Collections.singletonList(out), true);
moduleTable.add(l);
}
return l;
}
@Override
void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
Path out = checkSingletonDirectory(paths);
if (moduleTable == null) {
moduleTable = new ModuleTable();
}
ModuleLocationHandler l = moduleTable.get(name);
if (l == null) {
l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
name, Collections.singletonList(out), true);
moduleTable.add(l);
} else {
l.searchPath = Collections.singletonList(out);
moduleTable.updatePaths(l);
}
}
@Override
Location getLocationForModule(Path file) {
return (moduleTable == null) ? null : moduleTable.get(file);
}
private boolean listed;
@Override
Iterable<Set<Location>> listLocationsForModules() throws IOException {
if (!listed && outputDir != null) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(outputDir)) {
for (Path p : stream) {
getLocationForModule(p.getFileName().toString());
}
}
listed = true;
}
if (moduleTable == null || moduleTable.isEmpty())
return Collections.emptySet();
return Collections.singleton(moduleTable.locations());
}
@Override
boolean contains(Path file) throws IOException {
if (moduleTable != null) {
return moduleTable.contains(file);
} else {
return (outputDir) != null && normalize(file).startsWith(normalize(outputDir));
}
}
}
private class SimpleLocationHandler extends BasicLocationHandler {
protected Collection<Path> searchPath;
SimpleLocationHandler(Location location, Option... options) {
super(location, options);
}
@Override
boolean handleOption(Option option, String value) {
if (!options.contains(option)) {
return false;
}
searchPath = value == null ? null
: Collections.unmodifiableCollection(createPath().addFiles(value));
return true;
}
@Override
Collection<Path> getPaths() {
return searchPath;
}
@Override
void setPaths(Iterable<? extends Path> files) {
SearchPath p;
if (files == null) {
p = computePath(null);
} else {
p = createPath().addFiles(files);
}
searchPath = Collections.unmodifiableCollection(p);
}
protected SearchPath computePath(String value) {
return createPath().addFiles(value);
}
protected SearchPath createPath() {
return new SearchPath();
}
@Override
boolean contains(Path file) throws IOException {
return Locations.this.contains(searchPath, file);
}
}
private class ClassPathLocationHandler extends SimpleLocationHandler {
ClassPathLocationHandler() {
super(StandardLocation.CLASS_PATH, Option.CLASS_PATH);
}
@Override
Collection<Path> getPaths() {
lazy();
return searchPath;
}
@Override
protected SearchPath computePath(String value) {
String cp = value;
if (cp == null) {
cp = System.getProperty("env.class.path");
}
if (cp == null && System.getProperty("application.home") == null) {
cp = System.getProperty("java.class.path");
}
if (cp == null) {
cp = ".";
}
return createPath().addFiles(cp);
}
@Override
protected SearchPath createPath() {
return new SearchPath()
.expandJarClassPaths(true)
.emptyPathDefault(getPath("."));
}
private void lazy() {
if (searchPath == null) {
setPaths(null);
}
}
}
private class BootClassPathLocationHandler extends BasicLocationHandler {
private Collection<Path> searchPath;
final Map<Option, String> optionValues = new EnumMap<>(Option.class);
private boolean isDefault;
BootClassPathLocationHandler() {
super(StandardLocation.PLATFORM_CLASS_PATH,
Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH,
Option.XBOOTCLASSPATH_PREPEND,
Option.XBOOTCLASSPATH_APPEND,
Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS,
Option.EXTDIRS, Option.DJAVA_EXT_DIRS);
}
boolean isDefault() {
lazy();
return isDefault;
}
@Override
boolean handleOption(Option option, String value) {
if (!options.contains(option)) {
return false;
}
option = canonicalize(option);
optionValues.put(option, value);
if (option == BOOT_CLASS_PATH) {
optionValues.remove(XBOOTCLASSPATH_PREPEND);
optionValues.remove(XBOOTCLASSPATH_APPEND);
}
searchPath = null;
return true;
}
private Option canonicalize(Option option) {
switch (option) {
case XBOOTCLASSPATH:
return Option.BOOT_CLASS_PATH;
case DJAVA_ENDORSED_DIRS:
return Option.ENDORSEDDIRS;
case DJAVA_EXT_DIRS:
return Option.EXTDIRS;
default:
return option;
}
}
@Override
Collection<Path> getPaths() {
lazy();
return searchPath;
}
@Override
void setPaths(Iterable<? extends Path> files) {
if (files == null) {
searchPath = null;
} else {
isDefault = false;
SearchPath p = new SearchPath().addFiles(files, false);
searchPath = Collections.unmodifiableCollection(p);
optionValues.clear();
}
}
SearchPath computePath() throws IOException {
SearchPath path = new SearchPath();
String bootclasspathOpt = optionValues.get(BOOT_CLASS_PATH);
String endorseddirsOpt = optionValues.get(ENDORSEDDIRS);
String extdirsOpt = optionValues.get(EXTDIRS);
String xbootclasspathPrependOpt = optionValues.get(XBOOTCLASSPATH_PREPEND);
String xbootclasspathAppendOpt = optionValues.get(XBOOTCLASSPATH_APPEND);
path.addFiles(xbootclasspathPrependOpt);
if (endorseddirsOpt != null) {
path.addDirectories(endorseddirsOpt);
} else {
path.addDirectories(System.getProperty("java.endorsed.dirs"), false);
}
if (bootclasspathOpt != null) {
path.addFiles(bootclasspathOpt);
} else {
Collection<Path> systemClasses = systemClasses();
if (systemClasses != null) {
path.addFiles(systemClasses, false);
} else {
String files = System.getProperty("sun.boot.class.path");
path.addFiles(files, false);
}
}
path.addFiles(xbootclasspathAppendOpt);
if (extdirsOpt != null) {
path.addDirectories(extdirsOpt);
} else {
Path jfxrt = javaHome.resolve("lib/jfxrt.jar");
if (Files.exists(jfxrt)) {
path.addFile(jfxrt, false);
}
path.addDirectories(System.getProperty("java.ext.dirs"), false);
}
isDefault =
(xbootclasspathPrependOpt == null)
&& (bootclasspathOpt == null)
&& (xbootclasspathAppendOpt == null);
return path;
}
private Collection<Path> systemClasses() throws IOException {
if (Files.isRegularFile(thisSystemModules)) {
return Collections.singleton(thisSystemModules);
}
Path modules = javaHome.resolve("modules");
if (Files.isDirectory(modules.resolve("java.base"))) {
try (Stream<Path> listedModules = Files.list(modules)) {
return listedModules.collect(Collectors.toList());
}
}
return null;
}
private void lazy() {
if (searchPath == null) {
try {
searchPath = Collections.unmodifiableCollection(computePath());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
@Override
boolean contains(Path file) throws IOException {
return Locations.this.contains(searchPath, file);
}
}
private class ModuleLocationHandler extends LocationHandler implements Location {
private final LocationHandler parent;
private final String name;
private final String moduleName;
private final boolean output;
Collection<Path> searchPath;
ModuleLocationHandler(LocationHandler parent, String name, String moduleName,
Collection<Path> searchPath, boolean output) {
this.parent = parent;
this.name = name;
this.moduleName = moduleName;
this.searchPath = searchPath;
this.output = output;
}
@Override @DefinedBy(Api.COMPILER)
public String getName() {
return name;
}
@Override @DefinedBy(Api.COMPILER)
public boolean isOutputLocation() {
return output;
}
@Override
boolean handleOption(Option option, String value) {
throw new UnsupportedOperationException();
}
@Override
Collection<Path> getPaths() {
return Collections.unmodifiableCollection(searchPath);
}
@Override
void setPaths(Iterable<? extends Path> paths) throws IOException {
parent.setPathsForModule(moduleName, paths);
}
@Override
void setPathsForModule(String moduleName, Iterable<? extends Path> paths) {
throw new UnsupportedOperationException("not supported for " + name);
}
@Override
String inferModuleName() {
return moduleName;
}
@Override
boolean contains(Path file) throws IOException {
return Locations.this.contains(searchPath, file);
}
@Override
public String toString() {
return name;
}
}
private class ModuleTable {
private final Map<String, ModuleLocationHandler> nameMap = new LinkedHashMap<>();
private final Map<Path, ModuleLocationHandler> pathMap = new LinkedHashMap<>();
void add(ModuleLocationHandler h) {
nameMap.put(h.moduleName, h);
for (Path p : h.searchPath) {
pathMap.put(normalize(p), h);
}
}
void updatePaths(ModuleLocationHandler h) {
for (Iterator<Map.Entry<Path, ModuleLocationHandler>> iter = pathMap.entrySet().iterator();
iter.hasNext(); ) {
Map.Entry<Path, ModuleLocationHandler> e = iter.next();
if (e.getValue() == h) {
iter.remove();
}
}
for (Path p : h.searchPath) {
pathMap.put(normalize(p), h);
}
}
ModuleLocationHandler get(String name) {
return nameMap.get(name);
}
ModuleLocationHandler get(Path path) {
while (path != null) {
ModuleLocationHandler l = pathMap.get(path);
if (l != null)
return l;
path = path.getParent();
}
return null;
}
void clear() {
nameMap.clear();
pathMap.clear();
}
boolean isEmpty() {
return nameMap.isEmpty();
}
boolean contains(Path file) throws IOException {
return Locations.this.contains(pathMap.keySet(), file);
}
Set<Location> locations() {
return Collections.unmodifiableSet(nameMap.values().stream().collect(Collectors.toSet()));
}
}
private class ModulePathLocationHandler extends SimpleLocationHandler {
private ModuleTable moduleTable;
ModulePathLocationHandler(Location location, Option... options) {
super(location, options);
}
@Override
public boolean handleOption(Option option, String value) {
if (!options.contains(option)) {
return false;
}
setPaths(value == null ? null : getPathEntries(value));
return true;
}
@Override
public Location getLocationForModule(String moduleName) {
initModuleLocations();
return moduleTable.get(moduleName);
}
@Override
public Location getLocationForModule(Path file) {
initModuleLocations();
return moduleTable.get(file);
}
@Override
Iterable<Set<Location>> listLocationsForModules() {
if (searchPath == null)
return Collections.emptyList();
return ModulePathIterator::new;
}
@Override
boolean contains(Path file) throws IOException {
if (moduleTable == null) {
initModuleLocations();
}
return moduleTable.contains(file);
}
@Override
void setPaths(Iterable<? extends Path> paths) {
if (paths != null) {
for (Path p: paths) {
checkValidModulePathEntry(p);
}
}
super.setPaths(paths);
moduleTable = null;
}
@Override
void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
List<Path> checkedPaths = checkPaths(paths);
initModuleLocations();
ModuleLocationHandler l = moduleTable.get(name);
if (l == null) {
l = new ModuleLocationHandler(this, location.getName() + "[" + name + "]",
name, checkedPaths, true);
moduleTable.add(l);
} else {
l.searchPath = checkedPaths;
moduleTable.updatePaths(l);
}
}
private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
Objects.requireNonNull(paths);
List<Path> validPaths = new ArrayList<>();
for (Path p : paths) {
validPaths.add(checkDirectory(p));
}
return validPaths;
}
private void initModuleLocations() {
if (moduleTable != null) {
return;
}
moduleTable = new ModuleTable();
for (Set<Location> set : listLocationsForModules()) {
for (Location locn : set) {
if (locn instanceof ModuleLocationHandler) {
ModuleLocationHandler l = (ModuleLocationHandler) locn;
if (!moduleTable.nameMap.containsKey(l.moduleName)) {
moduleTable.add(l);
}
}
}
}
}
private void checkValidModulePathEntry(Path p) {
if (!Files.exists(p)) {
return;
}
if (Files.isDirectory(p)) {
return;
}
String name = p.getFileName().toString();
int lastDot = name.lastIndexOf(".");
if (lastDot > 0) {
switch (name.substring(lastDot)) {
case ".jar":
case ".jmod":
return;
}
}
throw new IllegalArgumentException(p.toString());
}
class ModulePathIterator implements Iterator<Set<Location>> {
Iterator<Path> pathIter = searchPath.iterator();
int pathIndex = 0;
Set<Location> next = null;
@Override
public boolean hasNext() {
if (next != null)
return true;
while (next == null) {
if (pathIter.hasNext()) {
Path path = pathIter.next();
if (Files.isDirectory(path)) {
next = scanDirectory(path);
} else {
next = scanFile(path);
}
pathIndex++;
} else
return false;
}
return true;
}
@Override
public Set<Location> next() {
hasNext();
if (next != null) {
Set<Location> result = next;
next = null;
return result;
}
throw new NoSuchElementException();
}
private Set<Location> scanDirectory(Path path) {
Set<Path> paths = new LinkedHashSet<>();
Path moduleInfoClass = null;
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
for (Path entry: stream) {
if (entry.endsWith("module-info.class")) {
moduleInfoClass = entry;
break;
}
paths.add(entry);
}
} catch (DirectoryIteratorException | IOException ignore) {
log.error(Errors.LocnCantReadDirectory(path));
return Collections.emptySet();
}
if (moduleInfoClass != null) {
try {
String moduleName = readModuleName(moduleInfoClass);
String name = location.getName()
+ "[" + pathIndex + ":" + moduleName + "]";
ModuleLocationHandler l = new ModuleLocationHandler(
ModulePathLocationHandler.this, name, moduleName,
Collections.singletonList(path), false);
return Collections.singleton(l);
} catch (ModuleNameReader.BadClassFile e) {
log.error(Errors.LocnBadModuleInfo(path));
return Collections.emptySet();
} catch (IOException e) {
log.error(Errors.LocnCantReadFile(path));
return Collections.emptySet();
}
}
Set<Location> result = new LinkedHashSet<>();
int index = 0;
for (Path entry : paths) {
Pair<String,Path> module = inferModuleName(entry);
if (module == null) {
continue;
}
String moduleName = module.fst;
Path modulePath = module.snd;
String name = location.getName()
+ "[" + pathIndex + "." + (index++) + ":" + moduleName + "]";
ModuleLocationHandler l = new ModuleLocationHandler(
ModulePathLocationHandler.this, name, moduleName,
Collections.singletonList(modulePath), false);
result.add(l);
}
return result;
}
private Set<Location> scanFile(Path path) {
Pair<String,Path> module = inferModuleName(path);
if (module == null) {
return Collections.emptySet();
}
String moduleName = module.fst;
Path modulePath = module.snd;
String name = location.getName()
+ "[" + pathIndex + ":" + moduleName + "]";
ModuleLocationHandler l = new ModuleLocationHandler(
ModulePathLocationHandler.this, name, moduleName,
Collections.singletonList(modulePath), false);
return Collections.singleton(l);
}
private Pair<String,Path> inferModuleName(Path p) {
if (Files.isDirectory(p)) {
if (Files.exists(p.resolve("module-info.class"))) {
String name = p.getFileName().toString();
if (SourceVersion.isName(name))
return new Pair<>(name, p);
}
return null;
}
if (p.getFileName().toString().endsWith(".jar") && fsInfo.exists(p)) {
FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
if (jarFSProvider == null) {
log.error(Errors.NoZipfsForArchive(p));
return null;
}
try (FileSystem fs = jarFSProvider.newFileSystem(p, fsEnv)) {
Path moduleInfoClass = fs.getPath("module-info.class");
if (Files.exists(moduleInfoClass)) {
String moduleName = readModuleName(moduleInfoClass);
return new Pair<>(moduleName, p);
}
Path mf = fs.getPath("META-INF/MANIFEST.MF");
if (Files.exists(mf)) {
try (InputStream in = Files.newInputStream(mf)) {
Manifest man = new Manifest(in);
Attributes attrs = man.getMainAttributes();
if (attrs != null) {
String moduleName = attrs.getValue(new Attributes.Name("Automatic-Module-Name"));
if (moduleName != null) {
if (isModuleName(moduleName)) {
return new Pair<>(moduleName, p);
} else {
log.error(Errors.LocnCantGetModuleNameForJar(p));
return null;
}
}
}
}
}
} catch (ModuleNameReader.BadClassFile e) {
log.error(Errors.LocnBadModuleInfo(p));
return null;
} catch (IOException e) {
log.error(Errors.LocnCantReadFile(p));
return null;
}
String fn = p.getFileName().toString();
String mn = fn.substring(0, fn.length()-4);
Matcher matcher = Pattern.compile("-(\\d+(\\.|$))").matcher(mn);
if (matcher.find()) {
int start = matcher.start();
mn = mn.substring(0, start);
}
mn = mn.replaceAll("[^A-Za-z0-9]", ".")
.replaceAll("(\\.)(\\1)+", ".")
.replaceAll("^\\.", "")
.replaceAll("\\.$", "");
if (!mn.isEmpty()) {
return new Pair<>(mn, p);
}
log.error(Errors.LocnCantGetModuleNameForJar(p));
return null;
}
if (p.getFileName().toString().endsWith(".jmod")) {
try {
JDK9Wrappers.JmodFile.checkMagic(p);
FileSystem fs = fileSystems.get(p);
if (fs == null) {
FileSystemProvider jarFSProvider = fsInfo.getJarFSProvider();
if (jarFSProvider == null) {
log.error(Errors.LocnCantReadFile(p));
return null;
}
fs = jarFSProvider.newFileSystem(p, Collections.emptyMap());
try {
Path moduleInfoClass = fs.getPath("classes/module-info.class");
String moduleName = readModuleName(moduleInfoClass);
Path modulePath = fs.getPath("classes");
fileSystems.put(p, fs);
closeables.add(fs);
fs = null;
return new Pair<>(moduleName, modulePath);
} finally {
if (fs != null)
fs.close();
}
}
} catch (ModuleNameReader.BadClassFile e) {
log.error(Errors.LocnBadModuleInfo(p));
} catch (IOException e) {
log.error(Errors.LocnCantReadFile(p));
return null;
}
}
if (warn && false) {
log.warning(Warnings.LocnUnknownFileOnModulePath(p));
}
return null;
}
private String readModuleName(Path path) throws IOException, ModuleNameReader.BadClassFile {
if (moduleNameReader == null)
moduleNameReader = new ModuleNameReader();
return moduleNameReader.readModuleName(path);
}
}
private boolean isModuleName(String name) {
int next;
int off = 0;
while ((next = name.indexOf('.', off)) != -1) {
String id = name.substring(off, next);
if (!SourceVersion.isName(id))
return false;
off = next+1;
}
String last = name.substring(off);
return SourceVersion.isName(last);
}
}
private class ModuleSourcePathLocationHandler extends BasicLocationHandler {
private ModuleTable moduleTable;
private List<Path> paths;
ModuleSourcePathLocationHandler() {
super(StandardLocation.MODULE_SOURCE_PATH,
Option.MODULE_SOURCE_PATH);
}
@Override
boolean handleOption(Option option, String value) {
init(value);
return true;
}
void init(String value) {
Collection<String> segments = new ArrayList<>();
for (String s: value.split(File.pathSeparator)) {
expandBraces(s, segments);
}
Map<String, List<Path>> map = new LinkedHashMap<>();
List<Path> noSuffixPaths = new ArrayList<>();
boolean anySuffix = false;
final String MARKER = "*";
for (String seg: segments) {
int markStart = seg.indexOf(MARKER);
if (markStart == -1) {
Path p = getPath(seg);
add(map, p, null);
noSuffixPaths.add(p);
} else {
if (markStart == 0 || !isSeparator(seg.charAt(markStart - 1))) {
throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
}
Path prefix = getPath(seg.substring(0, markStart - 1));
Path suffix;
int markEnd = markStart + MARKER.length();
if (markEnd == seg.length()) {
suffix = null;
} else if (!isSeparator(seg.charAt(markEnd))
|| seg.indexOf(MARKER, markEnd) != -1) {
throw new IllegalArgumentException("illegal use of " + MARKER + " in " + seg);
} else {
suffix = getPath(seg.substring(markEnd + 1));
anySuffix = true;
}
add(map, prefix, suffix);
if (suffix == null) {
noSuffixPaths.add(prefix);
}
}
}
initModuleTable(map);
paths = anySuffix ? null : noSuffixPaths;
}
private void initModuleTable(Map<String, List<Path>> map) {
moduleTable = new ModuleTable();
map.forEach((modName, modPath) -> {
boolean hasModuleInfo = modPath.stream().anyMatch(checkModuleInfo);
if (hasModuleInfo) {
String locnName = location.getName() + "[" + modName + "]";
ModuleLocationHandler l = new ModuleLocationHandler(this, locnName, modName,
modPath, false);
moduleTable.add(l);
}
});
}
private final Predicate<Path> checkModuleInfo =
p -> Files.exists(p.resolve("module-info.java"));
private boolean isSeparator(char ch) {
return (ch == File.separatorChar) || (ch == '/');
}
void add(Map<String, List<Path>> map, Path prefix, Path suffix) {
if (!Files.isDirectory(prefix)) {
if (warn) {
String key = Files.exists(prefix)
? "dir.path.element.not.directory"
: "dir.path.element.not.found";
log.warning(Lint.LintCategory.PATH, key, prefix);
}
return;
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(prefix, path -> Files.isDirectory(path))) {
for (Path entry: stream) {
Path path = (suffix == null) ? entry : entry.resolve(suffix);
if (Files.isDirectory(path)) {
String name = entry.getFileName().toString();
List<Path> paths = map.get(name);
if (paths == null)
map.put(name, paths = new ArrayList<>());
paths.add(path);
}
}
} catch (IOException e) {
System.err.println(e);
}
}
private void expandBraces(String value, Collection<String> results) {
int depth = 0;
int start = -1;
String prefix = null;
String suffix = null;
for (int i = 0; i < value.length(); i++) {
switch (value.charAt(i)) {
case '{':
depth++;
if (depth == 1) {
prefix = value.substring(0, i);
suffix = value.substring(getMatchingBrace(value, i) + 1);
start = i + 1;
}
break;
case ',':
if (depth == 1) {
String elem = value.substring(start, i);
expandBraces(prefix + elem + suffix, results);
start = i + 1;
}
break;
case '}':
switch (depth) {
case 0:
throw new IllegalArgumentException("mismatched braces");
case 1:
String elem = value.substring(start, i);
expandBraces(prefix + elem + suffix, results);
return;
default:
depth--;
}
break;
}
}
if (depth > 0)
throw new IllegalArgumentException("mismatched braces");
results.add(value);
}
int getMatchingBrace(String value, int offset) {
int depth = 1;
for (int i = offset + 1; i < value.length(); i++) {
switch (value.charAt(i)) {
case '{':
depth++;
break;
case '}':
if (--depth == 0)
return i;
break;
}
}
throw new IllegalArgumentException("mismatched braces");
}
@Override
boolean isSet() {
return (moduleTable != null);
}
@Override
Collection<Path> getPaths() {
if (paths == null) {
throw new IllegalStateException("paths not available");
}
return paths;
}
@Override
void setPaths(Iterable<? extends Path> files) throws IOException {
Map<String, List<Path>> map = new LinkedHashMap<>();
List<Path> newPaths = new ArrayList<>();
for (Path file : files) {
add(map, file, null);
newPaths.add(file);
}
initModuleTable(map);
paths = Collections.unmodifiableList(newPaths);
}
@Override
void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
List<Path> validPaths = checkPaths(paths);
if (moduleTable == null)
moduleTable = new ModuleTable();
ModuleLocationHandler l = moduleTable.get(name);
if (l == null) {
l = new ModuleLocationHandler(this,
location.getName() + "[" + name + "]",
name,
validPaths,
true);
moduleTable.add(l);
} else {
l.searchPath = validPaths;
moduleTable.updatePaths(l);
}
}
private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
Objects.requireNonNull(paths);
List<Path> validPaths = new ArrayList<>();
for (Path p : paths) {
validPaths.add(checkDirectory(p));
}
return validPaths;
}
@Override
Location getLocationForModule(String name) {
return (moduleTable == null) ? null : moduleTable.get(name);
}
@Override
Location getLocationForModule(Path file) {
return (moduleTable == null) ? null : moduleTable.get(file);
}
@Override
Iterable<Set<Location>> listLocationsForModules() {
if (moduleTable == null)
return Collections.emptySet();
return Collections.singleton(moduleTable.locations());
}
@Override
boolean contains(Path file) throws IOException {
return (moduleTable == null) ? false : moduleTable.contains(file);
}
}
private class SystemModulesLocationHandler extends BasicLocationHandler {
private Path systemJavaHome;
private Path modules;
private ModuleTable moduleTable;
SystemModulesLocationHandler() {
super(StandardLocation.SYSTEM_MODULES, Option.SYSTEM);
systemJavaHome = Locations.javaHome;
}
@Override
boolean handleOption(Option option, String value) {
if (!options.contains(option)) {
return false;
}
if (value == null) {
systemJavaHome = Locations.javaHome;
} else if (value.equals("none")) {
systemJavaHome = null;
} else {
update(getPath(value));
}
modules = null;
return true;
}
@Override
Collection<Path> getPaths() {
return (systemJavaHome == null) ? null : Collections.singleton(systemJavaHome);
}
@Override
void setPaths(Iterable<? extends Path> files) throws IOException {
if (files == null) {
systemJavaHome = null;
} else {
Path dir = checkSingletonDirectory(files);
update(dir);
}
}
@Override
void setPathsForModule(String name, Iterable<? extends Path> paths) throws IOException {
List<Path> checkedPaths = checkPaths(paths);
initSystemModules();
ModuleLocationHandler l = moduleTable.get(name);
if (l == null) {
l = new ModuleLocationHandler(this,
location.getName() + "[" + name + "]",
name,
checkedPaths,
true);
moduleTable.add(l);
} else {
l.searchPath = checkedPaths;
moduleTable.updatePaths(l);
}
}
private List<Path> checkPaths(Iterable<? extends Path> paths) throws IOException {
Objects.requireNonNull(paths);
List<Path> validPaths = new ArrayList<>();
for (Path p : paths) {
validPaths.add(checkDirectory(p));
}
return validPaths;
}
private void update(Path p) {
if (!isCurrentPlatform(p) && !Files.exists(p.resolve("lib").resolve("jrt-fs.jar")) &&
!Files.exists(systemJavaHome.resolve("modules")))
throw new IllegalArgumentException(p.toString());
systemJavaHome = p;
modules = null;
}
private boolean isCurrentPlatform(Path p) {
try {
return Files.isSameFile(p, Locations.javaHome);
} catch (IOException ex) {
throw new IllegalArgumentException(p.toString(), ex);
}
}
@Override
Location getLocationForModule(String name) throws IOException {
initSystemModules();
return moduleTable.get(name);
}
@Override
Location getLocationForModule(Path file) throws IOException {
initSystemModules();
return moduleTable.get(file);
}
@Override
Iterable<Set<Location>> listLocationsForModules() throws IOException {
initSystemModules();
return Collections.singleton(moduleTable.locations());
}
@Override
boolean contains(Path file) throws IOException {
initSystemModules();
return moduleTable.contains(file);
}
private void initSystemModules() throws IOException {
if (moduleTable != null)
return;
if (systemJavaHome == null) {
moduleTable = new ModuleTable();
return;
}
if (modules == null) {
try {
URI jrtURI = URI.create("jrt:/");
FileSystem jrtfs;
if (isCurrentPlatform(systemJavaHome)) {
jrtfs = FileSystems.getFileSystem(jrtURI);
} else {
try {
Map<String, String> attrMap =
Collections.singletonMap("java.home", systemJavaHome.toString());
jrtfs = FileSystems.newFileSystem(jrtURI, attrMap);
} catch (ProviderNotFoundException ex) {
URL javaHomeURL = systemJavaHome.resolve("jrt-fs.jar").toUri().toURL();
ClassLoader currentLoader = Locations.class.getClassLoader();
URLClassLoader fsLoader =
new URLClassLoader(new URL[] {javaHomeURL}, currentLoader);
jrtfs = FileSystems.newFileSystem(jrtURI, Collections.emptyMap(), fsLoader);
closeables.add(fsLoader);
}
closeables.add(jrtfs);
}
modules = jrtfs.getPath("/modules");
} catch (FileSystemNotFoundException | ProviderNotFoundException e) {
modules = systemJavaHome.resolve("modules");
if (!Files.exists(modules))
throw new IOException("can't find system classes", e);
}
}
moduleTable = new ModuleTable();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules, Files::isDirectory)) {
for (Path entry : stream) {
String moduleName = entry.getFileName().toString();
String name = location.getName() + "[" + moduleName + "]";
ModuleLocationHandler h = new ModuleLocationHandler(this,
name, moduleName, Collections.singletonList(entry), false);
moduleTable.add(h);
}
}
}
}
private class PatchModulesLocationHandler extends BasicLocationHandler {
private final ModuleTable moduleTable = new ModuleTable();
PatchModulesLocationHandler() {
super(StandardLocation.PATCH_MODULE_PATH, Option.PATCH_MODULE);
}
@Override
boolean handleOption(Option option, String value) {
if (!options.contains(option)) {
return false;
}
moduleTable.clear();
for (String v : value.split("\0")) {
int eq = v.indexOf('=');
if (eq > 0) {
String moduleName = v.substring(0, eq);
SearchPath mPatchPath = new SearchPath()
.addFiles(v.substring(eq + 1));
String name = location.getName() + "[" + moduleName + "]";
ModuleLocationHandler h = new ModuleLocationHandler(this, name,
moduleName, mPatchPath, false);
moduleTable.add(h);
} else {
log.error(Errors.LocnInvalidArgForXpatch(value));
}
}
return true;
}
@Override
boolean isSet() {
return !moduleTable.isEmpty();
}
@Override
Collection<Path> getPaths() {
throw new UnsupportedOperationException();
}
@Override
void setPaths(Iterable<? extends Path> files) throws IOException {
throw new UnsupportedOperationException();
}
@Override
void setPathsForModule(String moduleName, Iterable<? extends Path> files) throws IOException {
throw new UnsupportedOperationException();
}
@Override
Location getLocationForModule(String name) throws IOException {
return moduleTable.get(name);
}
@Override
Location getLocationForModule(Path file) throws IOException {
return moduleTable.get(file);
}
@Override
Iterable<Set<Location>> listLocationsForModules() throws IOException {
return Collections.singleton(moduleTable.locations());
}
@Override
boolean contains(Path file) throws IOException {
return moduleTable.contains(file);
}
}
Map<Location, LocationHandler> handlersForLocation;
Map<Option, LocationHandler> handlersForOption;
void initHandlers() {
handlersForLocation = new HashMap<>();
handlersForOption = new EnumMap<>(Option.class);
BasicLocationHandler[] handlers = {
new BootClassPathLocationHandler(),
new ClassPathLocationHandler(),
new SimpleLocationHandler(StandardLocation.SOURCE_PATH, Option.SOURCE_PATH),
new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_PATH, Option.PROCESSOR_PATH),
new SimpleLocationHandler(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, Option.PROCESSOR_MODULE_PATH),
new OutputLocationHandler(StandardLocation.CLASS_OUTPUT, Option.D),
new OutputLocationHandler(StandardLocation.SOURCE_OUTPUT, Option.S),
new OutputLocationHandler(StandardLocation.NATIVE_HEADER_OUTPUT, Option.H),
new ModuleSourcePathLocationHandler(),
new PatchModulesLocationHandler(),
new ModulePathLocationHandler(StandardLocation.UPGRADE_MODULE_PATH, Option.UPGRADE_MODULE_PATH),
new ModulePathLocationHandler(StandardLocation.MODULE_PATH, Option.MODULE_PATH),
new SystemModulesLocationHandler(),
};
for (BasicLocationHandler h : handlers) {
handlersForLocation.put(h.location, h);
for (Option o : h.options) {
handlersForOption.put(o, h);
}
}
}
boolean handleOption(Option option, String value) {
LocationHandler h = handlersForOption.get(option);
return (h == null ? false : h.handleOption(option, value));
}
boolean hasLocation(Location location) {
LocationHandler h = getHandler(location);
return (h == null ? false : h.isSet());
}
Collection<Path> getLocation(Location location) {
LocationHandler h = getHandler(location);
return (h == null ? null : h.getPaths());
}
Path getOutputLocation(Location location) {
if (!location.isOutputLocation()) {
throw new IllegalArgumentException();
}
LocationHandler h = getHandler(location);
return ((OutputLocationHandler) h).outputDir;
}
void setLocation(Location location, Iterable<? extends Path> files) throws IOException {
LocationHandler h = getHandler(location);
if (h == null) {
if (location.isOutputLocation()) {
h = new OutputLocationHandler(location);
} else {
h = new SimpleLocationHandler(location);
}
handlersForLocation.put(location, h);
}
h.setPaths(files);
}
Location getLocationForModule(Location location, String name) throws IOException {
LocationHandler h = getHandler(location);
return (h == null ? null : h.getLocationForModule(name));
}
Location getLocationForModule(Location location, Path file) throws IOException {
LocationHandler h = getHandler(location);
return (h == null ? null : h.getLocationForModule(file));
}
void setLocationForModule(Location location, String moduleName,
Iterable<? extends Path> files) throws IOException {
LocationHandler h = getHandler(location);
if (h == null) {
if (location.isOutputLocation()) {
h = new OutputLocationHandler(location);
} else {
h = new ModulePathLocationHandler(location);
}
handlersForLocation.put(location, h);
}
h.setPathsForModule(moduleName, files);
}
String inferModuleName(Location location) {
LocationHandler h = getHandler(location);
return (h == null ? null : h.inferModuleName());
}
Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
LocationHandler h = getHandler(location);
return (h == null ? null : h.listLocationsForModules());
}
boolean contains(Location location, Path file) throws IOException {
LocationHandler h = getHandler(location);
if (h == null)
throw new IllegalArgumentException("unknown location");
return h.contains(file);
}
protected LocationHandler getHandler(Location location) {
Objects.requireNonNull(location);
return (location instanceof LocationHandler)
? (LocationHandler) location
: handlersForLocation.get(location);
}
private boolean isArchive(Path file) {
String n = StringUtils.toLowerCase(file.getFileName().toString());
return fsInfo.isFile(file)
&& (n.endsWith(".jar") || n.endsWith(".zip"));
}
static Path normalize(Path p) {
try {
return p.toRealPath();
} catch (IOException e) {
return p.toAbsolutePath().normalize();
}
}
}