From dd0d36da016588616778c2177b9f6458133c5e84 Mon Sep 17 00:00:00 2001 From: Traqueur_ Date: Mon, 2 Feb 2026 19:53:29 +0100 Subject: [PATCH] feat: add processor give commands and some fixs --- README.md | 37 +++ .../AnnotationCommandProcessor.java | 242 +++++++++--------- .../AnnotationCommandProcessorTest.java | 21 +- .../traqueur/commands/api/CommandManager.java | 25 +- .../commands/api/arguments/Arguments.java | 25 ++ .../commands/api/logging/MessageHandler.java | 14 + .../traqueur/commands/api/models/Command.java | 8 +- .../commands/api/models/CommandBuilder.java | 101 ++++++++ .../commands/api/models/CommandInvoker.java | 6 +- .../api/models/collections/CommandTree.java | 27 +- .../traqueur/commands/api/utils/Patterns.java | 19 ++ .../impl/arguments/DoubleArgument.java | 34 --- .../impl/arguments/IntegerArgument.java | 34 --- .../commands/impl/arguments/LongArgument.java | 34 --- .../impl/arguments/NumberArgument.java | 43 ++++ .../impl/logging/InternalMessageHandler.java | 16 ++ .../impl/arguments/DoubleArgumentTest.java | 29 --- .../impl/arguments/IntegerArgumentTest.java | 32 --- .../impl/arguments/LongArgumentTest.java | 32 --- .../impl/arguments/NumberArgumentTest.java | 90 +++++++ gradle.properties | 2 +- .../fr/traqueur/commands/jda/JDAExecutor.java | 5 +- .../fr/traqueur/commands/jda/JDAPlatform.java | 5 +- .../commands/spigot/SpigotPlatform.java | 5 +- .../commands/velocity/VelocityPlatform.java | 5 +- 25 files changed, 528 insertions(+), 363 deletions(-) create mode 100644 core/src/main/java/fr/traqueur/commands/api/utils/Patterns.java delete mode 100644 core/src/main/java/fr/traqueur/commands/impl/arguments/DoubleArgument.java delete mode 100644 core/src/main/java/fr/traqueur/commands/impl/arguments/IntegerArgument.java delete mode 100644 core/src/main/java/fr/traqueur/commands/impl/arguments/LongArgument.java create mode 100644 core/src/main/java/fr/traqueur/commands/impl/arguments/NumberArgument.java delete mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/DoubleArgumentTest.java delete mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/IntegerArgumentTest.java delete mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/LongArgumentTest.java create mode 100644 core/src/test/java/fr/traqueur/commands/impl/arguments/NumberArgumentTest.java diff --git a/README.md b/README.md index b7d579c..6cfd33a 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,43 @@ dependencies { --- +## 🏷️ Annotations Addon + +The `annotations-addon` module provides annotation-based command registration. When using `@Arg` implicitly (without annotation), parameter names are used as argument names. This requires the `-parameters` compiler flag. + +### Maven + +```xml + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + true + + +``` + +### Gradle (Groovy) + +```groovy +tasks.withType(JavaCompile).configureEach { + options.compilerArgs.add('-parameters') +} +``` + +### Gradle (Kotlin DSL) + +```kotlin +tasks.withType().configureEach { + options.compilerArgs.add("-parameters") +} +``` + +> 💡 Without this flag, parameter names default to `arg0`, `arg1`, etc. You can always use `@Arg("name")` explicitly to avoid this requirement. + +--- + ## 💡 Example (Spigot) Be sure to extends all the classes from the platform you are using (Spigot, Velocity, etc.): diff --git a/annotations-addon/src/main/java/fr/traqueur/commands/annotations/AnnotationCommandProcessor.java b/annotations-addon/src/main/java/fr/traqueur/commands/annotations/AnnotationCommandProcessor.java index 7bc2e83..a2185ce 100644 --- a/annotations-addon/src/main/java/fr/traqueur/commands/annotations/AnnotationCommandProcessor.java +++ b/annotations-addon/src/main/java/fr/traqueur/commands/annotations/AnnotationCommandProcessor.java @@ -6,6 +6,7 @@ import fr.traqueur.commands.api.models.Command; import fr.traqueur.commands.api.models.CommandBuilder; import fr.traqueur.commands.api.resolver.SenderResolver; +import fr.traqueur.commands.api.utils.Patterns; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -31,30 +32,47 @@ public AnnotationCommandProcessor(CommandManager manager) { this.senderResolver = manager.getPlatform().getSenderResolver(); } - public void register(Object... handlers) { + public List> register(Object... handlers) { + List> allCommands = new ArrayList<>(); for (Object handler : handlers) { - processHandler(handler); + allCommands.addAll(processHandler(handler)); } + return allCommands; } - private void processHandler(Object handler) { + private List> processHandler(Object handler) { Class clazz = handler.getClass(); + validateCommandContainer(clazz); + collectTabCompleters(handler, clazz); + + List commandMethods = collectCommandMethods(handler, clazz); + Set allPaths = extractAllPaths(commandMethods); + + Map> builtCommands = buildAllCommands(commandMethods, allPaths); + Set rootCommands = organizeHierarchy(commandMethods, allPaths, builtCommands); + + return registerRootCommands(rootCommands, builtCommands); + } + + private void validateCommandContainer(Class clazz) { if (!clazz.isAnnotationPresent(CommandContainer.class)) { throw new IllegalArgumentException( "Class must be annotated with @CommandContainer: " + clazz.getName() ); } + } - // First pass: collect all @TabComplete methods + private void collectTabCompleters(Object handler, Class clazz) { tabCompleters.clear(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(TabComplete.class)) { processTabCompleter(handler, method); } } + } - // Second pass: collect all @Command methods and sort by depth + private List collectCommandMethods(Object handler, Class clazz) { List commandMethods = new ArrayList<>(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(fr.traqueur.commands.annotations.Command.class)) { @@ -63,46 +81,51 @@ private void processHandler(Object handler) { commandMethods.add(new CommandMethodInfo(handler, method, annotation.name())); } } + commandMethods.sort(Comparator.comparingInt(info -> Patterns.DOT.split(info.name).length)); + return commandMethods; + } - // Sort by depth (parents first) - commandMethods.sort(Comparator.comparingInt(info -> info.name.split("\\.").length)); - - // Collect all command paths to determine which have parents defined + private Set extractAllPaths(List commandMethods) { Set allPaths = new HashSet<>(); for (CommandMethodInfo info : commandMethods) { allPaths.add(info.name); } + return allPaths; + } - // Third pass: build ALL commands first + private Map> buildAllCommands(List commandMethods, Set allPaths) { Map> builtCommands = new LinkedHashMap<>(); - Set rootCommands = new LinkedHashSet<>(); - for (CommandMethodInfo info : commandMethods) { String parentPath = getParentPath(info.name); boolean hasParentInBatch = parentPath != null && allPaths.contains(parentPath); - Command command = buildCommand(info.handler, info.method, info.name, hasParentInBatch); builtCommands.put(info.name, command); } + return builtCommands; + } - // Fourth pass: organize hierarchy (add subcommands to parents) + private Set organizeHierarchy(List commandMethods, Set allPaths, + Map> builtCommands) { + Set rootCommands = new LinkedHashSet<>(); for (CommandMethodInfo info : commandMethods) { String parentPath = getParentPath(info.name); - if (parentPath != null && allPaths.contains(parentPath)) { - Command parent = builtCommands.get(parentPath); - Command child = builtCommands.get(info.name); - parent.addSubCommand(child); + builtCommands.get(parentPath).addSubCommand(builtCommands.get(info.name)); } else { rootCommands.add(info.name); } } + return rootCommands; + } - // Fifth pass: register only root commands + private List> registerRootCommands(Set rootCommands, Map> builtCommands) { + List> registeredCommands = new ArrayList<>(); for (String rootPath : rootCommands) { - Command rootCommand = builtCommands.get(rootPath); - manager.registerCommand(rootCommand); + Command command = builtCommands.get(rootPath); + manager.registerCommand(command); + registeredCommands.add(command); } + return registeredCommands; } private String getParentPath(String path) { @@ -167,67 +190,48 @@ private void processTabCompleter(Object handler, Method method) { private void processParameters(CommandBuilder builder, Method method, String commandPath) { Parameter[] params = method.getParameters(); - Type[] genericTypes = method.getGenericParameterTypes(); for (int i = 0; i < params.length; i++) { Parameter param = params[i]; - Class paramType = param.getType(); - - // First parameter is sender (skip it for args) - if (i == 0) { - Class senderType = paramType; - if (paramType == Optional.class) { - senderType = extractOptionalType(param); - } - if (senderResolver.canResolve(senderType)) { - continue; - } - } - // Must have @Arg annotation - Arg argAnnotation = param.getAnnotation(Arg.class); - if (argAnnotation == null) { - throw new IllegalArgumentException( - "Parameter '" + param.getName() + "' in method '" + method.getName() + - "' must be annotated with @Arg or be the sender type" - ); + if (i == 0 && isSenderParameter(param)) { + continue; } - String argName = argAnnotation.value(); - boolean isOptional = paramType == Optional.class; - boolean isInfinite = param.isAnnotationPresent(Infinite.class); + registerArgument(builder, param, commandPath); + } + } - // Determine the actual argument type - Class argType; - if (isOptional) { - argType = extractOptionalType(param); - } else { - argType = paramType; - } + private boolean isSenderParameter(Parameter param) { + Class paramType = param.getType(); + Class senderType = (paramType == Optional.class) ? extractOptionalType(param) : paramType; + return senderResolver.canResolve(senderType); + } - // If @Infinite, use Infinite.class as the type - if (isInfinite) { - argType = fr.traqueur.commands.api.arguments.Infinite.class; - } + private void registerArgument(CommandBuilder builder, Parameter param, String commandPath) { + String argName = getArgumentName(param); + Class argType = resolveArgumentType(param); + boolean isOptional = param.getType() == Optional.class; + TabCompleter completer = getTabCompleter(commandPath, argName); - // Get tab completer if exists - TabCompleter completer = getTabCompleter(commandPath, argName); + if (isOptional) { + builder.optionalArg(argName, argType, completer); + } else { + builder.arg(argName, argType, completer); + } + } - // Add argument to builder - if (isOptional) { - if (completer != null) { - builder.optionalArg(argName, argType, completer); - } else { - builder.optionalArg(argName, argType); - } - } else { - if (completer != null) { - builder.arg(argName, argType, completer); - } else { - builder.arg(argName, argType); - } - } + private String getArgumentName(Parameter param) { + Arg argAnnotation = param.getAnnotation(Arg.class); + return (argAnnotation != null) ? argAnnotation.value() : param.getName(); + } + + private Class resolveArgumentType(Parameter param) { + if (param.isAnnotationPresent(Infinite.class)) { + return fr.traqueur.commands.api.arguments.Infinite.class; } + Class paramType = param.getType(); + return (paramType == Optional.class) ? extractOptionalType(param) : paramType; } /** @@ -257,67 +261,71 @@ private TabCompleter getTabCompleter(String commandPath, String argName) { return (sender, args) -> { try { - Object result; - Parameter[] params = tcMethod.method.getParameters(); - - if (params.length == 0) { - result = tcMethod.method.invoke(tcMethod.handler); - } else if (params.length == 1) { - Object resolvedSender = senderResolver.resolve(sender, params[0].getType()); - result = tcMethod.method.invoke(tcMethod.handler, resolvedSender); - } else { - Object resolvedSender = senderResolver.resolve(sender, params[0].getType()); - String current = !args.isEmpty() ? args.getLast() : ""; - result = tcMethod.method.invoke(tcMethod.handler, resolvedSender, current); - } - + Object result = invokeTabCompleter(tcMethod, sender, args); return (List) result; } catch (Exception e) { - throw new RuntimeException("Failed to invoke tab completer", e); + throw new RuntimeException( + "Failed to invoke tab completer for command '" + commandPath + + "', argument '" + argName + "', method '" + tcMethod.method.getName() + "'", e); } }; } + private Object invokeTabCompleter(TabCompleterMethod tcMethod, S sender, List args) throws Exception { + Parameter[] params = tcMethod.method.getParameters(); + + if (params.length == 0) { + return tcMethod.method.invoke(tcMethod.handler); + } + + Object resolvedSender = senderResolver.resolve(sender, params[0].getType()); + if (params.length == 1) { + return tcMethod.method.invoke(tcMethod.handler, resolvedSender); + } + + String current = !args.isEmpty() ? args.getLast() : ""; + return tcMethod.method.invoke(tcMethod.handler, resolvedSender, current); + } + private void invokeMethod(Object handler, Method method, S sender, Arguments args) { try { Parameter[] params = method.getParameters(); - Object[] invokeArgs = new Object[params.length]; - - for (int i = 0; i < params.length; i++) { - Parameter param = params[i]; - Class paramType = param.getType(); - boolean isOptional = paramType == Optional.class; - - // First param: sender - if (i == 0) { - Class senderType = isOptional ? extractOptionalType(param) : paramType; - if (senderResolver.canResolve(senderType)) { - Object resolved = senderResolver.resolve(sender, senderType); - invokeArgs[i] = isOptional ? Optional.ofNullable(resolved) : resolved; - continue; - } - } - - // Other params: @Arg - Arg argAnnotation = param.getAnnotation(Arg.class); - if (argAnnotation != null) { - String argName = argAnnotation.value(); - - if (isOptional) { - invokeArgs[i] = args.getOptional(argName); - } else { - invokeArgs[i] = args.get(argName); - } - } - } - + Object[] invokeArgs = buildInvokeArgs(params, sender, args); method.invoke(handler, invokeArgs); - } catch (Exception e) { throw new RuntimeException("Failed to invoke command method: " + method.getName(), e); } } + private Object[] buildInvokeArgs(Parameter[] params, S sender, Arguments args) { + Object[] invokeArgs = new Object[params.length]; + + for (int i = 0; i < params.length; i++) { + Parameter param = params[i]; + + if (i == 0 && isSenderParameter(param)) { + invokeArgs[i] = resolveSender(param, sender); + } else { + invokeArgs[i] = resolveArgument(param, args); + } + } + return invokeArgs; + } + + private Object resolveSender(Parameter param, S sender) { + Class paramType = param.getType(); + boolean isOptional = paramType == Optional.class; + Class senderType = isOptional ? extractOptionalType(param) : paramType; + Object resolved = senderResolver.resolve(sender, senderType); + return isOptional ? Optional.ofNullable(resolved) : resolved; + } + + private Object resolveArgument(Parameter param, Arguments args) { + String argName = getArgumentName(param); + boolean isOptional = param.getType() == Optional.class; + return isOptional ? args.getOptional(argName) : args.get(argName); + } + private record CommandMethodInfo(Object handler, Method method, String name) {} private record TabCompleterMethod(Object handler, Method method) {} } \ No newline at end of file diff --git a/annotations-addon/src/test/java/fr/traqueur/commands/annotations/AnnotationCommandProcessorTest.java b/annotations-addon/src/test/java/fr/traqueur/commands/annotations/AnnotationCommandProcessorTest.java index cd3ddc9..2314706 100644 --- a/annotations-addon/src/test/java/fr/traqueur/commands/annotations/AnnotationCommandProcessorTest.java +++ b/annotations-addon/src/test/java/fr/traqueur/commands/annotations/AnnotationCommandProcessorTest.java @@ -363,16 +363,17 @@ void shouldThrowWhenMissingCommandContainer() { } @Test - @DisplayName("should throw when parameter is missing @Arg annotation") - void shouldThrowWhenMissingArgAnnotation() { - InvalidContainerMissingArg invalid = new InvalidContainerMissingArg(); - - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - () -> processor.register(invalid) - ); - - assertTrue(ex.getMessage().contains("@Arg")); + @DisplayName("should use parameter name when @Arg is missing") + void shouldUseParameterNameWhenArgMissing() { + InvalidContainerMissingArg container = new InvalidContainerMissingArg(); + + // Should not throw - @Arg is optional, uses parameter name instead + List> commands = processor.register(container); + + assertEquals(1, commands.size()); + Command cmd = commands.get(0); + // The command should have an argument named after the parameter + assertFalse(cmd.getArgs().isEmpty()); } } diff --git a/core/src/main/java/fr/traqueur/commands/api/CommandManager.java b/core/src/main/java/fr/traqueur/commands/api/CommandManager.java index 3dcab35..21bdbf7 100644 --- a/core/src/main/java/fr/traqueur/commands/api/CommandManager.java +++ b/core/src/main/java/fr/traqueur/commands/api/CommandManager.java @@ -17,10 +17,9 @@ import fr.traqueur.commands.api.parsing.ParseError; import fr.traqueur.commands.api.parsing.ParseResult; import fr.traqueur.commands.api.updater.Updater; +import fr.traqueur.commands.api.utils.Patterns; import fr.traqueur.commands.impl.arguments.BooleanArgument; -import fr.traqueur.commands.impl.arguments.DoubleArgument; -import fr.traqueur.commands.impl.arguments.IntegerArgument; -import fr.traqueur.commands.impl.arguments.LongArgument; +import fr.traqueur.commands.impl.arguments.NumberArgument; import fr.traqueur.commands.impl.logging.InternalLogger; import fr.traqueur.commands.impl.logging.InternalMessageHandler; import fr.traqueur.commands.impl.parsing.DefaultArgumentParser; @@ -159,7 +158,7 @@ public void unregisterCommand(String label) { * @param subcommands If the subcommands must be unregistered. */ public void unregisterCommand(String label, boolean subcommands) { - String[] rawArgs = label.split("\\."); + String[] rawArgs = Patterns.DOT.split(label); Optional> commandOptional = this.commands.findNode(rawArgs) .flatMap(result -> result.node().getCommand()); @@ -371,7 +370,7 @@ private void addCommand(Command command, String label) { } List> args = command.getArgs(); List> optArgs = command.getOptionalArgs(); - String[] labelParts = label.split("\\."); + String[] labelParts = Patterns.DOT.split(label); int labelSize = labelParts.length; command.setManager(this); @@ -472,11 +471,15 @@ private void registerInternalConverters() { // Primitive types (int.class, long.class, etc.) are registered to support primitive method parameters. this.registerConverter(Boolean.class, new BooleanArgument<>()); this.registerConverter(boolean.class, new BooleanArgument<>()); - this.registerConverter(Integer.class, new IntegerArgument()); - this.registerConverter(int.class, new IntegerArgument()); - this.registerConverter(Double.class, new DoubleArgument()); - this.registerConverter(double.class, new DoubleArgument()); - this.registerConverter(Long.class, new LongArgument()); - this.registerConverter(long.class, new LongArgument()); + this.registerConverter(Integer.class, new NumberArgument<>(Integer::valueOf)); + this.registerConverter(int.class, new NumberArgument<>(Integer::valueOf)); + this.registerConverter(Double.class, new NumberArgument<>(Double::valueOf)); + this.registerConverter(double.class, new NumberArgument<>(Double::valueOf)); + this.registerConverter(Long.class, new NumberArgument<>(Long::valueOf)); + this.registerConverter(long.class, new NumberArgument<>(Long::valueOf)); + this.registerConverter(Float.class, new NumberArgument<>(Float::valueOf)); + this.registerConverter(float.class, new NumberArgument<>(Float::valueOf)); + this.registerConverter(Byte.class, new NumberArgument<>(Byte::valueOf)); + this.registerConverter(byte.class, new NumberArgument<>(Byte::valueOf)); } } diff --git a/core/src/main/java/fr/traqueur/commands/api/arguments/Arguments.java b/core/src/main/java/fr/traqueur/commands/api/arguments/Arguments.java index 53200f3..0267072 100644 --- a/core/src/main/java/fr/traqueur/commands/api/arguments/Arguments.java +++ b/core/src/main/java/fr/traqueur/commands/api/arguments/Arguments.java @@ -32,24 +32,49 @@ public Arguments(Logger logger) { this.logger = logger; } + /** + * Convert all arguments to a simple map of String to Object. + * + * @return an unmodifiable map containing argument names as keys and their values + */ public Map toMap() { Map result = new HashMap<>(); arguments.forEach((k, v) -> result.put(k, v.value())); return result; } + /** + * Get the number of arguments stored. + * + * @return the number of arguments + */ public int size() { return arguments.size(); } + /** + * Check if there are no arguments stored. + * + * @return true if there are no arguments, false otherwise + */ public boolean isEmpty() { return arguments.isEmpty(); } + /** + * Get all argument keys. + * + * @return an unmodifiable set of argument names + */ public Set getKeys() { return Collections.unmodifiableSet(arguments.keySet()); } + /** + * Iterate over all arguments and apply the given action. + * + * @param action the action to apply to each argument (key, value) + */ public void forEach(BiConsumer action) { arguments.forEach((k, v) -> action.accept(k, v.value())); } diff --git a/core/src/main/java/fr/traqueur/commands/api/logging/MessageHandler.java b/core/src/main/java/fr/traqueur/commands/api/logging/MessageHandler.java index 27a8896..84f972e 100644 --- a/core/src/main/java/fr/traqueur/commands/api/logging/MessageHandler.java +++ b/core/src/main/java/fr/traqueur/commands/api/logging/MessageHandler.java @@ -42,4 +42,18 @@ public interface MessageHandler { * @return The command disabled message. */ String getCommandDisabledMessage(); + + /** + * This method is used to get the argument too long message. + * + * @return The argument too long message. + */ + String getArgumentTooLongMessage(); + + /** + * This method is used to get the invalid format message. + * + * @return The invalid format message. + */ + String getInvalidFormatMessage(); } diff --git a/core/src/main/java/fr/traqueur/commands/api/models/Command.java b/core/src/main/java/fr/traqueur/commands/api/models/Command.java index 24e5943..f4a00fe 100644 --- a/core/src/main/java/fr/traqueur/commands/api/models/Command.java +++ b/core/src/main/java/fr/traqueur/commands/api/models/Command.java @@ -6,12 +6,12 @@ import fr.traqueur.commands.api.arguments.Arguments; import fr.traqueur.commands.api.arguments.TabCompleter; import fr.traqueur.commands.api.requirements.Requirement; +import fr.traqueur.commands.api.utils.Patterns; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -23,8 +23,6 @@ * @param The type of the sender who use the command. */ public abstract class Command { - - private static final Pattern DOT_PATTERN = Pattern.compile("\\."); /** * The plugin that owns the command. */ @@ -456,7 +454,7 @@ public final T getPlugin() { public String generateDefaultUsage(S sender, String label) { StringBuilder usage = new StringBuilder("/"); - String[] parts = DOT_PATTERN.split(label); + String[] parts = Patterns.DOT.split(label); usage.append(String.join(" ", parts)); List> directSubs = this.getSubcommands().stream() @@ -470,7 +468,7 @@ public String generateDefaultUsage(S sender, String label) { usage.append(" <"); String subs = directSubs.stream() .map(Command::getName) - .map(str -> str.split("\\.")[0]) + .map(str -> Patterns.DOT.split(str)[0]) .collect(Collectors.joining("|")); usage.append(subs).append(">"); } diff --git a/core/src/main/java/fr/traqueur/commands/api/models/CommandBuilder.java b/core/src/main/java/fr/traqueur/commands/api/models/CommandBuilder.java index 6eba4fb..8370c1d 100644 --- a/core/src/main/java/fr/traqueur/commands/api/models/CommandBuilder.java +++ b/core/src/main/java/fr/traqueur/commands/api/models/CommandBuilder.java @@ -35,83 +35,184 @@ public CommandBuilder(CommandManager manager, String name) { this.command = new SimpleCommand(manager.getPlatform().getPlugin(), name); } + /** + * Set the command description. + * + * @param description the description text + * @return this builder for chaining + */ public CommandBuilder description(String description) { this.description = description; return this; } + /** + * Set the command usage string. + * + * @param usage the usage text + * @return this builder for chaining + */ public CommandBuilder usage(String usage) { this.usage = usage; return this; } + /** + * Set the required permission for this command. + * + * @param permission the permission node + * @return this builder for chaining + */ public CommandBuilder permission(String permission) { this.permission = permission; return this; } + /** + * Mark this command as game-only (cannot be used from console). + * + * @return this builder for chaining + */ public CommandBuilder gameOnly() { this.gameOnly = true; return this; } + /** + * Set whether this command is game-only. + * + * @param gameOnly true if game-only, false otherwise + * @return this builder for chaining + */ public CommandBuilder gameOnly(boolean gameOnly) { this.gameOnly = gameOnly; return this; } + /** + * Add an alias for this command. + * + * @param alias the alias to add + * @return this builder for chaining + */ public CommandBuilder alias(String alias) { this.command.addAlias(alias); return this; } + /** + * Add multiple aliases for this command. + * + * @param aliases the aliases to add + * @return this builder for chaining + */ public CommandBuilder aliases(String... aliases) { this.command.addAlias(aliases); return this; } + /** + * Add a required argument to this command. + * + * @param name the argument name + * @param type the argument type class + * @return this builder for chaining + */ public CommandBuilder arg(String name, Class type) { this.command.addArg(name, type); return this; } + /** + * Add a required argument with a custom tab completer. + * + * @param name the argument name + * @param type the argument type class + * @param completer the tab completer for this argument + * @return this builder for chaining + */ public CommandBuilder arg(String name, Class type, TabCompleter completer) { this.command.addArg(name, type, completer); return this; } + /** + * Add an optional argument to this command. + * + * @param name the argument name + * @param type the argument type class + * @return this builder for chaining + */ public CommandBuilder optionalArg(String name, Class type) { this.command.addOptionalArg(name, type); return this; } + /** + * Add an optional argument with a custom tab completer. + * + * @param name the argument name + * @param type the argument type class + * @param completer the tab completer for this argument + * @return this builder for chaining + */ public CommandBuilder optionalArg(String name, Class type, TabCompleter completer) { this.command.addOptionalArg(name, type, completer); return this; } + /** + * Add a requirement that must be met to execute this command. + * + * @param requirement the requirement to add + * @return this builder for chaining + */ public CommandBuilder requirement(Requirement requirement) { this.command.addRequirements(requirement); return this; } + /** + * Add multiple requirements that must be met to execute this command. + * + * @param requirements the requirements to add + * @return this builder for chaining + */ @SafeVarargs public final CommandBuilder requirements(Requirement... requirements) { this.command.addRequirements(requirements); return this; } + /** + * Add a subcommand to this command. + * + * @param subcommand the subcommand to add + * @return this builder for chaining + */ public CommandBuilder subcommand(Command subcommand) { this.command.addSubCommand(subcommand); return this; } + /** + * Add multiple subcommands to this command. + * + * @param subcommands the subcommands to add + * @return this builder for chaining + */ @SafeVarargs public final CommandBuilder subcommands(Command... subcommands) { this.command.addSubCommand(subcommands); return this; } + /** + * Set the executor for this command. + * + * @param executor the executor that handles command execution + * @return this builder for chaining + */ public CommandBuilder executor(BiConsumer executor) { this.executor = executor; return this; diff --git a/core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java b/core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java index 8c1a0df..47fc448 100644 --- a/core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java +++ b/core/src/main/java/fr/traqueur/commands/api/models/CommandInvoker.java @@ -8,9 +8,9 @@ import fr.traqueur.commands.api.models.collections.CommandTree; import fr.traqueur.commands.api.models.collections.CommandTree.MatchResult; import fr.traqueur.commands.api.requirements.Requirement; +import fr.traqueur.commands.api.utils.Patterns; import java.util.*; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -24,8 +24,6 @@ */ public record CommandInvoker(CommandManager manager) { - private static final Pattern DOT_PATTERN = Pattern.compile("\\."); - /** * Invokes a command based on the provided source, base label, and raw arguments. * @@ -298,7 +296,7 @@ public List suggest(S source, String base, String[] args) { private boolean allowedSuggestion(S src, String label, String opt) { String full = label + "." + opt.toLowerCase(); Optional> copt = manager.getCommands() - .findNode(DOT_PATTERN.split(full)) + .findNode(Patterns.DOT.split(full)) .flatMap(r -> r.node().getCommand()); if (copt.isEmpty()) return true; Command c = copt.get(); diff --git a/core/src/main/java/fr/traqueur/commands/api/models/collections/CommandTree.java b/core/src/main/java/fr/traqueur/commands/api/models/collections/CommandTree.java index 4225d94..07b3abf 100644 --- a/core/src/main/java/fr/traqueur/commands/api/models/collections/CommandTree.java +++ b/core/src/main/java/fr/traqueur/commands/api/models/collections/CommandTree.java @@ -1,6 +1,7 @@ package fr.traqueur.commands.api.models.collections; import fr.traqueur.commands.api.models.Command; +import fr.traqueur.commands.api.utils.Patterns; import java.util.*; import java.util.regex.Pattern; @@ -14,23 +15,25 @@ public class CommandTree { /** - * Pre-compiled pattern for splitting labels. - */ - private static final Pattern DOT_PATTERN = Pattern.compile("\\."); - - /** - * Valid label pattern: starts with letter, followed by letters, digits, underscores, or dots. - * Each segment must start with a letter. + * Valid label pattern: starts with letter, followed by letters, digits, underscores. + * Each segment must start with a letter to ensure valid command syntax across platforms. + * Example valid segments: "help", "setHome", "player_info" + * Example invalid segments: "123cmd", "_hidden", "cmd-name" */ private static final Pattern VALID_LABEL_SEGMENT = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]*$"); /** - * Maximum length for a single label segment. + * Maximum length for a single label segment (64 characters). + * This limit prevents excessively long command names that could cause + * display issues in help menus or tab completion interfaces. */ private static final int MAX_SEGMENT_LENGTH = 64; /** - * Maximum depth for nested commands. + * Maximum depth for nested commands (10 levels). + * This limit prevents deeply nested command hierarchies that could + * impact performance during command lookup and cause usability issues. + * Example: "admin.user.permission.group.add" has depth 5. */ private static final int MAX_DEPTH = 10; @@ -54,7 +57,7 @@ public void clear() { public void addCommand(String label, Command command) { validateLabel(label); - String[] parts = DOT_PATTERN.split(label); + String[] parts = Patterns.DOT.split(label); CommandNode node = root; for (String seg : parts) { @@ -77,7 +80,7 @@ private void validateLabel(String label) { throw new IllegalArgumentException("Command label cannot be null or empty"); } - String[] segments = DOT_PATTERN.split(label); + String[] segments = Patterns.DOT.split(label); if (segments.length > MAX_DEPTH) { throw new IllegalArgumentException( @@ -162,7 +165,7 @@ public Optional> findNode(String[] segments) { * Remove a command node by its full label. */ public void removeCommand(String label, boolean prune) { - CommandNode target = this.findNode(DOT_PATTERN.split(label)) + CommandNode target = this.findNode(Patterns.DOT.split(label)) .map(MatchResult::node) .orElse(null); if (target == null) return; diff --git a/core/src/main/java/fr/traqueur/commands/api/utils/Patterns.java b/core/src/main/java/fr/traqueur/commands/api/utils/Patterns.java new file mode 100644 index 0000000..8312c2b --- /dev/null +++ b/core/src/main/java/fr/traqueur/commands/api/utils/Patterns.java @@ -0,0 +1,19 @@ +package fr.traqueur.commands.api.utils; + +import java.util.regex.Pattern; + +/** + * Utility class providing commonly used compiled regex patterns. + * Using compiled patterns is more efficient than calling String.split() with a regex. + */ +public final class Patterns { + + /** + * Pattern for splitting strings on dots. + */ + public static final Pattern DOT = Pattern.compile("\\."); + + private Patterns() { + // Utility class + } +} diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/DoubleArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/DoubleArgument.java deleted file mode 100644 index 81129f8..0000000 --- a/core/src/main/java/fr/traqueur/commands/impl/arguments/DoubleArgument.java +++ /dev/null @@ -1,34 +0,0 @@ -package fr.traqueur.commands.impl.arguments; - - -import fr.traqueur.commands.api.arguments.ArgumentConverter; - -/** - * Convert a string to a double - */ -public class DoubleArgument implements ArgumentConverter { - - /** - * Default constructor. - */ - public DoubleArgument() { - } - - /** - * Convert a string to a double - * - * @param input the string to convert - * @return the double or null if the string is not a double - */ - @Override - public Double apply(String input) { - if (input == null || input.isEmpty()) { - return null; - } - try { - return Double.valueOf(input); - } catch (NumberFormatException e) { - return null; - } - } -} diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/IntegerArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/IntegerArgument.java deleted file mode 100644 index 8ab98f2..0000000 --- a/core/src/main/java/fr/traqueur/commands/impl/arguments/IntegerArgument.java +++ /dev/null @@ -1,34 +0,0 @@ -package fr.traqueur.commands.impl.arguments; - - -import fr.traqueur.commands.api.arguments.ArgumentConverter; - -/** - * Argument used to convert a string to an integer. - */ -public class IntegerArgument implements ArgumentConverter { - - /** - * Default constructor. - */ - public IntegerArgument() { - } - - /** - * Converts a string to an integer. - * - * @param input the string to convert - * @return the integer, or null if the string is not a valid integer - */ - @Override - public Integer apply(String input) { - if (input == null || input.isEmpty()) { - return null; - } - try { - return Integer.valueOf(input); - } catch (NumberFormatException e) { - return null; - } - } -} diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/LongArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/LongArgument.java deleted file mode 100644 index 6bd3c9b..0000000 --- a/core/src/main/java/fr/traqueur/commands/impl/arguments/LongArgument.java +++ /dev/null @@ -1,34 +0,0 @@ -package fr.traqueur.commands.impl.arguments; - - -import fr.traqueur.commands.api.arguments.ArgumentConverter; - -/** - * Argument used to convert a string to a long. - */ -public class LongArgument implements ArgumentConverter { - - /** - * Default constructor. - */ - public LongArgument() { - } - - /** - * Convert a string to a long. - * - * @param input the string to convert - * @return the long or null if the string is not a long - */ - @Override - public Long apply(String input) { - if (input == null || input.isEmpty()) { - return null; - } - try { - return Long.valueOf(input); - } catch (NumberFormatException e) { - return null; - } - } -} diff --git a/core/src/main/java/fr/traqueur/commands/impl/arguments/NumberArgument.java b/core/src/main/java/fr/traqueur/commands/impl/arguments/NumberArgument.java new file mode 100644 index 0000000..318d08e --- /dev/null +++ b/core/src/main/java/fr/traqueur/commands/impl/arguments/NumberArgument.java @@ -0,0 +1,43 @@ +package fr.traqueur.commands.impl.arguments; + +import fr.traqueur.commands.api.arguments.ArgumentConverter; + +import java.util.function.Function; + +/** + * Base class for numeric argument converters. + * Provides common null/empty checking and NumberFormatException handling. + * + * @param the numeric type to convert to + */ +public class NumberArgument implements ArgumentConverter { + + private final Function parser; + + /** + * Creates a new number argument converter. + * + * @param parser the function to parse the string into the target number type + */ + public NumberArgument(Function parser) { + this.parser = parser; + } + + /** + * Converts a string to a number. + * + * @param input the string to convert + * @return the parsed number, or null if the string is null, empty, or not a valid number + */ + @Override + public T apply(String input) { + if (input == null || input.isEmpty()) { + return null; + } + try { + return parser.apply(input); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/core/src/main/java/fr/traqueur/commands/impl/logging/InternalMessageHandler.java b/core/src/main/java/fr/traqueur/commands/impl/logging/InternalMessageHandler.java index 80a245c..3591794 100644 --- a/core/src/main/java/fr/traqueur/commands/impl/logging/InternalMessageHandler.java +++ b/core/src/main/java/fr/traqueur/commands/impl/logging/InternalMessageHandler.java @@ -54,4 +54,20 @@ public String getCommandDisabledMessage() { return "&cThis command is currently disabled."; } + /** + * {@inheritDoc} + */ + @Override + public String getArgumentTooLongMessage() { + return "&cArgument &e%arg% &cexceeds maximum length."; + } + + /** + * {@inheritDoc} + */ + @Override + public String getInvalidFormatMessage() { + return "&cInvalid format for argument &e%arg%&c."; + } + } diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/DoubleArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/DoubleArgumentTest.java deleted file mode 100644 index 804c8dc..0000000 --- a/core/src/test/java/fr/traqueur/commands/impl/arguments/DoubleArgumentTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package fr.traqueur.commands.impl.arguments; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class DoubleArgumentTest { - private DoubleArgument converter; - - @BeforeEach - void setUp() { - converter = new DoubleArgument(); - } - - @Test - void testApply_valid() { - assertEquals(3.14, converter.apply("3.14")); - assertEquals(-2.718, converter.apply("-2.718")); - assertEquals(0.0, converter.apply("0")); - } - - @Test - void testApply_invalid() { - assertNull(converter.apply("abc")); - assertNull(converter.apply(null)); - } -} \ No newline at end of file diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/IntegerArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/IntegerArgumentTest.java deleted file mode 100644 index aeeacb8..0000000 --- a/core/src/test/java/fr/traqueur/commands/impl/arguments/IntegerArgumentTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package fr.traqueur.commands.impl.arguments; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class IntegerArgumentTest { - - private IntegerArgument converter; - - @BeforeEach - void setUp() { - converter = new IntegerArgument(); - } - - @Test - void testApply_valid() { - assertEquals(0, converter.apply("0")); - assertEquals(123, converter.apply("123")); - assertEquals(-42, converter.apply("-42")); - } - - @Test - void testApply_invalid() { - assertNull(converter.apply("abc")); - assertNull(converter.apply("12.3")); - assertNull(converter.apply("")); - assertNull(converter.apply(null)); - } -} \ No newline at end of file diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/LongArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/LongArgumentTest.java deleted file mode 100644 index 9c77e65..0000000 --- a/core/src/test/java/fr/traqueur/commands/impl/arguments/LongArgumentTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package fr.traqueur.commands.impl.arguments; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -class LongArgumentTest { - - private LongArgument converter; - - @BeforeEach - void setUp() { - converter = new LongArgument(); - } - - @Test - void testApply_valid() { - assertEquals(0L, converter.apply("0")); - assertEquals(1234567890123L, converter.apply("1234567890123")); - assertEquals(-9876543210L, converter.apply("-9876543210")); - } - - @Test - void testApply_invalid() { - assertNull(converter.apply("abc")); - assertNull(converter.apply("12.34")); - assertNull(converter.apply("")); - assertNull(converter.apply(null)); - } -} \ No newline at end of file diff --git a/core/src/test/java/fr/traqueur/commands/impl/arguments/NumberArgumentTest.java b/core/src/test/java/fr/traqueur/commands/impl/arguments/NumberArgumentTest.java new file mode 100644 index 0000000..7aefb33 --- /dev/null +++ b/core/src/test/java/fr/traqueur/commands/impl/arguments/NumberArgumentTest.java @@ -0,0 +1,90 @@ +package fr.traqueur.commands.impl.arguments; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class NumberArgumentTest { + + @Test + void testInteger_valid() { + var converter = new NumberArgument<>(Integer::valueOf); + assertEquals(0, converter.apply("0")); + assertEquals(123, converter.apply("123")); + assertEquals(-42, converter.apply("-42")); + } + + @Test + void testInteger_invalid() { + var converter = new NumberArgument<>(Integer::valueOf); + assertNull(converter.apply("abc")); + assertNull(converter.apply("12.3")); + assertNull(converter.apply("")); + assertNull(converter.apply(null)); + } + + @Test + void testLong_valid() { + var converter = new NumberArgument<>(Long::valueOf); + assertEquals(0L, converter.apply("0")); + assertEquals(1234567890123L, converter.apply("1234567890123")); + assertEquals(-9876543210L, converter.apply("-9876543210")); + } + + @Test + void testLong_invalid() { + var converter = new NumberArgument<>(Long::valueOf); + assertNull(converter.apply("abc")); + assertNull(converter.apply("12.34")); + assertNull(converter.apply("")); + assertNull(converter.apply(null)); + } + + @Test + void testDouble_valid() { + var converter = new NumberArgument<>(Double::valueOf); + assertEquals(3.14, converter.apply("3.14")); + assertEquals(-2.718, converter.apply("-2.718")); + assertEquals(0.0, converter.apply("0")); + } + + @Test + void testDouble_invalid() { + var converter = new NumberArgument<>(Double::valueOf); + assertNull(converter.apply("abc")); + assertNull(converter.apply(null)); + } + + @Test + void testFloat_valid() { + var converter = new NumberArgument<>(Float::valueOf); + assertEquals(3.14f, converter.apply("3.14")); + assertEquals(-2.718f, converter.apply("-2.718")); + assertEquals(0.0f, converter.apply("0")); + } + + @Test + void testFloat_invalid() { + var converter = new NumberArgument<>(Float::valueOf); + assertNull(converter.apply("abc")); + assertNull(converter.apply(null)); + } + + @Test + void testByte_valid() { + var converter = new NumberArgument<>(Byte::valueOf); + assertEquals((byte) 0, converter.apply("0")); + assertEquals((byte) 127, converter.apply("127")); + assertEquals((byte) -128, converter.apply("-128")); + } + + @Test + void testByte_invalid() { + var converter = new NumberArgument<>(Byte::valueOf); + assertNull(converter.apply("abc")); + assertNull(converter.apply("128")); // overflow + assertNull(converter.apply("")); + assertNull(converter.apply(null)); + } +} diff --git a/gradle.properties b/gradle.properties index 1e71c76..42ba350 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=5.0.2 +version=5.1.0 diff --git a/jda/src/main/java/fr/traqueur/commands/jda/JDAExecutor.java b/jda/src/main/java/fr/traqueur/commands/jda/JDAExecutor.java index 2549745..4ceebdf 100644 --- a/jda/src/main/java/fr/traqueur/commands/jda/JDAExecutor.java +++ b/jda/src/main/java/fr/traqueur/commands/jda/JDAExecutor.java @@ -7,6 +7,7 @@ import fr.traqueur.commands.api.models.collections.CommandTree; import fr.traqueur.commands.api.parsing.ParseResult; import fr.traqueur.commands.api.requirements.Requirement; +import fr.traqueur.commands.api.utils.Patterns; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -41,7 +42,7 @@ public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent even JDAInteractionContext context = JDAInteractionContext.wrap(event); // Find command - String[] labelParts = label.split("\\."); + String[] labelParts = Patterns.DOT.split(label); Optional> found = commandManager.getCommands().findNode(labelParts); @@ -134,7 +135,7 @@ public void onCommandAutoCompleteInteraction(@NotNull CommandAutoCompleteInterac JDAInteractionContext context = JDAInteractionContext.wrap(event); // Find command - String[] labelParts = label.split("\\."); + String[] labelParts = Patterns.DOT.split(label); Optional> found = commandManager.getCommands().findNode(labelParts); diff --git a/jda/src/main/java/fr/traqueur/commands/jda/JDAPlatform.java b/jda/src/main/java/fr/traqueur/commands/jda/JDAPlatform.java index 341b29d..501948f 100644 --- a/jda/src/main/java/fr/traqueur/commands/jda/JDAPlatform.java +++ b/jda/src/main/java/fr/traqueur/commands/jda/JDAPlatform.java @@ -5,6 +5,7 @@ import fr.traqueur.commands.api.models.Command; import fr.traqueur.commands.api.models.CommandPlatform; import fr.traqueur.commands.api.resolver.SenderResolver; +import fr.traqueur.commands.api.utils.Patterns; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.*; @@ -112,7 +113,7 @@ public void sendMessage(JDAInteractionContext sender, String message) { @Override public void addCommand(Command command, String label) { - String[] parts = label.split("\\."); + String[] parts = Patterns.DOT.split(label); String rootName = parts[0].toLowerCase(); if (parts.length == 1) { @@ -185,7 +186,7 @@ public void addCommand(Command command, String label) @Override public void removeCommand(String label, boolean subcommand) { - String[] parts = label.split("\\."); + String[] parts = Patterns.DOT.split(label); String rootName = parts[0].toLowerCase(); if (!subcommand && parts.length == 1) { diff --git a/spigot/src/main/java/fr/traqueur/commands/spigot/SpigotPlatform.java b/spigot/src/main/java/fr/traqueur/commands/spigot/SpigotPlatform.java index b8e3598..f4d429c 100644 --- a/spigot/src/main/java/fr/traqueur/commands/spigot/SpigotPlatform.java +++ b/spigot/src/main/java/fr/traqueur/commands/spigot/SpigotPlatform.java @@ -5,6 +5,7 @@ import fr.traqueur.commands.api.models.Command; import fr.traqueur.commands.api.models.CommandPlatform; import fr.traqueur.commands.api.resolver.SenderResolver; +import fr.traqueur.commands.api.utils.Patterns; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandMap; @@ -128,7 +129,7 @@ public void sendMessage(CommandSender sender, String message) { */ @Override public void addCommand(Command command, String label) { - String[] labelParts = label.split("\\."); + String[] labelParts = Patterns.DOT.split(label); String cmdLabel = labelParts[0].toLowerCase(); boolean alreadyInTree = commandManager.getCommands() @@ -144,7 +145,7 @@ public void addCommand(Command command, String label) { cmd.setTabCompleter(spigotExecutor); cmd.setAliases( command.getAliases().stream() - .map(a -> a.split("\\.")[0]) + .map(a -> Patterns.DOT.split(a)[0]) .filter(a -> !a.equalsIgnoreCase(cmdLabel)) .distinct() .collect(Collectors.toList()) diff --git a/velocity/src/main/java/fr/traqueur/commands/velocity/VelocityPlatform.java b/velocity/src/main/java/fr/traqueur/commands/velocity/VelocityPlatform.java index 3e37df7..b5836e6 100644 --- a/velocity/src/main/java/fr/traqueur/commands/velocity/VelocityPlatform.java +++ b/velocity/src/main/java/fr/traqueur/commands/velocity/VelocityPlatform.java @@ -6,6 +6,7 @@ import fr.traqueur.commands.api.models.Command; import fr.traqueur.commands.api.models.CommandPlatform; import fr.traqueur.commands.api.resolver.SenderResolver; +import fr.traqueur.commands.api.utils.Patterns; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -115,7 +116,7 @@ public void sendMessage(CommandSource sender, String message) { */ @Override public void addCommand(Command command, String label) { - String[] labelParts = label.split("\\."); + String[] labelParts = Patterns.DOT.split(label); String cmdLabel = labelParts[0].toLowerCase(); com.velocitypowered.api.command.CommandManager velocityCmdManager = server.getCommandManager(); @@ -128,7 +129,7 @@ public void addCommand(Command command, String label) { if (!alreadyInTree && !alreadyInMap) { String[] aliases = command.getAliases().stream() - .map(a -> a.split("\\.")[0].toLowerCase()) + .map(a -> Patterns.DOT.split(a)[0].toLowerCase()) .filter(a -> !a.equalsIgnoreCase(cmdLabel)) .distinct() .toArray(String[]::new);