/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package 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.net.URI;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
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;
import jdk.internal.perf.PerfCounter;

A ModuleFinder that finds modules that are linked into the run-time image. The modules linked into the run-time image are assumed to have the Packages attribute.
/** * A {@code ModuleFinder} that finds modules that are linked into the * run-time image. * * The modules linked into the run-time image are assumed to have the * Packages attribute. */
public class SystemModuleFinder implements ModuleFinder { private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess(); private static final PerfCounter initTime = PerfCounter.newPerfCounter("jdk.module.finder.jimage.initTime"); private static final PerfCounter moduleCount = PerfCounter.newPerfCounter("jdk.module.finder.jimage.modules"); private static final PerfCounter packageCount = PerfCounter.newPerfCounter("jdk.module.finder.jimage.packages"); private static final PerfCounter exportsCount = PerfCounter.newPerfCounter("jdk.module.finder.jimage.exports"); // singleton finder to find modules in the run-time images private static final SystemModuleFinder INSTANCE; public static SystemModuleFinder getInstance() { return INSTANCE; }
For now, the module references are created eagerly on the assumption that service binding will require all modules to be located.
/** * For now, the module references are created eagerly on the assumption * that service binding will require all modules to be located. */
static { long t0 = System.nanoTime(); INSTANCE = new SystemModuleFinder(); initTime.addElapsedTimeFrom(t0); }
Holder class for the ImageReader
/** * Holder class for the ImageReader */
private static class SystemImage { static final ImageReader READER; static { long t0 = System.nanoTime(); READER = ImageReaderFactory.getImageReader(); initTime.addElapsedTimeFrom(t0); } static ImageReader reader() { return READER; } } private static boolean isFastPathSupported() { return SystemModules.MODULE_NAMES.length > 0; } private static String[] moduleNames() { if (isFastPathSupported()) // module names recorded at link time return SystemModules.MODULE_NAMES; // this happens when java.base is patched with java.base // from an exploded image return SystemImage.reader().getModuleNames(); } // the set of modules in the run-time image private final Set<ModuleReference> modules; // maps module name to module reference private final Map<String, ModuleReference> nameToModule; // module name to hashes private final Map<String, byte[]> hashes; private SystemModuleFinder() { String[] names = moduleNames(); int n = names.length; moduleCount.add(n); // fastpath is enabled by default. // It can be disabled for troubleshooting purpose. boolean disabled = System.getProperty("jdk.system.module.finder.disabledFastPath") != null; ModuleDescriptor[] descriptors; ModuleTarget[] targets; ModuleHashes[] recordedHashes; ModuleResolution[] moduleResolutions; // fast loading of ModuleDescriptor of system modules if (isFastPathSupported() && !disabled) { descriptors = SystemModules.descriptors(); targets = SystemModules.targets(); recordedHashes = SystemModules.hashes(); moduleResolutions = SystemModules.moduleResolutions(); } else { // if fast loading of ModuleDescriptors is disabled // fallback to read module-info.class descriptors = new ModuleDescriptor[n]; targets = new ModuleTarget[n]; recordedHashes = new ModuleHashes[n]; moduleResolutions = new ModuleResolution[n]; ImageReader imageReader = SystemImage.reader(); for (int i = 0; i < names.length; i++) { String mn = names[i]; ImageLocation loc = imageReader.findLocation(mn, "module-info.class"); ModuleInfo.Attributes attrs = ModuleInfo.read(imageReader.getResourceBuffer(loc), null); descriptors[i] = attrs.descriptor(); targets[i] = attrs.target(); recordedHashes[i] = attrs.recordedHashes(); moduleResolutions[i] = attrs.moduleResolution(); } } Map<String, byte[]> hashes = 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 (hashes == null) { hashes = mh.hashes(); } else { if (!secondSeen) { hashes = new HashMap<>(hashes); secondSeen = true; } hashes.putAll(mh.hashes()); } } } this.hashes = (hashes == null) ? Map.of() : hashes; ModuleReference[] mods = new ModuleReference[n]; @SuppressWarnings(value = {"rawtypes", "unchecked"}) Entry<String, ModuleReference>[] map = (Entry<String, ModuleReference>[])new Entry[n]; for (int i = 0; i < n; i++) { ModuleDescriptor md = descriptors[i]; // create the ModuleReference ModuleReference mref = toModuleReference(md, targets[i], recordedHashes[i], hashSupplier(names[i]), moduleResolutions[i]); mods[i] = mref; map[i] = Map.entry(names[i], mref); // counters packageCount.add(md.packages().size()); exportsCount.add(md.exports().size()); } modules = Set.of(mods); nameToModule = Map.ofEntries(map); } @Override public Optional<ModuleReference> find(String name) { Objects.requireNonNull(name); return Optional.ofNullable(nameToModule.get(name)); } @Override public Set<ModuleReference> findAll() { return modules; } private ModuleReference toModuleReference(ModuleDescriptor md, ModuleTarget target, ModuleHashes recordedHashes, HashSupplier hasher, ModuleResolution mres) { String mn = md.name(); URI uri = JNUA.create("jrt", "/".concat(mn)); Supplier<ModuleReader> readerSupplier = new Supplier<>() { @Override public ModuleReader get() { return new ImageModuleReader(mn, uri); } }; ModuleReference mref = new ModuleReferenceImpl(md, 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; } private HashSupplier hashSupplier(String name) { if (!hashes.containsKey(name)) return null; return new HashSupplier() { @Override public byte[] generate(String algorithm) { return hashes.get(name); } }; }
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. */
static class ImageModuleReader 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); } } } ImageModuleReader(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. */
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; } } }