Skip to content

Commit 13bf2c4

Browse files
committed
Initial implementation of FlagsArgument (and other changes)
Changes: - Implemented `FlagsArgument` (#483) - Moved var handles for `CommandNode` `children`,`literals`, and `arguments` to `CommandAPIHandler` - Added `FlagsArgumentCommon` FlagsArgumentRootNode` and `FlagsArgumentEndingNode` to handle the special node structure and parsing required - Updated `CustomArgument` - All `AbstractArgument` builder methods are delegated to the base argument - Replaced `CommandAPIExecutor` parameter of `AbstractArgument#addArgumentNodes` to a `Function` to allow object that hold arguments to better control how those arguments are executed - Added package `dev.jorel.commandapi.commandnodes` for class that extend `CommandNode` and related classes - Tweaked some exceptions - `GreedyArgumentException` - Changed because the `FlagsArgument` is sometimes greedy - only greedy iff it has no terminal branches - Greedy arguments are now detected when `AbstractArgument#addArgumentNodes` returns an empty list - Tweaked the exception message - `DuplicateNodeNameException` - Changed because literal arguments can conflict with other nodes if they are listed - Now thrown when two listed arguments have the same node name - Added `UnnamedArgumentCommandNode` to make sure unlisted arguments do not conflict - Renamed `MultiLiteralCommandNode` to `NamedLiteralCommandNode` for use by listed `Literal` arguments - Tweaked the exception message TODO: - Clean up code - Add tests - Remove test commands in CommandAPIMain - Add javadocs and documentation - Hope Mojang/brigadier#144 is resolved, otherwise be annoyed :(
1 parent c0260f6 commit 13bf2c4

26 files changed

Lines changed: 1301 additions & 317 deletions

File tree

commandapi-core/src/main/java/dev/jorel/commandapi/AbstractArgumentTree.java

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import com.mojang.brigadier.tree.CommandNode;
44
import dev.jorel.commandapi.arguments.AbstractArgument;
5-
import dev.jorel.commandapi.arguments.GreedyArgument;
6-
import dev.jorel.commandapi.exceptions.GreedyArgumentException;
75
import dev.jorel.commandapi.exceptions.MissingCommandExecutorException;
86

97
import java.util.ArrayList;
@@ -125,55 +123,34 @@ public List<List<String>> getBranchesAsStrings() {
125123
/**
126124
* Builds the Brigadier {@link CommandNode} structure for this argument tree.
127125
*
128-
* @param previousNodes A List of {@link CommandNode}s to add this argument onto.
129-
* @param previousArguments A List of CommandAPI arguments that came before this argument tree.
130-
* @param previousNonLiteralArgumentNames A List of Strings containing the node names that came before this argument.
131-
* @param <Source> The Brigadier Source object for running commands.
126+
* @param previousNodes A List of {@link CommandNode}s to add this argument onto.
127+
* @param previousArguments A List of CommandAPI arguments that came before this argument tree.
128+
* @param previousArgumentNames A List of Strings containing the node names that came before this argument.
129+
* @param <Source> The Brigadier Source object for running commands.
132130
*/
133131
public <Source> void buildBrigadierNode(
134132
List<CommandNode<Source>> previousNodes,
135-
List<Argument> previousArguments, List<String> previousNonLiteralArgumentNames
133+
List<Argument> previousArguments, List<String> previousArgumentNames
136134
) {
135+
CommandAPIHandler<Argument, CommandSender, Source> handler = CommandAPIHandler.getInstance();
136+
137137
// Check preconditions
138-
if (argument instanceof GreedyArgument && !arguments.isEmpty()) {
139-
// Argument is followed by at least some arguments
140-
throw new GreedyArgumentException(previousArguments, argument, getBranchesAsList());
141-
}
142138
if (!executor.hasAnyExecutors() && arguments.isEmpty()) {
143139
// If we don't have any executors then no branches is bad because this path can't be run at all
144140
throw new MissingCommandExecutorException(previousArguments, argument);
145141
}
146142

147143
// Create node for this argument
148-
previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousNonLiteralArgumentNames, executor);
144+
previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames,
145+
executor.hasAnyExecutors() ? args -> handler.generateBrigadierCommand(args, executor) : null);
149146

150147
// Add our branches as children to the node
151148
for (AbstractArgumentTree<?, Argument, CommandSender> child : arguments) {
152149
// We need a new list for each branch of the tree
153150
List<Argument> newPreviousArguments = new ArrayList<>(previousArguments);
154-
List<String> newPreviousArgumentNames = new ArrayList<>(previousNonLiteralArgumentNames);
151+
List<String> newPreviousArgumentNames = new ArrayList<>(previousArgumentNames);
155152

156153
child.buildBrigadierNode(previousNodes, newPreviousArguments, newPreviousArgumentNames);
157154
}
158155
}
159-
160-
/**
161-
* @return A list of paths that represent the possible branches of this argument tree as Argument objects.
162-
*/
163-
protected List<List<Argument>> getBranchesAsList() {
164-
if (arguments.isEmpty()) return List.of(List.of());
165-
166-
List<List<Argument>> branchesList = new ArrayList<>();
167-
168-
for (AbstractArgumentTree<?, Argument, CommandSender> branch : arguments) {
169-
for (List<Argument> subBranchList : branch.getBranchesAsList()) {
170-
List<Argument> newBranchList = new ArrayList<>();
171-
newBranchList.add(branch.argument);
172-
newBranchList.addAll(subBranchList);
173-
branchesList.add(newBranchList);
174-
}
175-
}
176-
177-
return branchesList;
178-
}
179156
}

commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandAPICommand.java

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@
2020
*******************************************************************************/
2121
package dev.jorel.commandapi;
2222

23+
import com.mojang.brigadier.Command;
2324
import com.mojang.brigadier.tree.CommandNode;
2425
import com.mojang.brigadier.tree.LiteralCommandNode;
2526
import dev.jorel.commandapi.arguments.AbstractArgument;
26-
import dev.jorel.commandapi.arguments.GreedyArgument;
27-
import dev.jorel.commandapi.exceptions.GreedyArgumentException;
2827
import dev.jorel.commandapi.exceptions.MissingCommandExecutorException;
2928
import dev.jorel.commandapi.exceptions.OptionalArgumentException;
3029

3130
import java.util.ArrayList;
3231
import java.util.Arrays;
3332
import java.util.List;
33+
import java.util.function.Function;
3434

3535
/**
3636
* A builder used to create commands to be registered by the CommandAPI.
@@ -317,30 +317,19 @@ protected <Source> void createArgumentNodes(LiteralCommandNode<Source> rootNode)
317317
previousArguments.add(commandNames);
318318

319319
// Add required arguments
320+
Function<List<Argument>, Command<Source>> executorCreator = executor.hasAnyExecutors() ?
321+
args -> handler.generateBrigadierCommand(args, executor) : null;
320322
for (int i = 0; i < requiredArguments.size(); i++) {
321323
Argument argument = requiredArguments.get(i);
322324
previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames,
323325
// Only the last required argument is executable
324-
i == requiredArguments.size() - 1 ? executor : null);
326+
i == requiredArguments.size() - 1 ? executorCreator : null);
325327
}
326328

327329
// Add optional arguments
328330
for (Argument argument : optionalArguments) {
329331
// All optional arguments are executable
330-
previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, executor);
331-
}
332-
333-
// Check greedy argument constraint
334-
// We need to check it down here so that all the combined arguments are properly considered after unpacking
335-
for (int i = 0; i < previousArguments.size() - 1 /* Minus one since we don't need to check last argument */; i++) {
336-
Argument argument = previousArguments.get(i);
337-
if (argument instanceof GreedyArgument) {
338-
throw new GreedyArgumentException(
339-
previousArguments.subList(0, i), // Arguments before this
340-
argument,
341-
List.of(previousArguments.subList(i + 1, previousArguments.size())) // Arguments after this
342-
);
343-
}
332+
previousNodes = argument.addArgumentNodes(previousNodes, previousArguments, previousArgumentNames, executorCreator);
344333
}
345334
}
346335

commandapi-core/src/main/java/dev/jorel/commandapi/AbstractCommandTree.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,10 @@ protected <Source> void createArgumentNodes(LiteralCommandNode<Source> rootNode)
120120
for (AbstractArgumentTree<?, Argument, CommandSender> argument : arguments) {
121121
// We need new previousArguments lists for each branch so they don't interfere
122122
List<Argument> previousArguments = new ArrayList<>();
123-
List<String> previousNonLiteralArgumentNames = new ArrayList<>();
123+
List<String> previousArgumentNames = new ArrayList<>();
124124
previousArguments.add(commandNames);
125125

126-
argument.buildBrigadierNode(List.of(rootNode), previousArguments, previousNonLiteralArgumentNames);
126+
argument.buildBrigadierNode(List.of(rootNode), previousArguments, previousArgumentNames);
127127
}
128128
}
129129
}

commandapi-core/src/main/java/dev/jorel/commandapi/CommandAPIHandler.java

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import com.mojang.brigadier.suggestion.SuggestionProvider;
4141
import com.mojang.brigadier.tree.CommandNode;
4242
import com.mojang.brigadier.suggestion.Suggestions;
43+
import com.mojang.brigadier.tree.ArgumentCommandNode;
4344
import com.mojang.brigadier.tree.LiteralCommandNode;
4445
import dev.jorel.commandapi.arguments.*;
4546
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
@@ -59,24 +60,35 @@
5960
* @param <Source> The class for running Brigadier commands
6061
*/
6162
@RequireField(in = CommandContext.class, name = "arguments", ofType = Map.class)
63+
@RequireField(in = CommandNode.class, name = "children", ofType = Map.class)
64+
@RequireField(in = CommandNode.class, name = "literals", ofType = Map.class)
65+
@RequireField(in = CommandNode.class, name = "arguments", ofType = Map.class)
6266
public class CommandAPIHandler<Argument
6367
/// @cond DOX
6468
extends AbstractArgument<?, ?, Argument, CommandSender>
6569
/// @endcond
6670
, CommandSender, Source> {
71+
// TODO: Need to ensure this can be safely "disposed of" when we're done (e.g. on reloads).
72+
// I hiiiiiiighly doubt we're storing class caches of classes that can be unloaded at runtime,
73+
// but this IS a generic class caching system and we don't want derpy memory leaks
74+
private static final Map<ClassCache, Field> FIELDS;
75+
6776
private static final SafeVarHandle<CommandContext<?>, Map<String, ParsedArgument<?, ?>>> commandContextArguments;
77+
// VarHandle seems incapable of setting final fields, so we have to use Field here
78+
private static final Field commandNodeChildren;
79+
private static final Field commandNodeLiterals;
80+
private static final Field commandNodeArguments;
6881

69-
// Compute all var handles all in one go so we don't do this during main server
70-
// runtime
82+
// Compute all var handles all in one go so we don't do this during main server runtime
7183
static {
84+
FIELDS = new HashMap<>();
85+
7286
commandContextArguments = SafeVarHandle.ofOrNull(CommandContext.class, "arguments", "arguments", Map.class);
87+
commandNodeChildren = CommandAPIHandler.getField(CommandNode.class, "children");
88+
commandNodeLiterals = CommandAPIHandler.getField(CommandNode.class, "literals");
89+
commandNodeArguments = CommandAPIHandler.getField(CommandNode.class, "arguments");
7390
}
7491

75-
// TODO: Need to ensure this can be safely "disposed of" when we're done (e.g. on reloads).
76-
// I hiiiiiiighly doubt we're storing class caches of classes that can be unloaded at runtime,
77-
// but this IS a generic class caching system and we don't want derpy memory leaks
78-
private static final Map<ClassCache, Field> FIELDS = new HashMap<>();
79-
8092
final CommandAPIPlatform<Argument, CommandSender, Source> platform;
8193
final List<RegisteredCommand> registeredCommands; // Keep track of what has been registered for type checking
8294
final Map<List<String>, Previewable<?, ?>> previewableArguments; // Arguments with previewable chat
@@ -141,7 +153,7 @@ public CommandAPIPlatform<Argument, CommandSender, Source> getPlatform() {
141153
// SECTION: Creating commands //
142154
////////////////////////////////
143155

144-
void registerCommand(ExecutableCommand<?, CommandSender> command, String namespace) {
156+
public void registerCommand(ExecutableCommand<?, CommandSender> command, String namespace) {
145157
platform.preCommandRegistration(command.getName());
146158

147159
List<RegisteredCommand> registeredCommandInformation = RegisteredCommand.fromExecutableCommand(command, namespace);
@@ -431,6 +443,10 @@ public Predicate<Source> generateBrigadierRequirements(CommandPermission permiss
431443
};
432444
}
433445

446+
////////////////////////////////
447+
// SECTION: Brigadier Helpers //
448+
////////////////////////////////
449+
434450
public void writeDispatcherToFile() {
435451
File file = CommandAPI.getConfiguration().getDispatcherFile();
436452
if (file != null) {
@@ -452,7 +468,7 @@ public void writeDispatcherToFile() {
452468
}
453469
}
454470

455-
LiteralCommandNode<Source> namespaceNode(LiteralCommandNode<Source> original, String namespace) {
471+
public LiteralCommandNode<Source> namespaceNode(LiteralCommandNode<Source> original, String namespace) {
456472
// Adapted from a section of `CraftServer#syncCommands`
457473
LiteralCommandNode<Source> clone = new LiteralCommandNode<>(
458474
namespace + ":" + original.getLiteral(),
@@ -469,6 +485,54 @@ LiteralCommandNode<Source> namespaceNode(LiteralCommandNode<Source> original, St
469485
return clone;
470486
}
471487

488+
public static <Source> Map<String, CommandNode<Source>> getCommandNodeChildren(CommandNode<Source> target) {
489+
try {
490+
return (Map<String, CommandNode<Source>>) commandNodeChildren.get(target);
491+
} catch (IllegalAccessException e) {
492+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
493+
}
494+
}
495+
496+
public static <Source> void setCommandNodeChildren(CommandNode<Source> target, Map<String, CommandNode<Source>> children) {
497+
try {
498+
commandNodeChildren.set(target, children);
499+
} catch (IllegalAccessException e) {
500+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
501+
}
502+
}
503+
504+
public static <Source> Map<String, LiteralCommandNode<Source>> getCommandNodeLiterals(CommandNode<Source> target) {
505+
try {
506+
return (Map<String, LiteralCommandNode<Source>>) commandNodeLiterals.get(target);
507+
} catch (IllegalAccessException e) {
508+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
509+
}
510+
}
511+
512+
public static <Source> void setCommandNodeLiterals(CommandNode<Source> target, Map<String, LiteralCommandNode<Source>> literals) {
513+
try {
514+
commandNodeLiterals.set(target, literals);
515+
} catch (IllegalAccessException e) {
516+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
517+
}
518+
}
519+
520+
public static <Source> Map<String, ArgumentCommandNode<Source, ?>> getCommandNodeArguments(CommandNode<Source> target) {
521+
try {
522+
return (Map<String, ArgumentCommandNode<Source, ?>>) commandNodeArguments.get(target);
523+
} catch (IllegalAccessException e) {
524+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
525+
}
526+
}
527+
528+
public static <Source> void setCommandNodeArguments(CommandNode<Source> target, Map<String, ArgumentCommandNode<Source, ?>> arguments) {
529+
try {
530+
commandNodeArguments.set(target, arguments);
531+
} catch (IllegalAccessException e) {
532+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
533+
}
534+
}
535+
472536
////////////////////////////////
473537
// SECTION: Parsing arguments //
474538
////////////////////////////////
@@ -522,7 +586,7 @@ public static <CommandSource> String getRawArgumentInput(CommandContext<CommandS
522586
* @return an CommandArguments object which can be used in (sender, args) ->
523587
* @throws CommandSyntaxException If an argument is improperly formatted and cannot be parsed
524588
*/
525-
CommandArguments argsToCommandArgs(CommandContext<Source> cmdCtx, List<Argument> args) throws CommandSyntaxException {
589+
public CommandArguments argsToCommandArgs(CommandContext<Source> cmdCtx, List<Argument> args) throws CommandSyntaxException {
526590
// Array for arguments for executor
527591
List<Object> argList = new ArrayList<>();
528592

@@ -564,7 +628,7 @@ CommandArguments argsToCommandArgs(CommandContext<Source> cmdCtx, List<Argument>
564628
* @return the Argument's corresponding object
565629
* @throws CommandSyntaxException when the input for the argument isn't formatted correctly
566630
*/
567-
Object parseArgument(CommandContext<Source> cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException {
631+
public Object parseArgument(CommandContext<Source> cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException {
568632
if (value.isListed()) {
569633
return value.parseArgument(cmdCtx, key, previousArgs);
570634
} else {

0 commit comments

Comments
 (0)