blob: 5fba5b39ae03b51b712998516dc5078369f62a9f [file] [log] [blame]
/*
* 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.
*/
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.
*/
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.
*/
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.
*
* @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.
*
* @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.
*
* @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.
*/
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.
*/
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.
*/
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.
*/
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
*
* @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.
*/
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.
*/
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, {@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.
*/
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 {@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;
}
}
}