1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-24 20:43:33 +08:00

Move internal declarations to internal package

This commit is contained in:
Him188 2020-12-27 23:54:47 +08:00
parent 8f4d51ca36
commit b0b6c20258
2 changed files with 188 additions and 171 deletions
mirai-core-api/src/commonMain/kotlin/event

View File

@ -14,18 +14,10 @@
package net.mamoe.mirai.event
import kotlinx.coroutines.*
import net.mamoe.mirai.utils.EventListenerLikeJava
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.castOrNull
import java.lang.reflect.Method
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.reflect.KClass
import kotlin.reflect.full.IllegalCallableAccessException
import kotlin.reflect.full.callSuspend
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.kotlinFunction
/**
* 标注一个函数为事件监听器.
@ -286,166 +278,4 @@ public fun <T> T.registerEvents(coroutineContext: CoroutineContext = EmptyCorout
public fun CoroutineScope.registerEvents(
host: ListenerHost,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Unit = globalEventChannel(coroutineContext).registerListenerHost(host)
private fun Method.isKotlinFunction(): Boolean {
if (getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false
if (declaringClass.getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false
@Suppress("RemoveRedundantQualifierName") // for strict
return declaringClass.getDeclaredAnnotation(kotlin.Metadata::class.java) != null
}
@Suppress("UNCHECKED_CAST")
internal fun Method.registerEventHandler(
owner: Any,
eventChannel: EventChannel<*>,
annotation: EventHandler,
coroutineContext: CoroutineContext,
): Listener<Event> {
this.isAccessible = true
val kotlinFunction = kotlin.runCatching { this.kotlinFunction }.getOrNull()
return if (kotlinFunction != null && isKotlinFunction()) {
// kotlin functions
val param = kotlinFunction.parameters
when (param.size) {
3 -> { // ownerClass, receiver, event
check(param[1].type == param[2].type) { "Illegal kotlin function ${kotlinFunction.name}. Receiver and param must have same type" }
check((param[1].type.classifier as? KClass<*>)?.isSubclassOf(Event::class) == true) {
"Illegal kotlin function ${kotlinFunction.name}. First param or receiver must be subclass of Event, but found ${param[1].type.classifier}"
}
}
2 -> { // ownerClass, event
check((param[1].type.classifier as? KClass<*>)?.isSubclassOf(Event::class) == true) {
"Illegal kotlin function ${kotlinFunction.name}. First param or receiver must be subclass of Event, but found ${param[1].type.classifier}"
}
}
else -> error("function ${kotlinFunction.name} must have one Event param")
}
lateinit var listener: Listener<*>
kotlin.runCatching {
kotlinFunction.isAccessible = true
}
suspend fun callFunction(event: Event): Any? {
try {
return when (param.size) {
3 -> {
if (kotlinFunction.isSuspend) {
kotlinFunction.callSuspend(owner, event, event)
} else withContext(Dispatchers.IO) { // for safety
kotlinFunction.call(owner, event, event)
}
}
2 -> {
if (kotlinFunction.isSuspend) {
kotlinFunction.callSuspend(owner, event)
} else withContext(Dispatchers.IO) { // for safety
kotlinFunction.call(owner, event)
}
}
else -> error("stub")
}
} catch (e: IllegalCallableAccessException) {
listener.completeExceptionally(e)
return ListeningStatus.STOPPED
} catch (e: Throwable) {
throw ExceptionInEventHandlerException(event, cause = e)
}
}
require(!kotlinFunction.returnType.isMarkedNullable) {
"Kotlin event handlers cannot have nullable return type."
}
require(kotlinFunction.parameters.none { it.type.isMarkedNullable }) {
"Kotlin event handlers cannot have nullable parameter type."
}
when (kotlinFunction.returnType.classifier) {
Unit::class, Nothing::class -> {
eventChannel.subscribeAlways(
param[1].type.classifier as KClass<out Event>,
coroutineContext,
annotation.concurrency,
annotation.priority
) {
if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) {
callFunction(this)
}
} else callFunction(this)
}.also { listener = it }
}
ListeningStatus::class -> {
eventChannel.subscribe(
param[1].type.classifier as KClass<out Event>,
coroutineContext,
annotation.concurrency,
annotation.priority
) {
if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) {
callFunction(this) as ListeningStatus
} else ListeningStatus.LISTENING
} else callFunction(this) as ListeningStatus
}.also { listener = it }
}
else -> error("Illegal method return type. Required Void, Nothing or ListeningStatus, found ${kotlinFunction.returnType.classifier}")
}
} else {
// java methods
val paramType = this.parameters[0].type
check(this.parameterCount == 1 && Event::class.java.isAssignableFrom(paramType)) {
"Illegal method parameter. Required one exact Event subclass. found ${this.parameters.contentToString()}"
}
suspend fun callMethod(event: Event): Any? {
fun Method.invokeWithErrorReport(self: Any?, vararg args: Any?): Any? = try {
invoke(self, *args)
} catch (exception: IllegalArgumentException) {
throw IllegalArgumentException(
"Internal Error: $exception, method=${this}, this=$self, arguments=$args, please report to https://github.com/mamoe/mirai",
exception
)
} catch (e: Throwable) {
throw ExceptionInEventHandlerException(event, cause = e)
}
return if (annotation.ignoreCancelled) {
if (event.castOrNull<CancellableEvent>()?.isCancelled != true) {
withContext(Dispatchers.IO) {
this@registerEventHandler.invokeWithErrorReport(owner, event)
}
} else ListeningStatus.LISTENING
} else withContext(Dispatchers.IO) {
this@registerEventHandler.invokeWithErrorReport(owner, event)
}
}
when (this.returnType) {
Void::class.java, Void.TYPE, Nothing::class.java -> {
eventChannel.subscribeAlways(
paramType.kotlin as KClass<out Event>,
coroutineContext,
annotation.concurrency,
annotation.priority
) {
callMethod(this)
}
}
ListeningStatus::class.java -> {
eventChannel.subscribe(
paramType.kotlin as KClass<out Event>,
coroutineContext,
annotation.concurrency,
annotation.priority
) {
callMethod(this) as ListeningStatus?
?: error("Java method EventHandler cannot return `null`: $this")
}
}
else -> error("Illegal method return type. Required Void or ListeningStatus, but found ${this.returnType.canonicalName}")
}
}
}
): Unit = globalEventChannel(coroutineContext).registerListenerHost(host)

View File

@ -0,0 +1,187 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.event.internal
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.event.*
import net.mamoe.mirai.utils.EventListenerLikeJava
import net.mamoe.mirai.utils.castOrNull
import java.lang.reflect.Method
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass
import kotlin.reflect.full.IllegalCallableAccessException
import kotlin.reflect.full.callSuspend
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.kotlinFunction
private fun Method.isKotlinFunction(): Boolean {
if (getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false
if (declaringClass.getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false
@Suppress("RemoveRedundantQualifierName") // for strict
return declaringClass.getDeclaredAnnotation(kotlin.Metadata::class.java) != null
}
@Suppress("UNCHECKED_CAST")
internal fun Method.registerEventHandler(
owner: Any,
eventChannel: EventChannel<*>,
annotation: EventHandler,
coroutineContext: CoroutineContext,
): Listener<Event> {
this.isAccessible = true
val kotlinFunction = kotlin.runCatching { this.kotlinFunction }.getOrNull()
return if (kotlinFunction != null && isKotlinFunction()) {
// kotlin functions
val param = kotlinFunction.parameters
when (param.size) {
3 -> { // ownerClass, receiver, event
check(param[1].type == param[2].type) { "Illegal kotlin function ${kotlinFunction.name}. Receiver and param must have same type" }
check((param[1].type.classifier as? KClass<*>)?.isSubclassOf(Event::class) == true) {
"Illegal kotlin function ${kotlinFunction.name}. First param or receiver must be subclass of Event, but found ${param[1].type.classifier}"
}
}
2 -> { // ownerClass, event
check((param[1].type.classifier as? KClass<*>)?.isSubclassOf(Event::class) == true) {
"Illegal kotlin function ${kotlinFunction.name}. First param or receiver must be subclass of Event, but found ${param[1].type.classifier}"
}
}
else -> error("function ${kotlinFunction.name} must have one Event param")
}
lateinit var listener: Listener<*>
kotlin.runCatching {
kotlinFunction.isAccessible = true
}
suspend fun callFunction(event: Event): Any? {
try {
return when (param.size) {
3 -> {
if (kotlinFunction.isSuspend) {
kotlinFunction.callSuspend(owner, event, event)
} else withContext(Dispatchers.IO) { // for safety
kotlinFunction.call(owner, event, event)
}
}
2 -> {
if (kotlinFunction.isSuspend) {
kotlinFunction.callSuspend(owner, event)
} else withContext(Dispatchers.IO) { // for safety
kotlinFunction.call(owner, event)
}
}
else -> error("stub")
}
} catch (e: IllegalCallableAccessException) {
listener.completeExceptionally(e)
return ListeningStatus.STOPPED
} catch (e: Throwable) {
throw ExceptionInEventHandlerException(event, cause = e)
}
}
require(!kotlinFunction.returnType.isMarkedNullable) {
"Kotlin event handlers cannot have nullable return type."
}
require(kotlinFunction.parameters.none { it.type.isMarkedNullable }) {
"Kotlin event handlers cannot have nullable parameter type."
}
when (kotlinFunction.returnType.classifier) {
Unit::class, Nothing::class -> {
eventChannel.subscribeAlways(
param[1].type.classifier as KClass<out Event>,
coroutineContext,
annotation.concurrency,
annotation.priority
) {
if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) {
callFunction(this)
}
} else callFunction(this)
}.also { listener = it }
}
ListeningStatus::class -> {
eventChannel.subscribe(
param[1].type.classifier as KClass<out Event>,
coroutineContext,
annotation.concurrency,
annotation.priority
) {
if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) {
callFunction(this) as ListeningStatus
} else ListeningStatus.LISTENING
} else callFunction(this) as ListeningStatus
}.also { listener = it }
}
else -> error("Illegal method return type. Required Void, Nothing or ListeningStatus, found ${kotlinFunction.returnType.classifier}")
}
} else {
// java methods
val paramType = this.parameters[0].type
check(this.parameterCount == 1 && Event::class.java.isAssignableFrom(paramType)) {
"Illegal method parameter. Required one exact Event subclass. found ${this.parameters.contentToString()}"
}
suspend fun callMethod(event: Event): Any? {
fun Method.invokeWithErrorReport(self: Any?, vararg args: Any?): Any? = try {
invoke(self, *args)
} catch (exception: IllegalArgumentException) {
throw IllegalArgumentException(
"Internal Error: $exception, method=${this}, this=$self, arguments=$args, please report to https://github.com/mamoe/mirai",
exception
)
} catch (e: Throwable) {
throw ExceptionInEventHandlerException(event, cause = e)
}
return if (annotation.ignoreCancelled) {
if (event.castOrNull<CancellableEvent>()?.isCancelled != true) {
withContext(Dispatchers.IO) {
this@registerEventHandler.invokeWithErrorReport(owner, event)
}
} else ListeningStatus.LISTENING
} else withContext(Dispatchers.IO) {
this@registerEventHandler.invokeWithErrorReport(owner, event)
}
}
when (this.returnType) {
Void::class.java, Void.TYPE, Nothing::class.java -> {
eventChannel.subscribeAlways(
paramType.kotlin as KClass<out Event>,
coroutineContext,
annotation.concurrency,
annotation.priority
) {
callMethod(this)
}
}
ListeningStatus::class.java -> {
eventChannel.subscribe(
paramType.kotlin as KClass<out Event>,
coroutineContext,
annotation.concurrency,
annotation.priority
) {
callMethod(this) as ListeningStatus?
?: error("Java method EventHandler cannot return `null`: $this")
}
}
else -> error("Illegal method return type. Required Void or ListeningStatus, but found ${this.returnType.canonicalName}")
}
}
}