diff -r 2c33418a6d57 -r 83b611b88ac8 jdk/src/java.base/share/classes/java/lang/ModuleLayer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.base/share/classes/java/lang/ModuleLayer.java Fri Apr 07 08:05:54 2017 +0000 @@ -0,0 +1,944 @@ +/* + * Copyright (c) 2014, 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 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.loader.ClassLoaderValue; +import jdk.internal.loader.Loader; +import jdk.internal.loader.LoaderPool; +import jdk.internal.module.ServicesCatalog; +import sun.security.util.SecurityConstants; + + +/** + * A layer of modules in the Java virtual machine. + * + *

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.

+ * + *

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.

+ * + *

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 + *

+ * + *

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 + * built-in into the Java virtual machine. The boot layer will often be + * the {@link #parents() parent} when creating additional layers.

+ * + *

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:

+ * + * + *

Qualified opens are handled in same way as qualified exports.

+ * + *

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.

+ * + *

Unless otherwise specified, passing a {@code null} argument to a method + * in this class causes a {@link NullPointerException NullPointerException} to + * be thrown.

+ * + *

Example usage:

+ * + *

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.

+ * + *
{@code
+ *     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 + * @spec JPMS + * @see Module#getLayer() + */ + +public final class ModuleLayer { + + // the empty layer + private static final ModuleLayer EMPTY_LAYER + = new ModuleLayer(Configuration.empty(), List.of(), null); + + // the configuration from which this ;ayer was created + private final Configuration cf; + + // parent layers, empty in the case of the empty layer + private final List parents; + + // maps module name to jlr.Module + private final Map nameToModule; + + /** + * Creates a new module layer from the modules in the given configuration. + */ + private ModuleLayer(Configuration cf, + List parents, + Function clf) + { + this.cf = cf; + this.parents = parents; // no need to do defensive copy + + Map map; + if (parents.isEmpty()) { + map = Collections.emptyMap(); + } 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. + * + *

Unless otherwise specified, passing a {@code null} argument to a + * method in this class causes a {@link NullPointerException + * NullPointerException} to be thrown.

+ * + * @apiNote Care should be taken with {@code Controller} objects, they + * should never be shared with untrusted code. + * + * @since 9 + * @spec JPMS + */ + 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 Read edges added by this method are weak + * 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 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 to read + * + * @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; + } + } + + + /** + * 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: + *
 {@code
+     *     ModuleLayer.defineModulesWithOneLoader(cf, List.of(thisLayer), parentLoader).layer();
+     * }
+ * + * @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 parent of the given 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 + * @throws SecurityException + * If {@code RuntimePermission("createClassLoader")} or + * {@code RuntimePermission("getClassLoader")} is denied by + * the security manager + * + * @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: + *
 {@code
+     *     ModuleLayer.defineModulesWithManyLoaders(cf, List.of(thisLayer), parentLoader).layer();
+     * }
+ * + * @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 parent of the given 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 + * @throws SecurityException + * If {@code RuntimePermission("createClassLoader")} or + * {@code RuntimePermission("getClassLoader")} is denied by + * the security manager + * + * @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: + *
 {@code
+     *     ModuleLayer.defineModules(cf, List.of(thisLayer), clf).layer();
+     * }
+ * + * @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 parent of the given 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 + * @throws SecurityException + * If {@code RuntimePermission("getClassLoader")} is denied by + * the security manager + */ + public ModuleLayer defineModules(Configuration cf, + Function 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. + * + *

The class loader created by this method implements direct + * delegation when loading types from modules. When its {@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.

+ * + *

Attempting to create a layer with all modules defined to the same + * class loader can fail for the following reasons: + * + *

    + * + *
  • Overlapping packages: Two or more modules in the + * configuration have the same package.

  • + * + *
  • Split delegation: The resulting class loader would + * need to delegate to more than one class loader in order to load types + * in a specific package.

  • + * + *
+ * + *

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.}".

+ * + *

If there is a security manager then the class loader created by + * this method will load classes and resources with privileges that are + * restricted by the calling context of this method.

+ * + * @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 configurations 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 + * @throws SecurityException + * If {@code RuntimePermission("createClassLoader")} or + * {@code RuntimePermission("getClassLoader")} is denied by + * the security manager + * + * @see #findLoader + */ + public static Controller defineModulesWithOneLoader(Configuration cf, + List parentLayers, + ClassLoader parentLoader) + { + List parents = new ArrayList<>(parentLayers); + checkConfiguration(cf, parents); + + checkCreateClassLoaderPermission(); + checkGetClassLoaderPermission(); + + 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. + * + *

The class loaders created by this method implement direct + * delegation when loading types from modules. When {@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 classes that do not map to a + * module then it delegates to the parent class loader.

+ * + *

If there is a security manager then the class loaders created by + * this method will load classes and resources with privileges that are + * restricted by the calling context of this method.

+ * + * @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 configurations 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.}" + * + * @throws SecurityException + * If {@code RuntimePermission("createClassLoader")} or + * {@code RuntimePermission("getClassLoader")} is denied by + * the security manager + * + * @see #findLoader + */ + public static Controller defineModulesWithManyLoaders(Configuration cf, + List parentLayers, + ClassLoader parentLoader) + { + List parents = new ArrayList<>(parentLayers); + checkConfiguration(cf, parents); + + checkCreateClassLoaderPermission(); + checkGetClassLoaderPermission(); + + 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. + * + *

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.

+ * + *

Creating a layer can fail for the following reasons:

+ * + *
    + * + *
  • Two or more modules with the same package are mapped to the + * same class loader.

  • + * + *
  • A module is mapped to a class loader that already has a + * module of the same name defined to it.

  • + * + *
  • A module is mapped to a class loader that has already + * defined types in any of the packages in the module.

  • + * + *
+ * + *

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.}" and the module is mapped to a class loader other than + * the {@link ClassLoader#getPlatformClassLoader() platform class loader}, + * or the function to map a module name to a class loader returns + * {@code null}.

+ * + *

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. + *

+ * + * @apiNote It is implementation specific as to whether creating a layer + * with this method is an atomic operation or not. Consequentially 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 configurations 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 + * @throws SecurityException + * If {@code RuntimePermission("getClassLoader")} is denied by + * the security manager + */ + public static Controller defineModules(Configuration cf, + List parentLayers, + Function clf) + { + List parents = new ArrayList<>(parentLayers); + checkConfiguration(cf, parents); + Objects.requireNonNull(clf); + + checkGetClassLoaderPermission(); + + // 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 parentLayers) + { + Objects.requireNonNull(cf); + + List 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++; + } + } + + private static void checkCreateClassLoaderPermission() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(SecurityConstants.CREATE_CLASSLOADER_PERMISSION); + } + + private static void checkGetClassLoaderPermission() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); + } + + /** + * 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 clf) + { + // HashMap allows null keys + Map> loaderToPackages = new HashMap<>(); + for (ResolvedModule resolvedModule : cf.modules()) { + ModuleDescriptor descriptor = resolvedModule.reference().descriptor(); + ClassLoader loader = clf.apply(descriptor.name()); + + Set 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 a 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 the list of this layer's parents unless this is the + * {@linkplain #empty empty layer}, which has no parents and so an + * empty list is returned. + * + * @return The list of this layer's parents + */ + public List parents() { + return parents; + } + + + /** + * Returns an ordered stream of layers. The first element is 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 layers() { + List allLayers = this.allLayers; + if (allLayers != null) + return allLayers.stream(); + + allLayers = new ArrayList<>(); + Set visited = new HashSet<>(); + Deque 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.contains(parent)) { + visited.add(parent); + stack.push(parent); + } + } + } + + this.allLayers = allLayers = Collections.unmodifiableList(allLayers); + return allLayers.stream(); + } + + private volatile List allLayers; + + /** + * Returns the set of the modules in this layer. + * + * @return A possibly-empty unmodifiable set of the modules in this layer + */ + public Set modules() { + Set modules = this.modules; + if (modules == null) { + this.modules = modules = + Collections.unmodifiableSet(new HashSet<>(nameToModule.values())); + } + return modules; + } + + private volatile Set 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 tree of layers 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 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) + .filter(map -> map.containsKey(name)) + .map(map -> map.get(name)) + .findAny(); + } + + + /** + * 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}. + * + *

If there is a security manager then its {@code checkPermission} + * method is called with a {@code RuntimePermission("getClassLoader")} + * permission to check that the caller is allowed to get access to the + * class loader.

+ * + * @apiNote This method does not return an {@code Optional} + * 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 + * + * @throws SecurityException if denied by the security manager + */ + public ClassLoader findLoader(String name) { + Optional 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 empty 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(); + nameToModule.values().forEach(servicesCatalog::register); + 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 list = CLV.get(loader); + if (list == null) { + list = new CopyOnWriteArrayList<>(); + List 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 layers(ClassLoader loader) { + List 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> CLV = new ClassLoaderValue<>(); +}