| /* | |
| * Copyright (c) 2014, 2024, 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 java.lang; | |
| import java.lang.module.Configuration; | |
| import java.lang.module.ModuleDescriptor; | |
| import java.lang.module.ResolvedModule; | |
| import java.util.ArrayDeque; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.Deque; | |
| import java.util.HashMap; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.Optional; | |
| import java.util.Set; | |
| import java.util.concurrent.CopyOnWriteArrayList; | |
| import java.util.function.Function; | |
| import java.util.stream.Collectors; | |
| import java.util.stream.Stream; | |
| import jdk.internal.javac.Restricted; | |
| import jdk.internal.loader.ClassLoaderValue; | |
| import jdk.internal.loader.Loader; | |
| import jdk.internal.loader.LoaderPool; | |
| import jdk.internal.module.ServicesCatalog; | |
| import jdk.internal.misc.CDS; | |
| import jdk.internal.reflect.CallerSensitive; | |
| import jdk.internal.reflect.Reflection; | |
| import jdk.internal.vm.annotation.Stable; | |
| /** | |
| * A layer of modules in the Java virtual machine. | |
| * | |
| * <p> A layer is created from a graph of modules in a {@link Configuration} | |
| * and a function that maps each module to a {@link ClassLoader}. | |
| * Creating a layer informs the Java virtual machine about the classes that | |
| * may be loaded from the modules so that the Java virtual machine knows which | |
| * module that each class is a member of. </p> | |
| * | |
| * <p> Creating a layer creates a {@link Module} object for each {@link | |
| * ResolvedModule} in the configuration. For each resolved module that is | |
| * {@link ResolvedModule#reads() read}, the {@code Module} {@link | |
| * Module#canRead reads} the corresponding run-time {@code Module}, which may | |
| * be in the same layer or a {@link #parents() parent} layer. </p> | |
| * | |
| * <p> The {@link #defineModulesWithOneLoader defineModulesWithOneLoader} and | |
| * {@link #defineModulesWithManyLoaders defineModulesWithManyLoaders} methods | |
| * provide convenient ways to create a module layer where all modules are | |
| * mapped to a single class loader or where each module is mapped to its own | |
| * class loader. The {@link #defineModules defineModules} method is for more | |
| * advanced cases where modules are mapped to custom class loaders by means of | |
| * a function specified to the method. Each of these methods has an instance | |
| * and static variant. The instance methods create a layer with the receiver | |
| * as the parent layer. The static methods are for more advanced cases where | |
| * there can be more than one parent layer or where a {@link | |
| * ModuleLayer.Controller Controller} is needed to control modules in the layer | |
| * </p> | |
| * | |
| * <p> A Java virtual machine has at least one non-empty layer, the {@link | |
| * #boot() boot} layer, that is created when the Java virtual machine is | |
| * started. The boot layer contains module {@code java.base} and is the only | |
| * layer in the Java virtual machine with a module named "{@code java.base}". | |
| * The modules in the boot layer are mapped to the bootstrap class loader and | |
| * other class loaders that are <a href="ClassLoader.html#builtinLoaders"> | |
| * built-in</a> into the Java virtual machine. The boot layer will often be | |
| * the {@link #parents() parent} when creating additional layers. </p> | |
| * | |
| * <p> Each {@code Module} in a layer is created so that it {@link | |
| * Module#isExported(String) exports} and {@link Module#isOpen(String) opens} | |
| * the packages described by its {@link ModuleDescriptor}. Qualified exports | |
| * (where a package is exported to a set of target modules rather than all | |
| * modules) are reified when creating the layer as follows: </p> | |
| * <ul> | |
| * <li> If module {@code X} exports a package to {@code Y}, and if the | |
| * runtime {@code Module} {@code X} reads {@code Module} {@code Y}, then | |
| * the package is exported to {@code Module} {@code Y} (which may be in | |
| * the same layer as {@code X} or a parent layer). </li> | |
| * | |
| * <li> If module {@code X} exports a package to {@code Y}, and if the | |
| * runtime {@code Module} {@code X} does not read {@code Y} then target | |
| * {@code Y} is located as if by invoking {@link #findModule(String) | |
| * findModule} to find the module in the layer or its parent layers. If | |
| * {@code Y} is found then the package is exported to the instance of | |
| * {@code Y} that was found. If {@code Y} is not found then the qualified | |
| * export is ignored. </li> | |
| * </ul> | |
| * | |
| * <p> Qualified opens are handled in same way as qualified exports. </p> | |
| * | |
| * <p> As when creating a {@code Configuration}, | |
| * {@link ModuleDescriptor#isAutomatic() automatic} modules receive special | |
| * treatment when creating a layer. An automatic module is created in the | |
| * Java virtual machine as a {@code Module} that reads every unnamed {@code | |
| * Module} in the Java virtual machine. </p> | |
| * | |
| * <p> Unless otherwise specified, passing a {@code null} argument to a method | |
| * in this class causes a {@link NullPointerException NullPointerException} to | |
| * be thrown. </p> | |
| * | |
| * <h2> Example </h2> | |
| * | |
| * <p> This example creates a configuration by resolving a module named | |
| * "{@code myapp}" with the configuration for the boot layer as the parent. It | |
| * then creates a new layer with the modules in this configuration. All modules | |
| * are defined to the same class loader. </p> | |
| * | |
| * {@snippet : | |
| * ModuleFinder finder = ModuleFinder.of(dir1, dir2, dir3); | |
| * ModuleLayer parent = ModuleLayer.boot(); | |
| * Configuration cf = parent.configuration() | |
| * .resolve(finder, ModuleFinder.of(), Set.of("myapp")); | |
| * ClassLoader scl = ClassLoader.getSystemClassLoader(); | |
| * ModuleLayer layer = parent.defineModulesWithOneLoader(cf, scl); | |
| * Class<?> c = layer.findLoader("myapp").loadClass("app.Main"); | |
| * } | |
| * | |
| * @since 9 | |
| * @see Module#getLayer() | |
| */ | |
| public final class ModuleLayer { | |
| // the empty layer (may be initialized from the CDS archive) | |
| private static @Stable ModuleLayer EMPTY_LAYER; | |
| static { | |
| CDS.initializeFromArchive(ModuleLayer.class); | |
| if (EMPTY_LAYER == null) { | |
| // create a new empty layer if there is no archived version. | |
| EMPTY_LAYER = new ModuleLayer(Configuration.empty(), List.of(), null); | |
| } | |
| } | |
| // the configuration from which this layer was created | |
| private final Configuration cf; | |
| // parent layers, empty in the case of the empty layer | |
| private final List<ModuleLayer> parents; | |
| // maps module name to jlr.Module | |
| private final Map<String, Module> nameToModule; | |
| /** | |
| * Creates a new module layer from the modules in the given configuration. | |
| */ | |
| private ModuleLayer(Configuration cf, | |
| List<ModuleLayer> parents, | |
| Function<String, ClassLoader> clf) | |
| { | |
| this.cf = cf; | |
| this.parents = parents; // no need to do defensive copy | |
| Map<String, Module> map; | |
| if (parents.isEmpty()) { | |
| map = Map.of(); | |
| } else { | |
| map = Module.defineModules(cf, clf, this); | |
| } | |
| this.nameToModule = map; // no need to do defensive copy | |
| } | |
| /** | |
| * Controls a module layer. The static methods defined by {@link ModuleLayer} | |
| * to create module layers return a {@code Controller} that can be used to | |
| * control modules in the layer. | |
| * | |
| * <p> Unless otherwise specified, passing a {@code null} argument to a | |
| * method in this class causes a {@link NullPointerException | |
| * NullPointerException} to be thrown. </p> | |
| * | |
| * @apiNote Care should be taken with {@code Controller} objects, they | |
| * should never be shared with untrusted code. | |
| * | |
| * @since 9 | |
| */ | |
| public static final class Controller { | |
| private final ModuleLayer layer; | |
| Controller(ModuleLayer layer) { | |
| this.layer = layer; | |
| } | |
| /** | |
| * Returns the layer that this object controls. | |
| * | |
| * @return the module layer | |
| */ | |
| public ModuleLayer layer() { | |
| return layer; | |
| } | |
| private void ensureInLayer(Module source) { | |
| if (source.getLayer() != layer) | |
| throw new IllegalArgumentException(source + " not in layer"); | |
| } | |
| /** | |
| * Updates module {@code source} in the layer to read module | |
| * {@code target}. This method is a no-op if {@code source} already | |
| * reads {@code target}. | |
| * | |
| * @implNote <em>Read edges</em> added by this method are <em>weak</em> | |
| * and do not prevent {@code target} from being GC'ed when {@code source} | |
| * is strongly reachable. | |
| * | |
| * @param source | |
| * The source module | |
| * @param target | |
| * The target module to read | |
| * | |
| * @return This controller | |
| * | |
| * @throws IllegalArgumentException | |
| * If {@code source} is not in the module layer | |
| * | |
| * @see Module#addReads | |
| */ | |
| public Controller addReads(Module source, Module target) { | |
| ensureInLayer(source); | |
| source.implAddReads(target); | |
| return this; | |
| } | |
| /** | |
| * Updates module {@code source} in the layer to export a package to | |
| * module {@code target}. This method is a no-op if {@code source} | |
| * already exports the package to at least {@code target}. | |
| * | |
| * @param source | |
| * The source module | |
| * @param pn | |
| * The package name | |
| * @param target | |
| * The target module | |
| * | |
| * @return This controller | |
| * | |
| * @throws IllegalArgumentException | |
| * If {@code source} is not in the module layer or the package | |
| * is not in the source module | |
| * | |
| * @see Module#addExports | |
| */ | |
| public Controller addExports(Module source, String pn, Module target) { | |
| ensureInLayer(source); | |
| source.implAddExports(pn, target); | |
| return this; | |
| } | |
| /** | |
| * Updates module {@code source} in the layer to open a package to | |
| * module {@code target}. This method is a no-op if {@code source} | |
| * already opens the package to at least {@code target}. | |
| * | |
| * @param source | |
| * The source module | |
| * @param pn | |
| * The package name | |
| * @param target | |
| * The target module | |
| * | |
| * @return This controller | |
| * | |
| * @throws IllegalArgumentException | |
| * If {@code source} is not in the module layer or the package | |
| * is not in the source module | |
| * | |
| * @see Module#addOpens | |
| */ | |
| public Controller addOpens(Module source, String pn, Module target) { | |
| ensureInLayer(source); | |
| source.implAddOpens(pn, target); | |
| return this; | |
| } | |
| /** | |
| * Enables native access for a module in the layer if the caller's module | |
| * has native access. | |
| * | |
| * @param target | |
| * The module to update | |
| * | |
| * @return This controller | |
| * | |
| * @throws IllegalArgumentException | |
| * If {@code target} is not in the module layer | |
| * | |
| * @throws IllegalCallerException | |
| * If the caller is in a module that does not have native access enabled | |
| * | |
| * @since 22 | |
| */ | |
| @CallerSensitive | |
| @Restricted | |
| public Controller enableNativeAccess(Module target) { | |
| ensureInLayer(target); | |
| Reflection.ensureNativeAccess(Reflection.getCallerClass(), Module.class, | |
| "enableNativeAccess", false); | |
| target.implAddEnableNativeAccess(); | |
| return this; | |
| } | |
| } | |
| /** | |
| * Creates a new module layer, with this layer as its parent, by defining the | |
| * modules in the given {@code Configuration} to the Java virtual machine. | |
| * This method creates one class loader and defines all modules to that | |
| * class loader. The {@link ClassLoader#getParent() parent} of each class | |
| * loader is the given parent class loader. This method works exactly as | |
| * specified by the static {@link | |
| * #defineModulesWithOneLoader(Configuration,List,ClassLoader) | |
| * defineModulesWithOneLoader} method when invoked with this layer as the | |
| * parent. In other words, if this layer is {@code thisLayer} then this | |
| * method is equivalent to invoking: | |
| * <pre> {@code | |
| * ModuleLayer.defineModulesWithOneLoader(cf, List.of(thisLayer), parentLoader).layer(); | |
| * }</pre> | |
| * | |
| * @param cf | |
| * The configuration for the layer | |
| * @param parentLoader | |
| * The parent class loader for the class loader created by this | |
| * method; may be {@code null} for the bootstrap class loader | |
| * | |
| * @return The newly created layer | |
| * | |
| * @throws IllegalArgumentException | |
| * If the given configuration has more than one parent or the parent | |
| * of the configuration is not the configuration for this layer | |
| * @throws LayerInstantiationException | |
| * If the layer cannot be created for any of the reasons specified | |
| * by the static {@code defineModulesWithOneLoader} method | |
| * | |
| * @see #findLoader | |
| */ | |
| public ModuleLayer defineModulesWithOneLoader(Configuration cf, | |
| ClassLoader parentLoader) { | |
| return defineModulesWithOneLoader(cf, List.of(this), parentLoader).layer(); | |
| } | |
| /** | |
| * Creates a new module layer, with this layer as its parent, by defining the | |
| * modules in the given {@code Configuration} to the Java virtual machine. | |
| * Each module is defined to its own {@link ClassLoader} created by this | |
| * method. The {@link ClassLoader#getParent() parent} of each class loader | |
| * is the given parent class loader. This method works exactly as specified | |
| * by the static {@link | |
| * #defineModulesWithManyLoaders(Configuration,List,ClassLoader) | |
| * defineModulesWithManyLoaders} method when invoked with this layer as the | |
| * parent. In other words, if this layer is {@code thisLayer} then this | |
| * method is equivalent to invoking: | |
| * <pre> {@code | |
| * ModuleLayer.defineModulesWithManyLoaders(cf, List.of(thisLayer), parentLoader).layer(); | |
| * }</pre> | |
| * | |
| * @param cf | |
| * The configuration for the layer | |
| * @param parentLoader | |
| * The parent class loader for each of the class loaders created by | |
| * this method; may be {@code null} for the bootstrap class loader | |
| * | |
| * @return The newly created layer | |
| * | |
| * @throws IllegalArgumentException | |
| * If the given configuration has more than one parent or the parent | |
| * of the configuration is not the configuration for this layer | |
| * @throws LayerInstantiationException | |
| * If the layer cannot be created for any of the reasons specified | |
| * by the static {@code defineModulesWithManyLoaders} method | |
| * | |
| * @see #findLoader | |
| */ | |
| public ModuleLayer defineModulesWithManyLoaders(Configuration cf, | |
| ClassLoader parentLoader) { | |
| return defineModulesWithManyLoaders(cf, List.of(this), parentLoader).layer(); | |
| } | |
| /** | |
| * Creates a new module layer, with this layer as its parent, by defining the | |
| * modules in the given {@code Configuration} to the Java virtual machine. | |
| * Each module is mapped, by name, to its class loader by means of the | |
| * given function. This method works exactly as specified by the static | |
| * {@link #defineModules(Configuration,List,Function) defineModules} | |
| * method when invoked with this layer as the parent. In other words, if | |
| * this layer is {@code thisLayer} then this method is equivalent to | |
| * invoking: | |
| * <pre> {@code | |
| * ModuleLayer.defineModules(cf, List.of(thisLayer), clf).layer(); | |
| * }</pre> | |
| * | |
| * @param cf | |
| * The configuration for the layer | |
| * @param clf | |
| * The function to map a module name to a class loader | |
| * | |
| * @return The newly created layer | |
| * | |
| * @throws IllegalArgumentException | |
| * If the given configuration has more than one parent or the parent | |
| * of the configuration is not the configuration for this layer | |
| * @throws LayerInstantiationException | |
| * If the layer cannot be created for any of the reasons specified | |
| * by the static {@code defineModules} method | |
| */ | |
| public ModuleLayer defineModules(Configuration cf, | |
| Function<String, ClassLoader> clf) { | |
| return defineModules(cf, List.of(this), clf).layer(); | |
| } | |
| /** | |
| * Creates a new module layer by defining the modules in the given {@code | |
| * Configuration} to the Java virtual machine. This method creates one | |
| * class loader and defines all modules to that class loader. | |
| * | |
| * <p> The class loader created by this method implements <em>direct | |
| * delegation</em> when loading classes from modules. If the {@link | |
| * ClassLoader#loadClass(String, boolean) loadClass} method is invoked to | |
| * load a class then it uses the package name of the class to map it to a | |
| * module. This may be a module in this layer and hence defined to the same | |
| * class loader. It may be a package in a module in a parent layer that is | |
| * exported to one or more of the modules in this layer. The class | |
| * loader delegates to the class loader of the module, throwing {@code | |
| * ClassNotFoundException} if not found by that class loader. | |
| * When {@code loadClass} is invoked to load classes that do not map to a | |
| * module then it delegates to the parent class loader. </p> | |
| * | |
| * <p> The class loader created by this method locates resources | |
| * ({@link ClassLoader#getResource(String) getResource}, {@link | |
| * ClassLoader#getResources(String) getResources}, and other resource | |
| * methods) in all modules in the layer before searching the parent class | |
| * loader. </p> | |
| * | |
| * <p> Attempting to create a layer with all modules defined to the same | |
| * class loader can fail for the following reasons: | |
| * | |
| * <ul> | |
| * | |
| * <li><p> <em>Overlapping packages</em>: Two or more modules in the | |
| * configuration have the same package. </p></li> | |
| * | |
| * <li><p> <em>Split delegation</em>: The resulting class loader would | |
| * need to delegate to more than one class loader in order to load | |
| * classes in a specific package. </p></li> | |
| * | |
| * </ul> | |
| * | |
| * <p> In addition, a layer cannot be created if the configuration contains | |
| * a module named "{@code java.base}", or a module contains a package named | |
| * "{@code java}" or a package with a name starting with "{@code java.}". </p> | |
| * | |
| * @param cf | |
| * The configuration for the layer | |
| * @param parentLayers | |
| * The list of parent layers in search order | |
| * @param parentLoader | |
| * The parent class loader for the class loader created by this | |
| * method; may be {@code null} for the bootstrap class loader | |
| * | |
| * @return A controller that controls the newly created layer | |
| * | |
| * @throws IllegalArgumentException | |
| * If the parent(s) of the given configuration do not match the | |
| * configuration of the parent layers, including order | |
| * @throws LayerInstantiationException | |
| * If all modules cannot be defined to the same class loader for any | |
| * of the reasons listed above | |
| * | |
| * @see #findLoader | |
| */ | |
| public static Controller defineModulesWithOneLoader(Configuration cf, | |
| List<ModuleLayer> parentLayers, | |
| ClassLoader parentLoader) | |
| { | |
| List<ModuleLayer> parents = List.copyOf(parentLayers); | |
| checkConfiguration(cf, parents); | |
| try { | |
| Loader loader = new Loader(cf.modules(), parentLoader); | |
| loader.initRemotePackageMap(cf, parents); | |
| ModuleLayer layer = new ModuleLayer(cf, parents, mn -> loader); | |
| return new Controller(layer); | |
| } catch (IllegalArgumentException | IllegalStateException e) { | |
| throw new LayerInstantiationException(e.getMessage()); | |
| } | |
| } | |
| /** | |
| * Creates a new module layer by defining the modules in the given {@code | |
| * Configuration} to the Java virtual machine. Each module is defined to | |
| * its own {@link ClassLoader} created by this method. The {@link | |
| * ClassLoader#getParent() parent} of each class loader is the given parent | |
| * class loader. | |
| * | |
| * <p> The class loaders created by this method implement <em>direct | |
| * delegation</em> when loading classes from modules. If the {@link | |
| * ClassLoader#loadClass(String, boolean) loadClass} method is invoked to | |
| * load a class then it uses the package name of the class to map it to a | |
| * module. The package may be in the module defined to the class loader. | |
| * The package may be exported by another module in this layer to the | |
| * module defined to the class loader. It may be in a package exported by a | |
| * module in a parent layer. The class loader delegates to the class loader | |
| * of the module, throwing {@code ClassNotFoundException} if not found by | |
| * that class loader. When {@code loadClass} is invoked to load a class | |
| * that does not map to a module then it delegates to the parent class | |
| * loader. </p> | |
| * | |
| * <p> The class loaders created by this method locate resources | |
| * ({@link ClassLoader#getResource(String) getResource}, {@link | |
| * ClassLoader#getResources(String) getResources}, and other resource | |
| * methods) in the module defined to the class loader before searching | |
| * the parent class loader. </p> | |
| * | |
| * @param cf | |
| * The configuration for the layer | |
| * @param parentLayers | |
| * The list of parent layers in search order | |
| * @param parentLoader | |
| * The parent class loader for each of the class loaders created by | |
| * this method; may be {@code null} for the bootstrap class loader | |
| * | |
| * @return A controller that controls the newly created layer | |
| * | |
| * @throws IllegalArgumentException | |
| * If the parent(s) of the given configuration do not match the | |
| * configuration of the parent layers, including order | |
| * @throws LayerInstantiationException | |
| * If the layer cannot be created because the configuration contains | |
| * a module named "{@code java.base}" or a module contains a package | |
| * named "{@code java}" or a package with a name starting with | |
| * "{@code java.}" | |
| * | |
| * @see #findLoader | |
| */ | |
| public static Controller defineModulesWithManyLoaders(Configuration cf, | |
| List<ModuleLayer> parentLayers, | |
| ClassLoader parentLoader) | |
| { | |
| List<ModuleLayer> parents = List.copyOf(parentLayers); | |
| checkConfiguration(cf, parents); | |
| LoaderPool pool = new LoaderPool(cf, parents, parentLoader); | |
| try { | |
| ModuleLayer layer = new ModuleLayer(cf, parents, pool::loaderFor); | |
| return new Controller(layer); | |
| } catch (IllegalArgumentException | IllegalStateException e) { | |
| throw new LayerInstantiationException(e.getMessage()); | |
| } | |
| } | |
| /** | |
| * Creates a new module layer by defining the modules in the given {@code | |
| * Configuration} to the Java virtual machine. The given function maps each | |
| * module in the configuration, by name, to a class loader. Creating the | |
| * layer informs the Java virtual machine about the classes that may be | |
| * loaded so that the Java virtual machine knows which module that each | |
| * class is a member of. | |
| * | |
| * <p> The class loader delegation implemented by the class loaders must | |
| * respect module readability. The class loaders should be | |
| * {@link ClassLoader#registerAsParallelCapable parallel-capable} so as to | |
| * avoid deadlocks during class loading. In addition, the entity creating | |
| * a new layer with this method should arrange that the class loaders be | |
| * ready to load from these modules before there are any attempts to load | |
| * classes or resources. </p> | |
| * | |
| * <p> Creating a layer can fail for the following reasons: </p> | |
| * | |
| * <ul> | |
| * | |
| * <li><p> Two or more modules with the same package are mapped to the | |
| * same class loader. </p></li> | |
| * | |
| * <li><p> A module is mapped to a class loader that already has a | |
| * module of the same name defined to it. </p></li> | |
| * | |
| * <li><p> A module is mapped to a class loader that has already | |
| * defined types in any of the packages in the module. </p></li> | |
| * | |
| * </ul> | |
| * | |
| * <p> In addition, a layer cannot be created if the configuration contains | |
| * a module named "{@code java.base}", a configuration contains a module | |
| * with a package named "{@code java}" or a package name starting with | |
| * "{@code java.}", or the function to map a module name to a class loader | |
| * returns {@code null} or the {@linkplain ClassLoader#getPlatformClassLoader() | |
| * platform class loader}. </p> | |
| * | |
| * <p> If the function to map a module name to class loader throws an error | |
| * or runtime exception then it is propagated to the caller of this method. | |
| * </p> | |
| * | |
| * @apiNote It is implementation specific as to whether creating a layer | |
| * with this method is an atomic operation or not. Consequently it is | |
| * possible for this method to fail with some modules, but not all, defined | |
| * to the Java virtual machine. | |
| * | |
| * @param cf | |
| * The configuration for the layer | |
| * @param parentLayers | |
| * The list of parent layers in search order | |
| * @param clf | |
| * The function to map a module name to a class loader | |
| * | |
| * @return A controller that controls the newly created layer | |
| * | |
| * @throws IllegalArgumentException | |
| * If the parent(s) of the given configuration do not match the | |
| * configuration of the parent layers, including order | |
| * @throws LayerInstantiationException | |
| * If creating the layer fails for any of the reasons listed above | |
| */ | |
| public static Controller defineModules(Configuration cf, | |
| List<ModuleLayer> parentLayers, | |
| Function<String, ClassLoader> clf) | |
| { | |
| List<ModuleLayer> parents = List.copyOf(parentLayers); | |
| checkConfiguration(cf, parents); | |
| Objects.requireNonNull(clf); | |
| // The boot layer is checked during module system initialization | |
| if (boot() != null) { | |
| checkForDuplicatePkgs(cf, clf); | |
| } | |
| try { | |
| ModuleLayer layer = new ModuleLayer(cf, parents, clf); | |
| return new Controller(layer); | |
| } catch (IllegalArgumentException | IllegalStateException e) { | |
| throw new LayerInstantiationException(e.getMessage()); | |
| } | |
| } | |
| /** | |
| * Checks that the parent configurations match the configuration of | |
| * the parent layers. | |
| */ | |
| private static void checkConfiguration(Configuration cf, | |
| List<ModuleLayer> parentLayers) | |
| { | |
| Objects.requireNonNull(cf); | |
| List<Configuration> parentConfigurations = cf.parents(); | |
| if (parentLayers.size() != parentConfigurations.size()) | |
| throw new IllegalArgumentException("wrong number of parents"); | |
| int index = 0; | |
| for (ModuleLayer parent : parentLayers) { | |
| if (parent.configuration() != parentConfigurations.get(index)) { | |
| throw new IllegalArgumentException( | |
| "Parent of configuration != configuration of this Layer"); | |
| } | |
| index++; | |
| } | |
| } | |
| /** | |
| * Checks a configuration and the module-to-loader mapping to ensure that | |
| * no two modules mapped to the same class loader have the same package. | |
| * It also checks that no two automatic modules have the same package. | |
| * | |
| * @throws LayerInstantiationException | |
| */ | |
| private static void checkForDuplicatePkgs(Configuration cf, | |
| Function<String, ClassLoader> clf) | |
| { | |
| // HashMap allows null keys | |
| Map<ClassLoader, Set<String>> loaderToPackages = new HashMap<>(); | |
| for (ResolvedModule resolvedModule : cf.modules()) { | |
| ModuleDescriptor descriptor = resolvedModule.reference().descriptor(); | |
| ClassLoader loader = clf.apply(descriptor.name()); | |
| Set<String> loaderPackages | |
| = loaderToPackages.computeIfAbsent(loader, k -> new HashSet<>()); | |
| for (String pkg : descriptor.packages()) { | |
| boolean added = loaderPackages.add(pkg); | |
| if (!added) { | |
| throw fail("More than one module with package %s mapped" + | |
| " to the same class loader", pkg); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Creates a LayerInstantiationException with the message formatted from | |
| * the given format string and arguments. | |
| */ | |
| private static LayerInstantiationException fail(String fmt, Object ... args) { | |
| String msg = String.format(fmt, args); | |
| return new LayerInstantiationException(msg); | |
| } | |
| /** | |
| * Returns the configuration for this layer. | |
| * | |
| * @return The configuration for this layer | |
| */ | |
| public Configuration configuration() { | |
| return cf; | |
| } | |
| /** | |
| * Returns an unmodifiable list of this layer's parents, in search | |
| * order. If this is the {@linkplain #empty() empty layer} then an | |
| * empty list is returned. | |
| * | |
| * @return A possibly-empty unmodifiable list of this layer's parents | |
| */ | |
| public List<ModuleLayer> parents() { | |
| return parents; | |
| } | |
| /** | |
| * Returns an ordered stream of layers. The first element is this layer, | |
| * the remaining elements are the parent layers in DFS order. | |
| * | |
| * @implNote For now, the assumption is that the number of elements will | |
| * be very low and so this method does not use a specialized spliterator. | |
| */ | |
| Stream<ModuleLayer> layers() { | |
| List<ModuleLayer> allLayers = this.allLayers; | |
| if (allLayers != null) | |
| return allLayers.stream(); | |
| allLayers = new ArrayList<>(); | |
| Set<ModuleLayer> visited = new HashSet<>(); | |
| Deque<ModuleLayer> stack = new ArrayDeque<>(); | |
| visited.add(this); | |
| stack.push(this); | |
| while (!stack.isEmpty()) { | |
| ModuleLayer layer = stack.pop(); | |
| allLayers.add(layer); | |
| // push in reverse order | |
| for (int i = layer.parents.size() - 1; i >= 0; i--) { | |
| ModuleLayer parent = layer.parents.get(i); | |
| if (visited.add(parent)) { | |
| stack.push(parent); | |
| } | |
| } | |
| } | |
| this.allLayers = allLayers = Collections.unmodifiableList(allLayers); | |
| return allLayers.stream(); | |
| } | |
| private volatile List<ModuleLayer> allLayers; | |
| /** | |
| * Returns an unmodifiable set of the modules in this layer. | |
| * | |
| * @return A possibly-empty unmodifiable set of the modules in this layer | |
| */ | |
| public Set<Module> modules() { | |
| Set<Module> modules = this.modules; | |
| if (modules == null) { | |
| this.modules = modules = Set.copyOf(nameToModule.values()); | |
| } | |
| return modules; | |
| } | |
| private volatile Set<Module> modules; | |
| /** | |
| * Returns the module with the given name in this layer, or if not in this | |
| * layer, the {@linkplain #parents() parent} layers. Finding a module in | |
| * parent layers is equivalent to invoking {@code findModule} on each | |
| * parent, in search order, until the module is found or all parents have | |
| * been searched. In a <em>tree of layers</em> then this is equivalent to | |
| * a depth-first search. | |
| * | |
| * @param name | |
| * The name of the module to find | |
| * | |
| * @return The module with the given name or an empty {@code Optional} | |
| * if there isn't a module with this name in this layer or any | |
| * parent layer | |
| */ | |
| public Optional<Module> findModule(String name) { | |
| Objects.requireNonNull(name); | |
| if (this == EMPTY_LAYER) | |
| return Optional.empty(); | |
| Module m = nameToModule.get(name); | |
| if (m != null) | |
| return Optional.of(m); | |
| return layers() | |
| .skip(1) // skip this layer | |
| .map(l -> l.nameToModule.get(name)) | |
| .filter(Objects::nonNull) | |
| .findAny(); | |
| } | |
| /** | |
| * Updates the module with the given {@code name} in this layer | |
| * to allow access to restricted methods. | |
| * | |
| * @param name the name of the module for which the native access | |
| * should be enabled | |
| * @return {@code true} iff the module is present in this layer, | |
| * {@code false} otherwise | |
| */ | |
| boolean addEnableNativeAccess(String name) { | |
| Module m = nameToModule.get(name); | |
| if (m != null) { | |
| m.implAddEnableNativeAccess(); | |
| return true; | |
| } else { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Returns the {@code ClassLoader} for the module with the given name. If | |
| * a module of the given name is not in this layer then the {@link #parents() | |
| * parent} layers are searched in the manner specified by {@link | |
| * #findModule(String) findModule}. | |
| * | |
| * @apiNote This method does not return an {@code Optional<ClassLoader>} | |
| * because `null` must be used to represent the bootstrap class loader. | |
| * | |
| * @param name | |
| * The name of the module to find | |
| * | |
| * @return The ClassLoader that the module is defined to | |
| * | |
| * @throws IllegalArgumentException if a module of the given name is not | |
| * defined in this layer or any parent of this layer | |
| */ | |
| public ClassLoader findLoader(String name) { | |
| Optional<Module> om = findModule(name); | |
| // can't use map(Module::getClassLoader) as class loader can be null | |
| if (om.isPresent()) { | |
| return om.get().getClassLoader(); | |
| } else { | |
| throw new IllegalArgumentException("Module " + name | |
| + " not known to this layer"); | |
| } | |
| } | |
| /** | |
| * Returns a string describing this module layer. | |
| * | |
| * @return A possibly empty string describing this module layer | |
| */ | |
| @Override | |
| public String toString() { | |
| return modules().stream() | |
| .map(Module::getName) | |
| .collect(Collectors.joining(", ")); | |
| } | |
| /** | |
| * Returns the <em>empty</em> layer. There are no modules in the empty | |
| * layer. It has no parents. | |
| * | |
| * @return The empty layer | |
| */ | |
| public static ModuleLayer empty() { | |
| return EMPTY_LAYER; | |
| } | |
| /** | |
| * Returns the boot layer. The boot layer contains at least one module, | |
| * {@code java.base}. Its parent is the {@link #empty() empty} layer. | |
| * | |
| * @apiNote This method returns {@code null} during startup and before | |
| * the boot layer is fully initialized. | |
| * | |
| * @return The boot layer | |
| */ | |
| public static ModuleLayer boot() { | |
| return System.bootLayer; | |
| } | |
| /** | |
| * Returns the ServicesCatalog for this Layer, creating it if not | |
| * already created. | |
| */ | |
| ServicesCatalog getServicesCatalog() { | |
| ServicesCatalog servicesCatalog = this.servicesCatalog; | |
| if (servicesCatalog != null) | |
| return servicesCatalog; | |
| synchronized (this) { | |
| servicesCatalog = this.servicesCatalog; | |
| if (servicesCatalog == null) { | |
| servicesCatalog = ServicesCatalog.create(); | |
| for (Module m : nameToModule.values()) { | |
| servicesCatalog.register(m); | |
| } | |
| this.servicesCatalog = servicesCatalog; | |
| } | |
| } | |
| return servicesCatalog; | |
| } | |
| private volatile ServicesCatalog servicesCatalog; | |
| /** | |
| * Record that this layer has at least one module defined to the given | |
| * class loader. | |
| */ | |
| void bindToLoader(ClassLoader loader) { | |
| // CLV.computeIfAbsent(loader, (cl, clv) -> new CopyOnWriteArrayList<>()) | |
| List<ModuleLayer> list = CLV.get(loader); | |
| if (list == null) { | |
| list = new CopyOnWriteArrayList<>(); | |
| List<ModuleLayer> previous = CLV.putIfAbsent(loader, list); | |
| if (previous != null) list = previous; | |
| } | |
| list.add(this); | |
| } | |
| /** | |
| * Returns a stream of the layers that have at least one module defined to | |
| * the given class loader. | |
| */ | |
| static Stream<ModuleLayer> layers(ClassLoader loader) { | |
| List<ModuleLayer> list = CLV.get(loader); | |
| if (list != null) { | |
| return list.stream(); | |
| } else { | |
| return Stream.empty(); | |
| } | |
| } | |
| // the list of layers with modules defined to a class loader | |
| private static final ClassLoaderValue<List<ModuleLayer>> CLV = new ClassLoaderValue<>(); | |
| } |