Split kotlin modules (#316)
This commit is contained in:
parent
762de3dfba
commit
d2a47ad941
17 changed files with 127 additions and 78 deletions
|
|
@ -0,0 +1,7 @@
|
|||
# Module cloud-kotlin-coroutines-annotations
|
||||
|
||||
cloud-annotations extensions for Kotlin coroutine integration.
|
||||
|
||||
# Package cloud.commandframework.kotlin.coroutines.annotations
|
||||
|
||||
cloud-kotlin-coroutines-annotations classes and functions.
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2021 Alexander Söderberg & Contributors
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
package cloud.commandframework.kotlin.coroutines.annotations
|
||||
|
||||
import cloud.commandframework.annotations.AnnotationParser
|
||||
import cloud.commandframework.annotations.MethodCommandExecutionHandler
|
||||
import cloud.commandframework.context.CommandContext
|
||||
import cloud.commandframework.execution.CommandExecutionCoordinator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.future.future
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Predicate
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.reflect.full.callSuspend
|
||||
import kotlin.reflect.jvm.kotlinFunction
|
||||
|
||||
/**
|
||||
* Adds coroutine support to the [AnnotationParser].
|
||||
*
|
||||
* @param scope coroutine scope
|
||||
* @param context coroutine context
|
||||
* @return annotation parser
|
||||
* @since 1.6.0
|
||||
*/
|
||||
public fun <C> AnnotationParser<C>.installCoroutineSupport(
|
||||
scope: CoroutineScope = GlobalScope,
|
||||
context: CoroutineContext = EmptyCoroutineContext
|
||||
): AnnotationParser<C> {
|
||||
if (manager().commandExecutionCoordinator() is CommandExecutionCoordinator.SimpleCoordinator) {
|
||||
RuntimeException(
|
||||
"""You are highly advised to not use the simple command execution coordinator together
|
||||
with coroutine support. Consider using the asynchronous command execution coordinator instead."""
|
||||
)
|
||||
.printStackTrace()
|
||||
}
|
||||
|
||||
val predicate = Predicate<Method> { it.kotlinFunction?.isSuspend == true }
|
||||
registerCommandExecutionMethodFactory(predicate) {
|
||||
KotlinMethodCommandExecutionHandler(scope, context, it)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private class KotlinMethodCommandExecutionHandler<C>(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val coroutineContext: CoroutineContext,
|
||||
context: CommandMethodContext<C>
|
||||
) : MethodCommandExecutionHandler<C>(context) {
|
||||
|
||||
private val paramsWithoutContinuation = parameters().filterNot { Continuation::class.java == it.type }.toTypedArray()
|
||||
|
||||
override fun executeFuture(commandContext: CommandContext<C>): CompletableFuture<Void?> {
|
||||
val instance = context().instance()
|
||||
val params = createParameterValues(
|
||||
commandContext,
|
||||
commandContext.flags(),
|
||||
paramsWithoutContinuation
|
||||
)
|
||||
|
||||
// We need to propagate exceptions to the caller.
|
||||
return coroutineScope.future(this@KotlinMethodCommandExecutionHandler.coroutineContext) {
|
||||
try {
|
||||
context().method().kotlinFunction!!.callSuspend(instance, *params.toTypedArray())
|
||||
} catch (e: InvocationTargetException) { // unwrap invocation exception
|
||||
e.cause?.let { throw it } ?: throw e // if cause exists, throw, else rethrow invocation exception
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2021 Alexander Söderberg & Contributors
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
package cloud.commandframework.kotlin.coroutines.annotations
|
||||
|
||||
import cloud.commandframework.CommandManager
|
||||
import cloud.commandframework.annotations.AnnotationParser
|
||||
import cloud.commandframework.annotations.CommandMethod
|
||||
import cloud.commandframework.exceptions.CommandExecutionException
|
||||
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator
|
||||
import cloud.commandframework.internal.CommandRegistrationHandler
|
||||
import cloud.commandframework.meta.CommandMeta
|
||||
import cloud.commandframework.meta.SimpleCommandMeta
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class KotlinAnnotatedMethodsTest {
|
||||
|
||||
companion object {
|
||||
val executorService: ExecutorService = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
|
||||
private lateinit var commandManager: CommandManager<TestCommandSender>
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
commandManager = TestCommandManager()
|
||||
}
|
||||
|
||||
private fun awaitCommands() {
|
||||
executorService.shutdown()
|
||||
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test suspending command methods`(): Unit = runBlocking {
|
||||
AnnotationParser(commandManager, TestCommandSender::class.java) {
|
||||
SimpleCommandMeta.empty()
|
||||
}
|
||||
.installCoroutineSupport()
|
||||
.parse(CommandMethods())
|
||||
|
||||
commandManager.executeCommand(TestCommandSender(), "test").await()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test suspending command methods with exception`(): Unit = runBlocking {
|
||||
AnnotationParser(commandManager, TestCommandSender::class.java) {
|
||||
SimpleCommandMeta.empty()
|
||||
}
|
||||
.installCoroutineSupport()
|
||||
.parse(CommandMethods())
|
||||
|
||||
assertThrows<CommandExecutionException> {
|
||||
commandManager.executeCommand(TestCommandSender(), "test-exception").await()
|
||||
}
|
||||
}
|
||||
|
||||
private class TestCommandSender
|
||||
|
||||
private class TestCommandManager : CommandManager<TestCommandSender>(
|
||||
AsynchronousCommandExecutionCoordinator.newBuilder<TestCommandSender>()
|
||||
.withExecutor(executorService)
|
||||
.build(),
|
||||
CommandRegistrationHandler.nullCommandRegistrationHandler()
|
||||
) {
|
||||
|
||||
override fun hasPermission(sender: TestCommandSender, permission: String): Boolean = true
|
||||
|
||||
override fun createDefaultCommandMeta(): CommandMeta = SimpleCommandMeta.empty()
|
||||
}
|
||||
|
||||
public class CommandMethods {
|
||||
|
||||
@CommandMethod("test")
|
||||
public suspend fun suspendingCommand(): Unit =
|
||||
withContext(Dispatchers.Default) {
|
||||
println("called from thread: ${Thread.currentThread().name}")
|
||||
}
|
||||
|
||||
@CommandMethod("test-exception")
|
||||
public suspend fun suspendingCommandWithException(): Unit = throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue