From 146290283fbeed4ec4071b976d4d2838c984b86f Mon Sep 17 00:00:00 2001 From: Mitchell Syer Date: Thu, 29 Jan 2026 18:48:33 -0500 Subject: [PATCH] ChildFirstClassLoader (#1873) * ChildFirstClassLoader * Lint * ChildFirstURLClassLoader * Fix reference --- .../impl/util/ChildFirstURLClassLoader.kt | 90 +++++++++++++++++++ .../tachidesk/manga/impl/util/PackageTools.kt | 2 +- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/ChildFirstURLClassLoader.kt diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/ChildFirstURLClassLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/ChildFirstURLClassLoader.kt new file mode 100644 index 00000000..b46ba45f --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/ChildFirstURLClassLoader.kt @@ -0,0 +1,90 @@ +package suwayomi.tachidesk.manga.impl.util + +import java.io.IOException +import java.io.InputStream +import java.net.URL +import java.net.URLClassLoader +import java.util.Enumeration + +/** + * A parent-last class loader that will try in order: + * - the system class loader + * - the child class loader + * - the parent class loader. + */ +class ChildFirstURLClassLoader( + urls: Array, + parent: ClassLoader? = null, +) : URLClassLoader(urls, parent) { + private val systemClassLoader: ClassLoader? = getSystemClassLoader() + + override fun loadClass( + name: String?, + resolve: Boolean, + ): Class<*> { + var c = findLoadedClass(name) + + if (c == null && systemClassLoader != null) { + try { + c = systemClassLoader.loadClass(name) + } catch (_: ClassNotFoundException) { + } + } + + if (c == null) { + c = + try { + findClass(name) + } catch (_: ClassNotFoundException) { + super.loadClass(name, resolve) + } + } + + if (resolve) { + resolveClass(c) + } + + return c + } + + override fun getResource(name: String?): URL? = + systemClassLoader?.getResource(name) + ?: findResource(name) + ?: super.getResource(name) + + override fun getResources(name: String?): Enumeration { + val systemUrls = systemClassLoader?.getResources(name) + val localUrls = findResources(name) + val parentUrls = parent?.getResources(name) + val urls = + buildList { + while (systemUrls?.hasMoreElements() == true) { + add(systemUrls.nextElement()) + } + + while (localUrls?.hasMoreElements() == true) { + add(localUrls.nextElement()) + } + + while (parentUrls?.hasMoreElements() == true) { + add(parentUrls.nextElement()) + } + } + + return object : Enumeration { + val iterator = urls.iterator() + + override fun hasMoreElements() = iterator.hasNext() + + override fun nextElement() = iterator.next() + } + } + + override fun getResourceAsStream(name: String?): InputStream? { + return try { + getResource(name)?.openStream() + } catch (_: IOException) { + return null + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt index 2b6873c4..9194d324 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt @@ -156,7 +156,7 @@ object PackageTools { ): Any { try { logger.debug { "loading jar with path: $jarPath" } - val classLoader = jarLoaderMap[jarPath] ?: URLClassLoader(arrayOf(Path(jarPath).toUri().toURL())) + val classLoader = jarLoaderMap[jarPath] ?: ChildFirstURLClassLoader(arrayOf(Path(jarPath).toUri().toURL())) val classToLoad = Class.forName(className, false, classLoader) jarLoaderMap[jarPath] = classLoader