Reflection commands, fix genref
This commit is contained in:
1 parent
cc6f64c3b7
commit
51ae06d7cd
270 files changed
+3319
-134
No files matched your search
@@ -0,0 +1,3 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands
|
||||
|
||||
open class CommandBase{}
|
||||
@@ -0,0 +1,11 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands
|
||||
|
||||
data class CommandParams(
|
||||
val name: String,
|
||||
val desc: String,
|
||||
val usage: String,
|
||||
val perm: String?,
|
||||
val aliases: List<String>,
|
||||
val completeTags: Array<String>,
|
||||
val isLastOptional: Boolean,
|
||||
)
|
||||
@@ -0,0 +1,125 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands
|
||||
|
||||
import org.bukkit.command.CommandSender
|
||||
import tech.carcadex.kotlinbukkitkit.commands.annotations.*
|
||||
import tech.carcadex.kotlinbukkitkit.commands.exceptions.TypeParseException
|
||||
import tech.carcadex.kotlinbukkitkit.commands.exceptions.UnsupportedTypeException
|
||||
import tech.carcadex.kotlinbukkitkit.commands.service.MessagesService
|
||||
import tech.carcadex.kotlinbukkitkit.commands.service.TypeService
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.full.createType
|
||||
import kotlin.reflect.full.findAnnotations
|
||||
import kotlin.reflect.full.hasAnnotation
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
object Parser {
|
||||
fun paramsFromAnnotations(clazz: KClass<*>): CommandParams {
|
||||
val name = clazz.findAnnotations<Command>().first().name
|
||||
val desc = clazz.findAnnotations<Description>().getOrNull(0)?.desc ?: ""
|
||||
val usage = clazz.findAnnotations<Usage>().getOrNull(0)?.usage ?: ""
|
||||
val perm = clazz.findAnnotations<Permission>().getOrNull(0)?.perm
|
||||
val cmdAliases: Array<String> = clazz.findAnnotations<Aliases>().getOrNull(0)?.aliases ?: emptyArray()
|
||||
val completeTags: Array<String>? = null
|
||||
val isLastOptional = false
|
||||
var argsSize = 1
|
||||
val aliases = arrayListOf(name)
|
||||
aliases.addAll(cmdAliases)
|
||||
return CommandParams(name, desc, usage, perm, aliases, completeTags ?: emptyArray(), isLastOptional)
|
||||
}
|
||||
fun paramsFromAnnotations(func: KFunction<*>): CommandParams {
|
||||
|
||||
val name = if(func.hasAnnotation<Command>())func.findAnnotations<Command>().first().name else
|
||||
if(func.hasAnnotation<SubCommand>()) func.findAnnotations<SubCommand>().first().name else "default"
|
||||
val desc = func.findAnnotations<Description>().getOrNull(0)?.desc ?: ""
|
||||
val usage = func.findAnnotations<Usage>().getOrNull(0)?.usage ?: ""
|
||||
val perm = func.findAnnotations<Permission>().getOrNull(0)?.perm
|
||||
val cmdAliases: Array<String> = func.findAnnotations<Aliases>().getOrNull(0)?.aliases ?: emptyArray()
|
||||
val completeTags: Array<String>? = func.findAnnotations<TabComplete>().getOrNull(0)?.complete
|
||||
val isLastOptional = func.parameters[func.parameters.size - 1].hasAnnotation<Optional>()
|
||||
var argsSize = func.parameters.size
|
||||
if (func.parameters.isNotEmpty() && func.parameters[0].type.javaType.typeName == CommandSender::class.qualifiedName!!) {
|
||||
--argsSize
|
||||
}
|
||||
val aliases = arrayListOf(name)
|
||||
aliases.addAll(cmdAliases)
|
||||
return CommandParams(name, desc, usage, perm, aliases, completeTags ?: emptyArray(), isLastOptional)
|
||||
}
|
||||
|
||||
fun parseParam(params: List<KParameter>): List<(String, CommandSender) -> Any?> {
|
||||
val argsParsers = mutableListOf<(String, CommandSender) -> Any?>()
|
||||
val enums = mutableMapOf<Int, (String, CommandSender) -> Enum<*>>()
|
||||
for ((argIndex, argType) in params.withIndex()) {
|
||||
val clazz = Class.forName(replacePrimitives(argType.type.javaType.typeName))
|
||||
if (TypeService.registered(argType.type)) {
|
||||
argsParsers.add { s, sender ->
|
||||
val context = TypeService.TypeContext(s, sender)
|
||||
try {
|
||||
TypeService.byType(argType.type)(context)
|
||||
} catch (e: Throwable) {
|
||||
if (context.errorMessage != null) MessagesService.byTag(context.errorMessage!!)(sender)
|
||||
throw TypeParseException()
|
||||
}
|
||||
}
|
||||
} else if (!clazz.isEnum) throw UnsupportedTypeException("Type ${argType.type.javaType.typeName} is not supported")
|
||||
else {
|
||||
enums[argIndex] = { it, sender ->
|
||||
var res: Enum<*>? = null;
|
||||
for (e in clazz.enumConstants) {
|
||||
if ((e as Enum<*>).name.equals(it, ignoreCase = true)) res = e;
|
||||
}
|
||||
if (res == null) MessagesService.byTag("#no-such-enum")(sender)
|
||||
res ?: throw TypeParseException()
|
||||
}
|
||||
argsParsers.add { _, _ -> {} }
|
||||
}
|
||||
}
|
||||
enums.forEach {
|
||||
argsParsers[it.key] = it.value
|
||||
}
|
||||
return argsParsers;
|
||||
}
|
||||
|
||||
fun replacePrimitives(s: String): String = when(s) {
|
||||
"int" -> "java.lang.Integer"
|
||||
"short" -> "java.lang.Short"
|
||||
"long" -> "java.lang.Long"
|
||||
"double" -> "java.lang.Double"
|
||||
"byte" -> "java.lang.Byte"
|
||||
"boolean" -> "java.lang.Boolean"
|
||||
else -> s
|
||||
}
|
||||
|
||||
fun paramMap(
|
||||
cmd: CommandParams,
|
||||
argsFromExecutor: Array<String>,
|
||||
argsSize: Int,
|
||||
sender: CommandSender,
|
||||
argsParsers: List<(String, CommandSender) -> Any?>,
|
||||
func: KFunction<*>,
|
||||
instanceAtStart: Boolean = false,
|
||||
instance: Any = Any()
|
||||
): MutableMap<KParameter, Any?> {
|
||||
val tempArgs = if (cmd.isLastOptional && argsFromExecutor.size < argsSize) argsFromExecutor.copyOf(argsSize)
|
||||
else argsFromExecutor
|
||||
|
||||
val finalArgs = mutableListOf<Any?>()
|
||||
if(instanceAtStart) finalArgs.add(instance)
|
||||
if(func.parameters.isNotEmpty() && (
|
||||
func.parameters[0 + if(instanceAtStart) 1 else 0].type == (CommandSender::class.createType()) ||
|
||||
func.parameters[0 + if(instanceAtStart) 1 else 0].type.isSubtypeOf(CommandSender::class.createType())
|
||||
)
|
||||
)
|
||||
finalArgs.add(sender)
|
||||
finalArgs.addAll(tempArgs.mapIndexed { i, it ->
|
||||
if (it == null) return@mapIndexed null
|
||||
argsParsers[i + 1](it, sender)
|
||||
})
|
||||
|
||||
val map = mutableMapOf<KParameter, Any?>()
|
||||
for ((i, arg) in finalArgs.withIndex()) map[func.parameters[i]] = arg
|
||||
return map
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands
|
||||
|
||||
import org.bukkit.command.CommandSender
|
||||
import tech.carcadex.kotlinbukkitkit.commands.annotations.Command
|
||||
import tech.carcadex.kotlinbukkitkit.commands.annotations.Permission
|
||||
import tech.carcadex.kotlinbukkitkit.commands.manager.CommandManager
|
||||
|
||||
typealias Sender = CommandSender
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.exceptions
|
||||
|
||||
class TypeParseException() : RuntimeException() {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.exceptions
|
||||
|
||||
class UnknownCompleteException(val msg: String) : RuntimeException(msg) {
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.manager
|
||||
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.OfflinePlayer
|
||||
import org.bukkit.World
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.plugin.Plugin
|
||||
import tech.carcadex.kotlinbukkitkit.commands.annotations.Command
|
||||
import tech.carcadex.kotlinbukkitkit.commands.exceptions.UnsupportedTypeException
|
||||
import tech.carcadex.kotlinbukkitkit.extensions.offlinePlayerOrNull
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.callSuspendBy
|
||||
import kotlin.reflect.full.findAnnotations
|
||||
import kotlin.reflect.full.hasAnnotation
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
class CommandManager(val plugin: Plugin) {
|
||||
private val types = mutableMapOf<String, (String) -> Any?>()
|
||||
|
||||
init {
|
||||
types[CommandSender::class.qualifiedName!!] = { it }
|
||||
types[String::class.qualifiedName!!] = { it }
|
||||
types["java.lang.String"] = { it }
|
||||
types[Int::class.qualifiedName!!] = { it.toInt() }
|
||||
types["java.lang.Integer"] = { it.toInt() }
|
||||
types[UInt::class.qualifiedName!!] = { it.toUInt() }
|
||||
types[Long::class.qualifiedName!!] = { it.toLong() }
|
||||
types["java.lang.Long"] = { it.toInt() }
|
||||
types[ULong::class.qualifiedName!!] = { it.toULong() }
|
||||
types[Short::class.qualifiedName!!] = { it.toShort() }
|
||||
types["java.lang.Short"] = { it.toInt() }
|
||||
types[UShort::class.qualifiedName!!] = { it.toUShort() }
|
||||
types[Float::class.qualifiedName!!] = { it.toFloat() }
|
||||
types["java.lang.Float"] = { it.toInt() }
|
||||
types[Double::class.qualifiedName!!] = { it.toDouble() }
|
||||
types["java.lang.Double"] = { it.toInt() }
|
||||
types[Byte::class.qualifiedName!!] = { it.toByte() }
|
||||
types["java.lang.Byte"] = { it.toInt() }
|
||||
types[UByte::class.qualifiedName!!] = { it.toUByte() }
|
||||
types[Boolean::class.qualifiedName!!] = { it.toBoolean() }
|
||||
types["java.lang.Boolean"] = { it.toInt() }
|
||||
|
||||
types[Player::class.qualifiedName!!] = { Bukkit.getPlayer(it) }
|
||||
types[OfflinePlayer::class.qualifiedName!!] = { offlinePlayerOrNull(it) }
|
||||
types[World::class.qualifiedName!!] = { Bukkit.getWorld(it) }
|
||||
types[Material::class.qualifiedName!!] = { Material.matchMaterial(it) }
|
||||
}
|
||||
|
||||
private fun register(commandExecutor: CommandExecutor, aliases: Array<String>,
|
||||
desc: String = "", usage: String = "") {
|
||||
CommandRegister.reg(plugin, commandExecutor, aliases, desc, usage);
|
||||
}
|
||||
fun register(f: KFunction<Unit>) {
|
||||
if(f.hasAnnotation<Command>()) {
|
||||
val cmdName = f.findAnnotations<Command>().first().name
|
||||
val argsParsers = mutableListOf<(String) -> Any?>({
|
||||
return@mutableListOf types[CommandSender::class.qualifiedName!!]!!(it)
|
||||
})
|
||||
|
||||
for (argType in f.parameters) {
|
||||
val clazz = Class.forName(argType.type.javaType.typeName)
|
||||
if(argType.type.javaType.typeName in this.types) {
|
||||
argsParsers.add(this.types[argType.type.javaType.typeName]!!)
|
||||
} else if(!clazz.isEnum) throw UnsupportedTypeException("Type ${argType.type.javaType.typeName} is not supported") else {
|
||||
argsParsers.add {
|
||||
for(e in clazz.enumConstants) {
|
||||
if((e as Enum<*>).name.equals(it, ignoreCase = true)) return@add e;
|
||||
}
|
||||
throw NoSuchElementException("No such value in enum")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(CommandExecutor { sender, command, label, args ->
|
||||
val finalArgs = mutableListOf<Any?>(sender)
|
||||
finalArgs.addAll(args.mapIndexed { i, it -> argsParsers[i](it) })
|
||||
val map = mutableMapOf<KParameter, Any?>()
|
||||
var i = 0;
|
||||
for(arg in finalArgs) {
|
||||
map[f.parameters[i]] = arg
|
||||
++i
|
||||
}
|
||||
f.callBy(map)
|
||||
return@CommandExecutor true;
|
||||
}, arrayOf(cmdName))
|
||||
} else throw IllegalArgumentException("It must be a command!")
|
||||
}
|
||||
|
||||
fun type(clazz: KClass<*>, converter: (String) -> Any) {
|
||||
types[clazz.qualifiedName!!] = converter
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.manager
|
||||
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.command.TabCompleter
|
||||
import org.bukkit.plugin.Plugin
|
||||
import tech.carcadex.kotlinbukkitkit.commands.CommandParams
|
||||
import tech.carcadex.kotlinbukkitkit.commands.Parser
|
||||
import tech.carcadex.kotlinbukkitkit.commands.Parser.paramsFromAnnotations
|
||||
import tech.carcadex.kotlinbukkitkit.commands.annotations.Command
|
||||
import tech.carcadex.kotlinbukkitkit.commands.exceptions.TypeParseException
|
||||
import tech.carcadex.kotlinbukkitkit.commands.register.CommandRegister
|
||||
import tech.carcadex.kotlinbukkitkit.commands.service.MessagesService
|
||||
import tech.carcadex.kotlinbukkitkit.commands.service.SubCommandsService
|
||||
import tech.carcadex.kotlinbukkitkit.commands.service.TabCompleteService
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.full.createType
|
||||
import kotlin.reflect.full.hasAnnotation
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
class ReflectionCommandManager(private val plugin: Plugin) {
|
||||
|
||||
private fun register(
|
||||
commandExecutor: CommandExecutor, commandParams: CommandParams, tabCompleter: TabCompleter =
|
||||
TabCompleter { _, _, _, _ -> mutableListOf() }
|
||||
) {
|
||||
CommandRegister.register(
|
||||
plugin,
|
||||
commandExecutor,
|
||||
commandParams.aliases.toTypedArray(),
|
||||
commandParams.desc,
|
||||
commandParams.usage,
|
||||
tabCompleter
|
||||
);
|
||||
}
|
||||
|
||||
fun register(commandClass: Any) {
|
||||
val (name, subs) = SubCommandsService.registerRootCommand(commandClass)
|
||||
val subsNames = mutableListOf<String>()
|
||||
subs.forEach {
|
||||
subsNames.addAll(it.context.aliases)
|
||||
}
|
||||
val context = paramsFromAnnotations(commandClass::class)
|
||||
register(CommandExecutor { sender, _, _, args ->
|
||||
if (context.perm != null && !sender.hasPermission(context.perm)) {
|
||||
MessagesService.byTag("#no-perm")(sender)
|
||||
return@CommandExecutor true
|
||||
}
|
||||
SubCommandsService.handleCommand(name, sender, args);
|
||||
true
|
||||
},
|
||||
context,
|
||||
TabCompleter { sender, cmd, alias, args ->
|
||||
if (args.size == 1) {
|
||||
return@TabCompleter subsNames
|
||||
} else if (args.size > 1) {
|
||||
val sub = args[0];
|
||||
if (subs.any { it.name.equals(sub, ignoreCase = true) }) {
|
||||
val argsForSub = args.copyOfRange(1, args.size)
|
||||
return@TabCompleter subs.filter { it.name.equals(sub, ignoreCase = true) }.first().tabCompleter
|
||||
.onTabComplete(sender, cmd, alias, argsForSub)
|
||||
} else return@TabCompleter emptyList()
|
||||
} else return@TabCompleter emptyList()
|
||||
})
|
||||
}
|
||||
|
||||
fun register(func: KFunction<Unit>) {
|
||||
if (func.hasAnnotation<Command>()) {
|
||||
val cmd = paramsFromAnnotations(func)
|
||||
var argsSize = func.parameters.size
|
||||
if (func.parameters.isNotEmpty() && (
|
||||
func.parameters[0].type == (CommandSender::class.createType()) ||
|
||||
func.parameters[0].type.isSubtypeOf(CommandSender::class.createType())
|
||||
)
|
||||
) {
|
||||
--argsSize
|
||||
}
|
||||
val argsParsers = Parser.parseParam(func.parameters)
|
||||
|
||||
|
||||
register(CommandExecutor { sender, _, _, argsFromExecutor ->
|
||||
if (cmd.perm != null && !sender.hasPermission(cmd.perm)) {
|
||||
MessagesService.byTag("#no-perm")(sender)
|
||||
return@CommandExecutor true
|
||||
}
|
||||
try {
|
||||
func.callBy(
|
||||
|
||||
Parser.paramMap(cmd, argsFromExecutor, argsSize, sender, argsParsers, func)
|
||||
|
||||
)
|
||||
} catch (ignored: TypeParseException) {
|
||||
} catch (e: IllegalArgumentException) {
|
||||
MessagesService.byTag("#wrong-usage")(sender)
|
||||
}
|
||||
|
||||
return@CommandExecutor true
|
||||
|
||||
},
|
||||
cmd,
|
||||
cmd.completeTags.let {
|
||||
TabCompleteService.completerReflection(it.toMutableList(),
|
||||
func.parameters.map { it.type.javaType as Class<*> })
|
||||
}
|
||||
?: TabCompleter { _, _, _, _ -> mutableListOf() }
|
||||
)
|
||||
} else throw IllegalArgumentException("It must be a command!")
|
||||
}
|
||||
|
||||
fun tabComplete(tag: String, func: (CommandSender) -> List<String>) {
|
||||
TabCompleteService.register(tag, func)
|
||||
}
|
||||
|
||||
|
||||
fun message(tag: String, func: (CommandSender) -> Unit) {
|
||||
MessagesService.register(tag, func)
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -1,26 +1,25 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.manager
|
||||
package tech.carcadex.kotlinbukkitkit.commands.register
|
||||
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.command.*
|
||||
import org.bukkit.plugin.Plugin
|
||||
import java.util.*
|
||||
|
||||
|
||||
class CommandRegister(
|
||||
aliases: Array<String>,
|
||||
desc: String?,
|
||||
usage: String?,
|
||||
protected val owner: CommandExecutor,
|
||||
protected val registeredWith: Any,
|
||||
val pl: Plugin
|
||||
) : Command(aliases[0], desc!!, usage!!, Arrays.asList(*aliases)), PluginIdentifiableCommand {
|
||||
private val executor: CommandExecutor,
|
||||
private val tabCompleter: TabCompleter,
|
||||
private val pl: Plugin
|
||||
) : Command(aliases[0], desc!!, usage!!, listOf(*aliases)), PluginIdentifiableCommand {
|
||||
|
||||
|
||||
override fun execute(sender: CommandSender, label: String, args: Array<String>): Boolean {
|
||||
if (!testPermission(sender)) {
|
||||
return true
|
||||
}
|
||||
return if (owner.onCommand(sender, this, label, args)) {
|
||||
return if (executor.onCommand(sender, this, label, args)) {
|
||||
true
|
||||
} else {
|
||||
sender.sendMessage(usageMessage)
|
||||
@@ -29,10 +28,10 @@ class CommandRegister(
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun reg(plugin: Plugin, executor: CommandExecutor, aliases: Array<String>, desc: String?, usage: String?) {
|
||||
fun register(plugin: Plugin, executor: CommandExecutor, aliases: Array<String>, desc: String?, usage: String?, tabCompleter: TabCompleter) {
|
||||
if(aliases.size < 1) throw IllegalArgumentException("Could not found name")
|
||||
try {
|
||||
val reg = CommandRegister(aliases, desc, usage, executor, Any(), plugin)
|
||||
val reg = CommandRegister(aliases, desc, usage, executor, tabCompleter, plugin)
|
||||
val field = Bukkit.getServer().javaClass.getDeclaredField("commandMap")
|
||||
field.isAccessible = true
|
||||
val map = field[Bukkit.getServer()] as CommandMap
|
||||
@@ -44,4 +43,8 @@ class CommandRegister(
|
||||
}
|
||||
|
||||
override fun getPlugin(): Plugin = pl
|
||||
|
||||
override fun tabComplete(sender: CommandSender, alias: String, args: Array<out String>): MutableList<String> {
|
||||
return tabCompleter.onTabComplete(sender, this, alias, args)!!
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.service
|
||||
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.command.CommandSender
|
||||
|
||||
object MessagesService {
|
||||
private val messages = mutableMapOf<String, (CommandSender) -> Unit>()
|
||||
|
||||
init {
|
||||
//REGISTER DEFAULT MESSAGES
|
||||
messages["#no-such-player"] = { it.sendMessage("${ChatColor.RED}No such player was found") }
|
||||
messages["#no-such-world"] = { it.sendMessage("${ChatColor.RED}No such world was found") }
|
||||
messages["#no-such-material"] = { it.sendMessage("${ChatColor.RED}No such material was found") }
|
||||
messages["#no-such-enum"] = { it.sendMessage("${ChatColor.RED}No such value for this enum was found") }
|
||||
messages["#not-a-number"] = { it.sendMessage("${ChatColor.RED}It is not a number") }
|
||||
messages["#wrong-usage"] = { it.sendMessage("${ChatColor.RED}Wrong usage of command") }
|
||||
messages["#no-perm"] = { it.sendMessage("${ChatColor.RED}You have not permissions for this command!") }
|
||||
}
|
||||
|
||||
fun register(tag: String, func: (CommandSender) -> Unit) {
|
||||
messages[tag] = func
|
||||
}
|
||||
|
||||
fun byTag(tag: String): (CommandSender) -> Unit = messages[tag] ?: throw NoSuchElementException("Could not found message $tag")
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.service
|
||||
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.command.TabCompleter
|
||||
import tech.carcadex.kotlinbukkitkit.commands.CommandParams
|
||||
import tech.carcadex.kotlinbukkitkit.commands.Parser
|
||||
import tech.carcadex.kotlinbukkitkit.commands.annotations.Command
|
||||
import tech.carcadex.kotlinbukkitkit.commands.annotations.Default
|
||||
import tech.carcadex.kotlinbukkitkit.commands.exceptions.TypeParseException
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.full.*
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
object SubCommandsService {
|
||||
data class SubCommand(val name: String, val context: CommandParams,
|
||||
val executor: (sender: CommandSender, args: Array<String>) -> Boolean, val tabCompleter: TabCompleter)
|
||||
|
||||
|
||||
private val subs = mutableMapOf<String, List<SubCommand>>()
|
||||
private val defaults = mutableMapOf<String, SubCommand>()
|
||||
|
||||
fun registerRootCommand(command: Any): Pair<String, List<SubCommand>> {
|
||||
val clazz = command::class
|
||||
if(!clazz.hasAnnotation<Command>()) throw IllegalArgumentException("$command has not Command annotation")
|
||||
val name = clazz.findAnnotation<Command>()!!.name.lowercase()
|
||||
|
||||
val res = mutableListOf<SubCommand>()
|
||||
for(func in clazz.functions) {
|
||||
if(func.hasAnnotation<tech.carcadex.kotlinbukkitkit.commands.annotations.SubCommand>()) {
|
||||
res.add(parseSubCommand(func, command))
|
||||
} else if(func.hasAnnotation<Default>()) {
|
||||
defaults[name] = parseSubCommand(func, command)
|
||||
}
|
||||
}
|
||||
subs[name] = res
|
||||
return Pair(name, res)
|
||||
}
|
||||
|
||||
fun handleCommand(rootCommand: String, sender: CommandSender, args: Array<String>) {
|
||||
if(rootCommand.lowercase() in subs) {
|
||||
if(args.isNotEmpty()) for(sub in subs[rootCommand.lowercase()]!!) {
|
||||
if(sub.name == args[0]) {
|
||||
sub.executor(sender, args.copyOfRange(1, args.size))
|
||||
return
|
||||
}
|
||||
}
|
||||
defaults[rootCommand]?.executor?.let { it(sender, args) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun parseSubCommand(func: KFunction<*>, rootCommand: Any): SubCommand {
|
||||
|
||||
val name = if(func.hasAnnotation<Default>()) "default" else func.findAnnotations(tech.carcadex.kotlinbukkitkit.commands.annotations.SubCommand::class).first().name
|
||||
val context = Parser.paramsFromAnnotations(func)
|
||||
val argsParsers = Parser.parseParam(func.parameters.toTypedArray().copyOfRange(1, func.parameters.size).toList())
|
||||
var argsSize = func.parameters.size
|
||||
if (func.parameters.isNotEmpty() && (
|
||||
func.parameters[0].type == (CommandSender::class.createType()) ||
|
||||
func.parameters[0].type.isSubtypeOf(CommandSender::class.createType())
|
||||
)) {
|
||||
--argsSize
|
||||
}
|
||||
val executor: (sender: CommandSender, args: Array<String>) -> Boolean = { sender, argsFromExecutor ->
|
||||
if (context.perm != null && !sender.hasPermission(context.perm)) {
|
||||
MessagesService.byTag("#no-perm")(sender)
|
||||
true
|
||||
} else {
|
||||
try {
|
||||
func.callBy(Parser.paramMap(context, argsFromExecutor, argsSize, sender, argsParsers, func, true, rootCommand))
|
||||
true
|
||||
} catch (ignored: TypeParseException) {
|
||||
false
|
||||
} catch (e: IllegalArgumentException) {
|
||||
MessagesService.byTag("#wrong-usage")(sender)
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return SubCommand(name, context, executor, context.completeTags.let {
|
||||
TabCompleteService.completerReflection(it.toMutableList(),
|
||||
func.parameters.map { it.type.javaType as Class<*> })
|
||||
}
|
||||
?: TabCompleter { _, _, _, _ -> mutableListOf() })
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.service
|
||||
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.command.TabCompleter
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
object TabCompleteService {
|
||||
private val completes = mutableMapOf<String, (CommandSender) -> List<String>>()
|
||||
init {
|
||||
//REGISTER DEFAULT COMPLETES
|
||||
completes["#players"] = { Bukkit.getOnlinePlayers().map { it.name } }
|
||||
completes["#offlinePlayers"] = { Bukkit.getOfflinePlayers().map { it.name ?: "" } }
|
||||
completes["#worlds"] = { Bukkit.getWorlds().map { it.name } }
|
||||
completes["#empty"] = { emptyList() }
|
||||
}
|
||||
|
||||
fun register(tag: String, func: (CommandSender) -> List<String>) {
|
||||
completes[tag] = func
|
||||
}
|
||||
|
||||
fun completerReflection(list: MutableList<String>, params: List<Class<*>>): TabCompleter {
|
||||
val complete: MutableList<(CommandSender) -> List<String>> = ArrayList(params.size)
|
||||
for(i in params.indices) complete.add { mutableListOf() }
|
||||
for((i, tag) in list.withIndex()) {
|
||||
complete[i] = reflectionCompleteParser(tag, params[i+if(params[0] == CommandSender::class.java) 1 else 0])
|
||||
}
|
||||
return createTabCompleter(complete)
|
||||
}
|
||||
|
||||
private fun createTabCompleter(completes: List<(CommandSender) -> List<String>>): TabCompleter =
|
||||
TabCompleter { sender, _, _, args ->
|
||||
if(args.size <= completes.size)
|
||||
completes[args.size-1](sender)
|
||||
else mutableListOf()
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun reflectionCompleteParser(tagLine: String, paramClass: Class<*>): (CommandSender) -> List<String> =
|
||||
when(tagLine.split(":")[0]) {
|
||||
"#enum" -> { _ -> enumReflectionParse(paramClass) }
|
||||
else -> defaultCompleteParse(tagLine)
|
||||
}
|
||||
|
||||
|
||||
private fun defaultCompleteParse(tagLine: String): (CommandSender) -> List<String> =
|
||||
if(tagLine in completes) completes[tagLine]!! else
|
||||
when(tagLine.split(":")[0]) {
|
||||
"#range" -> { _ -> rangeCompleteParse(tagLine) }
|
||||
else -> { _ -> emptyList() }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun rangeCompleteParse(tagLine: String): List<String> {
|
||||
val diapason = tagLine.replace("#range:", "").split("-");
|
||||
return (diapason[0].toInt()..diapason[1].toInt()).map { it.toString() }
|
||||
}
|
||||
|
||||
private fun enumReflectionParse(enumClass: Class<*>): List<String> {
|
||||
if(enumClass.isEnum) { return enumClass.enumConstants.map { (it as Enum<*>).name }
|
||||
} else throw IllegalArgumentException("#enum complete but param ${enumClass.canonicalName} is not enum")
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package tech.carcadex.kotlinbukkitkit.commands.service
|
||||
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.OfflinePlayer
|
||||
import org.bukkit.World
|
||||
import org.bukkit.command.CommandSender
|
||||
import org.bukkit.entity.Player
|
||||
import tech.carcadex.kotlinbukkitkit.commands.exceptions.UnsupportedTypeException
|
||||
import tech.carcadex.kotlinbukkitkit.extensions.offlinePlayerOrNull
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.javaType
|
||||
|
||||
object TypeService {
|
||||
data class TypeContext(val arg: String, val sender: CommandSender, var errorMessage: String? = null)
|
||||
|
||||
private val types = mutableMapOf<KClass<*>, (TypeContext) -> Any?>()
|
||||
|
||||
//REGISTER DEFAULT TYPES
|
||||
init {
|
||||
types[CommandSender::class] = { it.arg }
|
||||
types[String::class] = { it.arg }
|
||||
types[java.lang.String::class] = { it.arg }
|
||||
types[Int::class] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
types[java.lang.Integer::class] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
types[UInt::class] = { it.errorMessage = "#not-a-number"; it.arg.toUInt() }
|
||||
types[Long::class] = { it.errorMessage = "#not-a-number"; it.arg.toLong() }
|
||||
types[java.lang.Long::class] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
types[ULong::class] = { it.errorMessage = "#not-a-number"; it.arg.toULong() }
|
||||
types[Short::class] = { it.errorMessage = "#not-a-number"; it.arg.toShort() }
|
||||
types[java.lang.Short::class] = { it.arg.toInt() }
|
||||
types[UShort::class] = { it.errorMessage = "#not-a-number"; it.arg.toUShort() }
|
||||
types[Float::class] = { it.errorMessage = "#not-a-number"; it.arg.toFloat() }
|
||||
types[java.lang.Float::class] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
types[Double::class] = { it.errorMessage = "#not-a-number"; it.arg.toDouble() }
|
||||
types[java.lang.Double::class] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
types[Byte::class] = { it.errorMessage = "#not-a-number"; it.arg.toByte() }
|
||||
types[java.lang.Byte::class] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
types[UByte::class] = { it.errorMessage = "#not-a-number"; it.arg.toUByte() }
|
||||
types[Boolean::class] = { it.errorMessage = "#not-a-number"; it.arg.toBoolean() }
|
||||
types[java.lang.Boolean::class] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
|
||||
types[Player::class] = {
|
||||
Bukkit.getPlayer(it.arg).also { o -> if(o == null) it.errorMessage = "#no-such-player" }
|
||||
}
|
||||
types[OfflinePlayer::class] = {
|
||||
offlinePlayerOrNull(it.arg).also { o -> if(o == null) it.errorMessage = "#no-such-player" }
|
||||
}
|
||||
types[World::class] = { Bukkit.getWorld(it.arg).also { o -> if(o == null) it.errorMessage = "#no-such-world" } }
|
||||
|
||||
types[Material::class] = { Material.matchMaterial(it.arg).also { o ->
|
||||
if(o == null) it.errorMessage = "#no-such-material" } }
|
||||
// types[CommandSender::class.qualifiedName!!] = { it }
|
||||
// types[String::class.qualifiedName!!] = { it }
|
||||
// types["java.lang.String"] = { it }
|
||||
// types[Int::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
// types["java.lang.Integer"] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
// types[UInt::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toUInt() }
|
||||
// types[Long::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toLong() }
|
||||
// types["java.lang.Long"] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
// types[ULong::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toULong() }
|
||||
// types[Short::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toShort() }
|
||||
// types["java.lang.Short"] = { it.arg.toInt() }
|
||||
// types[UShort::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toUShort() }
|
||||
// types[Float::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toFloat() }
|
||||
// types["java.lang.Float"] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
// types[Double::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toDouble() }
|
||||
// types["java.lang.Double"] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
// types[Byte::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toByte() }
|
||||
// types["java.lang.Byte"] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
// types[UByte::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toUByte() }
|
||||
// types[Boolean::class.qualifiedName!!] = { it.errorMessage = "#not-a-number"; it.arg.toBoolean() }
|
||||
// types["java.lang.Boolean"] = { it.errorMessage = "#not-a-number"; it.arg.toInt() }
|
||||
//
|
||||
// types[Player::class.qualifiedName!!] = {
|
||||
// Bukkit.getPlayer(it.arg).also { o -> if(o == null) it.errorMessage = "#no-such-player" }
|
||||
// }
|
||||
// types[OfflinePlayer::class.qualifiedName!!] = {
|
||||
// offlinePlayerOrNull(it.arg).also { o -> if(o == null) it.errorMessage = "#no-such-player" }
|
||||
// }
|
||||
// types[World::class.qualifiedName!!] = { Bukkit.getWorld(it.arg).also { o -> if(o == null) it.errorMessage = "#no-such-world" } }
|
||||
//
|
||||
// types[Material::class.qualifiedName!!] = { Material.matchMaterial(it.arg).also { o ->
|
||||
// if(o == null) it.errorMessage = "#no-such-material" } }
|
||||
}
|
||||
|
||||
fun registered(clazz: KClass<*>): Boolean = clazz in types
|
||||
fun registered(clazz: Class<*>): Boolean = registered(clazz.kotlin)
|
||||
fun registered(name: String): Boolean = registered(Class.forName(name))
|
||||
fun registered(type: Type): Boolean = registered(type as Class<*>)
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun registered(type: KType): Boolean = registered(type.javaType)
|
||||
|
||||
fun register(clazz: KClass<*>, func: (TypeContext) -> Any) {
|
||||
types[clazz] = func
|
||||
}
|
||||
|
||||
fun byClass(clazz: KClass<*>): (TypeContext) -> Any? = types[clazz] ?: throw UnsupportedTypeException(
|
||||
"Type ${clazz.qualifiedName} is not supported!")
|
||||
fun byClass(clazz: Class<*>): (TypeContext) -> Any? = types[clazz.kotlin] ?: throw UnsupportedTypeException(
|
||||
"Type ${clazz.canonicalName} is not supported!")
|
||||
fun byClassName(name: String) = byClass(Class.forName(name))
|
||||
fun byType(type: Type): (TypeContext) -> Any? = byClass(type as Class<*>)
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun byType(type: KType): (TypeContext) -> Any? = byType(type.javaType)
|
||||
}
|
||||
Reference in new issue
Block a user