blob: ab052ec0b30476bfaa22786288adbb661d454e3d [file] [log] [blame]
/*
* Copyright (c) 2015, 2016, 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.io.InputStream;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleReference;
import java.lang.module.ModuleReader;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
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.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.stream.Stream;
import jdk.internal.module.ModulePatcher.PatchedModuleReader;
import jdk.internal.misc.VM;
/**
* The platform or application class loader. Resources loaded from modules
* defined to the boot class loader are also loaded via an instance of this
* ClassLoader type.
*
* <p> This ClassLoader supports loading of classes and resources from modules.
* Modules are defined to the ClassLoader by invoking the {@link #loadModule}
* method. Defining a module to this ClassLoader has the effect of making the
* types in the module visible. </p>
*
* <p> This ClassLoader also supports loading of classes and resources from a
* class path of URLs that are specified to the ClassLoader at construction
* time. The class path may expand at runtime (the Class-Path attribute in JAR
* files or via instrumentation agents). </p>
*
* <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 is a module defined to a
* BuiltinClassLoader containing this package then the class loader delegates
* directly to that class loader. If there isn't a module containing the
* package then it delegates the search to the parent class loader and if not
* found in the parent then it searches the class path. The main difference
* between this and the usual delegation model is that it allows the platform
* class loader to delegate to the application class loader, important with
* upgraded modules defined to the platform class loader.
*/
public class BuiltinClassLoader
extends SecureClassLoader
{
static {
if (!ClassLoader.registerAsParallelCapable())
throw new InternalError("Unable to register as parallel capable");
}
// parent ClassLoader
private final BuiltinClassLoader parent;
// the URL class path or null if there is no class path
private final URLClassPath ucp;
/**
* A module defined/loaded by a built-in class loader.
*
* A LoadedModule encapsulates a ModuleReference along with its CodeSource
* URL to avoid needing to create this URL when defining classes.
*/
private static class LoadedModule {
private final BuiltinClassLoader loader;
private final ModuleReference mref;
private final URL codeSourceURL; // may be null
LoadedModule(BuiltinClassLoader loader, ModuleReference mref) {
URL url = null;
if (mref.location().isPresent()) {
try {
url = mref.location().get().toURL();
} catch (MalformedURLException | IllegalArgumentException e) { }
}
this.loader = loader;
this.mref = mref;
this.codeSourceURL = url;
}
BuiltinClassLoader loader() { return loader; }
ModuleReference mref() { return mref; }
String name() { return mref.descriptor().name(); }
URL codeSourceURL() { return codeSourceURL; }
}
// maps package name to loaded module for modules in the boot layer
private static final Map<String, LoadedModule> packageToModule
= new ConcurrentHashMap<>(1024);
// maps a module name to a module reference
private final Map<String, ModuleReference> nameToModule;
// maps a module reference to a module reader
private final Map<ModuleReference, ModuleReader> moduleToReader;
// cache of resource name -> list of URLs.
// used only for resources that are not in module packages
private volatile SoftReference<Map<String, List<URL>>> resourceCache;
/**
* Create a new instance.
*/
BuiltinClassLoader(String name, BuiltinClassLoader parent, URLClassPath ucp) {
// ensure getParent() returns null when the parent is the boot loader
super(name, parent == null || parent == ClassLoaders.bootLoader() ? null : parent);
this.parent = parent;
this.ucp = ucp;
this.nameToModule = new ConcurrentHashMap<>();
this.moduleToReader = new ConcurrentHashMap<>();
}
/**
* Register a module this this class loader. This has the effect of making
* the types in the module visible.
*/
public void loadModule(ModuleReference mref) {
assert !VM.isModuleSystemInited();
String mn = mref.descriptor().name();
if (nameToModule.putIfAbsent(mn, mref) != null) {
throw new InternalError(mn + " already defined to this loader");
}
LoadedModule loadedModule = new LoadedModule(this, mref);
for (String pn : mref.descriptor().packages()) {
LoadedModule other = packageToModule.putIfAbsent(pn, loadedModule);
if (other != null) {
throw new InternalError(pn + " in modules " + mn + " and "
+ other.mref().descriptor().name());
}
}
}
/**
* Returns the {@code ModuleReference} for the named module defined to
* this class loader; or {@code null} if not defined.
*
* @param name The name of the module to find
*/
protected ModuleReference findModule(String name) {
return nameToModule.get(name);
}
// -- finding resources
/**
* Returns a URL to a resource of the given name in a module defined to
* this class loader.
*/
@Override
public URL findResource(String mn, String name) throws IOException {
URL url = null;
if (mn != null) {
// find in module
ModuleReference mref = nameToModule.get(mn);
if (mref != null) {
url = findResource(mref, name);
}
} else {
// find on class path
url = findResourceOnClassPath(name);
}
return checkURL(url); // check access before returning
}
/**
* Returns an input stream to a resource of the given name in a module
* defined to this class loader.
*/
public InputStream findResourceAsStream(String mn, String name)
throws IOException
{
// Need URL to resource when running with a security manager so that
// the right permission check is done.
if (System.getSecurityManager() != null || mn == null) {
URL url = findResource(mn, name);
return (url != null) ? url.openStream() : null;
}
// find in module defined to this loader, no security manager
ModuleReference mref = nameToModule.get(mn);
if (mref != null) {
return moduleReaderFor(mref).open(name).orElse(null);
} else {
return null;
}
}
/**
* Finds a resource with the given name in the modules defined to this
* class loader or its class path.
*/
@Override
public URL findResource(String name) {
String pn = ResourceHelper.getPackageName(name);
LoadedModule module = packageToModule.get(pn);
if (module != null) {
// resource is in a package of a module defined to this loader
if (module.loader() == this
&& (name.endsWith(".class") || isOpen(module.mref(), pn))) {
try {
return findResource(module.name(), name); // checks URL
} catch (IOException ioe) {
return null;
}
}
} else {
// not in a module package but may be in module defined to this loader
try {
List<URL> urls = findMiscResource(name);
if (!urls.isEmpty()) {
URL url = urls.get(0);
if (url != null) {
return checkURL(url); // check access before returning
}
}
} catch (IOException ioe) {
return null;
}
}
// search class path
URL url = findResourceOnClassPath(name);
return checkURL(url);
}
/**
* Returns an enumeration of URL objects to all the resources with the
* given name in modules defined to this class loader or on the class
* path of this loader.
*/
@Override
public Enumeration<URL> findResources(String name) throws IOException {
List<URL> checked = new ArrayList<>(); // list of checked URLs
String pn = ResourceHelper.getPackageName(name);
LoadedModule module = packageToModule.get(pn);
if (module != null) {
// resource is in a package of a module defined to this loader
if (module.loader() == this
&& (name.endsWith(".class") || isOpen(module.mref(), pn))) {
URL url = findResource(module.name(), name); // checks URL
if (url != null) {
checked.add(url);
}
}
} else {
// not in a package of a module defined to this loader
for (URL url : findMiscResource(name)) {
url = checkURL(url);
if (url != null) {
checked.add(url);
}
}
}
// search class path
Enumeration<URL> e = findResourcesOnClassPath(name);
while (e.hasMoreElements()) {
URL url = checkURL(e.nextElement());
if (url != null) {
checked.add(url);
}
}
return Collections.enumeration(checked);
}
/**
* Returns the list of URLs to a "miscellaneous" resource in modules
* defined to this loader. A miscellaneous resource is not in a module
* package, e.g. META-INF/services/p.S.
*
* The cache used by this method avoids repeated searching of all modules.
*/
private List<URL> findMiscResource(String name) throws IOException {
SoftReference<Map<String, List<URL>>> ref = this.resourceCache;
Map<String, List<URL>> map = (ref != null) ? ref.get() : null;
if (map != null) {
List<URL> urls = map.get(name);
if (urls != null)
return urls;
}
// search all modules for the resource
List<URL> urls;
try {
urls = AccessController.doPrivileged(
new PrivilegedExceptionAction<>() {
@Override
public List<URL> run() throws IOException {
List<URL> result = new ArrayList<>();
for (ModuleReference mref : nameToModule.values()) {
URI u = moduleReaderFor(mref).find(name).orElse(null);
if (u != null) {
try {
result.add(u.toURL());
} catch (MalformedURLException |
IllegalArgumentException e) {
}
}
}
return result;
}
});
} catch (PrivilegedActionException pae) {
throw (IOException) pae.getCause();
}
// only cache resources after all modules have been defined
if (VM.isModuleSystemInited()) {
if (map == null) {
map = new ConcurrentHashMap<>();
this.resourceCache = new SoftReference<>(map);
}
if (urls.isEmpty())
urls = Collections.emptyList();
map.putIfAbsent(name, urls);
}
return urls;
}
/**
* Returns the URL to a resource in a module or {@code null} if not found.
*/
private URL findResource(ModuleReference mref, String name) throws IOException {
URI u;
if (System.getSecurityManager() == null) {
u = moduleReaderFor(mref).find(name).orElse(null);
} else {
try {
u = AccessController.doPrivileged(new PrivilegedExceptionAction<> () {
@Override
public URI run() throws IOException {
return moduleReaderFor(mref).find(name).orElse(null);
}
});
} catch (PrivilegedActionException pae) {
throw (IOException) pae.getCause();
}
}
if (u != null) {
try {
return u.toURL();
} catch (MalformedURLException | IllegalArgumentException e) { }
}
return null;
}
/**
* Returns the URL to a resource in a module. Returns {@code null} if not found
* or an I/O error occurs.
*/
private URL findResourceOrNull(ModuleReference mref, String name) {
try {
return findResource(mref, name);
} catch (IOException ignore) {
return null;
}
}
/**
* Returns a URL to a resource on the class path.
*/
private URL findResourceOnClassPath(String name) {
if (ucp != null) {
if (System.getSecurityManager() == null) {
return ucp.findResource(name, false);
} else {
PrivilegedAction<URL> pa = () -> ucp.findResource(name, false);
return AccessController.doPrivileged(pa);
}
} else {
// no class path
return null;
}
}
/**
* Returns the URLs of all resources of the given name on the class path.
*/
private Enumeration<URL> findResourcesOnClassPath(String name) {
if (ucp != null) {
if (System.getSecurityManager() == null) {
return ucp.findResources(name, false);
} else {
PrivilegedAction<Enumeration<URL>> pa;
pa = () -> ucp.findResources(name, false);
return AccessController.doPrivileged(pa);
}
} else {
// no class path
return Collections.emptyEnumeration();
}
}
// -- finding/loading classes
/**
* Finds the class with the specified binary name.
*/
@Override
protected Class<?> findClass(String cn) throws ClassNotFoundException {
// no class loading until VM is fully initialized
if (!VM.isModuleSystemInited())
throw new ClassNotFoundException(cn);
// find the candidate module for this class
LoadedModule loadedModule = findLoadedModule(cn);
Class<?> c = null;
if (loadedModule != null) {
// attempt to load class in module defined to this loader
if (loadedModule.loader() == this) {
c = findClassInModuleOrNull(loadedModule, cn);
}
} else {
// search class path
if (ucp != null) {
c = findClassOnClassPathOrNull(cn);
}
}
// not found
if (c == null)
throw new ClassNotFoundException(cn);
return c;
}
/**
* Finds the class with the specified binary name in a module.
* This method returns {@code null} if the class cannot be found
* or not defined in the specified module.
*/
@Override
protected Class<?> findClass(String mn, String cn) {
if (mn != null) {
// find the candidate module for this class
LoadedModule loadedModule = findLoadedModule(mn, cn);
if (loadedModule == null) {
return null;
}
// attempt to load class in module defined to this loader
assert loadedModule.loader() == this;
return findClassInModuleOrNull(loadedModule, cn);
}
// search class path
if (ucp != null) {
return findClassOnClassPathOrNull(cn);
}
return null;
}
/**
* Loads the class with the specified binary name.
*/
@Override
protected Class<?> loadClass(String cn, boolean resolve)
throws ClassNotFoundException
{
Class<?> c = loadClassOrNull(cn, resolve);
if (c == null)
throw new ClassNotFoundException(cn);
return c;
}
/**
* A variation of {@code loadCass} to load a class with the specified
* binary name. This method returns {@code null} when the class is not
* found.
*/
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
synchronized (getClassLoadingLock(cn)) {
// check if already loaded
Class<?> c = findLoadedClass(cn);
if (c == null) {
// find the candidate module for this class
LoadedModule loadedModule = findLoadedModule(cn);
if (loadedModule != null) {
// package is in a module
BuiltinClassLoader loader = loadedModule.loader();
if (loader == this) {
if (VM.isModuleSystemInited()) {
c = findClassInModuleOrNull(loadedModule, cn);
}
} else {
// delegate to the other loader
c = loader.loadClassOrNull(cn);
}
} else {
// check parent
if (parent != null) {
c = parent.loadClassOrNull(cn);
}
// check class path
if (c == null && ucp != null && VM.isModuleSystemInited()) {
c = findClassOnClassPathOrNull(cn);
}
}
}
if (resolve && c != null)
resolveClass(c);
return c;
}
}
/**
* A variation of {@code loadCass} to load a class with the specified
* binary name. This method returns {@code null} when the class is not
* found.
*/
protected Class<?> loadClassOrNull(String cn) {
return loadClassOrNull(cn, false);
}
/**
* Find the candidate loaded 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) {
int pos = cn.lastIndexOf('.');
if (pos < 0)
return null; // unnamed package
String pn = cn.substring(0, pos);
return packageToModule.get(pn);
}
/**
* Find the candidate loaded module for the given class name
* in the named module. Returns {@code null} if the named module
* is not defined to this class loader or does not contain
* the API package for the class.
*/
private LoadedModule findLoadedModule(String mn, String cn) {
LoadedModule loadedModule = findLoadedModule(cn);
if (loadedModule != null && mn.equals(loadedModule.name())) {
return loadedModule;
} else {
return null;
}
}
/**
* 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) {
if (System.getSecurityManager() == null) {
return defineClass(cn, loadedModule);
} else {
PrivilegedAction<Class<?>> pa = () -> defineClass(cn, loadedModule);
return AccessController.doPrivileged(pa);
}
}
/**
* Finds the class with the specified binary name on the class path.
*
* @return the resulting Class or {@code null} if not found
*/
private Class<?> findClassOnClassPathOrNull(String cn) {
String path = cn.replace('.', '/').concat(".class");
if (System.getSecurityManager() == null) {
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(cn, res);
} catch (IOException ioe) {
// TBD on how I/O errors should be propagated
}
}
return null;
} else {
// avoid use of lambda here
PrivilegedAction<Class<?>> pa = new PrivilegedAction<>() {
public Class<?> run() {
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(cn, res);
} catch (IOException ioe) {
// TBD on how I/O errors should be propagated
}
}
return null;
}
};
return AccessController.doPrivileged(pa);
}
}
/**
* 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) {
ModuleReference mref = loadedModule.mref();
ModuleReader reader = moduleReaderFor(mref);
try {
ByteBuffer bb = null;
URL csURL = null;
// locate class file, special handling for patched modules to
// avoid locating the resource twice
String rn = cn.replace('.', '/').concat(".class");
if (reader instanceof PatchedModuleReader) {
Resource r = ((PatchedModuleReader)reader).findResource(rn);
if (r != null) {
bb = r.getByteBuffer();
csURL = r.getCodeSourceURL();
}
} else {
bb = reader.read(rn).orElse(null);
csURL = loadedModule.codeSourceURL();
}
if (bb == null) {
// class not found
return null;
}
CodeSource cs = new CodeSource(csURL, (CodeSigner[]) null);
try {
// define class to VM
return defineClass(cn, bb, cs);
} finally {
reader.release(bb);
}
} catch (IOException ioe) {
// TBD on how I/O errors should be propagated
return null;
}
}
/**
* Defines the given binary class name to the VM, loading the class
* bytes via the given Resource object.
*
* @return the resulting Class
* @throws IOException if reading the resource fails
* @throws SecurityException if there is a sealing violation (JAR spec)
*/
private Class<?> defineClass(String cn, Resource res) throws IOException {
URL url = res.getCodeSourceURL();
// if class is in a named package then ensure that the package is defined
int pos = cn.lastIndexOf('.');
if (pos != -1) {
String pn = cn.substring(0, pos);
Manifest man = res.getManifest();
defineOrCheckPackage(pn, man, url);
}
// defines the class to the runtime
ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
return defineClass(cn, bb, cs);
} else {
byte[] b = res.getBytes();
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
return defineClass(cn, b, 0, b.length, cs);
}
}
// -- packages
/**
* Defines a package in this ClassLoader. If the package is already defined
* then its sealing needs to be checked if sealed by the legacy sealing
* mechanism.
*
* @throws SecurityException if there is a sealing violation (JAR spec)
*/
protected Package defineOrCheckPackage(String pn, Manifest man, URL url) {
Package pkg = getAndVerifyPackage(pn, man, url);
if (pkg == null) {
try {
if (man != null) {
pkg = definePackage(pn, man, url);
} else {
pkg = definePackage(pn, null, null, null, null, null, null, null);
}
} catch (IllegalArgumentException iae) {
// defined by another thread so need to re-verify
pkg = getAndVerifyPackage(pn, man, url);
if (pkg == null)
throw new InternalError("Cannot find package: " + pn);
}
}
return pkg;
}
/**
* Get the Package with the specified package name. If defined
* then verify that it against the manifest and code source.
*
* @throws SecurityException if there is a sealing violation (JAR spec)
*/
private Package getAndVerifyPackage(String pn, Manifest man, URL url) {
Package pkg = getDefinedPackage(pn);
if (pkg != null) {
if (pkg.isSealed()) {
if (!pkg.isSealed(url)) {
throw new SecurityException(
"sealing violation: package " + pn + " is sealed");
}
} else {
// can't seal package if already defined without sealing
if ((man != null) && isSealed(pn, man)) {
throw new SecurityException(
"sealing violation: can't seal package " + pn +
": already defined");
}
}
}
return pkg;
}
/**
* Defines a new package in this ClassLoader. The attributes in the specified
* Manifest are use to get the package version and sealing information.
*
* @throws IllegalArgumentException if the package name duplicates an
* existing package either in this class loader or one of its ancestors
*/
private Package definePackage(String pn, Manifest man, URL url) {
String specTitle = null;
String specVersion = null;
String specVendor = null;
String implTitle = null;
String implVersion = null;
String implVendor = null;
String sealed = null;
URL sealBase = null;
if (man != null) {
Attributes attr = man.getAttributes(pn.replace('.', '/').concat("/"));
if (attr != null) {
specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE);
specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION);
specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR);
implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
sealed = attr.getValue(Attributes.Name.SEALED);
}
attr = man.getMainAttributes();
if (attr != null) {
if (specTitle == null)
specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE);
if (specVersion == null)
specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION);
if (specVendor == null)
specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR);
if (implTitle == null)
implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
if (implVersion == null)
implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
if (implVendor == null)
implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
if (sealed == null)
sealed = attr.getValue(Attributes.Name.SEALED);
}
// package is sealed
if ("true".equalsIgnoreCase(sealed))
sealBase = url;
}
return definePackage(pn,
specTitle,
specVersion,
specVendor,
implTitle,
implVersion,
implVendor,
sealBase);
}
/**
* Returns {@code true} if the specified package name is sealed according to
* the given manifest.
*/
private boolean isSealed(String pn, Manifest man) {
String path = pn.replace('.', '/').concat("/");
Attributes attr = man.getAttributes(path);
String sealed = null;
if (attr != null)
sealed = attr.getValue(Attributes.Name.SEALED);
if (sealed == null && (attr = man.getMainAttributes()) != null)
sealed = attr.getValue(Attributes.Name.SEALED);
return "true".equalsIgnoreCase(sealed);
}
// -- permissions
/**
* Returns the permissions for the given CodeSource.
*/
@Override
protected PermissionCollection getPermissions(CodeSource cs) {
PermissionCollection perms = super.getPermissions(cs);
// add the permission to access the resource
URL url = cs.getLocation();
if (url == null)
return perms;
Permission p = null;
try {
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
/**
* 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.
*/
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.
*/
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.
*
* @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())
return true;
for (ModuleDescriptor.Opens opens : descriptor.opens()) {
String source = opens.source();
if (!opens.isQualified() && source.equals(pn)) {
return true;
}
}
return false;
}
/**
* Checks access to the given URL. We use URLClassPath for consistent
* checking with java.net.URLClassLoader.
*/
private static URL checkURL(URL url) {
return URLClassPath.checkURL(url);
}
}