Continued implementing support for annotations, finished permission check

This commit is contained in:
Norbi Peti 2023-03-14 17:32:17 +01:00
parent af7d097f9b
commit 4e617fdbc9
5 changed files with 50 additions and 59 deletions

View file

@ -129,9 +129,9 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
var mainCommandNode: LiteralCommandNode<TP>? = null var mainCommandNode: LiteralCommandNode<TP>? = null
for (meth in command.javaClass.methods) { for (meth in command.javaClass.methods) {
val ann = meth.getAnnotation(Subcommand::class.java) ?: continue val ann = meth.getAnnotation(Subcommand::class.java) ?: continue
val methodPath = CommandUtils.getCommandPath(meth.name, ' ') val fullPath = command.commandPath + CommandUtils.getCommandPath(meth.name, ' ')
val (lastNode, mainNode, remainingPath) = registerNodeFromPath(command.commandPath + methodPath) val (lastNode, mainNode, remainingPath) = registerNodeFromPath(fullPath)
lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command))) lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command), fullPath))
if (mainCommandNode == null) mainCommandNode = mainNode if (mainCommandNode == null) mainCommandNode = mainNode
else if (mainNode!!.name != mainCommandNode.name) { 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) MainPlugin.Instance.logger.warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.javaClass.simpleName)
@ -148,19 +148,27 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* *
* @param method The subcommand method * @param method The subcommand method
* @param command The command object * @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 * @return The executable node
*/ */
private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, path: String, argHelpManager: CommandArgumentHelpManager<TC, TP>): LiteralCommandNode<TP> { private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, remainingPath: String,
argHelpManager: CommandArgumentHelpManager<TC, TP>, fullPath: String): LiteralCommandNode<TP> {
val (params, _) = getCommandParametersAndSender(method, argHelpManager) // Param order is important val (params, _) = getCommandParametersAndSender(method, argHelpManager) // Param order is important
val paramMap = HashMap<String, CommandArgument>() val paramMap = HashMap<String, CommandArgument>()
for (param in params) { for (param in params) {
paramMap[param.name] = param paramMap[param.name] = param
} }
val helpText = command.getHelpText(method, ann) 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 { helpText }, // TODO: Help text getter support
{ sender: TP -> hasPermission(sender, command, method) }) { sender: TP, data: SubcommandData<TC, TP> -> hasPermission(sender, data) },
method.annotations.filterNot { it is Subcommand }.toTypedArray(),
fullPath
)
.executes { context: CommandContext<TP> -> executeCommand(context) } .executes { context: CommandContext<TP> -> executeCommand(context) }
var parent: ArgumentBuilder<TP, *> = node var parent: ArgumentBuilder<TP, *> = node
for (param in params) { // Register parameters in the right order for (param in params) { // Register parameters in the right order
@ -225,7 +233,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
numAnn.upperLimit numAnn.upperLimit
), ),
param.isAnnotationPresent(OptionalArg::class.java), param.isAnnotationPresent(OptionalArg::class.java),
name) name, param.annotations.filterNot { it is OptionalArg || it is NumberArg || it is TextArg }.toTypedArray())
}, parameters[0].type) }, parameters[0].type)
} }
@ -338,7 +346,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
invokeCommand.run();*/return 0 invokeCommand.run();*/return 0
} }
abstract fun hasPermission(context: CommandContext<TP>): Boolean abstract fun hasPermission(sender: TP, data: SubcommandData<TC, TP>): Boolean
val commandsText: Array<String> get() = commandHelp.toTypedArray() val commandsText: Array<String> get() = commandHelp.toTypedArray()
/** /**

View file

@ -5,7 +5,6 @@ import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ButtonPlugin import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.architecture.Component import buttondevteam.lib.architecture.Component
import buttondevteam.lib.chat.commands.CommandUtils import buttondevteam.lib.chat.commands.CommandUtils
import buttondevteam.lib.chat.commands.CommandUtils.subcommandPath
import buttondevteam.lib.chat.commands.MCCommandSettings import buttondevteam.lib.chat.commands.MCCommandSettings
import buttondevteam.lib.chat.commands.SubcommandData import buttondevteam.lib.chat.commands.SubcommandData
import buttondevteam.lib.player.ChromaGamerBase import buttondevteam.lib.player.ChromaGamerBase
@ -83,29 +82,26 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
} }
} }
override fun hasPermission(context: CommandContext<Command2MCSender>): Boolean { override fun hasPermission(sender: Command2MCSender, data: SubcommandData<ICommand2MC, Command2MCSender>): Boolean {
return hasPermission(context.source.sender, context.subcommandPath) 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 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 // TODO: Register a permission for the main command as well - the previous implementation relied on the way the commands were defined
val perms = arrayOf( val perms = arrayOf(
cmdperm + path, cmdperm,
if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null permGroup(data).let { if (it.isEmpty()) null else "chroma.$it" }
) )
for (perm in perms) { for (perm in perms) {
if (perm != null) { if (perm != null) {
if (p) { //Use OfflinePlayer to avoid fetching player data if (p) { //Use OfflinePlayer to avoid fetching player data
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas( p = if (mcsender is OfflinePlayer) MainPlugin.permission.playerHas(
if (sender is Player) sender.location.world.name else null, if (mcsender is Player) mcsender.location.world?.name else null,
sender as OfflinePlayer, mcsender as OfflinePlayer,
perm perm
) else false //Use sender's method ) 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 } else break //If any of the permissions aren't granted then don't allow
} }
} }
@ -125,28 +121,6 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
return group ?: "" 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 <T> The annotation type
* @param <V> The type of the value
* @return The value returned by the first superclass or def
</V></T> */
private fun <T : Annotation?, V> getAnnForValue(sourceCl: Class<*>, annCl: Class<T>, annMethod: Function<T, V>, 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. * Automatically colors the message red.
* {@see super#addParamConverter} * {@see super#addParamConverter}
@ -177,7 +151,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
} }
fun unregisterCommands(component: Component<*>) { fun unregisterCommands(component: Component<*>) {
unregisterCommandIf({ node: CoreCommandNode<Command2MCSender?, ICommand2MC?> -> unregisterCommandIf({ node ->
Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin } Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin }
.map { comp: ButtonPlugin -> component.javaClass.simpleName == comp.javaClass.simpleName }.orElse(false) .map { comp: ButtonPlugin -> component.javaClass.simpleName == comp.javaClass.simpleName }.orElse(false)
}, true) }, true)
@ -190,10 +164,9 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
private fun handleCommand(sender: Command2MCSender, commandline: String, checkPlugin: Boolean): Boolean { private fun handleCommand(sender: Command2MCSender, commandline: String, checkPlugin: Boolean): Boolean {
val i = commandline.indexOf(' ') val i = commandline.indexOf(' ')
val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash
var pcmd: PluginCommand //Our commands aren't PluginCommands, unless it's specified in the plugin.yml
return if ((!checkPlugin return if ((!checkPlugin || (MainPlugin.Instance.prioritizeCustomCommands.get() == true))
|| MainPlugin.Instance.prioritizeCustomCommands.get()) || Bukkit.getPluginCommand(mainpath).also { pcmd = it } == null //Our commands aren't PluginCommands || Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != false)
|| pcmd.plugin is ButtonPlugin) //Unless it's specified in the plugin.yml
super.handleCommand(sender, commandline) else false super.handleCommand(sender, commandline) else false
} }

View file

@ -40,6 +40,9 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
* @param argumentsInOrder A list of the command arguments in the order they are expected * @param argumentsInOrder A list of the command arguments in the order they are expected
* @param command The command object that has this subcommand * @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 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 <S : Command2Sender, TC : ICommand2<*>> literal( fun <S : Command2Sender, TC : ICommand2<*>> literal(
name: String, name: String,
@ -48,11 +51,13 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
argumentsInOrder: List<CommandArgument>, argumentsInOrder: List<CommandArgument>,
command: TC, command: TC,
helpTextGetter: (Any) -> Array<String>, helpTextGetter: (Any) -> Array<String>,
hasPermission: (S) -> Boolean hasPermission: (S, SubcommandData<TC, S>) -> Boolean,
annotations: Array<Annotation>,
fullPath: String
): CoreCommandBuilder<S, TC, SubcommandData<TC, S>> { ): CoreCommandBuilder<S, TC, SubcommandData<TC, S>> {
return CoreCommandBuilder( return CoreCommandBuilder(
name, name,
SubcommandData(senderType, arguments, argumentsInOrder, command, helpTextGetter, hasPermission) SubcommandData(senderType, arguments, argumentsInOrder, command, helpTextGetter, hasPermission, annotations, fullPath)
) )
} }

View file

@ -9,5 +9,6 @@ class CommandArgument(
val greedy: Boolean, val greedy: Boolean,
val limits: Pair<Double, Double>, val limits: Pair<Double, Double>,
val optional: Boolean, val optional: Boolean,
val description: String val description: String,
val annotations: Array<Annotation> // TODO: Annotations for parameters as well
) )

View file

@ -41,11 +41,15 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
/** /**
* A function that determines whether the user has permission to run this subcommand. * A function that determines whether the user has permission to run this subcommand.
*/ */
private val permissionCheck: (TP) -> Boolean, private val permissionCheck: (TP, SubcommandData<TC, TP>) -> Boolean,
/** /**
* All annotations implemented by the method that executes the command. Can be used to add custom metadata when implementing a platform. * All annotations implemented by the method that executes the command. Can be used to add custom metadata when implementing a platform.
*/ */
val annotations: Array<Annotation> val annotations: Array<Annotation>,
/**
* The full command path of this subcommand.
*/
val fullPath: String
) : NoOpSubcommandData(helpTextGetter) { ) : NoOpSubcommandData(helpTextGetter) {
/** /**
@ -55,6 +59,6 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
* @return Whether the user has permission * @return Whether the user has permission
*/ */
fun hasPermission(sender: TP): Boolean { fun hasPermission(sender: TP): Boolean {
return permissionCheck(sender) return permissionCheck(sender, this)
} }
} }