8000 Initial implementation of `FlagsArgument` (and other changes) · CommandAPI/CommandAPI@22c2ffa · GitHub
[go: up one dir, main page]

Skip to content

Commit 22c2ffa

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 fbc2c5e commit 22c2ffa

File tree

26 files changed

+1305
-325
lines changed

26 files changed

+1305
-325
lines changed

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 <S 10000 ource> 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
8000
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 = argum E96D ent.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 & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import com.mojang.brigadier.exceptions.CommandSyntaxException;
2828
import com.mojang.brigadier.suggestion.SuggestionProvider;
2929
import com.mojang.brigadier.suggestion.Suggestions;
30+
import com.mojang.brigadier.tree.ArgumentCommandNode;
31+
import com.mojang.brigadier.tree.CommandNode;
3032
import com.mojang.brigadier.tree.LiteralCommandNode;
3133
import dev.jorel.commandapi.arguments.*;
3234
import dev.jorel.commandapi.commandsenders.AbstractCommandSender;
@@ -51,24 +53,35 @@
5153
* @param <Source> The class for running Brigadier commands
5254
*/
5355
@RequireField(in = CommandContext.class, name = "arguments", ofType = Map.class)
56+
@RequireField(in = CommandNode.class, name = "children", ofType = Map.class)
57+
@RequireField(in = CommandNode.class, name = "literals", ofType = Map.class)
58+
@RequireField(in = CommandNode.class, name = "arguments", ofType = Map.class)
5459
public class CommandAPIHandler<Argument
5560
/// @cond DOX
5661
extends AbstractArgument<?, ?, Argument, CommandSender>
5762
/// @endcond
5863
, CommandSender, Source> {
64+
// TODO: Need to ensure this can be safely "disposed of" when we're done (e.g. on reloads).
65+
// I hiiiiiiighly doubt we're storing class caches of classes that can be unloaded at runtime,
66+
// but this IS a generic class caching system and we don't want derpy memory leaks
67+
private static final Map<ClassCache, Field> FIELDS;
68+
5969
private static final SafeVarHandle<CommandContext<?>, Map<String, ParsedArgument<?, ?>>> commandContextArguments;
70+
// VarHandle seems incapable of setting final fields, so we have to use Field here
71+
private static final Field commandNodeChildren;
72+
private static final Field commandNodeLiterals;
73+
private static final Field commandNodeArguments;
6074

61-
// Compute all var handles all in one go so we don't do this during main server
62-
// runtime
75+
// Compute all var handles all in one go so we don't do this during main server runtime
6376
static {
77+
FIELDS = new HashMap<>();
78+
6479
commandContextArguments = SafeVarHandle.ofOrNull(CommandContext.class, "arguments", "arguments", Map.class);
80+
commandNodeChildren = CommandAPIHandler.getField(CommandNode.class, "children");
81+
commandNodeLiterals = CommandAPIHandler.getField(CommandNode.class, "literals");
82+
commandNodeArguments = CommandAPIHandler.getField(CommandNode.class, "arguments");
6583
}
6684

67-
// TODO: Need to ensure this can be safely "disposed of" when we're done (e.g. on reloads).
68-
// I hiiiiiiighly doubt we're storing class caches of classes that can be unloaded at runtime,
69-
// but this IS a generic class caching system and we don't want derpy memory leaks
70-
private static final Map<ClassCache, Field> FIELDS = new HashMap<>();
71-
7285
final CommandAPIPlatform<Argument, CommandSender, Source> platform;
7386
final List<RegisteredCommand> registeredCommands; // Keep track of what has been registered for type checking
7487
final Map<List<String>, Previewable<?, ?>> previewableArguments; // Arguments with previewable chat
@@ -132,7 +145,7 @@ public CommandAPIPlatform<Argument, CommandSender, Source> getPlatform() {
132145
// SECTION: Creating commands //
133146
////////////////////////////////
134147

135-
void registerCommand(ExecutableCommand<?, CommandSender> command) {
148+
public void registerCommand(ExecutableCommand<?, CommandSender> command) {
136149
platform.preCommandRegistration(command.getName());
137150

138151
List<RegisteredCommand> registeredCommandInformation = RegisteredCommand.fromExecutableCommand(command);
@@ -422,6 +435,10 @@ public Predicate<Source> generateBrigadierRequirements(CommandPermission permiss
422435
};
423436
}
424437

438+
////////////////////////////////
439+
// SECTION: Brigadier Helpers //
440+
////////////////////////////////
441+
425442
public void writeDispatcherToFile() {
426443
File file = CommandAPI.getConfiguration().getDispatcherFile();
427444
if (file != null) {
@@ -443,6 +460,54 @@ public void writeDispatcherToFile() {
443460
}
444461
}
445462

463+
public static <Source> Map<String, CommandNode<Source>> getCommandNodeChildren(CommandNode<Source> target) {
464+
try {
465+
return (Map<String, CommandNode<Source>>) commandNodeChildren.get(target);
466+
} catch (IllegalAccessException e) {
467+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
468+
}
469+
}
470+
471+
public static <Source> void setCommandNodeChildren(CommandNode<Source> target, Map<String, CommandNode<Source>> children) {
472+
try {
473+
commandNodeChildren.set(target, children);
474+
} catch (IllegalAccessException e) {
475+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
476+
}
477+
}
478+
479+
public static <Source> Map<String, LiteralCommandNode<Source>> getCommandNodeLiterals(CommandNode<Source> target) {
480+
try {
481+
return (Map<String, LiteralCommandNode<Source>>) commandNodeLiterals.get(target);
482+
} catch (IllegalAccessException e) {
483+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
484+
}
485+
}
486+
487+
public static <Source> void setCommandNodeLiterals(CommandNode<Source> target, Map<String, LiteralCommandNode<Source>> literals) {
488+
try {
489+
commandNodeLiterals.set(target, literals);
490+
} catch (IllegalAccessException e) {
491+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
492+
}
493+
}
494+
495+
public static <Source> Map<String, ArgumentCommandNode<Source, ?>> getCommandNodeArguments(CommandNode<Source> target) {
496+
try {
497+
return (Map<String, ArgumentCommandNode<Source, ?>>) commandNodeArguments.get(target);
498+
} catch (IllegalAccessException e) {
499+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
500+
}
501+
}
502+
503+
public static <Source> void setCommandNodeArguments(CommandNode<Source> target, Map<String, ArgumentCommandNode<Source, ?>> arguments) {
504+
try {
505+
commandNodeArguments.set(target, arguments);
506+
} catch (IllegalAccessException e) {
507+
throw new IllegalStateException("This shouldn't happen. The field should be accessible.", e);
508+
}
509+
}
510+
446511
////////////////////////////////
447512
// SECTION: Parsing arguments //
448513
////////////////////////////////
@@ -496,7 +561,7 @@ public static <CommandSource> String getRawArgumentInput(CommandContext<CommandS
496561
* @return an CommandArguments object which can be used in (sender, args) ->
497562
* @throws CommandSyntaxException If an argument is improperly formatted and cannot be parsed
498563
*/
499-
CommandArguments argsToCommandArgs(CommandContext<Source> cmdCtx, List<Argument> args) throws CommandSyntaxException {
564+
public CommandArguments argsToCommandArgs(CommandContext<Source> cmdCtx, List<Argument> args) throws CommandSyntaxException {
500565
// Array for arguments for executor
501566
List<Object> argList = new ArrayList<>();
502567

@@ -538,7 +603,7 @@ CommandArguments argsToCommandArgs(CommandContext<Source> cmdCtx, List<Argument>
538603
* @return the Argument's corresponding object
539604
* @throws CommandSyntaxException when the input for the argument isn't formatted correctly
540605
*/
541-
Object parseArgument(CommandContext<Source> cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException {
606+
public Object parseArgument(CommandContext<Source> cmdCtx, String key, Argument value, CommandArguments previousArgs) throws CommandSyntaxException {
542607
if (value.isListed()) {
543608
return value.parseArgument(cmdCtx, key, previousArgs);
544609
} else {

0 commit comments

Comments
 (0)
0