/*
* Copyright (c) 2015, 2017, 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 jdk.internal.module;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.jimage.ImageLocation;
import jdk.internal.jimage.ImageReader;
import jdk.internal.jimage.ImageReaderFactory;
import jdk.internal.misc.JavaNetUriAccess;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.module.ModuleHashes.HashSupplier;
The factory for SystemModules objects and for creating ModuleFinder objects
that find modules in the runtime image.
This class supports initializing the module system when the runtime is an
images build, an exploded build, or an images build with java.base patched
by an exploded java.base. It also supports a testing mode that re-parses
the module-info.class resources in the run-time image.
/**
* The factory for SystemModules objects and for creating ModuleFinder objects
* that find modules in the runtime image.
*
* This class supports initializing the module system when the runtime is an
* images build, an exploded build, or an images build with java.base patched
* by an exploded java.base. It also supports a testing mode that re-parses
* the module-info.class resources in the run-time image.
*/
public final class SystemModuleFinders {
private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess();
private static final boolean USE_FAST_PATH;
static {
String value = System.getProperty("jdk.system.module.finder.disableFastPath");
if (value == null) {
USE_FAST_PATH = true;
} else {
USE_FAST_PATH = (value.length() > 0) && !Boolean.parseBoolean(value);
}
}
// cached ModuleFinder returned from ofSystem
private static volatile ModuleFinder cachedSystemModuleFinder;
private SystemModuleFinders() { }
Returns the SystemModules object to reconstitute all modules. Returns
null if this is an exploded build or java.base is patched by an exploded
build.
/**
* Returns the SystemModules object to reconstitute all modules. Returns
* null if this is an exploded build or java.base is patched by an exploded
* build.
*/
static SystemModules allSystemModules() {
if (USE_FAST_PATH) {
return SystemModulesMap.allSystemModules();
} else {
return null;
}
}
Returns a SystemModules object to reconstitute the modules for the
given initial module. If the initial module is null then return the
SystemModules object to reconstitute the default modules.
Return null if there is no SystemModules class for the initial module,
this is an exploded build, or java.base is patched by an exploded build.
/**
* Returns a SystemModules object to reconstitute the modules for the
* given initial module. If the initial module is null then return the
* SystemModules object to reconstitute the default modules.
*
* Return null if there is no SystemModules class for the initial module,
* this is an exploded build, or java.base is patched by an exploded build.
*/
static SystemModules systemModules(String initialModule) {
if (USE_FAST_PATH) {
if (initialModule == null) {
return SystemModulesMap.defaultSystemModules();
}
String[] initialModules = SystemModulesMap.moduleNames();
for (int i = 0; i < initialModules.length; i++) {
String moduleName = initialModules[i];
if (initialModule.equals(moduleName)) {
String cn = SystemModulesMap.classNames()[i];
try {
// one-arg Class.forName as java.base may not be defined
Constructor<?> ctor = Class.forName(cn).getConstructor();
return (SystemModules) ctor.newInstance();
} catch (Exception e) {
throw new InternalError(e);
}
}
}
}
return null;
}
Returns a ModuleFinder that is backed by the given SystemModules object.
API Note: The returned ModuleFinder is thread safe.
/**
* Returns a ModuleFinder that is backed by the given SystemModules object.
*
* @apiNote The returned ModuleFinder is thread safe.
*/
static ModuleFinder of(SystemModules systemModules) {
ModuleDescriptor[] descriptors = systemModules.moduleDescriptors();
ModuleTarget[] targets = systemModules.moduleTargets();
ModuleHashes[] recordedHashes = systemModules.moduleHashes();
ModuleResolution[] moduleResolutions = systemModules.moduleResolutions();
int moduleCount = descriptors.length;
ModuleReference[] mrefs = new ModuleReference[moduleCount];
@SuppressWarnings(value = {"rawtypes", "unchecked"})
Map.Entry<String, ModuleReference>[] map
= (Map.Entry<String, ModuleReference>[])new Map.Entry[moduleCount];
Map<String, byte[]> nameToHash = generateNameToHash(recordedHashes);
for (int i = 0; i < moduleCount; i++) {
String name = descriptors[i].name();
HashSupplier hashSupplier = hashSupplier(nameToHash, name);
ModuleReference mref = toModuleReference(descriptors[i],
targets[i],
recordedHashes[i],
hashSupplier,
moduleResolutions[i]);
mrefs[i] = mref;
map[i] = Map.entry(name, mref);
}
return new SystemModuleFinder(mrefs, map);
}
Returns the ModuleFinder to find all system modules. Supports both
images and exploded builds.
API Note: Used by ModuleFinder.ofSystem()
/**
* Returns the ModuleFinder to find all system modules. Supports both
* images and exploded builds.
*
* @apiNote Used by ModuleFinder.ofSystem()
*/
public static ModuleFinder ofSystem() {
ModuleFinder finder = cachedSystemModuleFinder;
if (finder != null) {
return finder;
}
// probe to see if this is an images build
String home = System.getProperty("java.home");
Path modules = Paths.get(home, "lib", "modules");
if (Files.isRegularFile(modules)) {
if (USE_FAST_PATH) {
SystemModules systemModules = allSystemModules();
if (systemModules != null) {
finder = of(systemModules);
}
}
// fall back to parsing the module-info.class files in image
if (finder == null) {
finder = ofModuleInfos();
}
cachedSystemModuleFinder = finder;
return finder;
}
// exploded build (do not cache module finder)
Path dir = Paths.get(home, "modules");
if (!Files.isDirectory(dir))
throw new InternalError("Unable to detect the run-time image");
ModuleFinder f = ModulePath.of(ModuleBootstrap.patcher(), dir);
return new ModuleFinder() {
@Override
public Optional<ModuleReference> find(String name) {
PrivilegedAction<Optional<ModuleReference>> pa = () -> f.find(name);
return AccessController.doPrivileged(pa);
}
@Override
public Set<ModuleReference> findAll() {
PrivilegedAction<Set<ModuleReference>> pa = f::findAll;
return AccessController.doPrivileged(pa);
}
};
}
Parses the module-info.class of all module in the runtime image and
returns a ModuleFinder to find the modules.
API Note: The returned ModuleFinder is thread safe.
/**
* Parses the module-info.class of all module in the runtime image and
* returns a ModuleFinder to find the modules.
*
* @apiNote The returned ModuleFinder is thread safe.
*/
private static ModuleFinder ofModuleInfos() {
// parse the module-info.class in every module
Map<String, ModuleInfo.Attributes> nameToAttributes = new HashMap<>();
Map<String, byte[]> nameToHash = new HashMap<>();
ImageReader reader = SystemImage.reader();
for (String mn : reader.getModuleNames()) {
ImageLocation loc = reader.findLocation(mn, "module-info.class");
ModuleInfo.Attributes attrs
= ModuleInfo.read(reader.getResourceBuffer(loc), null);
nameToAttributes.put(mn, attrs);
ModuleHashes hashes = attrs.recordedHashes();
if (hashes != null) {
for (String name : hashes.names()) {
nameToHash.computeIfAbsent(name, k -> hashes.hashFor(name));
}
}
}
// create a ModuleReference for each module
Set<ModuleReference> mrefs = new HashSet<>();
Map<String, ModuleReference> nameToModule = new HashMap<>();
for (Map.Entry<String, ModuleInfo.Attributes> e : nameToAttributes.entrySet()) {
String mn = e.getKey();
ModuleInfo.Attributes attrs = e.getValue();
HashSupplier hashSupplier = hashSupplier(nameToHash, mn);
ModuleReference mref = toModuleReference(attrs.descriptor(),
attrs.target(),
attrs.recordedHashes(),
hashSupplier,
attrs.moduleResolution());
mrefs.add(mref);
nameToModule.put(mn, mref);
}
return new SystemModuleFinder(mrefs, nameToModule);
}
A ModuleFinder that finds module in an array or set of modules.
/**
* A ModuleFinder that finds module in an array or set of modules.
*/
private static class SystemModuleFinder implements ModuleFinder {
final Set<ModuleReference> mrefs;
final Map<String, ModuleReference> nameToModule;
SystemModuleFinder(ModuleReference[] array,
Map.Entry<String, ModuleReference>[] map) {
this.mrefs = Set.of(array);
this.nameToModule = Map.ofEntries(map);
}
SystemModuleFinder(Set<ModuleReference> mrefs,
Map<String, ModuleReference> nameToModule) {
this.mrefs = Collections.unmodifiableSet(mrefs);
this.nameToModule = Collections.unmodifiableMap(nameToModule);
}
@Override
public Optional<ModuleReference> find(String name) {
Objects.requireNonNull(name);
return Optional.ofNullable(nameToModule.get(name));
}
@Override
public Set<ModuleReference> findAll() {
return mrefs;
}
}
Creates a ModuleReference to the system module.
/**
* Creates a ModuleReference to the system module.
*/
static ModuleReference toModuleReference(ModuleDescriptor descriptor,
ModuleTarget target,
ModuleHashes recordedHashes,
HashSupplier hasher,
ModuleResolution mres) {
String mn = descriptor.name();
URI uri = JNUA.create("jrt", "/".concat(mn));
Supplier<ModuleReader> readerSupplier = new Supplier<>() {
@Override
public ModuleReader get() {
return new SystemModuleReader(mn, uri);
}
};
ModuleReference mref = new ModuleReferenceImpl(descriptor,
uri,
readerSupplier,
null,
target,
recordedHashes,
hasher,
mres);
// may need a reference to a patched module if --patch-module specified
mref = ModuleBootstrap.patcher().patchIfNeeded(mref);
return mref;
}
Generates a map of module name to hash value.
/**
* Generates a map of module name to hash value.
*/
static Map<String, byte[]> generateNameToHash(ModuleHashes[] recordedHashes) {
Map<String, byte[]> nameToHash = null;
boolean secondSeen = false;
// record the hashes to build HashSupplier
for (ModuleHashes mh : recordedHashes) {
if (mh != null) {
// if only one module contain ModuleHashes, use it
if (nameToHash == null) {
nameToHash = mh.hashes();
} else {
if (!secondSeen) {
nameToHash = new HashMap<>(nameToHash);
secondSeen = true;
}
nameToHash.putAll(mh.hashes());
}
}
}
return (nameToHash != null) ? nameToHash : Collections.emptyMap();
}
Returns a HashSupplier that returns the hash of the given module.
/**
* Returns a HashSupplier that returns the hash of the given module.
*/
static HashSupplier hashSupplier(Map<String, byte[]> nameToHash, String name) {
byte[] hash = nameToHash.get(name);
if (hash != null) {
// avoid lambda here
return new HashSupplier() {
@Override
public byte[] generate(String algorithm) {
return hash;
}
};
} else {
return null;
}
}
Holder class for the ImageReader
API Note: This class must be loaded before a security manager is set.
/**
* Holder class for the ImageReader
*
* @apiNote This class must be loaded before a security manager is set.
*/
private static class SystemImage {
static final ImageReader READER = ImageReaderFactory.getImageReader();
static ImageReader reader() {
return READER;
}
}
A ModuleReader for reading resources from a module linked into the
run-time image.
/**
* A ModuleReader for reading resources from a module linked into the
* run-time image.
*/
private static class SystemModuleReader implements ModuleReader {
private final String module;
private volatile boolean closed;
If there is a security manager set then check permission to
connect to the run-time image.
/**
* If there is a security manager set then check permission to
* connect to the run-time image.
*/
private static void checkPermissionToConnect(URI uri) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
URLConnection uc = uri.toURL().openConnection();
sm.checkPermission(uc.getPermission());
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
}
SystemModuleReader(String module, URI uri) {
checkPermissionToConnect(uri);
this.module = module;
}
Returns the ImageLocation for the given resource, null
if not found. /**
* Returns the ImageLocation for the given resource, {@code null}
* if not found.
*/
private ImageLocation findImageLocation(String name) throws IOException {
Objects.requireNonNull(name);
if (closed)
throw new IOException("ModuleReader is closed");
ImageReader imageReader = SystemImage.reader();
if (imageReader != null) {
return imageReader.findLocation(module, name);
} else {
// not an images build
return null;
}
}
@Override
public Optional<URI> find(String name) throws IOException {
ImageLocation location = findImageLocation(name);
if (location != null) {
URI u = URI.create("jrt:/" + module + "/" + name);
return Optional.of(u);
} else {
return Optional.empty();
}
}
@Override
public Optional<InputStream> open(String name) throws IOException {
return read(name).map(this::toInputStream);
}
private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer?
try {
int rem = bb.remaining();
byte[] bytes = new byte[rem];
bb.get(bytes);
return new ByteArrayInputStream(bytes);
} finally {
release(bb);
}
}
@Override
public Optional<ByteBuffer> read(String name) throws IOException {
ImageLocation location = findImageLocation(name);
if (location != null) {
return Optional.of(SystemImage.reader().getResourceBuffer(location));
} else {
return Optional.empty();
}
}
@Override
public void release(ByteBuffer bb) {
Objects.requireNonNull(bb);
ImageReader.releaseByteBuffer(bb);
}
@Override
public Stream<String> list() throws IOException {
if (closed)
throw new IOException("ModuleReader is closed");
Spliterator<String> s = new ModuleContentSpliterator(module);
return StreamSupport.stream(s, false);
}
@Override
public void close() {
// nothing else to do
closed = true;
}
}
A Spliterator for traversing the resources of a module linked into the
run-time image.
/**
* A Spliterator for traversing the resources of a module linked into the
* run-time image.
*/
private static class ModuleContentSpliterator implements Spliterator<String> {
final String moduleRoot;
final Deque<ImageReader.Node> stack;
Iterator<ImageReader.Node> iterator;
ModuleContentSpliterator(String module) throws IOException {
moduleRoot = "/modules/" + module;
stack = new ArrayDeque<>();
// push the root node to the stack to get started
ImageReader.Node dir = SystemImage.reader().findNode(moduleRoot);
if (dir == null || !dir.isDirectory())
throw new IOException(moduleRoot + " not a directory");
stack.push(dir);
iterator = Collections.emptyIterator();
}
Returns the name of the next non-directory node or null
if there are no remaining nodes to visit. /**
* Returns the name of the next non-directory node or {@code null} if
* there are no remaining nodes to visit.
*/
private String next() throws IOException {
for (;;) {
while (iterator.hasNext()) {
ImageReader.Node node = iterator.next();
String name = node.getName();
if (node.isDirectory()) {
// build node
ImageReader.Node dir = SystemImage.reader().findNode(name);
assert dir.isDirectory();
stack.push(dir);
} else {
// strip /modules/$MODULE/ prefix
return name.substring(moduleRoot.length() + 1);
}
}
if (stack.isEmpty()) {
return null;
} else {
ImageReader.Node dir = stack.poll();
assert dir.isDirectory();
iterator = dir.getChildren().iterator();
}
}
}
@Override
public boolean tryAdvance(Consumer<? super String> action) {
String next;
try {
next = next();
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
if (next != null) {
action.accept(next);
return true;
} else {
return false;
}
}
@Override
public Spliterator<String> trySplit() {
return null;
}
@Override
public int characteristics() {
return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE;
}
@Override
public long estimateSize() {
return Long.MAX_VALUE;
}
}
}