From 4e617fdbc9c0ce32ac31491353f0a3ab28998d09 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 14 Mar 2023 17:32:17 +0100 Subject: [PATCH] Continued implementing support for annotations, finished permission check --- .../java/buttondevteam/lib/chat/Command2.kt | 32 +++++++---- .../java/buttondevteam/lib/chat/Command2MC.kt | 55 +++++-------------- .../lib/chat/CoreCommandBuilder.kt | 9 ++- .../lib/chat/commands/CommandArgument.kt | 3 +- .../lib/chat/commands/SubcommandData.kt | 10 +++- 5 files changed, 50 insertions(+), 59 deletions(-) diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt index 4d0e381..a472f3b 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt @@ -129,9 +129,9 @@ abstract class Command2, TP : Command2Sender>( var mainCommandNode: LiteralCommandNode? = null for (meth in command.javaClass.methods) { val ann = meth.getAnnotation(Subcommand::class.java) ?: continue - val methodPath = CommandUtils.getCommandPath(meth.name, ' ') - val (lastNode, mainNode, remainingPath) = registerNodeFromPath(command.commandPath + methodPath) - lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command))) + val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ') + val (lastNode, mainNode, remainingPath) = registerNodeFromPath(fullPath) + lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command), fullPath)) if (mainCommandNode == null) mainCommandNode = mainNode else if (mainNode!!.name != mainCommandNode.name) { MainPlugin.Instance.logger.warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.javaClass.simpleName) @@ -146,21 +146,29 @@ abstract class Command2, TP : Command2Sender>( /** * Returns the node that can actually execute the given subcommand. * - * @param method The subcommand method + * @param method The subcommand method * @param command The command object - * @param path The command path + * @param ann The subcommand annotation + * @param remainingPath The command path + * @param argHelpManager The object that gets the usage text from code + * @param fullPath The full command path as registered * @return The executable node */ - private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, path: String, argHelpManager: CommandArgumentHelpManager): LiteralCommandNode { + private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, remainingPath: String, + argHelpManager: CommandArgumentHelpManager, fullPath: String): LiteralCommandNode { val (params, _) = getCommandParametersAndSender(method, argHelpManager) // Param order is important val paramMap = HashMap() for (param in params) { paramMap[param.name] = param } val helpText = command.getHelpText(method, ann) - val node = CoreCommandBuilder.literal(path, params[0].type, paramMap, params, command, + val node = CoreCommandBuilder.literal( + remainingPath, params[0].type, paramMap, params, command, { helpText }, // TODO: Help text getter support - { sender: TP -> hasPermission(sender, command, method) }) + { sender: TP, data: SubcommandData -> hasPermission(sender, data) }, + method.annotations.filterNot { it is Subcommand }.toTypedArray(), + fullPath + ) .executes { context: CommandContext -> executeCommand(context) } var parent: ArgumentBuilder = node for (param in params) { // Register parameters in the right order @@ -224,9 +232,9 @@ abstract class Command2, TP : Command2Sender>( numAnn.lowerLimit, numAnn.upperLimit ), - param.isAnnotationPresent(OptionalArg::class.java), - name) - }, parameters[0].type) + param.isAnnotationPresent(OptionalArg::class.java), + name, param.annotations.filterNot { it is OptionalArg || it is NumberArg || it is TextArg }.toTypedArray()) + }, parameters[0].type) } /** @@ -338,7 +346,7 @@ abstract class Command2, TP : Command2Sender>( invokeCommand.run();*/return 0 } - abstract fun hasPermission(context: CommandContext): Boolean + abstract fun hasPermission(sender: TP, data: SubcommandData): Boolean val commandsText: Array get() = commandHelp.toTypedArray() /** diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt index 8452195..71eb0ca 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt @@ -5,7 +5,6 @@ import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.architecture.ButtonPlugin import buttondevteam.lib.architecture.Component import buttondevteam.lib.chat.commands.CommandUtils -import buttondevteam.lib.chat.commands.CommandUtils.subcommandPath import buttondevteam.lib.chat.commands.MCCommandSettings import buttondevteam.lib.chat.commands.SubcommandData import buttondevteam.lib.player.ChromaGamerBase @@ -83,29 +82,26 @@ class Command2MC : Command2('/', true), Listener } } - override fun hasPermission(context: CommandContext): Boolean { - return hasPermission(context.source.sender, context.subcommandPath) - } + override fun hasPermission(sender: Command2MCSender, data: SubcommandData): Boolean { + val mcsender = sender.sender + if (mcsender is ConsoleCommandSender) return true //Always allow the console - fun hasPermission(sender: CommandSender, path: String): Boolean { - if (sender is ConsoleCommandSender) return true //Always allow the console - var pg: String var p = true - val cmdperm = "chroma.command.$path" + val cmdperm = "chroma.command.${data.fullPath.replace(' ', '.')}" // TODO: Register a permission for the main command as well - the previous implementation relied on the way the commands were defined val perms = arrayOf( - cmdperm + path, - if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null + cmdperm, + permGroup(data).let { if (it.isEmpty()) null else "chroma.$it" } ) for (perm in perms) { if (perm != null) { if (p) { //Use OfflinePlayer to avoid fetching player data - p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas( - if (sender is Player) sender.location.world.name else null, - sender as OfflinePlayer, + p = if (mcsender is OfflinePlayer) MainPlugin.permission.playerHas( + if (mcsender is Player) mcsender.location.world?.name else null, + mcsender as OfflinePlayer, perm ) else false //Use sender's method - if (!p) p = sender.hasPermission(perm) + if (!p) p = mcsender.hasPermission(perm) } else break //If any of the permissions aren't granted then don't allow } } @@ -125,28 +121,6 @@ class Command2MC : Command2('/', true), Listener return group ?: "" } - /** - * Loops until it finds a value that is **not** the same as def - * - * @param sourceCl The class which has the annotation - * @param annCl The annotation to get - * @param annMethod The annotation method to check - * @param def The value to ignore when looking for the result - * @param The annotation type - * @param The type of the value - * @return The value returned by the first superclass or def - */ - private fun getAnnForValue(sourceCl: Class<*>, annCl: Class, annMethod: Function, def: V): V { - var cl: Class<*>? = sourceCl - while (cl != null) { - val cc = cl.getAnnotation(annCl) - var r: V - if (cc != null && annMethod.apply(cc).also { r = it } !== def) return r - cl = cl.superclass - } - return def - } - /** * Automatically colors the message red. * {@see super#addParamConverter} @@ -177,7 +151,7 @@ class Command2MC : Command2('/', true), Listener } fun unregisterCommands(component: Component<*>) { - unregisterCommandIf({ node: CoreCommandNode -> + unregisterCommandIf({ node -> Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin } .map { comp: ButtonPlugin -> component.javaClass.simpleName == comp.javaClass.simpleName }.orElse(false) }, true) @@ -190,10 +164,9 @@ class Command2MC : Command2('/', true), Listener private fun handleCommand(sender: Command2MCSender, commandline: String, checkPlugin: Boolean): Boolean { val i = commandline.indexOf(' ') val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash - var pcmd: PluginCommand - return if ((!checkPlugin - || MainPlugin.Instance.prioritizeCustomCommands.get()) || Bukkit.getPluginCommand(mainpath).also { pcmd = it } == null //Our commands aren't PluginCommands - || pcmd.plugin is ButtonPlugin) //Unless it's specified in the plugin.yml + //Our commands aren't PluginCommands, unless it's specified in the plugin.yml + return if ((!checkPlugin || (MainPlugin.Instance.prioritizeCustomCommands.get() == true)) + || Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != false) super.handleCommand(sender, commandline) else false } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt index ca36e87..a82fcf1 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/CoreCommandBuilder.kt @@ -40,6 +40,9 @@ class CoreCommandBuilder, TSD : NoOpSubcom * @param argumentsInOrder A list of the command arguments in the order they are expected * @param command The command object that has this subcommand * @param helpTextGetter Custom help text that can depend on the context. The function receives the sender as the command itself receives it. + * @param hasPermission A function that determines whether the user has permission to run this subcommand + * @param annotations All annotations implemented by the method that executes the command + * @param fullPath The full command path of this subcommand. */ fun > literal( name: String, @@ -48,11 +51,13 @@ class CoreCommandBuilder, TSD : NoOpSubcom argumentsInOrder: List, command: TC, helpTextGetter: (Any) -> Array, - hasPermission: (S) -> Boolean + hasPermission: (S, SubcommandData) -> Boolean, + annotations: Array, + fullPath: String ): CoreCommandBuilder> { return CoreCommandBuilder( name, - SubcommandData(senderType, arguments, argumentsInOrder, command, helpTextGetter, hasPermission) + SubcommandData(senderType, arguments, argumentsInOrder, command, helpTextGetter, hasPermission, annotations, fullPath) ) } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.kt index 90b6c78..bd8b7fa 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgument.kt @@ -9,5 +9,6 @@ class CommandArgument( val greedy: Boolean, val limits: Pair, val optional: Boolean, - val description: String + val description: String, + val annotations: Array // TODO: Annotations for parameters as well ) diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt index f918706..9294225 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/SubcommandData.kt @@ -41,11 +41,15 @@ class SubcommandData, TP : Command2Sender>( /** * A function that determines whether the user has permission to run this subcommand. */ - private val permissionCheck: (TP) -> Boolean, + private val permissionCheck: (TP, SubcommandData) -> Boolean, /** * All annotations implemented by the method that executes the command. Can be used to add custom metadata when implementing a platform. */ - val annotations: Array + val annotations: Array, + /** + * The full command path of this subcommand. + */ + val fullPath: String ) : NoOpSubcommandData(helpTextGetter) { /** @@ -55,6 +59,6 @@ class SubcommandData, TP : Command2Sender>( * @return Whether the user has permission */ fun hasPermission(sender: TP): Boolean { - return permissionCheck(sender) + return permissionCheck(sender, this) } } \ No newline at end of file