|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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. |
|
* |
|
* <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(); |
|
} |
|
|
|
|
|
private final LoaderPool pool; |
|
|
|
|
|
private final ClassLoader parent; |
|
|
|
|
|
private final Map<String, ModuleReference> nameToModule; |
|
|
|
|
|
private final Map<String, LoadedModule> localPackageToModule; |
|
|
|
|
|
private final Map<String, ClassLoader> remotePackageToLoader |
|
= new ConcurrentHashMap<>(); |
|
|
|
|
|
private final Map<ModuleReference, ModuleReader> moduleToReader |
|
= new ConcurrentHashMap<>(); |
|
|
|
|
|
private final AccessControlContext acc; |
|
|
|
|
|
|
|
*/ |
|
private static class LoadedModule { |
|
private final ModuleReference mref; |
|
private final URL url; |
|
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; } |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
if (pool == null) { |
|
assert nameToModule.containsKey(mn); |
|
continue; |
|
} |
|
|
|
loader = pool.loaderFor(mn); |
|
assert loader != null; |
|
|
|
} else { |
|
|
|
|
|
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 |
|
|
|
assert layer.findModule(mn).isPresent(); |
|
loader = layer.findLoader(mn); |
|
if (loader == null) |
|
loader = ClassLoaders.platformClassLoader(); |
|
} |
|
|
|
|
|
String target = resolvedModule.name(); |
|
ModuleDescriptor descriptor = other.reference().descriptor(); |
|
for (ModuleDescriptor.Exports e : descriptor.exports()) { |
|
boolean delegate; |
|
if (e.isQualified()) { |
|
|
|
delegate = (other.configuration() == cf) |
|
&& e.targets().contains(target); |
|
} else { |
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private Optional<ModuleLayer> findModuleLayer(ModuleLayer parent, Configuration cf) { |
|
return SharedSecrets.getJavaLangAccess().layers(parent) |
|
.filter(l -> l.configuration() == cf) |
|
.findAny(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public LoaderPool pool() { |
|
return pool; |
|
} |
|
|
|
|
|
// -- resources -- |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected URL findResource(String mn, String name) throws IOException { |
|
ModuleReference mref = (mn != null) ? nameToModule.get(mn) : null; |
|
if (mref == null) |
|
return null; |
|
|
|
|
|
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(); |
|
} |
|
|
|
|
|
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); |
|
|
|
|
|
URL url = findResource(name); |
|
if (url == null) { |
|
|
|
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); |
|
|
|
|
|
List<URL> urls = findResourcesAsList(name); |
|
|
|
|
|
Enumeration<URL> e; |
|
if (parent != null) { |
|
e = parent.getResources(name); |
|
} else { |
|
e = BootLoader.findResources(name); |
|
} |
|
|
|
|
|
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(); |
|
} |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
|
|
|
|
*/ |
|
@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; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@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; |
|
} |
|
|
|
|
|
|
|
*/ |
|
@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)) { |
|
|
|
Class<?> c = findLoadedClass(cn); |
|
|
|
if (c == null) { |
|
|
|
LoadedModule loadedModule = findLoadedModule(cn); |
|
|
|
if (loadedModule != null) { |
|
|
|
|
|
c = findClassInModuleOrNull(loadedModule, cn); |
|
|
|
} else { |
|
|
|
|
|
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 |
|
|
|
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; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Class<?> findClassInModuleOrNull(LoadedModule loadedModule, String cn) { |
|
PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule); |
|
return AccessController.doPrivileged(pa, acc); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Class<?> defineClass(String cn, LoadedModule loadedModule) { |
|
ModuleReader reader = moduleReaderFor(loadedModule.mref()); |
|
|
|
try { |
|
|
|
String rn = cn.replace('.', '/').concat(".class"); |
|
ByteBuffer bb = reader.read(rn).orElse(null); |
|
if (bb == null) { |
|
|
|
return null; |
|
} |
|
|
|
try { |
|
return defineClass(cn, bb, loadedModule.codeSource()); |
|
} finally { |
|
reader.release(bb); |
|
} |
|
|
|
} catch (IOException ioe) { |
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
// -- permissions |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
protected PermissionCollection getPermissions(CodeSource cs) { |
|
PermissionCollection perms = super.getPermissions(cs); |
|
|
|
URL url = cs.getLocation(); |
|
if (url == null) |
|
return perms; |
|
|
|
|
|
try { |
|
Permission p = url.openConnection().getPermission(); |
|
if (p != null) { |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private LoadedModule findLoadedModule(String cn) { |
|
String pn = packageName(cn); |
|
return pn.isEmpty() ? null : localPackageToModule.get(pn); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private String packageName(String cn) { |
|
int pos = cn.lastIndexOf('.'); |
|
return (pos < 0) ? "" : cn.substring(0, pos); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private ModuleReader moduleReaderFor(ModuleReference mref) { |
|
return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref)); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private ModuleReader createModuleReader(ModuleReference mref) { |
|
try { |
|
return mref.open(); |
|
} catch (IOException e) { |
|
// Return a null module reader to avoid a future class load |
|
|
|
return new NullModuleReader(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
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"); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
} |