From d143de1f078e13c38e19a79115d36586a6e9e065 Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Fri, 6 Jan 2023 00:09:18 +0100 Subject: [PATCH 01/15] allowing required arguments to come after optional arguments --- .../commandapi/AbstractCommandAPICommand.java | 24 +++++++++++--- .../arguments/AbstractArgument.java | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index b37551b57b..0356ae7c5e 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.function.Predicate; @@ -306,6 +307,7 @@ private List getArgumentsToRegister(Argument[] argumentsArray) { // Check optional argument constraints // They can only be at the end, no required argument can follow an optional argument + // This method also ignores linked arguments int firstOptionalArgumentIndex = -1; for (int i = 0, optionalArgumentIndex = -1; i < argumentsArray.length; i++) { if (argumentsArray[i].isOptional()) { @@ -322,11 +324,23 @@ private List getArgumentsToRegister(Argument[] argumentsArray) { // Create a List of arrays that hold arguments to register optional arguments // if optional arguments have been found if (firstOptionalArgumentIndex != -1) { - for (int i = 0; i <= argumentsArray.length; i++) { - if (i >= firstOptionalArgumentIndex) { - Argument[] arguments = (Argument[]) new AbstractArgument[i]; - System.arraycopy(argumentsArray, 0, arguments, 0, i); - argumentsToRegister.add(arguments); + for (int i = 0; i < argumentsArray.length; i++) { + if (i >= firstOptionalArgumentIndex - 1) { + if (!argumentsArray[i].hasLinkedArguments()) { + List arguments = new ArrayList<>(); + for (int j = 0; j <= i; j++) { + arguments.add(argumentsArray[j]); + if (argumentsArray[j].hasLinkedArguments()) { + arguments.addAll((List) argumentsArray[j].getLinkedArguments()); + } + } + argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); + } else { + List linkedArguments = (List) argumentsArray[i].getLinkedArguments(); + List arguments = new ArrayList<>(Arrays.asList(argumentsArray).subList(0, i + 1)); + arguments.addAll(linkedArguments); + argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); + } } } } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index bcd853116b..0112d274fa 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -25,6 +25,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.jorel.commandapi.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -255,6 +256,7 @@ public Impl setListed(boolean listed) { ///////////////// private boolean isOptional = false; + private final List> linkedArguments = new ArrayList<>(); /** * Returns true if this argument will be optional when executing the command this argument is included in @@ -276,6 +278,37 @@ public Impl setOptional(boolean optional) { return instance(); } + /** + * Returns a list of arguments linked to this argument. + * + * @return A list of arguments linked to this argument + */ + public List> getLinkedArguments() { + return linkedArguments; + } + + /** + * Returns true if this argument has linked arguments. + * + * @return true if this argument has linked arguments + */ + public boolean hasLinkedArguments() { + return !linkedArguments.isEmpty(); + } + + /** + * Adds linked arguments to this argument. Linked arguments are used to have required arguments after optional arguments + * by ignoring they exist until they are added to the arguments array for registration + * + * @param linkedArguments The arguments to link + * @return this current argument + */ + @SafeVarargs + public final Impl linkArguments(AbstractArgument... linkedArguments) { + this.linkedArguments.addAll(Arrays.asList(linkedArguments)); + return instance(); + } + /////////// // Other // /////////// From 09d1bd26baf034ad089ef0640487427510d31786 Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Fri, 6 Jan 2023 00:30:51 +0100 Subject: [PATCH 02/15] change AbstractArgument#linkArguments to accept an actual argument inplementation --- .../dev/jorel/commandapi/arguments/AbstractArgument.java | 6 +++--- .../src/main/java/dev/jorel/commandapi/CommandAPIMain.java | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 0112d274fa..adf18caf27 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -256,7 +256,7 @@ public Impl setListed(boolean listed) { ///////////////// private boolean isOptional = false; - private final List> linkedArguments = new ArrayList<>(); + private final List linkedArguments = new ArrayList<>(); /** * Returns true if this argument will be optional when executing the command this argument is included in @@ -283,7 +283,7 @@ public Impl setOptional(boolean optional) { * * @return A list of arguments linked to this argument */ - public List> getLinkedArguments() { + public List getLinkedArguments() { return linkedArguments; } @@ -304,7 +304,7 @@ public boolean hasLinkedArguments() { * @return this current argument */ @SafeVarargs - public final Impl linkArguments(AbstractArgument... linkedArguments) { + public final Impl linkArguments(Argument... linkedArguments) { this.linkedArguments.addAll(Arrays.asList(linkedArguments)); return instance(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index 7fecbc3274..dde5ef6c33 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -26,7 +26,11 @@ import java.util.Map; import java.util.Map.Entry; +import dev.jorel.commandapi.arguments.IntegerArgument; +import dev.jorel.commandapi.arguments.PlayerArgument; +import dev.jorel.commandapi.arguments.StringArgument; import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.InvalidPluginException; import org.bukkit.plugin.Plugin; From 5567697527b80154381db4bc2c2852a363d84ddb Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Fri, 6 Jan 2023 10:41:50 +0100 Subject: [PATCH 03/15] Remove imports from CommandAPIMain. Rename AbstractArgument#linkArguments() into AbstractArgument#combineWith() --- .../commandapi/AbstractCommandAPICommand.java | 8 ++++---- .../commandapi/arguments/AbstractArgument.java | 16 ++++++++-------- .../dev/jorel/commandapi/CommandAPIMain.java | 4 ---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 0356ae7c5e..b9fa4fef6c 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -326,17 +326,17 @@ private List getArgumentsToRegister(Argument[] argumentsArray) { if (firstOptionalArgumentIndex != -1) { for (int i = 0; i < argumentsArray.length; i++) { if (i >= firstOptionalArgumentIndex - 1) { - if (!argumentsArray[i].hasLinkedArguments()) { + if (!argumentsArray[i].hasCombinedArguments()) { List arguments = new ArrayList<>(); for (int j = 0; j <= i; j++) { arguments.add(argumentsArray[j]); - if (argumentsArray[j].hasLinkedArguments()) { - arguments.addAll((List) argumentsArray[j].getLinkedArguments()); + if (argumentsArray[j].hasCombinedArguments()) { + arguments.addAll(argumentsArray[j].getCombinedArguments()); } } argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); } else { - List linkedArguments = (List) argumentsArray[i].getLinkedArguments(); + List linkedArguments = argumentsArray[i].getCombinedArguments(); List arguments = new ArrayList<>(Arrays.asList(argumentsArray).subList(0, i + 1)); arguments.addAll(linkedArguments); argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index adf18caf27..cc40829358 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -256,7 +256,7 @@ public Impl setListed(boolean listed) { ///////////////// private boolean isOptional = false; - private final List linkedArguments = new ArrayList<>(); + private final List combinedArguments = new ArrayList<>(); /** * Returns true if this argument will be optional when executing the command this argument is included in @@ -283,8 +283,8 @@ public Impl setOptional(boolean optional) { * * @return A list of arguments linked to this argument */ - public List getLinkedArguments() { - return linkedArguments; + public List getCombinedArguments() { + return combinedArguments; } /** @@ -292,20 +292,20 @@ public List getLinkedArguments() { * * @return true if this argument has linked arguments */ - public boolean hasLinkedArguments() { - return !linkedArguments.isEmpty(); + public boolean hasCombinedArguments() { + return !combinedArguments.isEmpty(); } /** * Adds linked arguments to this argument. Linked arguments are used to have required arguments after optional arguments * by ignoring they exist until they are added to the arguments array for registration * - * @param linkedArguments The arguments to link + * @param combinedArguments The arguments to link * @return this current argument */ @SafeVarargs - public final Impl linkArguments(Argument... linkedArguments) { - this.linkedArguments.addAll(Arrays.asList(linkedArguments)); + public final Impl combineWith(Argument... combinedArguments) { + this.combinedArguments.addAll(Arrays.asList(combinedArguments)); return instance(); } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index dde5ef6c33..7fecbc3274 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -26,11 +26,7 @@ import java.util.Map; import java.util.Map.Entry; -import dev.jorel.commandapi.arguments.IntegerArgument; -import dev.jorel.commandapi.arguments.PlayerArgument; -import dev.jorel.commandapi.arguments.StringArgument; import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.InvalidPluginException; import org.bukkit.plugin.Plugin; From 8aecdfe54b9f702cfde56efbb2b667723a83b1bb Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Fri, 6 Jan 2023 11:02:17 +0100 Subject: [PATCH 04/15] make AbstractArgument#combineWith() copy requirements and permissions to the combined argument --- .../commandapi/arguments/AbstractArgument.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index cc40829358..2bd0751a45 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -225,6 +225,13 @@ public final Impl withRequirement(Predicate requirement) { return instance(); } + /** + * Resets the requirements for this command + */ + public final void resetRequirements() { + this.requirements = s -> true; + } + ///////////////// // Listability // ///////////////// @@ -305,7 +312,12 @@ public boolean hasCombinedArguments() { */ @SafeVarargs public final Impl combineWith(Argument... combinedArguments) { - this.combinedArguments.addAll(Arrays.asList(combinedArguments)); + for (Argument argument : combinedArguments) { + argument.resetRequirements(); + argument.withRequirement(this.requirements); + argument.withPermission(this.permission); + this.combinedArguments.add(argument); + } return instance(); } From e206166c914ed8acd746cf18780eddf185884c54 Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Fri, 6 Jan 2023 22:41:11 +0100 Subject: [PATCH 05/15] add documentation for the combineWith method --- .../commandapi/examples/java/Examples.java | 24 ++++++ .../commandapi/examples/kotlin/Examples.kt | 24 ++++++ .../examples/kotlin/ExamplesKotlinDSL.kt | 24 ++++++ docssrc/src/optional_arguments.md | 78 +++++++++++++++++++ 4 files changed, 150 insertions(+) diff --git a/commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java b/commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java index 8cc04e9e21..a989db160b 100644 --- a/commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java +++ b/commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java @@ -1330,6 +1330,30 @@ void resultingcommandexecutor3(){ /* ANCHOR_END: argumentsayhicmd2 */ } +{ +/* ANCHOR: argumentrate */ +new CommandAPICommand("rate") + .withOptionalArguments(new StringArgument("topic").combineWith(new IntegerArgument("rating", 0, 10))) + .withOptionalArguments(new PlayerArgument("target")) + .executes((sender, args) -> { + String topic = (String) args.get("topic"); + if(topic == null) { + sender.sendMessage("You didn't give a rating"); + return; + } + + // We know this is not null because rating is required if topic is given + int rating = (int) args.get("rating"); + + // The target player is optional, so give it a default here + CommandSender target = (CommandSender) args.getOrDefault("target", sender); + + target.sendMessage("Your " + topic + " was rated: " + rating + "/10"); + }) + .register(); +/* ANCHOR_END: argumentrate */ +} + @SuppressWarnings("unused") public void argumentCasting() { /* ANCHOR: argumentcasting */ diff --git a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt index 711b468c9e..2818fb46a2 100644 --- a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt +++ b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt @@ -1255,6 +1255,30 @@ CommandAPICommand("sayhi") /* ANCHOR_END: argumentsayhicmd2 */ } +fun ratecommand() { +/* ANCHOR: argumentrate */ +CommandAPICommand("rate") + .withOptionalArguments(StringArgument("topic").combineWith(IntegerArgument("rating", 0, 10))) + .withOptionalArguments(PlayerArgument("target")) + .executes(CommandExecutor { sender, args -> + val topic: String? = args["topic"] as String? + if (topic == null) { + sender.sendMessage("You didn't give a rating!") + return@CommandExecutor + } + + // We know this is not null because rating is required if topic is given + val rating = args["rating"] as Int + + // The target player is optional, so give it a default here + val target: CommandSender = args.getOrDefault("target", sender) as CommandSender + + target.sendMessage("Your $topic was rated: $rating/10") + }) + .register() +/* ANCHOR_END: argumentrate */ +} + @Suppress("unused") public fun argumentCasting() { /* ANCHOR: argumentcasting */ diff --git a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt index e308c73bd5..ffbe4f1e8e 100644 --- a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt +++ b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt @@ -1399,6 +1399,30 @@ commandAPICommand("sayhi") { /* ANCHOR_END: argumentsayhicmd2 */ } +fun ratecommand() { +/* ANCHOR: argumentrate */ +commandAPICommand("rate") { + argument(StringArgument("topic").setOptional(true).combineWith(IntegerArgument("rating", 0, 10))) + playerArgument("target", optional = true) + anyExecutor { sender, args -> + val topic: String? = args["topic"] as String? + if (topic == null) { + sender.sendMessage("You didn't give a rating!") + return@anyExecutor + } + + // We know this is not null because rating is required if topic is given + val rating = args["rating"] as Int + + // The target player is optional, so give it a default here + val target: CommandSender = args.getOrDefault("target", sender) as CommandSender + + target.sendMessage("Your $topic was rated: $rating/10") + } +} +/* ANCHOR_END: argumentrate */ +} + @Suppress("unused") fun argumentCasting() { /* ANCHOR: argumentcasting */ diff --git a/docssrc/src/optional_arguments.md b/docssrc/src/optional_arguments.md index dd5b73b3d3..f6eb0f0cb2 100644 --- a/docssrc/src/optional_arguments.md +++ b/docssrc/src/optional_arguments.md @@ -117,3 +117,81 @@ This is how the `getOrDefault` method is being implemented: + +## Implementing required arguments after optional arguments + +We've now talked about how to implement optional arguments and how to avoid null values returned by optional arguments when they aren't provided when executing the command. + +Now we also want to talk about how to implement required arguments after optional arguments. For this, the CommandAPI implements a `combineWith` method for arguments: + +```java +AbstractArgument combineWith(Argument... combinedArguments); +``` + +You will need to use this method if you want to have a required argument after an optional argument. In general, this is which pattern the CommandAPI follows while dealing with optional arguments: + +1. You have a `CommandAPICommand` and you add arguments to it. +2. After your required arguments, you can provide optional arguments. + +At this point your command is basically done. Any attempt to add a required argument will result in an `OptionalArgumentException`. However, this is where the `combineWith` method comes in. +This method allows you to combine arguments. Let's say you have an optional `StringArgument` (here simplified to `A`) and you want a required `PlayerArgument` (here simplified to `B`). +Argument `B` should only be required if argument `A` is given. To implement that logic, we are going to use the `combineWith` method so that we have this syntax: + +```java +A.combineWith(B) +``` + +This does two things: + +1. When checking optional argument constraints the argument `B` will be ignored so the `OptionalArgumentException` will not be thrown +2. It allows you to define additional optional arguments afterwards which can only be entered if argument `B` has been entered + +This is how you would add another optional `PlayerArgument` (here simplified to `C`): + +```java +new CommandAPICommand("mycommand") + .withOptionalArguments(A.combineWith(B)) + .withOptionalArguments(C) +``` + +Let's say you declare your arguments like this: + +```java +new CommandAPICommand("mycommand") + .withOptionalArguments(A.combineWith(B)) + .withArguments(C) +``` + +This would result in an `OptionalArgumentException` because you are declaring a required argument after an optional argument without creating that exception for argument `C` like you do for argument `B`. + +
+ +### Example - Required arguments after optional arguments + +We want to register a command `/rate` with the following syntax: + +```mccmd +/rate - Sends an information message +/rate - Rates a topic with a rating and sends a message to the command sender +/rate - Rates a topic with a rating and sends a message to the target +``` + +To implement that structure we make use of the `combineWith` method to make the argument after the optional argument \ required: + +
+ +```java,Java +{{#include ../../commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java:argumentrate}} +``` + +```kotlin,Kotlin +{{#include ../../commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt:argumentrate}} +``` + +```kotlin,Kotlin_DSL +{{#include ../../commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt:argumentrate}} +``` + +
+ +
From d340cd39c7230aa1834da400010d0ee89cb0503f Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Fri, 6 Jan 2023 23:28:10 +0100 Subject: [PATCH 06/15] fixing argument generation in AbstractCommandAPICommand#getArgumentsToRegister() --- .../commandapi/AbstractCommandAPICommand.java | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index b9fa4fef6c..baba33c850 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -325,22 +325,17 @@ private List getArgumentsToRegister(Argument[] argumentsArray) { // if optional arguments have been found if (firstOptionalArgumentIndex != -1) { for (int i = 0; i < argumentsArray.length; i++) { - if (i >= firstOptionalArgumentIndex - 1) { - if (!argumentsArray[i].hasCombinedArguments()) { - List arguments = new ArrayList<>(); - for (int j = 0; j <= i; j++) { - arguments.add(argumentsArray[j]); - if (argumentsArray[j].hasCombinedArguments()) { - arguments.addAll(argumentsArray[j].getCombinedArguments()); - } + if (i >= firstOptionalArgumentIndex) { + List arguments = new ArrayList<>(); + Argument[] argumentsWithoutCombined = (Argument[]) new AbstractArgument[i]; + System.arraycopy(argumentsArray, 0, argumentsWithoutCombined, 0, i); + for (Argument argument : argumentsWithoutCombined) { + arguments.add(argument); + if (argument.hasCombinedArguments()) { + arguments.addAll(argument.getCombinedArguments()); } - argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); - } else { - List linkedArguments = argumentsArray[i].getCombinedArguments(); - List arguments = new ArrayList<>(Arrays.asList(argumentsArray).subList(0, i + 1)); - arguments.addAll(linkedArguments); - argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); } + argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); } } } From 9b5b66365fa742fcd24577d6f1df4ce1fee20bab Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Fri, 6 Jan 2023 23:46:42 +0100 Subject: [PATCH 07/15] fix AbstractCommandAPICommand#getArgumentsToRegister() for real this time --- .../java/dev/jorel/commandapi/AbstractCommandAPICommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index baba33c850..4c23a394b2 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -324,7 +324,7 @@ private List getArgumentsToRegister(Argument[] argumentsArray) { // Create a List of arrays that hold arguments to register optional arguments // if optional arguments have been found if (firstOptionalArgumentIndex != -1) { - for (int i = 0; i < argumentsArray.length; i++) { + for (int i = 0; i <= argumentsArray.length; i++) { if (i >= firstOptionalArgumentIndex) { List arguments = new ArrayList<>(); Argument[] argumentsWithoutCombined = (Argument[]) new AbstractArgument[i]; From a598955cbbac1d0d5a1e1bfc88273524479cb85e Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Sat, 7 Jan 2023 12:02:02 +0100 Subject: [PATCH 08/15] handle chained calls of AbstractArgument#combineWith() correctly --- .../commandapi/AbstractCommandAPICommand.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 4c23a394b2..893e1d4ebc 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -330,10 +330,7 @@ private List getArgumentsToRegister(Argument[] argumentsArray) { Argument[] argumentsWithoutCombined = (Argument[]) new AbstractArgument[i]; System.arraycopy(argumentsArray, 0, argumentsWithoutCombined, 0, i); for (Argument argument : argumentsWithoutCombined) { - arguments.add(argument); - if (argument.hasCombinedArguments()) { - arguments.addAll(argument.getCombinedArguments()); - } + arguments.addAll(unpackCombinedArguments(argument)); } argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); } @@ -341,4 +338,16 @@ private List getArgumentsToRegister(Argument[] argumentsArray) { } return argumentsToRegister; } + + private List unpackCombinedArguments(Argument argument) { + if (!argument.hasCombinedArguments()) { + return List.of(argument); + } + List combinedArguments = new ArrayList<>(); + combinedArguments.add(argument); + for (Argument subArgument : argument.getCombinedArguments()) { + combinedArguments.addAll(unpackCombinedArguments(subArgument)); + } + return combinedArguments; + } } From b5f0ee9ff84f3a5124754644cd5b49b6a00c6103 Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Sat, 7 Jan 2023 12:19:54 +0100 Subject: [PATCH 09/15] make requirements and permissions copy values correctly --- .../commandapi/AbstractCommandAPICommand.java | 1 + .../commandapi/arguments/AbstractArgument.java | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 893e1d4ebc..6e5afdcc1d 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -346,6 +346,7 @@ private List unpackCombinedArguments(Argument argument) { List combinedArguments = new ArrayList<>(); combinedArguments.add(argument); for (Argument subArgument : argument.getCombinedArguments()) { + subArgument.copyPermissionsAndRequirements(argument); combinedArguments.addAll(unpackCombinedArguments(subArgument)); } return combinedArguments; diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 2bd0751a45..5b1014a8b6 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -313,9 +313,6 @@ public boolean hasCombinedArguments() { @SafeVarargs public final Impl combineWith(Argument... combinedArguments) { for (Argument argument : combinedArguments) { - argument.resetRequirements(); - argument.withRequirement(this.requirements); - argument.withPermission(this.permission); this.combinedArguments.add(argument); } return instance(); @@ -339,6 +336,18 @@ public List getEntityNames(Object argument) { return Arrays.asList(new String[]{null}); } + /** + * Copies permissions and requirements from the provided argument to this argument + * This also resets additional permissions and requirements. + * + * @param argument The argument to copy permissions and requirements from + */ + public void copyPermissionsAndRequirements(Argument argument) { + this.resetRequirements(); + this.withRequirement(argument.getRequirements()); + this.withPermission(argument.getArgumentPermission()); + } + @Override public String toString() { return this.getNodeName() + "<" + this.getClass().getSimpleName() + ">"; From 7e54622e76de2a2488c5675a85c154fdfb19416f Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Sat, 7 Jan 2023 12:21:57 +0100 Subject: [PATCH 10/15] removes an unused import in AbstractCommandAPICommand --- .../java/dev/jorel/commandapi/AbstractCommandAPICommand.java | 1 - 1 file changed, 1 deletion(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 6e5afdcc1d..0a36f42651 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.function.Predicate; From 162fd7601a8081c4955b3af85fe4f97704cc2fda Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Sat, 7 Jan 2023 12:24:14 +0100 Subject: [PATCH 11/15] improve a message for the combined arguments examples --- .../java/dev/jorel/commandapi/examples/java/Examples.java | 6 +++++- .../kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt | 6 +++++- .../jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java b/commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java index a989db160b..dda000c1ae 100644 --- a/commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java +++ b/commandapi-documentation-code/src/main/java/dev/jorel/commandapi/examples/java/Examples.java @@ -1338,7 +1338,11 @@ void resultingcommandexecutor3(){ .executes((sender, args) -> { String topic = (String) args.get("topic"); if(topic == null) { - sender.sendMessage("You didn't give a rating"); + sender.sendMessage( + "Usage: /rate (optional)", + "Select a topic to rate, then give a rating between 0 and 10", + "You can optionally add a player at the end to give the rating to" + ); return; } diff --git a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt index 2818fb46a2..341ed27c67 100644 --- a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt +++ b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/Examples.kt @@ -1263,7 +1263,11 @@ CommandAPICommand("rate") .executes(CommandExecutor { sender, args -> val topic: String? = args["topic"] as String? if (topic == null) { - sender.sendMessage("You didn't give a rating!") + sender.sendMessage( + "Usage: /rate (optional)", + "Select a topic to rate, then give a rating between 0 and 10", + "You can optionally add a player at the end to give the rating to" + ) return@CommandExecutor } diff --git a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt index ffbe4f1e8e..1ccb7b4781 100644 --- a/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt +++ b/commandapi-documentation-code/src/main/kotlin/dev/jorel/commandapi/examples/kotlin/ExamplesKotlinDSL.kt @@ -1407,7 +1407,11 @@ commandAPICommand("rate") { anyExecutor { sender, args -> val topic: String? = args["topic"] as String? if (topic == null) { - sender.sendMessage("You didn't give a rating!") + sender.sendMessage( + "Usage: /rate (optional)", + "Select a topic to rate, then give a rating between 0 and 10", + "You can optionally add a player at the end to give the rating to" + ) return@anyExecutor } From 858097f5b131c9bec7e79176e82683c6afb5b99e Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Sat, 7 Jan 2023 12:27:30 +0100 Subject: [PATCH 12/15] improve JavaDocs for AbstractArgument#combineWith() --- .../java/dev/jorel/commandapi/arguments/AbstractArgument.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 5b1014a8b6..5a18a767ef 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -304,10 +304,10 @@ public boolean hasCombinedArguments() { } /** - * Adds linked arguments to this argument. Linked arguments are used to have required arguments after optional arguments + * Adds combined arguments to this argument. Combined arguments are used to have required arguments after optional arguments * by ignoring they exist until they are added to the arguments array for registration * - * @param combinedArguments The arguments to link + * @param combinedArguments The arguments to combine to this argument * @return this current argument */ @SafeVarargs From 6f0878e848599286013c6b86d0c69b5c323fe328 Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Sat, 7 Jan 2023 15:27:52 +0100 Subject: [PATCH 13/15] optimize AbstractCommandAPICommand#getArgumentsToRegister(), modify OptionalArgumentException --- .../commandapi/AbstractCommandAPICommand.java | 56 +++++++++---------- .../arguments/AbstractArgument.java | 2 +- .../exceptions/OptionalArgumentException.java | 2 +- 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 0a36f42651..44f98fce11 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -29,13 +29,15 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; import java.util.function.Predicate; /** * A builder used to create commands to be registered by the CommandAPI. - * @param The class extending this class, used as the return type for chain calls - * @param The implementation of AbstractArgument used by the class extending this class + * + * @param The class extending this class, used as the return type for chain calls + * @param The implementation of AbstractArgument used by the class extending this class * @param The CommandSender class used by the class extending this class */ public abstract class AbstractCommandAPICommand, @@ -91,7 +93,7 @@ public final Impl withArguments(Argument... args) { /** * Appends the optional arguments to the current command builder. - * + *

* This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional * * @param args A List that represents the arguments that this @@ -108,7 +110,7 @@ public Impl withOptionalArguments(List args) { /** * Appends the optional arguments to the current command builder. - * + *

* This also calls {@link AbstractArgument#setOptional(boolean)} on each argument to make sure they are optional * * @param args Arguments that this command can accept @@ -303,38 +305,32 @@ public Impl copy() { private List getArgumentsToRegister(Argument[] argumentsArray) { List argumentsToRegister = new ArrayList<>(); + List currentCommand = new ArrayList<>(); - // Check optional argument constraints - // They can only be at the end, no required argument can follow an optional argument - // This method also ignores linked arguments - int firstOptionalArgumentIndex = -1; - for (int i = 0, optionalArgumentIndex = -1; i < argumentsArray.length; i++) { - if (argumentsArray[i].isOptional()) { - if (firstOptionalArgumentIndex == -1) { - firstOptionalArgumentIndex = i; - } - optionalArgumentIndex = i; - } else if (optionalArgumentIndex != -1) { - // Argument is not optional - throw new OptionalArgumentException(meta.commandName); + Iterator argumentIterator = List.of(argumentsArray).iterator(); + + // Collect all required arguments, adding them as a command once finding the first optional + while(argumentIterator.hasNext()) { + Argument next = argumentIterator.next(); + if(next.isOptional()) { + argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0])); + currentCommand.addAll(unpackCombinedArguments(next)); + break; } + currentCommand.addAll(unpackCombinedArguments(next)); } - // Create a List of arrays that hold arguments to register optional arguments - // if optional arguments have been found - if (firstOptionalArgumentIndex != -1) { - for (int i = 0; i <= argumentsArray.length; i++) { - if (i >= firstOptionalArgumentIndex) { - List arguments = new ArrayList<>(); - Argument[] argumentsWithoutCombined = (Argument[]) new AbstractArgument[i]; - System.arraycopy(argumentsArray, 0, argumentsWithoutCombined, 0, i); - for (Argument argument : argumentsWithoutCombined) { - arguments.addAll(unpackCombinedArguments(argument)); - } - argumentsToRegister.add(arguments.toArray((Argument[]) new AbstractArgument[0])); - } + // Collect the optional arguments, adding each one as a valid command + while (argumentIterator.hasNext()) { + Argument next = argumentIterator.next(); + if(!next.isOptional()) { + throw new OptionalArgumentException(meta.commandName); // non-optional argument after optional } + argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0])); + currentCommand.addAll(unpackCombinedArguments(next)); } + // All the arguments expanded, also handles when there are no optional arguments + argumentsToRegister.add((Argument[]) currentCommand.toArray(new AbstractArgument[0])); return argumentsToRegister; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index 5a18a767ef..caa1447ab6 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -228,7 +228,7 @@ public final Impl withRequirement(Predicate requirement) { /** * Resets the requirements for this command */ - public final void resetRequirements() { + void resetRequirements() { this.requirements = s -> true; } diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/OptionalArgumentException.java b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/OptionalArgumentException.java index 8dc480b529..fac87a56c6 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/OptionalArgumentException.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/exceptions/OptionalArgumentException.java @@ -3,7 +3,7 @@ public class OptionalArgumentException extends RuntimeException { public OptionalArgumentException(String commandName) { - super("Failed to register command /" + commandName + " because a required argument cannot follow an optional argument!"); + super("Failed to register command /" + commandName + " because an unlinked required argument cannot follow an optional argument!"); } } From 7a90e778aed3d3989ba612d32f3f1c884be6d929 Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Sat, 7 Jan 2023 15:47:23 +0100 Subject: [PATCH 14/15] remove an unnecessary check in AbstractCommandAPICommand#register --- .../dev/jorel/commandapi/AbstractCommandAPICommand.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java index 44f98fce11..28e593c09d 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java @@ -275,12 +275,8 @@ public void register() { if (executor.hasAnyExecutors()) { // Need to cast handler to the right CommandSender type so that argumentsArray and executor are accepted CommandAPIHandler handler = (CommandAPIHandler) CommandAPIHandler.getInstance(); - if (argumentsToRegister.isEmpty()) { - handler.register(meta, argumentsArray, executor, isConverted); - } else { - for (Argument[] arguments : argumentsToRegister) { - handler.register(meta, arguments, executor, isConverted); - } + for (Argument[] arguments : argumentsToRegister) { + handler.register(meta, arguments, executor, isConverted); } } From af681d72a5150a226a2400d4133c723a78d5f3f2 Mon Sep 17 00:00:00 2001 From: DerEchtePilz Date: Sat, 7 Jan 2023 16:01:27 +0100 Subject: [PATCH 15/15] improve JavaDocs of AbstractArgument#combineWith() --- .../dev/jorel/commandapi/arguments/AbstractArgument.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java index caa1447ab6..b041029ae9 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/AbstractArgument.java @@ -305,7 +305,10 @@ public boolean hasCombinedArguments() { /** * Adds combined arguments to this argument. Combined arguments are used to have required arguments after optional arguments - * by ignoring they exist until they are added to the arguments array for registration + * by ignoring they exist until they are added to the arguments array for registration. + * + * This method also causes permissions and requirements from this argument to be copied over to the arguments you want to combine + * this argument with. Their permissions and requirements will be ignored. * * @param combinedArguments The arguments to combine to this argument * @return this current argument