/*
 * 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.loader;

import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

import jdk.internal.misc.SharedSecrets;
import jdk.internal.module.Resources;

A class loader that loads classes and resources from a collection of modules, or from a single module where the class loader is a member of a pool of class loaders.

The delegation model used by this ClassLoader differs to the regular delegation model. When requested to load a class then this ClassLoader first maps the class name to its package name. If there a module defined to the Loader containing the package then the class loader attempts to load from that module. If the package is instead defined to a module in a "remote" ClassLoader then this class loader delegates directly to that class loader. The map of package name to remote class loader is created based on the modules read by modules defined to this class loader. If the package is not local or remote then this class loader will delegate to the parent class loader. This allows automatic modules (for example) to link to types in the unnamed module of the parent class loader.

See Also:
/** * A class loader that loads classes and resources from a collection of * modules, or from a single module where the class loader is a member * of a pool of class loaders. * * <p> The delegation model used by this ClassLoader differs to the regular * delegation model. When requested to load a class then this ClassLoader first * maps the class name to its package name. If there a module defined to the * Loader containing the package then the class loader attempts to load from * that module. If the package is instead defined to a module in a "remote" * ClassLoader then this class loader delegates directly to that class loader. * The map of package name to remote class loader is created based on the * modules read by modules defined to this class loader. If the package is not * local or remote then this class loader will delegate to the parent class * loader. This allows automatic modules (for example) to link to types in the * unnamed module of the parent class loader. * * @see ModuleLayer#defineModulesWithOneLoader * @see ModuleLayer#defineModulesWithManyLoaders */
public final class Loader extends SecureClassLoader { static { ClassLoader.registerAsParallelCapable(); } // the pool this loader is a member of; can be null private final LoaderPool pool; // parent ClassLoader, can be null private final ClassLoader parent; // maps a module name to a module reference private final Map<String, ModuleReference> nameToModule; // maps package name to a module loaded by this class loader private final Map<String, LoadedModule> localPackageToModule; // maps package name to a remote class loader, populated post initialization private final Map<String, ClassLoader> remotePackageToLoader = new ConcurrentHashMap<>(); // maps a module reference to a module reader, populated lazily private final Map<ModuleReference, ModuleReader> moduleToReader = new ConcurrentHashMap<>(); // ACC used when loading classes and resources private final AccessControlContext acc;
A module defined/loaded to a Loader.
/** * A module defined/loaded to a {@code Loader}. */
private static class LoadedModule { private final ModuleReference mref; private final URL url; // may be null private final CodeSource cs; LoadedModule(ModuleReference mref) { URL url = null; if (mref.location().isPresent()) { try { url = mref.location().get().toURL(); } catch (MalformedURLException | IllegalArgumentException e) { } } this.mref = mref; this.url = url; this.cs = new CodeSource(url, (CodeSigner[]) null); } ModuleReference mref() { return mref; } String name() { return mref.descriptor().name(); } URL location() { return url; } CodeSource codeSource() { return cs; } }
Creates a Loader in a loader pool that loads classes/resources from one module.
/** * Creates a {@code Loader} in a loader pool that loads classes/resources * from one module. */
public Loader(ResolvedModule resolvedModule, LoaderPool pool, ClassLoader parent) { super("Loader-" + resolvedModule.name(), parent); this.pool = pool; this.parent = parent; ModuleReference mref = resolvedModule.reference(); ModuleDescriptor descriptor = mref.descriptor(); String mn = descriptor.name(); this.nameToModule = Map.of(mn, mref); Map<String, LoadedModule> localPackageToModule = new HashMap<>(); LoadedModule lm = new LoadedModule(mref); descriptor.packages().forEach(pn -> localPackageToModule.put(pn, lm)); this.localPackageToModule = localPackageToModule; this.acc = AccessController.getContext(); }
Creates a Loader that loads classes/resources from a collection of modules.
Throws:
/** * Creates a {@code Loader} that loads classes/resources from a collection * of modules. * * @throws IllegalArgumentException * If two or more modules have the same package */
public Loader(Collection<ResolvedModule> modules, ClassLoader parent) { super(parent); this.pool = null; this.parent = parent; Map<String, ModuleReference> nameToModule = new HashMap<>(); Map<String, LoadedModule> localPackageToModule = new HashMap<>(); for (ResolvedModule resolvedModule : modules) { ModuleReference mref = resolvedModule.reference(); ModuleDescriptor descriptor = mref.descriptor(); nameToModule.put(descriptor.name(), mref); descriptor.packages().forEach(pn -> { LoadedModule lm = new LoadedModule(mref); if (localPackageToModule.put(pn, lm) != null) throw new IllegalArgumentException("Package " + pn + " in more than one module"); }); } this.nameToModule = nameToModule; this.localPackageToModule = localPackageToModule; this.acc = AccessController.getContext(); }
Completes initialization of this Loader. This method populates remotePackageToLoader with the packages of the remote modules, where "remote modules" are the modules read by modules defined to this loader.
Params:
  • cf – the Configuration containing at least modules to be defined to this class loader
  • parentModuleLayers – the parent ModuleLayers
/** * Completes initialization of this Loader. This method populates * remotePackageToLoader with the packages of the remote modules, where * "remote modules" are the modules read by modules defined to this loader. * * @param cf the Configuration containing at least modules to be defined to * this class loader * * @param parentModuleLayers the parent ModuleLayers */
public Loader initRemotePackageMap(Configuration cf, List<ModuleLayer> parentModuleLayers) { for (String name : nameToModule.keySet()) { ResolvedModule resolvedModule = cf.findModule(name).get(); assert resolvedModule.configuration() == cf; for (ResolvedModule other : resolvedModule.reads()) { String mn = other.name(); ClassLoader loader; if (other.configuration() == cf) { // The module reads another module in the newly created // layer. If all modules are defined to the same class // loader then the packages are local. if (pool == null) { assert nameToModule.containsKey(mn); continue; } loader = pool.loaderFor(mn); assert loader != null; } else { // find the layer for the target module ModuleLayer layer = parentModuleLayers.stream() .map(parent -> findModuleLayer(parent, other.configuration())) .flatMap(Optional::stream) .findAny() .orElseThrow(() -> new InternalError("Unable to find parent layer")); // find the class loader for the module // For now we use the platform loader for modules defined to the // boot loader assert layer.findModule(mn).isPresent(); loader = layer.findLoader(mn); if (loader == null) loader = ClassLoaders.platformClassLoader(); } // find the packages that are exported to the target module String target = resolvedModule.name(); ModuleDescriptor descriptor = other.reference().descriptor(); for (ModuleDescriptor.Exports e : descriptor.exports()) { boolean delegate; if (e.isQualified()) { // qualified export in same configuration delegate = (other.configuration() == cf) && e.targets().contains(target); } else { // unqualified delegate = true; } if (delegate) { String pn = e.source(); ClassLoader l = remotePackageToLoader.putIfAbsent(pn, loader); if (l != null && l != loader) { throw new IllegalArgumentException("Package " + pn + " cannot be imported from multiple loaders"); } } } } } return this; }
Find the layer corresponding to the given configuration in the tree of layers rooted at the given parent.
/** * Find the layer corresponding to the given configuration in the tree * of layers rooted at the given parent. */
private Optional<ModuleLayer> findModuleLayer(ModuleLayer parent, Configuration cf) { return SharedSecrets.getJavaLangAccess().layers(parent) .filter(l -> l.configuration() == cf) .findAny(); }
Returns the loader pool that this loader is in or null if this loader is not in a loader pool.
/** * Returns the loader pool that this loader is in or {@code null} if this * loader is not in a loader pool. */
public LoaderPool pool() { return pool; } // -- resources --
Returns a URL to a resource of the given name in a module defined to this class loader.
/** * Returns a URL to a resource of the given name in a module defined to * this class loader. */
@Override protected URL findResource(String mn, String name) throws IOException { ModuleReference mref = (mn != null) ? nameToModule.get(mn) : null; if (mref == null) return null; // not defined to this class loader // locate resource URL url = null; try { url = AccessController.doPrivileged( new PrivilegedExceptionAction<URL>() { @Override public URL run() throws IOException { Optional<URI> ouri = moduleReaderFor(mref).find(name); if (ouri.isPresent()) { try { return ouri.get().toURL(); } catch (MalformedURLException | IllegalArgumentException e) { } } return null; } }); } catch (PrivilegedActionException pae) { throw (IOException) pae.getCause(); } // check access with permissions restricted by ACC if (url != null && System.getSecurityManager() != null) { try { URL urlToCheck = url; url = AccessController.doPrivileged( new PrivilegedExceptionAction<URL>() { @Override public URL run() throws IOException { return URLClassPath.checkURL(urlToCheck); } }, acc); } catch (PrivilegedActionException pae) { url = null; } } return url; } @Override public URL findResource(String name) { String pn = Resources.toPackageName(name); LoadedModule module = localPackageToModule.get(pn); if (module != null) { try { URL url = findResource(module.name(), name); if (url != null && (name.endsWith(".class") || url.toString().endsWith("/") || isOpen(module.mref(), pn))) { return url; } } catch (IOException ioe) { // ignore } } else { for (ModuleReference mref : nameToModule.values()) { try { URL url = findResource(mref.descriptor().name(), name); if (url != null) return url; } catch (IOException ioe) { // ignore } } } return null; } @Override public Enumeration<URL> findResources(String name) throws IOException { return Collections.enumeration(findResourcesAsList(name)); } @Override public URL getResource(String name) { Objects.requireNonNull(name); // this loader URL url = findResource(name); if (url == null) { // parent loader if (parent != null) { url = parent.getResource(name); } else { url = BootLoader.findResource(name); } } return url; } @Override public Enumeration<URL> getResources(String name) throws IOException { Objects.requireNonNull(name); // this loader List<URL> urls = findResourcesAsList(name); // parent loader Enumeration<URL> e; if (parent != null) { e = parent.getResources(name); } else { e = BootLoader.findResources(name); } // concat the URLs with the URLs returned by the parent return new Enumeration<>() { final Iterator<URL> iterator = urls.iterator(); @Override public boolean hasMoreElements() { return (iterator.hasNext() || e.hasMoreElements()); } @Override public URL nextElement() { if (iterator.hasNext()) { return iterator.next(); } else { return e.nextElement(); } } }; }
Finds the resources with the given name in this class loader.
/** * Finds the resources with the given name in this class loader. */
private List<URL> findResourcesAsList(String name) throws IOException { String pn = Resources.toPackageName(name); LoadedModule module = localPackageToModule.get(pn); if (module != null) { URL url = findResource(module.name(), name); if (url != null && (name.endsWith(".class") || url.toString().endsWith("/") || isOpen(module.mref(), pn))) { return List.of(url); } else { return Collections.emptyList(); } } else { List<URL> urls = new ArrayList<>(); for (ModuleReference mref : nameToModule.values()) { URL url = findResource(mref.descriptor().name(), name); if (url != null) { urls.add(url); } } return urls; } } // -- finding/loading classes
Finds the class with the specified binary name.
/** * Finds the class with the specified binary name. */
@Override protected Class<?> findClass(String cn) throws ClassNotFoundException { Class<?> c = null; LoadedModule loadedModule = findLoadedModule(cn); if (loadedModule != null) c = findClassInModuleOrNull(loadedModule, cn); if (c == null) throw new ClassNotFoundException(cn); return c; }
Finds the class with the specified binary name in the given module. This method returns null if the class cannot be found.
/** * Finds the class with the specified binary name in the given module. * This method returns {@code null} if the class cannot be found. */
@Override protected Class<?> findClass(String mn, String cn) { Class<?> c = null; LoadedModule loadedModule = findLoadedModule(cn); if (loadedModule != null && loadedModule.name().equals(mn)) c = findClassInModuleOrNull(loadedModule, cn); return c; }
Loads the class with the specified binary name.
/** * Loads the class with the specified binary name. */
@Override protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { String pn = packageName(cn); if (!pn.isEmpty()) { sm.checkPackageAccess(pn); } } synchronized (getClassLoadingLock(cn)) { // check if already loaded Class<?> c = findLoadedClass(cn); if (c == null) { LoadedModule loadedModule = findLoadedModule(cn); if (loadedModule != null) { // class is in module defined to this class loader c = findClassInModuleOrNull(loadedModule, cn); } else { // type in another module or visible via the parent loader String pn = packageName(cn); ClassLoader loader = remotePackageToLoader.get(pn); if (loader == null) { // type not in a module read by any of the modules // defined to this loader, so delegate to parent // class loader loader = parent; } if (loader == null) { c = BootLoader.loadClassOrNull(cn); } else { c = loader.loadClass(cn); } } } if (c == null) throw new ClassNotFoundException(cn); if (resolve) resolveClass(c); return c; } }
Finds the class with the specified binary name if in a module defined to this ClassLoader.
Returns:the resulting Class or null if not found
/** * Finds the class with the specified binary name if in a module * defined to this ClassLoader. * * @return the resulting Class or {@code null} if not found */
private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) { PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule); return AccessController.doPrivileged(pa, acc); }
Defines the given binary class name to the VM, loading the class bytes from the given module.
Returns:the resulting Class or null if an I/O error occurs
/** * Defines the given binary class name to the VM, loading the class * bytes from the given module. * * @return the resulting Class or {@code null} if an I/O error occurs */
private Class<?> defineClass(String cn, LoadedModule loadedModule) { ModuleReader reader = moduleReaderFor(loadedModule.mref()); try { // read class file String rn = cn.replace('.', '/').concat(".class"); ByteBuffer bb = reader.read(rn).orElse(null); if (bb == null) { // class not found return null; } try { return defineClass(cn, bb, loadedModule.codeSource()); } finally { reader.release(bb); } } catch (IOException ioe) { // TBD on how I/O errors should be propagated return null; } } // -- permissions
Returns the permissions for the given CodeSource.
/** * Returns the permissions for the given CodeSource. */
@Override protected PermissionCollection getPermissions(CodeSource cs) { PermissionCollection perms = super.getPermissions(cs); URL url = cs.getLocation(); if (url == null) return perms; // add the permission to access the resource try { Permission p = url.openConnection().getPermission(); if (p != null) { // for directories then need recursive access if (p instanceof FilePermission) { String path = p.getName(); if (path.endsWith(File.separator)) { path += "-"; p = new FilePermission(path, "read"); } } perms.add(p); } } catch (IOException ioe) { } return perms; } // -- miscellaneous supporting methods
Find the candidate module for the given class name. Returns null if none of the modules defined to this class loader contain the API package for the class.
/** * Find the candidate module for the given class name. * Returns {@code null} if none of the modules defined to this * class loader contain the API package for the class. */
private LoadedModule findLoadedModule(String cn) { String pn = packageName(cn); return pn.isEmpty() ? null : localPackageToModule.get(pn); }
Returns the package name for the given class name
/** * Returns the package name for the given class name */
private String packageName(String cn) { int pos = cn.lastIndexOf('.'); return (pos < 0) ? "" : cn.substring(0, pos); }
Returns the ModuleReader for the given module.
/** * Returns the ModuleReader for the given module. */
private ModuleReader moduleReaderFor(ModuleReference mref) { return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref)); }
Creates a ModuleReader for the given module.
/** * Creates a ModuleReader for the given module. */
private ModuleReader createModuleReader(ModuleReference mref) { try { return mref.open(); } catch (IOException e) { // Return a null module reader to avoid a future class load // attempting to open the module again. return new NullModuleReader(); } }
A ModuleReader that doesn't read any resources.
/** * A ModuleReader that doesn't read any resources. */
private static class NullModuleReader implements ModuleReader { @Override public Optional<URI> find(String name) { return Optional.empty(); } @Override public Stream<String> list() { return Stream.empty(); } @Override public void close() { throw new InternalError("Should not get here"); } }
Returns true if the given module opens the given package unconditionally.
Implementation Note:This method currently iterates over each of the open packages. This will be replaced once the ModuleDescriptor.Opens API is updated.
/** * Returns true if the given module opens the given package * unconditionally. * * @implNote This method currently iterates over each of the open * packages. This will be replaced once the ModuleDescriptor.Opens * API is updated. */
private boolean isOpen(ModuleReference mref, String pn) { ModuleDescriptor descriptor = mref.descriptor(); if (descriptor.isOpen() || descriptor.isAutomatic()) return true; for (ModuleDescriptor.Opens opens : descriptor.opens()) { String source = opens.source(); if (!opens.isQualified() && source.equals(pn)) { return true; } } return false; } }