Skip to content

Commit e5ba0cf

Browse files
committed
Resources & ServiceLoader resolving
1 parent ff763f7 commit e5ba0cf

File tree

15 files changed

+300
-7
lines changed

15 files changed

+300
-7
lines changed

mirai-console/backend/integration-test/testers/plugin-depend-on-other/src/PluginDependOnOther.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package net.mamoe.console.integrationtest.ep.dependonother
1212
import net.mamoe.console.integrationtest.ep.mcitselftest.MCITSelfTestPlugin
1313
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
1414
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
15+
import net.mamoe.mirai.utils.error
1516
import net.mamoe.mirai.utils.info
1617
import kotlin.test.assertFailsWith
1718
import kotlin.test.assertNotEquals
@@ -45,7 +46,8 @@ public object PluginDependOnOther : KotlinPlugin(
4546
logger.info { "Gson located $gsonC <${gsonC.classLoader}>" }
4647
assertSame(gsonC, Class.forName(gsonC.name, false, pluginDepDynDownload.classLoader))
4748
assertFailsWith<ClassNotFoundException> {
48-
Class.forName("com.zaxxer.sparsebits.SparseBitSet") // private in dynamic-dep-download
49+
val c = Class.forName("com.zaxxer.sparsebits.SparseBitSet") // private in dynamic-dep-download
50+
logger.error { "C: $c, from: ${c.classLoader}" }
4951
}
5052
assertFailsWith<ClassNotFoundException> {
5153
Class.forName("net.mamoe.assertion.something.not.existing")

mirai-console/backend/integration-test/testers/service-loader/module-service-loader-impl/.nested-module.txt

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2019-2021 Mamoe Technologies and contributors.
3+
*
4+
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
*
7+
* https://github.com/mamoe/mirai/blob/master/LICENSE
8+
*/
9+
10+
@file:Suppress("UnusedImport")
11+
12+
plugins {
13+
kotlin("jvm")
14+
kotlin("plugin.serialization")
15+
id("java")
16+
}
17+
18+
version = "0.0.0"
19+
20+
kotlin {
21+
explicitApiWarning()
22+
}
23+
24+
dependencies {
25+
api(project(":mirai-console.integration-test"))
26+
api(parent!!.project("module-service-loader-typedef"))
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Copyright 2019-2022 Mamoe Technologies and contributors.
3+
#
4+
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
#
7+
# https://github.com/mamoe/mirai/blob/dev/LICENSE
8+
#
9+
10+
net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2019-2022 Mamoe Technologies and contributors.
3+
*
4+
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
*
7+
* https://github.com/mamoe/mirai/blob/dev/LICENSE
8+
*/
9+
package net.mamoe.console.integrationtest.mod.serviceimpl
10+
11+
import net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef
12+
13+
public class ServiceImpl : ServiceTypedef {
14+
}

mirai-console/backend/integration-test/testers/service-loader/module-service-loader-typedef/.nested-module.txt

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright 2019-2022 Mamoe Technologies and contributors.
3+
*
4+
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
*
7+
* https://github.com/mamoe/mirai/blob/dev/LICENSE
8+
*/
9+
10+
package net.mamoe.console.integrationtest.mod.servicetypedef
11+
12+
public interface ServiceTypedef {
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Copyright 2019-2022 Mamoe Technologies and contributors.
3+
#
4+
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
#
7+
# https://github.com/mamoe/mirai/blob/dev/LICENSE
8+
#
9+
10+
net.mamoe.console.itest.serviceloader.PMain

mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/.nested-module.txt

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2019-2021 Mamoe Technologies and contributors.
3+
*
4+
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
*
7+
* https://github.com/mamoe/mirai/blob/master/LICENSE
8+
*/
9+
10+
@file:Suppress("UnusedImport")
11+
12+
plugins {
13+
kotlin("jvm")
14+
kotlin("plugin.serialization")
15+
id("java")
16+
}
17+
18+
version = "0.0.0"
19+
20+
kotlin {
21+
explicitApiWarning()
22+
}
23+
24+
dependencies {
25+
api(project(":mirai-console.integration-test"))
26+
api(parent!!.project("module-service-loader-typedef"))
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Copyright 2019-2022 Mamoe Technologies and contributors.
3+
#
4+
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
#
7+
# https://github.com/mamoe/mirai/blob/dev/LICENSE
8+
#
9+
10+
net.mamoe.console.itest.serviceloader.ndep.PS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Copyright 2019-2022 Mamoe Technologies and contributors.
3+
#
4+
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
#
7+
# https://github.com/mamoe/mirai/blob/dev/LICENSE
8+
#
9+
10+
net.mamoe.console.itest.serviceloader.ndep.PMain
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2019-2022 Mamoe Technologies and contributors.
3+
*
4+
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
*
7+
* https://github.com/mamoe/mirai/blob/dev/LICENSE
8+
*/
9+
10+
package net.mamoe.console.itest.serviceloader.ndep
11+
12+
import net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef
13+
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
14+
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
15+
import net.mamoe.mirai.utils.info
16+
import java.util.*
17+
import kotlin.test.assertEquals
18+
19+
20+
internal class PS : ServiceTypedef
21+
22+
internal object PMain : KotlinPlugin(JvmPluginDescription("net.mamoe.console.itest.serviceloader-ndp", "0.0.0") {
23+
dependsOn("net.mamoe.console.itest.serviceloader")
24+
}) {
25+
override fun onEnable() {
26+
val loader = ServiceLoader.load(
27+
Class.forName("net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef"),
28+
javaClass.classLoader,
29+
)
30+
val services = loader.asSequence().map { it.javaClass.name }.toMutableList()
31+
services.sort()
32+
services.forEach { service ->
33+
logger.info { "Service: $service" }
34+
}
35+
assertEquals(mutableListOf(
36+
"net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl",
37+
"net.mamoe.console.itest.serviceloader.ndep.PS",
38+
), services)
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2019-2022 Mamoe Technologies and contributors.
3+
*
4+
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5+
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6+
*
7+
* https://github.com/mamoe/mirai/blob/dev/LICENSE
8+
*/
9+
10+
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
11+
12+
package net.mamoe.console.itest.serviceloader
13+
14+
import net.mamoe.mirai.console.internal.plugin.DynLibClassLoader
15+
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
16+
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
17+
import net.mamoe.mirai.utils.info
18+
import java.io.File
19+
import java.util.*
20+
import kotlin.test.assertEquals
21+
22+
internal object PMain : KotlinPlugin(JvmPluginDescription("net.mamoe.console.itest.serviceloader", "0.0.0")) {
23+
init {
24+
val cl = PMain.javaClass.classLoader.parent as DynLibClassLoader
25+
cl.addLib(File("modules/module-service-loader-typedef-0.0.0.jar"))
26+
cl.addLib(File("modules/module-service-loader-impl-0.0.0.jar"))
27+
}
28+
29+
override fun onEnable() {
30+
val loader = ServiceLoader.load(
31+
Class.forName("net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef"),
32+
javaClass.classLoader,
33+
)
34+
val services = loader.asSequence().map { it.javaClass.name}.toMutableList()
35+
services.forEach { service ->
36+
logger.info { "Service: $service" }
37+
}
38+
assertEquals(mutableListOf("net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl"), services)
39+
}
40+
}

mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt

+96-6
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,26 @@ internal class DynLibClassLoader(
8282
clName?.let { return "DynLibClassLoader{$it}" }
8383
return "DynLibClassLoader@" + hashCode()
8484
}
85+
86+
override fun getResource(name: String?): URL? {
87+
if (name == null) return null
88+
findResource(name)?.let { return it }
89+
if (parent is DynLibClassLoader) {
90+
return parent.getResource(name)
91+
}
92+
return null
93+
}
94+
95+
override fun getResources(name: String?): Enumeration<URL> {
96+
if (name == null) return Collections.emptyEnumeration()
97+
val res = findResources(name)
98+
return if (parent is DynLibClassLoader) {
99+
res + parent.getResources(name)
100+
} else {
101+
res
102+
}
103+
}
104+
85105
}
86106

87107
@Suppress("JoinDeclarationAndAssignment")
@@ -210,7 +230,8 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
210230
findLoadedClass(name)?.let { return it }
211231
try {
212232
return super.findClass(name)
213-
} catch (ignored: ClassNotFoundException) {}
233+
} catch (ignored: ClassNotFoundException) {
234+
}
214235
}
215236
}
216237
return null
@@ -265,11 +286,48 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
265286

266287
internal fun loadedClass(name: String): Class<*>? = super.findLoadedClass(name)
267288

268-
//// 只允许插件 getResource 时获取插件自身资源, https://github.com/mamoe/mirai-console/issues/205
269-
override fun getResources(name: String?): Enumeration<URL> = findResources(name)
270-
override fun getResource(name: String?): URL? = findResource(name)
271-
// getResourceAsStream 在 URLClassLoader 中通过 getResource 确定资源
272-
// 因此无需 override getResourceAsStream
289+
private fun getRes(name: String, shared: Boolean): Enumeration<URL> {
290+
val src = mutableListOf<Enumeration<URL>>(
291+
findResources(name),
292+
)
293+
if (dependencies.isEmpty()) {
294+
if (shared) {
295+
src.add(sharedLibrariesLogger.getResources(name))
296+
}
297+
} else {
298+
dependencies.forEach { dep ->
299+
src.add(dep.getRes(name, false))
300+
}
301+
}
302+
src.add(pluginIndependentCL.getResources(name))
303+
304+
val resolved = mutableSetOf<URL>()
305+
src.forEach { nested -> nested.iterator().forEach { resolved.add(it) } }
306+
307+
return Collections.enumeration(resolved)
308+
}
309+
310+
override fun getResources(name: String?): Enumeration<URL> {
311+
name ?: return Collections.emptyEnumeration()
312+
313+
if (name.startsWith("META-INF/mirai-console-plugin/"))
314+
return findResources(name)
315+
316+
return getRes(name, true)
317+
}
318+
319+
override fun getResource(name: String?): URL? {
320+
name ?: return null
321+
if (name.startsWith("META-INF/mirai-console-plugin/"))
322+
return findResource(name)
323+
findResource(name)?.let { return it }
324+
// parent: ctx.sharedLibrariesLoader
325+
sharedLibrariesLogger.getResource(name)?.let { return it }
326+
dependencies.forEach { dep ->
327+
dep.getResource(name)?.let { return it }
328+
}
329+
return pluginIndependentCL.getResource(name)
330+
}
273331

274332
override fun toString(): String {
275333
return "JvmPluginClassLoader{${file.name}}"
@@ -278,3 +336,35 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
278336

279337
private fun String.pkgName(): String = substringBeforeLast('.', "")
280338
internal fun Artifact.depId(): String = "$groupId:$artifactId"
339+
340+
private operator fun <E> Enumeration<E>.plus(next: Enumeration<E>): Enumeration<E> {
341+
return compoundEnumerations(listOf(this, next).iterator())
342+
}
343+
344+
private fun <E> compoundEnumerations(iter: Iterator<Enumeration<E>>): Enumeration<E> {
345+
return object : Enumeration<E> {
346+
private lateinit var crt: Enumeration<E>
347+
override fun hasMoreElements(): Boolean {
348+
return (::crt.isInitialized && crt.hasMoreElements()) || iter.hasNext()
349+
}
350+
351+
override fun nextElement(): E {
352+
if (::crt.isInitialized) {
353+
val c = crt
354+
return if (c.hasMoreElements()) {
355+
c.nextElement()
356+
} else if (iter.hasNext()) {
357+
crt = iter.next()
358+
nextElement()
359+
} else {
360+
throw NoSuchElementException()
361+
}
362+
} else if (iter.hasNext()) {
363+
crt = iter.next()
364+
return nextElement()
365+
} else {
366+
throw NoSuchElementException()
367+
}
368+
}
369+
}
370+
}

0 commit comments

Comments
 (0)