From 7ba0703146e251f79acf4e26fd8fb2f0429df828 Mon Sep 17 00:00:00 2001 From: Esmaybe Date: Sat, 2 Aug 2025 21:21:38 +0200 Subject: [PATCH] Fix issues --- .../minestom/AnnotationCommand.java | 407 ++++-------------- 1 file changed, 87 insertions(+), 320 deletions(-) diff --git a/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java b/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java index 621cc87..df4825f 100644 --- a/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java +++ b/minestom/src/main/java/com/jazzkuh/commandlib/minestom/AnnotationCommand.java @@ -1,23 +1,9 @@ package com.jazzkuh.commandlib.minestom; -import com.jazzkuh.commandlib.common.AnnotationCommandImpl; -import com.jazzkuh.commandlib.common.AnnotationCommandParser; -import com.jazzkuh.commandlib.common.AnnotationCommandSender; -import com.jazzkuh.commandlib.common.AnnotationSubCommand; -import com.jazzkuh.commandlib.common.annotations.Completion; -import com.jazzkuh.commandlib.common.annotations.Greedy; +import com.jazzkuh.commandlib.common.*; import com.jazzkuh.commandlib.common.annotations.Main; -import com.jazzkuh.commandlib.common.annotations.Optional; import com.jazzkuh.commandlib.common.annotations.Subcommand; -import com.jazzkuh.commandlib.common.exception.ArgumentException; -import com.jazzkuh.commandlib.common.exception.CommandException; -import com.jazzkuh.commandlib.common.exception.ContextResolverException; -import com.jazzkuh.commandlib.common.exception.ErrorException; -import com.jazzkuh.commandlib.common.exception.ParameterException; -import com.jazzkuh.commandlib.common.exception.PermissionException; -import com.jazzkuh.commandlib.common.resolvers.CompletionResolver; -import com.jazzkuh.commandlib.common.resolvers.ContextResolver; -import com.jazzkuh.commandlib.common.resolvers.Resolvers; +import com.jazzkuh.commandlib.common.exception.*; import com.jazzkuh.commandlib.minestom.terminal.LoggingConsoleSender; import com.jazzkuh.commandlib.minestom.utils.StringUtils; import com.jazzkuh.commandlib.minestom.utils.permission.Permissable; @@ -30,44 +16,24 @@ import net.minestom.server.command.ConsoleSender; import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; -import net.minestom.server.command.builder.arguments.Argument; -import net.minestom.server.command.builder.arguments.ArgumentBoolean; -import net.minestom.server.command.builder.arguments.ArgumentString; import net.minestom.server.command.builder.arguments.ArgumentStringArray; -import net.minestom.server.command.builder.arguments.ArgumentWord; -import net.minestom.server.command.builder.arguments.number.ArgumentDouble; -import net.minestom.server.command.builder.arguments.number.ArgumentFloat; -import net.minestom.server.command.builder.arguments.number.ArgumentInteger; -import net.minestom.server.command.builder.arguments.number.ArgumentLong; import net.minestom.server.command.builder.suggestion.SuggestionEntry; import net.minestom.server.entity.Player; import org.codehaus.plexus.util.ReflectionUtils; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.*; public class AnnotationCommand extends Command implements AnnotationCommandImpl { - private static final ComponentLogger LOGGER = ComponentLogger.logger("CommandLibrary"); protected String commandName; protected final List mainCommands = new ArrayList<>(); protected final List subCommands = new ArrayList<>(); - protected final Map>> commandArguments = new HashMap<>(); public AnnotationCommand(String commandName) { super(commandName); - this.commandName = commandName; this.init(); } @@ -91,7 +57,7 @@ private void init() { List mainCommandMethods = Arrays.stream(this.getClass().getMethods()) .filter(method -> method.isAnnotationPresent(Main.class)) .toList(); - + mainCommandMethods.forEach(method -> this.mainCommands.add(AnnotationCommandParser.parse(this, method))); List allAliases = new ArrayList<>(); @@ -116,22 +82,28 @@ private void init() { .toList(); subcommandMethods.forEach(method -> this.subCommands.add(AnnotationCommandParser.parse(this, method))); - // We register subcommands first to make sure they don't get overridden by main commands - for (AnnotationSubCommand subCommand : this.subCommands) { - this.createBrigadierSyntax(subCommand); - } - - for (AnnotationSubCommand mainCommand : this.mainCommands) { - this.createBrigadierSyntax(mainCommand); - } + ArgumentStringArray params = new ArgumentStringArray("params"); + params.setDefaultValue(new String[0]); + params.setSuggestionCallback((sender, context, suggestionCallback) -> { + String[] args = this.fixArguments(context.get(params)); - this.setDefaultExecutor(this::executeDefault); + List suggestions = this.suggest(sender, args); + if (suggestions.isEmpty()) return; + for (String suggestion : suggestions) { + SuggestionEntry suggestionEntry = new SuggestionEntry(suggestion); + if (suggestionCallback.getEntries().contains(suggestionEntry)) continue; + suggestionCallback.addEntry(suggestionEntry); + } + }); + setDefaultExecutor(this::execute); + addSyntax(this::execute, params); + boolean allMainCommandsHavePermissions = !this.mainCommands.isEmpty() && this.mainCommands.stream().allMatch(cmd -> cmd.getPermission() != null); if (allMainCommandsHavePermissions) { - this.setCondition((commandSender, s) -> { + setCondition((commandSender, s) -> { Permissable permissable = new Permissable(null); if (commandSender instanceof Player player) { permissable = new Permissable(player.getUuid()); @@ -143,219 +115,58 @@ private void init() { } } - private void createBrigadierSyntax(AnnotationSubCommand command) { - Method method = command.getMethod(); - Parameter[] parameters = method.getParameters(); - List> arguments = new ArrayList<>(); - - List usageParamNames = this.parseUsageParameterNames(command); - - for (int i = 1; i < parameters.length; i++) { - Parameter parameter = parameters[i]; - String paramName = usageParamNames.size() >= i ? usageParamNames.get(i - 1) : parameter.getName(); - - Argument argument = this.createArgumentForParameter(parameter, paramName); - arguments.add(argument); - } - - this.commandArguments.put(command, arguments); - - if (method.isAnnotationPresent(Subcommand.class)) { - ArgumentWord subcommandArg = new ArgumentWord(command.getName() + "_sub"); - subcommandArg.from(command.getName()); - for (String alias : command.getAliases()) { - subcommandArg.from(alias); - } - - if (arguments.isEmpty()) { - this.addSyntax((sender, context) -> executeSubcommand(command, sender, context), subcommandArg); - return; - } - - this.createOptionalSyntaxCombinations(command, subcommandArg, arguments); - return; - } - - if (arguments.isEmpty()) { - this.addSyntax((sender, context) -> executeMainCommand(command, sender, context)); - return; - } - - this.createOptionalSyntaxCombinations(command, null, arguments); - } - - private void createOptionalSyntaxCombinations(AnnotationSubCommand command, ArgumentWord subcommandArg, List> arguments) { - Parameter[] parameters = command.getMethod().getParameters(); - - List> requiredArgs = new ArrayList<>(); - List> optionalArgs = new ArrayList<>(); - - for (int i = 0; i < arguments.size(); i++) { - Parameter param = parameters[i + 1]; - if (param.isAnnotationPresent(Optional.class)) optionalArgs.add(arguments.get(i)); - else requiredArgs.add(arguments.get(i)); - } - - int totalOptional = optionalArgs.size(); - for (int optionalCount = 0; optionalCount <= totalOptional; optionalCount++) { - List> syntaxArgs = new ArrayList<>(); - if (subcommandArg != null) - syntaxArgs.add(subcommandArg); - - syntaxArgs.addAll(requiredArgs); - syntaxArgs.addAll(optionalArgs.subList(0, optionalCount)); - - Argument[] argsArray = syntaxArgs.toArray(new Argument[0]); - addSyntax((sender, context) -> { - if (subcommandArg != null) { - this.executeSubcommand(command, sender, context); - return; - } - - this.executeMainCommand(command, sender, context); - }, argsArray); - } - } - - private Argument createArgumentForParameter(Parameter parameter, String name) { - Class type = parameter.getType(); - - if (parameter.isAnnotationPresent(Greedy.class) && type == String.class) { - ArgumentStringArray greedyArg = new ArgumentStringArray(name); - this.setupCompletionForArgument(greedyArg, parameter); - return greedyArg; - } - - if (type == String.class) { - ArgumentString stringArg = new ArgumentString(name); - this.setupCompletionForArgument(stringArg, parameter); - return stringArg; - } else if (type == Integer.class || type == int.class) { - return new ArgumentInteger(name); - } else if (type == Double.class || type == double.class) { - return new ArgumentDouble(name); - } else if (type == Float.class || type == float.class) { - return new ArgumentFloat(name); - } else if (type == Long.class || type == long.class) { - return new ArgumentLong(name); - } else if (type == Boolean.class || type == boolean.class) { - return new ArgumentBoolean(name); - } else if (type == UUID.class) { - ArgumentString uuidArg = new ArgumentString(name); - this.setupCompletionForArgument(uuidArg, parameter); - return uuidArg; - } else if (type.isEnum()) { - ArgumentString enumArg = new ArgumentString(name); - this.setupEnumCompletion(enumArg, (Class) type); - return enumArg; - } else { - ArgumentString customArg = new ArgumentString(name); - this.setupCompletionForArgument(customArg, parameter); - return customArg; + private String[] fixArguments(String[] args) { + if (args.length > 0 && args[args.length - 1].equals("\u0000")) { + args = Arrays.copyOf(args, args.length); + args[args.length - 1] = ""; } - } - - private > void setupCompletionForArgument(T argument, Parameter parameter) { - argument.setSuggestionCallback((sender, context, suggestionCallback) -> { - String currentInput = ""; - - if (argument instanceof ArgumentString) { - currentInput = context.getOrDefault((ArgumentString) argument, ""); - } else if (argument instanceof ArgumentStringArray) { - String[] array = context.getOrDefault((ArgumentStringArray) argument, new String[0]); - currentInput = array.length > 0 ? array[array.length - 1] : ""; - } - List suggestions = getParameterCompletions(sender, parameter, currentInput); - for (String suggestion : suggestions) { - SuggestionEntry entry = new SuggestionEntry(suggestion); - if (suggestionCallback.getEntries().contains(entry)) - continue; - - suggestionCallback.addEntry(entry); - } - }); + return args; } - @SuppressWarnings("unchecked") - private void setupEnumCompletion(ArgumentString argument, Class enumClass) { - argument.setSuggestionCallback((sender, context, suggestionCallback) -> { - String currentInput = context.getOrDefault(argument, ""); - List enumValues = EnumSet.allOf(enumClass).stream() - .map(e -> e.toString().toLowerCase()) - .toList(); - - List matches = currentInput.isEmpty() ? enumValues : StringUtils.copyPartialMatches(currentInput, enumValues, new ArrayList<>()); - for (String match : matches) { - suggestionCallback.addEntry(new SuggestionEntry(match)); - } - }); + @Override + public String getCommandName() { + return this.commandName; } - private List getParameterCompletions(CommandSender sender, Parameter parameter, String currentInput) { - AnnotationCommandSender commandSender = new AnnotationCommandSender<>(sender); + public void execute(CommandSender sender, CommandContext context) { + String[] args = Arrays.stream(context.getInput().split(" ")).skip(1).toArray(String[]::new); - if (parameter.isAnnotationPresent(Completion.class)) { - Completion completion = parameter.getAnnotation(Completion.class); - CompletionResolver resolver = Resolvers.completion(completion.value()); - if (resolver != null) { - List allCompletions = resolver.resolve(commandSender, currentInput); - - if (currentInput.isEmpty()) { - return allCompletions; - } - - return StringUtils.copyPartialMatches(currentInput, allCompletions, new ArrayList<>()); + if (args.length < 1) { + if (this.mainCommands.isEmpty()) { + this.formatUsage(sender); + return; } - } - CompletionResolver completionResolver = Resolvers.completion(parameter.getType()); - if (completionResolver != null) { - List allCompletions = completionResolver.resolve(commandSender, currentInput); - - if (currentInput.isEmpty()) { - return allCompletions; + if (this.mainCommands.size() == 1) { + this.executeCommand(this.mainCommands.get(0), sender, args); + } else { + this.formatUsage(sender); } - - return StringUtils.copyPartialMatches(currentInput, allCompletions, new ArrayList<>()); + return; } - return new ArrayList<>(); - } - - private void executeDefault(CommandSender sender, CommandContext context) { - if (this.mainCommands.isEmpty()) { - this.formatUsage(sender); + for (AnnotationSubCommand subCommand : subCommands) { + if (!args[0].equalsIgnoreCase(subCommand.getName()) && !subCommand.getAliases().contains(args[0].toLowerCase())) + continue; + this.executeCommand(subCommand, sender, args); return; } - if (this.mainCommands.size() == 1) { - AnnotationSubCommand singleMain = this.mainCommands.get(0); - List> args = this.commandArguments.get(singleMain); - if (args.isEmpty()) { - this.executeMainCommand(singleMain, sender, context); - return; - } + for (AnnotationSubCommand mainCommand : mainCommands) { + this.executeCommand(mainCommand, sender, args); + return; } this.formatUsage(sender); } - private void executeMainCommand(AnnotationSubCommand command, CommandSender sender, CommandContext context) { - this.executeCommandWithContext(command, sender, context); - } - - private void executeSubcommand(AnnotationSubCommand command, CommandSender sender, CommandContext context) { - this.executeCommandWithContext(command, sender, context); - } - - private void executeCommandWithContext(AnnotationSubCommand subCommand, CommandSender sender, CommandContext context) { + private void executeCommand(AnnotationSubCommand subCommand, CommandSender sender, String[] args) { Permissable permissable = new Permissable(null); - if (sender instanceof ConsoleSender) - sender = new LoggingConsoleSender(); + if (sender instanceof ConsoleSender) sender = new LoggingConsoleSender(); if (sender instanceof Player player) { permissable = new Permissable(player.getUuid()); - LOGGER.info("Command executed by {}: {} {}", player.getUsername(), this.getCommandName(), context.getInput()); + LOGGER.info("Command executed by {}: {} {}", player.getUsername(), this.getCommandName(), String.join(" ", args)); } if (subCommand.getPermission() != null && !(sender instanceof ConsoleSender) && !permissable.hasPermission(subCommand.getPermission())) { @@ -364,8 +175,11 @@ private void executeCommandWithContext(AnnotationSubCommand subCommand, CommandS return; } + AnnotationCommandExecutor commandExecutor = new AnnotationCommandExecutor<>(subCommand, this); + AnnotationCommandSender commandSender = new AnnotationCommandSender<>(sender); + try { - this.executeMethodWithBrigadierContext(subCommand, sender, context); + commandExecutor.execute(commandSender, args); } catch (CommandException commandException) { if (commandException instanceof ArgumentException) { this.formatUsage(sender); @@ -381,102 +195,55 @@ private void executeCommandWithContext(AnnotationSubCommand subCommand, CommandS } } - private void executeMethodWithBrigadierContext(AnnotationSubCommand subCommand, CommandSender sender, CommandContext context) throws CommandException { - Method method = subCommand.getMethod(); - Parameter[] parameters = method.getParameters(); - List> arguments = this.commandArguments.get(subCommand); - - Object[] resolvedParameters = new Object[parameters.length]; - resolvedParameters[0] = sender; - - for (int i = 1; i < parameters.length; i++) { - resolvedParameters[i] = this.resolveParameter(parameters[i], i - 1, arguments, context); + public List suggest(CommandSender sender, String[] args) { + AnnotationCommandSender commandSender = new AnnotationCommandSender<>(sender); + Permissable permissable = new Permissable(null); + if (sender instanceof Player player) { + permissable = new Permissable(player.getUuid()); } - try { - method.setAccessible(true); - method.invoke(this, resolvedParameters); - } catch (Exception e) { - e.printStackTrace(); - throw new ErrorException(e.getMessage()); - } - } + List options = new ArrayList<>(); - private Object resolveParameter(Parameter parameter, int argIndex, List> arguments, CommandContext context) throws CommandException { - if (argIndex >= arguments.size()) { - return handleMissingArgument(parameter); + for (AnnotationSubCommand mainCommand : mainCommands) { + if (mainCommand.getPermission() == null || permissable.hasPermission(mainCommand.getPermission())) { + AnnotationCommandExecutor mainCommandExecutor = new AnnotationCommandExecutor<>(mainCommand, this); + options.addAll(mainCommandExecutor.complete(commandSender, args)); + } } - Argument argument = arguments.get(argIndex); - Object value = context.get(argument); - - if (value != null) { - return this.resolveParameterValue(parameter, value, parameter.getType()); - } + if (args.length == 1 && !this.subCommands.isEmpty()) { + for (AnnotationSubCommand subCommand : this.subCommands) { + if (subCommand.getPermission() == null) { + options.add(subCommand.getName()); + continue; + } - return this.handleMissingArgument(parameter); - } + if (permissable.hasPermission(subCommand.getPermission())) + options.add(subCommand.getName()); + } - private Object handleMissingArgument(Parameter parameter) throws ArgumentException { - if (parameter.isAnnotationPresent(Optional.class)) { - return null; + return StringUtils.copyPartialMatches(args[0], options, new ArrayList<>(options.size())); } - throw new ArgumentException(); - } - - private Object resolveParameterValue(Parameter parameter, Object value, Class paramClass) throws CommandException { - if (parameter.isAnnotationPresent(Greedy.class) && paramClass == String.class && value instanceof String[]) - return String.join(" ", (String[]) value); - - if (paramClass.isInstance(value)) - return value; - - if (value instanceof String stringValue) { - if (paramClass.isEnum()) { - try { - return Enum.valueOf((Class) paramClass, stringValue.toUpperCase()); - } catch (Exception e) { - throw new ParameterException("Cannot resolve parameter " + stringValue + " for type " + paramClass.getSimpleName()); - } - } - - ContextResolver contextResolver = Resolvers.context(paramClass); - if (contextResolver != null) { - Object resolved = contextResolver.resolve(stringValue); - if (resolved == null) { - throw new ParameterException("Cannot resolve parameter " + stringValue + " for type " + paramClass.getSimpleName()); + for (AnnotationSubCommand subCommand : this.subCommands) { + if (args.length < 1) { + if (subCommand.getPermission() == null) { + options.add(subCommand.getName()); + continue; } - return resolved; + if (permissable.hasPermission(subCommand.getPermission())) + options.add(subCommand.getName()); + continue; } - } - - return value; - } - - private List parseUsageParameterNames(AnnotationSubCommand command) { - List paramNames = new ArrayList<>(); - - if (command.usage() == null || command.usage().isEmpty()) - return paramNames; - String usage = command.usage().trim(); - - Pattern pattern = java.util.regex.Pattern.compile("[<\\[]([^>\\]]+)[>\\]]"); - Matcher matcher = pattern.matcher(usage); - - while (matcher.find()) { - String paramName = matcher.group(1).trim(); - paramNames.add(paramName); + if (!args[0].equalsIgnoreCase(subCommand.getName()) && !subCommand.getAliases().contains(args[0].toLowerCase())) + continue; + AnnotationCommandExecutor subCommandExecutor = new AnnotationCommandExecutor<>(subCommand, this); + options.addAll(subCommandExecutor.complete(commandSender, args)); } - return paramNames; - } - - @Override - public String getCommandName() { - return this.commandName; + return options; } public void register(CommandManager commandManager) {