package net.minestom.server.extras.selfmodification; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.LinkedList; import java.util.List; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.LoggerFactory; /** * Classloader part of a hierarchy of classloader. */ public abstract class HierarchyClassLoader extends JavaInteropURLClassloader { static { ClassLoader.registerAsParallelCapable(); } protected final List children = new LinkedList<>(); protected final List parents = new LinkedList<>(); public HierarchyClassLoader(String name, URL[] urls, ClassLoader parent) { super(name, urls, parent); } public void addChild(@NotNull MinestomExtensionClassLoader loader) { synchronized (HierarchyClassLoader.class) { this.children.add(loader); loader.parents.add(this); } } @Override public String findLibrary(String libname) { // We are increasing the visibility of this method knowingly to suppress an issue with LWJGL running code it shouldn't run. return super.findLibrary(libname); } /** * Finds a resource under the given name within this classloader, or if that * fails, in any of its children. If the resource is found, the resource is * opened as an {@link InputStream}, otherwise {@code null} is returned. * *

This method distinguishes itself from {@link #getResourceAsStream(String)}, * which will first query the parent classloader and only then this classloader. * Further, {@link #getResourceAsStream(String)} does not query children classloaders. * *

When failing to open a resource using {@link URL#openStream()}, the resource * is deemed to no exist in this classloader and will search in children classloaders. * *

The {@link InputStream} returned by this method must be closed manually, it is * unspecified whether the returned object will be closed whenever this classloader * is closed. A warning will be logged in that circumstance. * * @param name The pathname of the resource. See {@link #findResource(String)}. * @return The {@link InputStream} of the corresponding found resource, or null if such a resource does not exist. * @implNote Search occurs depth-first, which means that registration order can matter quite a lot. * @since 0.0.1 */ @Nullable public InputStream getResourceAsStreamWithChildren(@NotNull String name) { URL url = this.findResource(name); if (url != null) { try { return url.openStream(); } catch (IOException e) { LoggerFactory.getLogger(HierarchyClassLoader.class).warn("Unable to open URL '{}' (from pathname '{}') from classloader '{}'!", url, name, this.getName(), e); } } for (MinestomExtensionClassLoader child : this.children) { InputStream childInput = child.getResourceAsStreamWithChildren(name); if (childInput != null) { return childInput; } } return null; } /** * Finds a resource under the given name within this classloader, or if that * fails, in any of its children. If the resource is found, the resource is * returned as an URL, otherwise {@code null} is returned. * *

This method distinguishes itself from {@link #getResource(String)}, * which will first query the parent classloader and only then this classloader. * Further, {@link #getResource(String)} does not query children classloaders. * * @param name The pathname of the resource. * @return The {@link URL} of the corresponding found resource, or null if such a resource does not exist. * @implNote Search occurs depth-first, which means that registration order can matter quite a lot. * @since 4.0.0-a20241006 */ @Nullable @ApiStatus.AvailableSince(value = "4.0.0-a20241006") public URL getResourceAsURLWithChildren(@NotNull String name) { URL url = this.findResource(name); if (url != null) { return url; } for (MinestomExtensionClassLoader child : this.children) { URL childURL = child.getResourceAsURLWithChildren(name); if (childURL != null) { return childURL; } } return null; } public void removeChildInHierarchy(MinestomExtensionClassLoader child) { synchronized (HierarchyClassLoader.class) { this.children.remove(child); this.children.forEach(c -> c.removeChildInHierarchy(child)); } } }