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
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<TC : ICommand2<TP>, 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<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 paramMap = HashMap<String, CommandArgument>()
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<TC, TP> -> hasPermission(sender, data) },
method.annotations.filterNot { it is Subcommand }.toTypedArray(),
fullPath
)
.executes { context: CommandContext<TP> -> executeCommand(context) }
var parent: ArgumentBuilder<TP, *> = node
for (param in params) { // Register parameters in the right order
@ -224,9 +232,9 @@ abstract class Command2<TC : ICommand2<TP>, 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<TC : ICommand2<TP>, TP : Command2Sender>(
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()
/**

View file

@ -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<ICommand2MC, Command2MCSender>('/', true), Listener
}
}
override fun hasPermission(context: CommandContext<Command2MCSender>): Boolean {
return hasPermission(context.source.sender, context.subcommandPath)
}
override fun hasPermission(sender: Command2MCSender, data: SubcommandData<ICommand2MC, Command2MCSender>): 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<ICommand2MC, Command2MCSender>('/', 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 <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.
* {@see super#addParamConverter}
@ -177,7 +151,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
}
fun unregisterCommands(component: Component<*>) {
unregisterCommandIf({ node: CoreCommandNode<Command2MCSender?, ICommand2MC?> ->
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<ICommand2MC, Command2MCSender>('/', 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
}

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 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 <S : Command2Sender, TC : ICommand2<*>> literal(
name: String,
@ -48,11 +51,13 @@ class CoreCommandBuilder<S : Command2Sender, TC : ICommand2<*>, TSD : NoOpSubcom
argumentsInOrder: List<CommandArgument>,
command: TC,
helpTextGetter: (Any) -> Array<String>,
hasPermission: (S) -> Boolean
hasPermission: (S, SubcommandData<TC, S>) -> Boolean,
annotations: Array<Annotation>,
fullPath: String
): CoreCommandBuilder<S, TC, SubcommandData<TC, S>> {
return CoreCommandBuilder(
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 limits: Pair<Double, Double>,
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.
*/
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.
*/
val annotations: Array<Annotation>
val annotations: Array<Annotation>,
/**
* The full command path of this subcommand.
*/
val fullPath: String
) : NoOpSubcommandData(helpTextGetter) {
/**
@ -55,6 +59,6 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
* @return Whether the user has permission
*/
fun hasPermission(sender: TP): Boolean {
return permissionCheck(sender)
return permissionCheck(sender, this)
}
}