/* * 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 {@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. */ static { long t0 = System.nanoTime(); INSTANCE = new SystemModuleFinder(); initTime.addElapsedTimeFrom(t0); } /** * 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 modules; // maps module name to module reference private final Map nameToModule; // module name to hashes private final Map 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 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[] map = (Entry[])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 find(String name) { Objects.requireNonNull(name); return Optional.ofNullable(nameToModule.get(name)); } @Override public Set 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 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. */ 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. */ 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, {@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 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 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 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 list() throws IOException { if (closed) throw new IOException("ModuleReader is closed"); Spliterator 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. */ static class ModuleContentSpliterator implements Spliterator { final String moduleRoot; final Deque stack; Iterator 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 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 trySplit() { return null; } @Override public int characteristics() { return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE; } @Override public long estimateSize() { return Long.MAX_VALUE; } } }