org.eclipse.jdt.internal.compiler.util.JRTUtil Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2015, 2022 IBM Corporation.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Christoph Läubrich - adding helper for getting a JarFileSystem
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.util;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.channels.ClosedByInterruptException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.ProviderNotFoundException;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
public class JRTUtil {
public static final boolean DISABLE_CACHE = Boolean.getBoolean("org.eclipse.jdt.disable_JRT_cache"); //$NON-NLS-1$
public static final boolean PROPAGATE_IO_ERRORS = Boolean.getBoolean("org.eclipse.jdt.propagate_io_errors"); //$NON-NLS-1$
public static final String JAVA_BASE = "java.base"; //$NON-NLS-1$
public static final char[] JAVA_BASE_CHAR = JAVA_BASE.toCharArray();
public static final String MODULES_SUBDIR = "/modules"; //$NON-NLS-1$
static final String[] DEFAULT_MODULE = new String[]{JAVA_BASE};
static final String[] NO_MODULE = new String[0];
static final String MULTIPLE = "MU"; //$NON-NLS-1$
static final String DEFAULT_PACKAGE = ""; //$NON-NLS-1$
static String MODULE_TO_LOAD;
public static final String JRT_FS_JAR = "jrt-fs.jar"; //$NON-NLS-1$
static URI JRT_URI = URI.create("jrt:/"); //$NON-NLS-1$
public static final int NOTIFY_FILES = 0x0001;
public static final int NOTIFY_PACKAGES = 0x0002;
public static final int NOTIFY_MODULES = 0x0004;
public static final int NOTIFY_ALL = NOTIFY_FILES | NOTIFY_PACKAGES | NOTIFY_MODULES;
// TODO: Java 9 Think about clearing the cache too.
private static Map images = new ConcurrentHashMap<>();
/**
* Map from JDK home path to ct.sym file (located in /lib in the JDK)
*/
private static final Map ctSymFiles = new ConcurrentHashMap<>();
private static final Map JRT_FILE_SYSTEMS = new ConcurrentHashMap<>();
static final SoftClassCache classCache = new SoftClassCache();
public interface JrtFileVisitor {
public default FileVisitResult visitPackage(T dir, T mod, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
public default FileVisitResult visitFile(T file, T mod, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
/**
* Invoked when a root directory of a module being visited. The element returned
* contains only the module name segment - e.g. "java.base". Clients can use this to control
* how the JRT needs to be processed, for e.g., clients can skip a particular module
* by returning FileVisitResult.SKIP_SUBTREE
*/
public default FileVisitResult visitModule(T path, String name) throws IOException {
return FileVisitResult.CONTINUE;
}
}
/**
* @param image the path to the root of the JRE whose libraries we are interested in.
* @return may return {@code null}
* @deprecated use {@link JRTUtil#getJrtSystem(File, String)} instead
*/
public static JrtFileSystem getJrtSystem(File image) {
try {
return getJrtSystem(image, null);
} catch (IOException e) {
if(PROPAGATE_IO_ERRORS) {
throw new IllegalStateException(e);
}
return null;
}
}
/**
* @param image the path to the root of the JRE whose libraries we are interested in.
* @param release --release
version
*/
public static JrtFileSystem getJrtSystem(File image, String release) throws IOException {
Jdk jdk = new Jdk(image);
String key = jdk.path.toString();
if (release != null && !jdk.sameRelease(release)) {
key += "|" + release; //$NON-NLS-1$
}
try {
JrtFileSystem system = images.computeIfAbsent(key, x -> {
try {
return JrtFileSystem.getNewJrtFileSystem(jdk, release);
} catch (IOException e) {
// Needs better error handling downstream? But for now, make sure
// a dummy JrtFileSystem is not created.
String errorMessage = "Error: failed to create JrtFileSystem from " + image; //$NON-NLS-1$
throw new RuntimeIOException(errorMessage, e);
}
});
return system;
} catch (RuntimeIOException e) {
throw e.getCause();
}
}
/**
* @param path
* absolute path to java.home
* @return new {@link FileSystem} based on {@link #JRT_FS_JAR} for given Java home path.
* @throws IOException
* on any error
*/
public static FileSystem getJrtFileSystem(Path path) throws IOException {
try {
FileSystem fs = JRT_FILE_SYSTEMS.computeIfAbsent(path.toAbsolutePath().normalize(), p -> {
try {
return FileSystems.newFileSystem(JRTUtil.JRT_URI, Map.of("java.home", p.toString())); //$NON-NLS-1$
} catch (IOException e) {
throw new RuntimeIOException(e);
}
});
return fs;
} catch (RuntimeIOException e) {
throw e.getCause();
}
}
/**
* Convenient method to get access to the given archive as a {@link FileSystem}.
*
* Note: if the file system for given archive was already created before, the method will reuse existing file
* system, otherwise a new {@link FileSystem} object will be created.
*
* The caller should not close returned {@link FileSystem} as it might be shared with others.
*
* @param path
* absolute file path to a jar archive
* @return never null
*/
public static FileSystem getJarFileSystem(Path path) throws IOException {
URI uri = URI.create("jar:file:" + path.toUri().getRawPath()); //$NON-NLS-1$
try {
try {
return FileSystems.getFileSystem(uri);
} catch (FileSystemNotFoundException fne) {
try {
return FileSystems.newFileSystem(uri, Map.of(), ClassLoader.getSystemClassLoader());
} catch (FileSystemAlreadyExistsException e) {
return FileSystems.getFileSystem(uri);
}
}
} catch (ProviderNotFoundException e) {
throw new IOException("No provider for uri " + uri, e); //$NON-NLS-1$
}
}
@SuppressWarnings("resource") // getFs() does not transfer ownership
public static CtSym getCtSym(Path jdkHome) throws IOException {
CtSym ctSym;
try {
ctSym = ctSymFiles.compute(jdkHome.toAbsolutePath().normalize(), (Path x, CtSym current) -> {
if (current == null || !current.getFs().isOpen()) {
try {
return new CtSym(x);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
return current;
});
} catch (RuntimeIOException rio) {
throw rio.getCause();
}
return ctSym;
}
/** TEST ONLY (use when changing the "modules.to.load" property). */
public static void reset() {
images.clear();
classCache.clear();
MODULE_TO_LOAD = System.getProperty("modules.to.load"); //$NON-NLS-1$
}
/**
* Given the path of a modular image file, this method walks the archive content and
* notifies the supplied visitor about packages and files visited.
*
* The file system contains the following top level directories:
* /modules/$MODULE/$PATH
* /packages/$PACKAGE/$MODULE
* The latter provides quick look up of the module that contains a particular package. However,
* this method only notifies its clients of the entries within the modules (former) sub-directory.
* Clients can decide which notifications they want to receive. See {@link JRTUtil#NOTIFY_ALL},
* {@link JRTUtil#NOTIFY_FILES}, {@link JRTUtil#NOTIFY_PACKAGES} and {@link JRTUtil#NOTIFY_MODULES}.
*
* @param image a java.io.File handle to the JRT image.
* @param visitor an instance of JrtFileVisitor to be notified of the entries in the JRT image.
* @param notify flag indicating the notifications the client is interested in.
*/
public static void walkModuleImage(File image, final JRTUtil.JrtFileVisitor visitor, int notify) throws IOException {
JrtFileSystem system = getJrtSystem(image, null);
if (system == null) {
return;
}
system.walkModuleImage(visitor, notify);
}
public static void walkModuleImage(File image, String release, final JRTUtil.JrtFileVisitor visitor, int notify) throws IOException {
JrtFileSystem system = getJrtSystem(image, release);
if (system == null) {
return;
}
system.walkModuleImage(visitor, notify);
}
public static InputStream getContentFromJrt(File jrt, String fileName, String module) throws IOException {
JrtFileSystem system = getJrtSystem(jrt, null);
if (system == null) {
throw new FileNotFoundException(String.valueOf(jrt));
}
return system.getContentFromJrt(fileName, module);
}
public static byte[] getClassfileContent(File jrt, String fileName, String module) throws IOException {
JrtFileSystem system = getJrtSystem(jrt, null);
if (system == null) {
throw new FileNotFoundException(String.valueOf(jrt));
}
return system.getClassfileContent(fileName, module);
}
public static ClassFileReader getClassfile(File jrt, String fileName, String module) throws IOException, ClassFormatException {
JrtFileSystem system = getJrtSystem(jrt, null);
if (system == null) {
throw new FileNotFoundException(String.valueOf(jrt));
}
return system.getClassfile(fileName, module);
}
public static ClassFileReader getClassfile(File jrt, String fileName, String module, Predicate moduleNameFilter) throws IOException, ClassFormatException {
JrtFileSystem system = getJrtSystem(jrt, null);
if (system == null) {
throw new FileNotFoundException(String.valueOf(jrt));
}
return system.getClassfile(fileName, module, moduleNameFilter);
}
public static List getModulesDeclaringPackage(File jrt, String qName, String moduleName) {
JrtFileSystem system = getJrtSystem(jrt);
if (system == null) {
return List.of();
}
return system.getModulesDeclaringPackage(qName, moduleName);
}
public static boolean hasCompilationUnit(File jrt, String qualifiedPackageName, String moduleName) {
JrtFileSystem system = getJrtSystem(jrt);
if (system == null) {
return false;
}
return system.hasClassFile(qualifiedPackageName, moduleName);
}
/*
* Returns only the file name after removing trailing '/' if any for folders
*/
public static String sanitizedFileName(Path path) {
String p = path.getFileName().toString();
if (p.length() > 1 && p.charAt(p.length() - 1) == '/') {
return p.substring(0, p.length() - 1);
}
return p;
}
/**
* Tries to read all bytes of the file denoted by path,
* returns null if the file could not be found or if the read was interrupted.
* @return bytes or null
* @throws IOException any IO exception other than NoSuchFileException
*/
public static byte[] safeReadBytes(Path path) throws IOException {
try {
return Files.readAllBytes(path);
} catch (ClosedByInterruptException e) {
// retry once again
try {
return Files.readAllBytes(path);
} catch (NoSuchFileException e2) {
return null;
} catch (ClosedByInterruptException e2) {
if (PROPAGATE_IO_ERRORS) {
throw e2;
}
return null;
}
} catch (NoSuchFileException e) {
return null;
}
}
/**
* @param image jrt file path
* @return JDK release corresponding to given jrt file, read from "release" file, if available. May return null.
*/
public static String getJdkRelease(File image) throws IOException {
JrtFileSystem jrt = getJrtSystem(image, null);
return jrt == null ? null : jrt.getJdkRelease();
}
}
class JrtFileSystemWithOlderRelease extends JrtFileSystem {
private final List releaseRoots;
private final CtSym ctSym;
/**
* The jrt file system is based on the location of the JRE home whose libraries
* need to be loaded.
*
* @param release the older release where classes and modules should be searched for.
*/
JrtFileSystemWithOlderRelease(Jdk jdkHome, String release) throws IOException {
super(jdkHome, release);
String releaseCode = CtSym.getReleaseCode(this.release);
this.ctSym = JRTUtil.getCtSym(this.jdk.path);
this.fs = this.ctSym.getFs();
if (!Files.exists(this.fs.getPath(releaseCode))
|| Files.exists(this.fs.getPath(releaseCode, "system-modules"))) { //$NON-NLS-1$
this.fs = null;
}
this.releaseRoots = this.ctSym.releaseRoots(releaseCode);
}
@Override
void walkModuleImage(final JRTUtil.JrtFileVisitor visitor, final int notify) throws IOException {
for (Path p : this.releaseRoots) {
Files.walkFileTree(p, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
int count = dir.getNameCount();
if (count == 1) {
return FileVisitResult.CONTINUE;
}
if (count == 2) {
// e.g. /9A/java.base
Path mod = dir.getName(1);
if ((JRTUtil.MODULE_TO_LOAD != null && JRTUtil.MODULE_TO_LOAD.length() > 0
&& JRTUtil.MODULE_TO_LOAD.indexOf(mod.toString()) == -1)) {
return FileVisitResult.SKIP_SUBTREE;
}
return ((notify & JRTUtil.NOTIFY_MODULES) == 0) ? FileVisitResult.CONTINUE
: visitor.visitModule(dir, JRTUtil.sanitizedFileName(mod));
}
if ((notify & JRTUtil.NOTIFY_PACKAGES) == 0) {
// client is not interested in packages
return FileVisitResult.CONTINUE;
}
return visitor.visitPackage(dir.subpath(2, count), dir.getName(1), attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if ((notify & JRTUtil.NOTIFY_FILES) == 0) {
return FileVisitResult.CONTINUE;
}
// This happens when a file in a default package is present. E.g. /modules/some.module/file.name
if (file.getNameCount() == 3) {
cachePackage(JRTUtil.DEFAULT_PACKAGE, file.getName(1).toString());
}
return visitor.visitFile(file.subpath(2, file.getNameCount()), file.getName(1), attrs);
}
});
}
}
}
final class RuntimeIOException extends RuntimeException {
private static final long serialVersionUID = 1L;
public RuntimeIOException(String message, IOException cause) {
super(message, cause);
}
public RuntimeIOException(IOException cause) {
super(cause);
}
@Override
public synchronized IOException getCause() {
return (IOException) super.getCause();
}
}
class Jdk {
final Path path;
final String release;
private static final Map pathToRelease = new ConcurrentHashMap<>();
public Jdk(File jrt) throws IOException {
this.path = toJdkHome(jrt);
try {
this.release = pathToRelease.computeIfAbsent(this.path, p -> {
try {
return readJdkReleaseFile(p);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
});
} catch (RuntimeIOException rio) {
throw rio.getCause();
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Jdk ["); //$NON-NLS-1$
if (this.path != null) {
builder.append("path="); //$NON-NLS-1$
builder.append(this.path);
builder.append(", "); //$NON-NLS-1$
}
if (this.release != null) {
builder.append("release="); //$NON-NLS-1$
builder.append(this.release);
}
builder.append("]"); //$NON-NLS-1$
return builder.toString();
}
boolean sameRelease(String other) {
long jdkLevel = CompilerOptions.versionToJdkLevel(this.release);
long otherJdkLevel = CompilerOptions.versionToJdkLevel(other);
return Long.compare(jdkLevel, otherJdkLevel) == 0;
}
static Path toJdkHome(File jrt) {
Path home;
Path normalized = jrt.toPath().toAbsolutePath().normalize();
if (jrt.getName().equals(JRTUtil.JRT_FS_JAR)) {
home = normalized.getParent().getParent();
} else {
home = normalized;
}
return home;
}
static String readJdkReleaseFile(Path javaHome) throws IOException {
Properties properties = new Properties();
try (InputStream in = Files.newInputStream(javaHome.resolve("release"))) { //$NON-NLS-1$
properties.load(in);
}
// Something like JAVA_VERSION="1.8.0_05"
String ver = properties.getProperty("JAVA_VERSION"); //$NON-NLS-1$
if (ver != null) {
ver = ver.replace("\"", ""); //$NON-NLS-1$//$NON-NLS-2$
}
return ver;
}
}
class JrtFileSystem {
private final Map packageToModule = new HashMap<>();
private final Map> packageToModules = new HashMap<>();
FileSystem fs;
final Path modRoot;
final Jdk jdk;
final String release;
public static JrtFileSystem getNewJrtFileSystem(Jdk jdk, String release) throws IOException {
if (release == null || jdk.sameRelease(release)) {
return new JrtFileSystem(jdk, null);
} else {
return new JrtFileSystemWithOlderRelease(jdk, release);
}
}
/**
* The jrt file system is based on the location of the JRE home whose libraries
* need to be loaded.
*/
JrtFileSystem(Jdk jdkHome, String release) throws IOException {
this.jdk = jdkHome;
this.release = release;
JRTUtil.MODULE_TO_LOAD = System.getProperty("modules.to.load"); //$NON-NLS-1$
this.fs = JRTUtil.getJrtFileSystem(this.jdk.path);
this.modRoot = this.fs.getPath(JRTUtil.MODULES_SUBDIR);
// Set up the root directory where modules are located
walkJrtForModules();
}
public List getModulesDeclaringPackage(String qualifiedPackageName, String moduleName) {
qualifiedPackageName = qualifiedPackageName.replace('.', '/');
String module = this.packageToModule.get(qualifiedPackageName);
if (moduleName == null) {
// wildcard search:
if (module == null)
return null;
if (module == JRTUtil.MULTIPLE)
return this.packageToModules.get(qualifiedPackageName);
return Collections.singletonList(module);
}
if (module != null) {
// specific search:
if (module == JRTUtil.MULTIPLE) {
List list = this.packageToModules.get(qualifiedPackageName);
if (list.contains(moduleName))
return Collections.singletonList(moduleName);
} else {
if (module.equals(moduleName))
return Collections.singletonList(moduleName);
}
}
return null;
}
public String[] getModules(String fileName) {
int idx = fileName.lastIndexOf('/');
String pack = null;
if (idx != -1) {
pack = fileName.substring(0, idx);
} else {
pack = JRTUtil.DEFAULT_PACKAGE;
}
String module = this.packageToModule.get(pack);
if (module != null) {
if (module == JRTUtil.MULTIPLE) {
List list = this.packageToModules.get(pack);
return list.toArray(new String[0]);
} else {
return new String[]{module};
}
}
return JRTUtil.DEFAULT_MODULE;
}
public boolean hasClassFile(String qualifiedPackageName, String module) {
if (module == null)
return false;
// easy checks first:
String knownModule = this.packageToModule.get(qualifiedPackageName);
if (knownModule == null || (knownModule != JRTUtil.MULTIPLE && !knownModule.equals(module)))
return false;
Path packagePath = this.fs.getPath(JRTUtil.MODULES_SUBDIR, module, qualifiedPackageName);
if (!Files.exists(packagePath))
return false;
// iterate files:
try {
try (Stream list = Files.list(packagePath)) {
return list.anyMatch(filePath -> filePath.toString().endsWith(SuffixConstants.SUFFIX_STRING_class)
|| filePath.toString().endsWith(SuffixConstants.SUFFIX_STRING_CLASS));
}
} catch (IOException e) {
return false;
}
}
public InputStream getContentFromJrt(String fileName, String module) throws IOException {
if (module != null) {
byte[] fileBytes = getFileBytes(fileName, module);
if(fileBytes == null) {
return null;
}
return new ByteArrayInputStream(fileBytes);
}
String[] modules = getModules(fileName);
for (String mod : modules) {
byte[] fileBytes = getFileBytes(fileName, mod);
if(fileBytes != null) {
return new ByteArrayInputStream(fileBytes);
}
}
return null;
}
private ClassFileReader getClassfile(String fileName, Predicate moduleNameFilter) throws IOException, ClassFormatException {
String[] modules = getModules(fileName);
for (String mod : modules) {
if (moduleNameFilter != null && !moduleNameFilter.test(mod)) {
continue;
}
ClassFileReader reader = getClassfileFromModule(fileName, mod);
if (reader != null) {
return reader;
}
}
return null;
}
byte[] getClassfileContent(String fileName, String module) throws IOException {
byte[] content = null;
if (module != null) {
content = getFileBytes(fileName, module);
} else {
String[] modules = getModules(fileName);
for (String mod : modules) {
content = getFileBytes(fileName, mod);
if (content != null) {
break;
}
}
}
return content;
}
private byte[] getFileBytes(String fileName, String module) throws IOException {
Path path = this.fs.getPath(JRTUtil.MODULES_SUBDIR, module, fileName);
if(JRTUtil.DISABLE_CACHE) {
return JRTUtil.safeReadBytes(path);
} else {
return JRTUtil.classCache.getClassBytes(this.jdk, path);
}
}
ClassFileReader getClassfileFromModule(String fileName, String module) throws IOException, ClassFormatException {
Path path = this.fs.getPath(JRTUtil.MODULES_SUBDIR, module, fileName);
byte[] content = null;
if(JRTUtil.DISABLE_CACHE) {
content = JRTUtil.safeReadBytes(path);
} else {
content = JRTUtil.classCache.getClassBytes(this.jdk, path);
}
if (content != null) {
ClassFileReader reader = new ClassFileReader(path.toUri(), content, fileName.toCharArray());
reader.moduleName = module.toCharArray();
return reader;
} else {
return null;
}
}
public ClassFileReader getClassfile(String fileName, String module, Predicate moduleNameFilter) throws IOException, ClassFormatException {
ClassFileReader reader = null;
if (module == null) {
reader = getClassfile(fileName, moduleNameFilter);
} else {
reader = getClassfileFromModule(fileName, module);
}
return reader;
}
public ClassFileReader getClassfile(String fileName, String module) throws IOException, ClassFormatException {
if (module == null) {
return getClassfile(fileName, (Predicate)null);
} else {
return getClassfileFromModule(fileName, module);
}
}
void walkJrtForModules() throws IOException {
Iterable roots = this.fs.getRootDirectories();
for (Path path : roots) {
try (DirectoryStream stream = Files.newDirectoryStream(path)) {
for (final Path subdir: stream) {
if (!subdir.toString().equals(JRTUtil.MODULES_SUBDIR)) {
Files.walkFileTree(subdir, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// e.g. /modules/java.base
Path relative = subdir.relativize(file);
cachePackage(relative.getParent().toString(), relative.getFileName().toString());
return FileVisitResult.CONTINUE;
}
});
}
}
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
}
}
void walkModuleImage(final JRTUtil.JrtFileVisitor visitor, final int notify) throws IOException {
Files.walkFileTree(this.modRoot, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
int count = dir.getNameCount();
if (count == 1) return FileVisitResult.CONTINUE;
if (count == 2) {
// e.g. /modules/java.base
Path mod = dir.getName(1);
if ((JRTUtil.MODULE_TO_LOAD != null && JRTUtil.MODULE_TO_LOAD.length() > 0 &&
JRTUtil.MODULE_TO_LOAD.indexOf(mod.toString()) == -1)) {
return FileVisitResult.SKIP_SUBTREE;
}
return ((notify & JRTUtil.NOTIFY_MODULES) == 0) ?
FileVisitResult.CONTINUE : visitor.visitModule(dir, JRTUtil.sanitizedFileName(mod));
}
if ((notify & JRTUtil.NOTIFY_PACKAGES) == 0) {
// We are dealing with a module or not client is not interested in packages
return FileVisitResult.CONTINUE;
}
return visitor.visitPackage(dir.subpath(2, count), dir.getName(1), attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if ((notify & JRTUtil.NOTIFY_FILES) == 0)
return FileVisitResult.CONTINUE;
int count = file.getNameCount();
// This happens when a file in a default package is present. E.g. /modules/some.module/file.name
if (count == 3) {
cachePackage(JRTUtil.DEFAULT_PACKAGE, file.getName(1).toString());
}
return visitor.visitFile(file.subpath(2, count), file.getName(1), attrs);
}
});
}
synchronized void cachePackage(String packageName, String module) {
packageName = packageName.replace('.', '/');
String currentModule = this.packageToModule.get(packageName);
if (currentModule == null) {
// Nothing found? Cache and return
this.packageToModule.put(packageName.intern(), module.intern());
return;
}
if(currentModule.equals(module)) {
// Same module found? Just return
return;
}
// We observe an additional module containing package
if (currentModule == JRTUtil.MULTIPLE) {
// We have already a list => update it
List list = this.packageToModules.get(packageName);
if (!list.contains(module)) {
if (JRTUtil.JAVA_BASE.equals(module)) {
list.add(0, JRTUtil.JAVA_BASE);
} else {
list.add(module.intern());
}
}
} else {
// We found a second module => create a list
List list = new ArrayList<>();
// Just do this as comparator might be overkill
if (JRTUtil.JAVA_BASE == currentModule || JRTUtil.JAVA_BASE.equals(currentModule)) {
list.add(currentModule.intern());
list.add(module.intern());
} else {
list.add(module.intern());
list.add(currentModule.intern());
}
packageName = packageName.intern();
this.packageToModules.put(packageName, list);
this.packageToModule.put(packageName, JRTUtil.MULTIPLE);
}
}
/**
* @return JDK release string (something like 1.8.0_05) read from the "release" file from JDK home
* directory
*/
public String getJdkRelease() {
return this.jdk.release;
}
}