jdk/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java
changeset 46096 62c77b334012
parent 44359 c6761862ca0b
child 46188 4586bc5d28d1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java	Mon Aug 07 09:37:16 2017 +0100
@@ -0,0 +1,576 @@
+/*
+ * 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;
+        }
+    }
+}