| /* | |
| * Copyright (c) 1997, 2025, 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.net; | |
| import java.io.Closeable; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.security.CodeSigner; | |
| import java.security.CodeSource; | |
| import java.security.Permissions; | |
| import java.security.PermissionCollection; | |
| import java.security.SecureClassLoader; | |
| import java.util.Enumeration; | |
| import java.util.List; | |
| import java.util.NoSuchElementException; | |
| import java.util.Objects; | |
| import java.util.Set; | |
| import java.util.WeakHashMap; | |
| import java.util.jar.Attributes; | |
| import java.util.jar.Attributes.Name; | |
| import java.util.jar.JarFile; | |
| import java.util.jar.Manifest; | |
| import jdk.internal.loader.Resource; | |
| import jdk.internal.loader.URLClassPath; | |
| import jdk.internal.access.SharedSecrets; | |
| import jdk.internal.perf.PerfCounter; | |
| /** | |
| * This class loader is used to load classes and resources from a search | |
| * path of URLs referring to both JAR files and directories. Any {@code jar:} | |
| * scheme URL (see {@link java.net.JarURLConnection}) is assumed to refer to a | |
| * JAR file. Any {@code file:} scheme URL that ends with a '/' is assumed to | |
| * refer to a directory. Otherwise, the URL is assumed to refer to a JAR file | |
| * which will be opened as needed. | |
| * <p> | |
| * This class loader supports the loading of classes and resources from the | |
| * contents of a <a href="../util/jar/JarFile.html#multirelease">multi-release</a> | |
| * JAR file that is referred to by a given URL. | |
| * | |
| * @author David Connelly | |
| * @since 1.2 | |
| */ | |
| public class URLClassLoader extends SecureClassLoader implements Closeable { | |
| /* The search path for classes and resources */ | |
| private final URLClassPath ucp; | |
| /** | |
| * Constructs a new URLClassLoader for the given URLs. The URLs will be | |
| * searched in the order specified for classes and resources after first | |
| * searching in the specified parent class loader. Any {@code jar:} | |
| * scheme URL is assumed to refer to a JAR file. Any {@code file:} scheme | |
| * URL that ends with a '/' is assumed to refer to a directory. Otherwise, | |
| * the URL is assumed to refer to a JAR file which will be downloaded and | |
| * opened as needed. | |
| * | |
| * @apiNote If {@code parent} is specified as {@code null} (for the | |
| * bootstrap class loader) then there is no guarantee that all platform | |
| * classes are visible. | |
| * See {@linkplain ClassLoader##builtinLoaders Run-time Built-in Class Loaders} | |
| * for information on the bootstrap class loader and other built-in class loaders. | |
| * | |
| * @param urls the URLs from which to load classes and resources | |
| * @param parent the parent class loader for delegation, can be {@code null} | |
| * for the bootstrap class loader | |
| * @throws NullPointerException if {@code urls} or any of its | |
| * elements is {@code null}. | |
| */ | |
| public URLClassLoader(URL[] urls, ClassLoader parent) { | |
| super(parent); | |
| this.ucp = new URLClassPath(urls); | |
| } | |
| /** | |
| * Constructs a new URLClassLoader for the specified URLs using the | |
| * {@linkplain ClassLoader#getSystemClassLoader() system class loader | |
| * as the parent}. The URLs will be searched in the order | |
| * specified for classes and resources after first searching in the | |
| * parent class loader. Any URL that ends with a '/' is assumed to | |
| * refer to a directory. Otherwise, the URL is assumed to refer to | |
| * a JAR file which will be downloaded and opened as needed. | |
| * | |
| * @param urls the URLs from which to load classes and resources | |
| * | |
| * @throws NullPointerException if {@code urls} or any of its | |
| * elements is {@code null}. | |
| */ | |
| public URLClassLoader(URL[] urls) { | |
| super(); | |
| this.ucp = new URLClassPath(urls); | |
| } | |
| /** | |
| * Constructs a new URLClassLoader for the specified URLs, parent | |
| * class loader, and URLStreamHandlerFactory. The parent argument | |
| * will be used as the parent class loader for delegation. The | |
| * factory argument will be used as the stream handler factory to | |
| * obtain protocol handlers when creating new jar URLs. | |
| * | |
| * @apiNote If {@code parent} is specified as {@code null} (for the | |
| * bootstrap class loader) then there is no guarantee that all platform | |
| * classes are visible. | |
| * See {@linkplain ClassLoader##builtinLoaders Run-time Built-in Class Loaders} | |
| * for information on the bootstrap class loader and other built-in class loaders. | |
| * | |
| * @param urls the URLs from which to load classes and resources | |
| * @param parent the parent class loader for delegation, can be {@code null} | |
| * for the bootstrap class loader | |
| * @param factory the URLStreamHandlerFactory to use when creating URLs | |
| * | |
| * @throws NullPointerException if {@code urls} or any of its | |
| * elements is {@code null}. | |
| */ | |
| public URLClassLoader(URL[] urls, ClassLoader parent, | |
| URLStreamHandlerFactory factory) { | |
| super(parent); | |
| this.ucp = new URLClassPath(urls, factory); | |
| } | |
| /** | |
| * Constructs a new named {@code URLClassLoader} for the specified URLs. | |
| * The URLs will be searched in the order specified for classes | |
| * and resources after first searching in the specified parent class loader. | |
| * Any URL that ends with a '/' is assumed to refer to a directory. | |
| * Otherwise, the URL is assumed to refer to a JAR file which will be | |
| * downloaded and opened as needed. | |
| * | |
| * @apiNote If {@code parent} is specified as {@code null} (for the | |
| * bootstrap class loader) then there is no guarantee that all platform | |
| * classes are visible. | |
| * See {@linkplain ClassLoader##builtinLoaders Run-time Built-in Class Loaders} | |
| * for information on the bootstrap class loader and other built-in class loaders. | |
| * | |
| * @param name class loader name; or {@code null} if not named | |
| * @param urls the URLs from which to load classes and resources | |
| * @param parent the parent class loader for delegation, can be {@code null} | |
| * for the bootstrap class loader | |
| * | |
| * @throws IllegalArgumentException if the given name is empty. | |
| * @throws NullPointerException if {@code urls} or any of its | |
| * elements is {@code null}. | |
| * | |
| * @since 9 | |
| */ | |
| public URLClassLoader(String name, | |
| URL[] urls, | |
| ClassLoader parent) { | |
| super(name, parent); | |
| this.ucp = new URLClassPath(urls); | |
| } | |
| /** | |
| * Constructs a new named {@code URLClassLoader} for the specified URLs, | |
| * parent class loader, and URLStreamHandlerFactory. | |
| * The parent argument will be used as the parent class loader for delegation. | |
| * The factory argument will be used as the stream handler factory to | |
| * obtain protocol handlers when creating new jar URLs. | |
| * | |
| * @apiNote If {@code parent} is specified as {@code null} (for the | |
| * bootstrap class loader) then there is no guarantee that all platform | |
| * classes are visible. | |
| * See {@linkplain ClassLoader##builtinLoaders Run-time Built-in Class Loaders} | |
| * for information on the bootstrap class loader and other built-in class loaders. | |
| * | |
| * @param name class loader name; or {@code null} if not named | |
| * @param urls the URLs from which to load classes and resources | |
| * @param parent the parent class loader for delegation, can be {@code null} | |
| * for the bootstrap class loader | |
| * @param factory the URLStreamHandlerFactory to use when creating URLs | |
| * | |
| * @throws IllegalArgumentException if the given name is empty. | |
| * @throws NullPointerException if {@code urls} or any of its | |
| * elements is {@code null}. | |
| * | |
| * @since 9 | |
| */ | |
| public URLClassLoader(String name, URL[] urls, ClassLoader parent, | |
| URLStreamHandlerFactory factory) { | |
| super(name, parent); | |
| this.ucp = new URLClassPath(urls, factory); | |
| } | |
| /* A map (used as a set) to keep track of closeable local resources | |
| * (either JarFiles or FileInputStreams). We don't care about | |
| * Http resources since they don't need to be closed. | |
| * | |
| * If the resource is coming from a jar file | |
| * we keep a (weak) reference to the JarFile object which can | |
| * be closed if URLClassLoader.close() called. Due to jar file | |
| * caching there will typically be only one JarFile object | |
| * per underlying jar file. | |
| * | |
| * For file resources, which is probably a less common situation | |
| * we have to keep a weak reference to each stream. | |
| */ | |
| private final WeakHashMap<Closeable,Void> | |
| closeables = new WeakHashMap<>(); | |
| /** | |
| * Returns an input stream for reading the specified resource. | |
| * If this loader is closed, then any resources opened by this method | |
| * will be closed. | |
| * | |
| * <p> The search order is described in the documentation for {@link | |
| * #getResource(String)}. </p> | |
| * | |
| * @param name | |
| * The resource name | |
| * | |
| * @return An input stream for reading the resource, or {@code null} | |
| * if the resource could not be found | |
| * | |
| * @throws NullPointerException If {@code name} is {@code null} | |
| * | |
| * @since 1.7 | |
| */ | |
| public InputStream getResourceAsStream(String name) { | |
| Objects.requireNonNull(name); | |
| URL url = getResource(name); | |
| try { | |
| if (url == null) { | |
| return null; | |
| } | |
| URLConnection urlc = url.openConnection(); | |
| InputStream is = urlc.getInputStream(); | |
| if (urlc instanceof JarURLConnection juc) { | |
| JarFile jar = juc.getJarFile(); | |
| synchronized (closeables) { | |
| if (!closeables.containsKey(jar)) { | |
| closeables.put(jar, null); | |
| } | |
| } | |
| } else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) { | |
| synchronized (closeables) { | |
| closeables.put(is, null); | |
| } | |
| } | |
| return is; | |
| } catch (IOException e) { | |
| return null; | |
| } | |
| } | |
| /** | |
| * Closes this URLClassLoader, so that it can no longer be used to load | |
| * new classes or resources that are defined by this loader. | |
| * Classes and resources defined by any of this loader's parents in the | |
| * delegation hierarchy are still accessible. Also, any classes or resources | |
| * that are already loaded, are still accessible. | |
| * <p> | |
| * In the case of jar: and file: URLs, it also closes any files | |
| * that were opened by it. If another thread is loading a | |
| * class when the {@code close} method is invoked, then the result of | |
| * that load is undefined. | |
| * <p> | |
| * The method makes a best effort attempt to close all opened files, | |
| * by catching {@link IOException}s internally. Unchecked exceptions | |
| * and errors are not caught. Calling close on an already closed | |
| * loader has no effect. | |
| * | |
| * @throws IOException if closing any file opened by this class loader | |
| * resulted in an IOException. Any such exceptions are caught internally. | |
| * If only one is caught, then it is re-thrown. If more than one exception | |
| * is caught, then the second and following exceptions are added | |
| * as suppressed exceptions of the first one caught, which is then re-thrown. | |
| * | |
| * @since 1.7 | |
| */ | |
| public void close() throws IOException { | |
| List<IOException> errors = ucp.closeLoaders(); | |
| // now close any remaining streams. | |
| synchronized (closeables) { | |
| Set<Closeable> keys = closeables.keySet(); | |
| for (Closeable c : keys) { | |
| try { | |
| c.close(); | |
| } catch (IOException ioex) { | |
| errors.add(ioex); | |
| } | |
| } | |
| closeables.clear(); | |
| } | |
| if (errors.isEmpty()) { | |
| return; | |
| } | |
| IOException firstex = errors.remove(0); | |
| // Suppress any remaining exceptions | |
| for (IOException error: errors) { | |
| firstex.addSuppressed(error); | |
| } | |
| throw firstex; | |
| } | |
| /** | |
| * Appends the specified URL to the list of URLs to search for | |
| * classes and resources. | |
| * <p> | |
| * If the URL specified is {@code null} or is already in the | |
| * list of URLs, or if this loader is closed, then invoking this | |
| * method has no effect. | |
| * | |
| * @param url the URL to be added to the search path of URLs | |
| */ | |
| protected void addURL(URL url) { | |
| ucp.addURL(url); | |
| } | |
| /** | |
| * Returns the search path of URLs for loading classes and resources. | |
| * This includes the original list of URLs specified to the constructor, | |
| * along with any URLs subsequently appended by the addURL() method. | |
| * @return the search path of URLs for loading classes and resources. | |
| */ | |
| public URL[] getURLs() { | |
| return ucp.getURLs(); | |
| } | |
| /** | |
| * Finds and loads the class with the specified name from the URL search | |
| * path. Any URLs referring to JAR files are loaded and opened as needed | |
| * until the class is found. | |
| * | |
| * @param name the name of the class | |
| * @return the resulting class | |
| * @throws ClassNotFoundException if the class could not be found, | |
| * or if the loader is closed. | |
| * @throws NullPointerException if {@code name} is {@code null}. | |
| */ | |
| protected Class<?> findClass(final String name) | |
| throws ClassNotFoundException | |
| { | |
| String path = name.replace('.', '/').concat(".class"); | |
| Resource res = ucp.getResource(path); | |
| if (res != null) { | |
| try { | |
| return defineClass(name, res); | |
| } catch (IOException e) { | |
| throw new ClassNotFoundException(name, e); | |
| } catch (ClassFormatError e2) { | |
| if (res.getDataError() != null) { | |
| e2.addSuppressed(res.getDataError()); | |
| } | |
| throw e2; | |
| } | |
| } | |
| throw new ClassNotFoundException(name); | |
| } | |
| /* | |
| * Retrieve the package using the specified package name. | |
| * If non-null, verify the package using the specified code | |
| * source and manifest. | |
| */ | |
| private Package getAndVerifyPackage(String pkgname, | |
| Manifest man, URL url) { | |
| Package pkg = getDefinedPackage(pkgname); | |
| if (pkg != null) { | |
| // Package found, so check package sealing. | |
| if (pkg.isSealed()) { | |
| // Verify that code source URL is the same. | |
| if (!pkg.isSealed(url)) { | |
| throw new SecurityException( | |
| "sealing violation: package " + pkgname + " is sealed"); | |
| } | |
| } else { | |
| // Make sure we are not attempting to seal the package | |
| // at this code source URL. | |
| if ((man != null) && isSealed(pkgname, man)) { | |
| throw new SecurityException( | |
| "sealing violation: can't seal package " + pkgname + | |
| ": already loaded"); | |
| } | |
| } | |
| } | |
| return pkg; | |
| } | |
| /* | |
| * Defines a Class using the class bytes obtained from the specified | |
| * Resource. The resulting Class must be resolved before it can be | |
| * used. | |
| */ | |
| private Class<?> defineClass(String name, Resource res) throws IOException { | |
| long t0 = System.nanoTime(); | |
| int i = name.lastIndexOf('.'); | |
| URL url = res.getCodeSourceURL(); | |
| if (i != -1) { | |
| String pkgname = name.substring(0, i); | |
| // Check if package already loaded. | |
| Manifest man = res.getManifest(); | |
| if (getAndVerifyPackage(pkgname, man, url) == null) { | |
| try { | |
| if (man != null) { | |
| definePackage(pkgname, man, url); | |
| } else { | |
| definePackage(pkgname, null, null, null, null, null, null, null); | |
| } | |
| } catch (IllegalArgumentException iae) { | |
| // parallel-capable class loaders: re-verify in case of a | |
| // race condition | |
| if (getAndVerifyPackage(pkgname, man, url) == null) { | |
| // Should never happen | |
| throw new AssertionError("Cannot find package " + | |
| pkgname); | |
| } | |
| } | |
| } | |
| } | |
| // Now read the class bytes and define the class | |
| java.nio.ByteBuffer bb = res.getByteBuffer(); | |
| if (bb != null) { | |
| // Use (direct) ByteBuffer: | |
| CodeSigner[] signers = res.getCodeSigners(); | |
| CodeSource cs = new CodeSource(url, signers); | |
| PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); | |
| return defineClass(name, bb, cs); | |
| } else { | |
| byte[] b = res.getBytes(); | |
| // must read certificates AFTER reading bytes. | |
| CodeSigner[] signers = res.getCodeSigners(); | |
| CodeSource cs = new CodeSource(url, signers); | |
| PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); | |
| return defineClass(name, b, 0, b.length, cs); | |
| } | |
| } | |
| /** | |
| * Defines a new package by name in this {@code URLClassLoader}. | |
| * The attributes contained in the specified {@code Manifest} | |
| * will be used to obtain package version and sealing information. | |
| * For sealed packages, the additional URL specifies the code source URL | |
| * from which the package was loaded. | |
| * | |
| * @param name the package name | |
| * @param man the {@code Manifest} containing package version and sealing | |
| * information | |
| * @param url the code source url for the package, or null if none | |
| * @throws IllegalArgumentException if the package name is | |
| * already defined by this class loader | |
| * @return the newly defined {@code Package} object | |
| */ | |
| protected Package definePackage(String name, Manifest man, URL url) { | |
| String specTitle = null, specVersion = null, specVendor = null; | |
| String implTitle = null, implVersion = null, implVendor = null; | |
| String sealed = null; | |
| URL sealBase = null; | |
| Attributes attr = SharedSecrets.javaUtilJarAccess() | |
| .getTrustedAttributes(man, name.replace('.', '/').concat("/")); | |
| if (attr != null) { | |
| specTitle = attr.getValue(Name.SPECIFICATION_TITLE); | |
| specVersion = attr.getValue(Name.SPECIFICATION_VERSION); | |
| specVendor = attr.getValue(Name.SPECIFICATION_VENDOR); | |
| implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE); | |
| implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION); | |
| implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR); | |
| sealed = attr.getValue(Name.SEALED); | |
| } | |
| attr = man.getMainAttributes(); | |
| if (attr != null) { | |
| if (specTitle == null) { | |
| specTitle = attr.getValue(Name.SPECIFICATION_TITLE); | |
| } | |
| if (specVersion == null) { | |
| specVersion = attr.getValue(Name.SPECIFICATION_VERSION); | |
| } | |
| if (specVendor == null) { | |
| specVendor = attr.getValue(Name.SPECIFICATION_VENDOR); | |
| } | |
| if (implTitle == null) { | |
| implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE); | |
| } | |
| if (implVersion == null) { | |
| implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION); | |
| } | |
| if (implVendor == null) { | |
| implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR); | |
| } | |
| if (sealed == null) { | |
| sealed = attr.getValue(Name.SEALED); | |
| } | |
| } | |
| if ("true".equalsIgnoreCase(sealed)) { | |
| sealBase = url; | |
| } | |
| return definePackage(name, specTitle, specVersion, specVendor, | |
| implTitle, implVersion, implVendor, sealBase); | |
| } | |
| /* | |
| * Returns true if the specified package name is sealed according to the | |
| * given manifest. | |
| * | |
| * @throws SecurityException if the package name is untrusted in the manifest | |
| */ | |
| private boolean isSealed(String name, Manifest man) { | |
| Attributes attr = SharedSecrets.javaUtilJarAccess() | |
| .getTrustedAttributes(man, name.replace('.', '/').concat("/")); | |
| String sealed = null; | |
| if (attr != null) { | |
| sealed = attr.getValue(Name.SEALED); | |
| } | |
| if (sealed == null) { | |
| if ((attr = man.getMainAttributes()) != null) { | |
| sealed = attr.getValue(Name.SEALED); | |
| } | |
| } | |
| return "true".equalsIgnoreCase(sealed); | |
| } | |
| /** | |
| * Finds the resource with the specified name on the URL search path. | |
| * | |
| * @param name the name of the resource | |
| * @return a {@code URL} for the resource, or {@code null} | |
| * if the resource could not be found, or if the loader is closed. | |
| */ | |
| public URL findResource(final String name) { | |
| return ucp.findResource(name); | |
| } | |
| /** | |
| * Returns an Enumeration of URLs representing all of the resources | |
| * on the URL search path having the specified name. | |
| * | |
| * @param name the resource name | |
| * @throws IOException if an I/O exception occurs | |
| * @return An {@code Enumeration} of {@code URL}s. | |
| * If the loader is closed, the Enumeration contains no elements. | |
| */ | |
| @Override | |
| public Enumeration<URL> findResources(final String name) | |
| throws IOException | |
| { | |
| final Enumeration<URL> e = ucp.findResources(name); | |
| return new Enumeration<>() { | |
| private URL url = null; | |
| private boolean next() { | |
| if (url != null) { | |
| return true; | |
| } | |
| if (!e.hasMoreElements()) { | |
| return false; | |
| } | |
| url = e.nextElement(); | |
| return url != null; | |
| } | |
| @Override | |
| public URL nextElement() { | |
| if (!next()) { | |
| throw new NoSuchElementException(); | |
| } | |
| URL u = url; | |
| url = null; | |
| return u; | |
| } | |
| @Override | |
| public boolean hasMoreElements() { | |
| return next(); | |
| } | |
| }; | |
| } | |
| /** | |
| * {@return an {@linkplain PermissionCollection empty Permission collection}} | |
| * | |
| * @param codesource the {@code CodeSource} | |
| * @throws NullPointerException if {@code codesource} is {@code null}. | |
| */ | |
| @Override | |
| protected PermissionCollection getPermissions(CodeSource codesource) { | |
| Objects.requireNonNull(codesource); | |
| return new Permissions(); | |
| } | |
| /** | |
| * Creates a new instance of URLClassLoader for the specified | |
| * URLs and parent class loader. | |
| * | |
| * @param urls the URLs to search for classes and resources | |
| * @param parent the parent class loader for delegation | |
| * @throws NullPointerException if {@code urls} or any of its | |
| * elements is {@code null}. | |
| * @return the resulting class loader | |
| */ | |
| public static URLClassLoader newInstance(final URL[] urls, | |
| final ClassLoader parent) { | |
| return new URLClassLoader(null, urls, parent); | |
| } | |
| /** | |
| * Creates a new instance of URLClassLoader for the specified | |
| * URLs and default parent class loader. | |
| * | |
| * @param urls the URLs to search for classes and resources | |
| * @throws NullPointerException if {@code urls} or any of its | |
| * elements is {@code null}. | |
| * @return the resulting class loader | |
| */ | |
| public static URLClassLoader newInstance(final URL[] urls) { | |
| return new URLClassLoader(urls); | |
| } | |
| static { | |
| ClassLoader.registerAsParallelCapable(); | |
| } | |
| } |