Initial cleanup attempt

Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
TheMode 2021-10-15 08:01:40 +02:00
parent 0bd519c894
commit 5ce7c6ccdb
9 changed files with 117 additions and 179 deletions

View File

@ -214,7 +214,7 @@ public final class CommandManager {
}
final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult.command,
commandQueryResult.args, input, false, true, syntax -> true, argument -> true);
commandQueryResult.content(), input, false, true, syntax -> true, argument -> true);
if (queryResult == null) {
// Invalid argument, return command node (default to root)
final int commandNode = commandIdentityMap.getOrDefault(commandQueryResult.command, 0);

View File

@ -7,7 +7,6 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CommandData {
private final Map<String, Object> dataMap = new ConcurrentHashMap<>();
public CommandData set(@NotNull String key, Object value) {
@ -15,8 +14,7 @@ public class CommandData {
return this;
}
@Nullable
public <T> T get(@NotNull String key) {
public <T> @Nullable T get(@NotNull String key) {
return (T) dataMap.get(key);
}
@ -24,8 +22,7 @@ public class CommandData {
return dataMap.containsKey(key);
}
@NotNull
public Map<String, Object> getDataMap() {
public @NotNull Map<String, Object> getDataMap() {
return dataMap;
}
}

View File

@ -20,11 +20,9 @@ import java.util.concurrent.TimeUnit;
/**
* Class responsible for parsing {@link Command}.
*/
public class CommandDispatcher {
public final class CommandDispatcher {
private final Map<String, Command> commandMap = new HashMap<>();
private final Set<Command> commands = new HashSet<>();
private final Cache<String, CommandResult> cache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.build();
@ -37,7 +35,6 @@ public class CommandDispatcher {
*/
public void register(@NotNull Command command) {
this.commandMap.put(command.getName().toLowerCase(), command);
// Register aliases
final String[] aliases = command.getAliases();
if (aliases != null) {
@ -45,20 +42,17 @@ public class CommandDispatcher {
this.commandMap.put(alias.toLowerCase(), command);
}
}
this.commands.add(command);
}
public void unregister(@NotNull Command command) {
this.commandMap.remove(command.getName().toLowerCase());
final String[] aliases = command.getAliases();
if (aliases != null) {
for (String alias : aliases) {
this.commandMap.remove(alias.toLowerCase());
}
}
this.commands.remove(command);
// Clear cache
@ -76,8 +70,7 @@ public class CommandDispatcher {
* @return the {@link Command} associated with the name, null if not any
*/
public @Nullable Command findCommand(@NotNull String commandName) {
commandName = commandName.toLowerCase();
return commandMap.getOrDefault(commandName, null);
return commandMap.getOrDefault(commandName.toLowerCase(), null);
}
/**
@ -90,9 +83,7 @@ public class CommandDispatcher {
public @NotNull CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
CommandResult commandResult = parse(commandString);
ParsedCommand parsedCommand = commandResult.parsedCommand;
if (parsedCommand != null) {
commandResult.commandData = parsedCommand.execute(source);
}
if (parsedCommand != null) commandResult.commandData = parsedCommand.execute(source);
return commandResult;
}
@ -117,16 +108,14 @@ public class CommandDispatcher {
final String commandName = parts[0];
final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString);
// Check if the command exists
if (commandQueryResult == null) {
return CommandResult.of(CommandResult.Type.UNKNOWN, commandName);
}
final Command command = commandQueryResult.command;
CommandResult result = new CommandResult();
result.input = commandString;
// Find the used syntax and fill CommandResult#type and CommandResult#parsedCommand
findParsedCommand(command, commandName, commandQueryResult.args, commandString, result);
findParsedCommand(commandQueryResult, result);
// Cache result
this.cache.put(commandString, result);
@ -134,32 +123,17 @@ public class CommandDispatcher {
return result;
}
private @Nullable ParsedCommand findParsedCommand(@NotNull Command command,
@NotNull String commandName, @NotNull String[] args,
@NotNull String commandString,
@NotNull CommandResult result) {
final boolean hasArgument = args.length > 0;
// Search for subcommand
if (hasArgument) {
final String firstArgument = args[0];
for (Command subcommand : command.getSubcommands()) {
if (Command.isValidName(subcommand, firstArgument)) {
return findParsedCommand(subcommand,
firstArgument, Arrays.copyOfRange(args, 1, args.length),
commandString, result);
}
}
}
final String input = commandName + StringUtils.SPACE + String.join(StringUtils.SPACE, args);
private void findParsedCommand(@NotNull CommandQueryResult queryResult,
@NotNull CommandResult result) {
final Command command = queryResult.command();
final String commandString = queryResult.input();
ParsedCommand parsedCommand = new ParsedCommand();
parsedCommand.command = command;
parsedCommand.commandString = commandString;
// The default executor should be used if no argument is provided
if (!hasArgument) {
if (queryResult.argsInput().isEmpty()) {
Optional<CommandSyntax> optionalSyntax = command.getSyntaxes()
.stream()
.filter(syntax -> syntax.getArguments().length == 0)
@ -170,21 +144,21 @@ public class CommandDispatcher {
final CommandSyntax syntax = optionalSyntax.get();
parsedCommand.syntax = syntax;
parsedCommand.executor = syntax.getExecutor();
parsedCommand.context = new CommandContext(input);
parsedCommand.context = new CommandContext(commandString);
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
return;
} else {
// No empty syntax, use default executor if any
final CommandExecutor defaultExecutor = command.getDefaultExecutor();
if (defaultExecutor != null) {
parsedCommand.executor = defaultExecutor;
parsedCommand.context = new CommandContext(input);
parsedCommand.context = new CommandContext(commandString);
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
return;
}
}
}
@ -200,12 +174,12 @@ public class CommandDispatcher {
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
for (CommandSyntax syntax : syntaxes) {
CommandParser.parse(syntax, syntax.getArguments(), args, commandString, validSyntaxes, syntaxesSuggestions);
CommandParser.parse(syntax, syntax.getArguments(), queryResult, validSyntaxes, syntaxesSuggestions);
}
// Check if there is at least one correct syntax
if (!validSyntaxes.isEmpty()) {
CommandContext context = new CommandContext(input);
CommandContext context = new CommandContext(commandString);
// Search the syntax with all perfect args
final ValidSyntaxHolder finalValidSyntax = CommandParser.findMostCorrectSyntax(validSyntaxes, context);
if (finalValidSyntax != null) {
@ -218,7 +192,7 @@ public class CommandDispatcher {
result.type = CommandResult.Type.SUCCESS;
result.parsedCommand = parsedCommand;
return parsedCommand;
return;
}
}
@ -238,13 +212,12 @@ public class CommandDispatcher {
result.type = CommandResult.Type.INVALID_SYNTAX;
result.parsedCommand = parsedCommand;
return parsedCommand;
return;
}
}
// No syntax found
result.type = CommandResult.Type.INVALID_SYNTAX;
result.parsedCommand = ParsedCommand.withDefaultExecutor(command, input);
return result.parsedCommand;
result.parsedCommand = ParsedCommand.withDefaultExecutor(command, commandString);
}
}

View File

@ -5,7 +5,6 @@ import net.minestom.server.command.builder.NodeMaker;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.command.builder.parser.CommandParser;
import net.minestom.server.command.builder.parser.ValidSyntaxHolder;
import net.minestom.server.utils.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -26,14 +25,12 @@ public class ArgumentGroup extends Argument<CommandContext> {
@Override
public CommandContext parse(@NotNull String input) throws ArgumentSyntaxException {
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>();
CommandParser.parse(null, group, input.split(StringUtils.SPACE), input, validSyntaxes, null);
CommandParser.parse(null, group, input, input, validSyntaxes, null);
CommandContext context = new CommandContext(input);
CommandParser.findMostCorrectSyntax(validSyntaxes, context);
if (validSyntaxes.isEmpty()) {
if (validSyntaxes.isEmpty())
throw new ArgumentSyntaxException("Invalid arguments", input, INVALID_ARGUMENTS_ERROR);
}
return context;
}

View File

@ -2,7 +2,10 @@ package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.arguments.*;
import net.minestom.server.command.builder.arguments.minecraft.*;
import net.minestom.server.command.builder.arguments.minecraft.registry.*;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentEnchantment;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentEntityType;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentParticle;
import net.minestom.server.command.builder.arguments.minecraft.registry.ArgumentPotionEffect;
import net.minestom.server.command.builder.arguments.number.ArgumentDouble;
import net.minestom.server.command.builder.arguments.number.ArgumentFloat;
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
@ -41,10 +44,10 @@ public class ArgumentParser {
ARGUMENT_FUNCTION_MAP.put("time", ArgumentTime::new);
ARGUMENT_FUNCTION_MAP.put("enchantment", ArgumentEnchantment::new);
ARGUMENT_FUNCTION_MAP.put("particle", ArgumentParticle::new);
ARGUMENT_FUNCTION_MAP.put("resourceLocation", ArgumentResourceLocation::new);
ARGUMENT_FUNCTION_MAP.put("resourcelocation", ArgumentResourceLocation::new);
ARGUMENT_FUNCTION_MAP.put("potion", ArgumentPotionEffect::new);
ARGUMENT_FUNCTION_MAP.put("entityType", ArgumentEntityType::new);
ARGUMENT_FUNCTION_MAP.put("blockState", ArgumentBlockState::new);
ARGUMENT_FUNCTION_MAP.put("entitytype", ArgumentEntityType::new);
ARGUMENT_FUNCTION_MAP.put("blockstate", ArgumentBlockState::new);
ARGUMENT_FUNCTION_MAP.put("intrange", ArgumentIntRange::new);
ARGUMENT_FUNCTION_MAP.put("floatrange", ArgumentFloatRange::new);
@ -66,7 +69,6 @@ public class ArgumentParser {
@ApiStatus.Experimental
public static @NotNull Argument<?>[] generate(@NotNull String format) {
List<Argument<?>> result = new ArrayList<>();
// 0 = no state
// 1 = inside angle bracket <>
int state = 0;
@ -75,11 +77,9 @@ public class ArgumentParser {
Function<String, Argument<?>> argumentFunction = null;
StringBuilder builder = new StringBuilder();
// test: Integer<name> String<hey>
for (int i = 0; i < format.length(); i++) {
char c = format.charAt(i);
final char c = format.charAt(i);
// No state
if (state == 0) {
if (c == ' ') {
@ -133,17 +133,15 @@ public class ArgumentParser {
result.add(new ArgumentLiteral(argument));
}
}
return result.toArray(Argument[]::new);
}
@Nullable
public static ArgumentResult validate(@NotNull Argument<?> argument,
@NotNull Argument<?>[] arguments, int argIndex,
@NotNull String[] inputArguments, int inputIndex) {
final boolean end = inputIndex == inputArguments.length;
if (end) // Stop if there is no input to analyze left
return null;
public static @Nullable ArgumentResult validate(@NotNull Argument<?> argument,
@NotNull Argument<?>[] arguments, int argIndex,
@NotNull String input, int inputIndex) {
final String[] inputArguments = input.split(StringUtils.SPACE);
// Stop if there is no input to analyze left
if (inputIndex == inputArguments.length) return null;
// the parsed argument value, null if incorrect
Object parsedValue = null;
@ -162,13 +160,10 @@ public class ArgumentParser {
// Argument is supposed to take the rest of the command input
for (int i = inputIndex; i < inputArguments.length; i++) {
final String arg = inputArguments[i];
if (builder.length() > 0)
builder.append(StringUtils.SPACE);
if (builder.length() > 0) builder.append(StringUtils.SPACE);
builder.append(arg);
}
rawArg = builder.toString();
try {
parsedValue = argument.parse(rawArg);
correct = true;
@ -181,29 +176,22 @@ public class ArgumentParser {
StringBuilder builder = new StringBuilder();
for (int i = inputIndex; i < inputArguments.length; i++) {
builder.append(inputArguments[i]);
rawArg = builder.toString();
try {
parsedValue = argument.parse(rawArg);
// Prevent quitting the parsing too soon if the argument
// does not allow space
final boolean lastArgumentIteration = argIndex + 1 == arguments.length;
if (lastArgumentIteration && i + 1 < inputArguments.length) {
if (!argument.allowSpace())
break;
if (!argument.allowSpace()) break;
builder.append(StringUtils.SPACE);
continue;
}
correct = true;
inputIndex = i + 1;
break;
} catch (ArgumentSyntaxException exception) {
argumentSyntaxException = exception;
if (!argument.allowSpace()) {
// rawArg should be the remaining
for (int j = i + 1; j < inputArguments.length; j++) {
@ -247,5 +235,4 @@ public class ArgumentParser {
// If correct
public Object parsedValue;
}
}

View File

@ -11,7 +11,10 @@ import net.minestom.server.utils.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import static net.minestom.server.command.builder.parser.ArgumentParser.validate;
@ -19,51 +22,43 @@ import static net.minestom.server.command.builder.parser.ArgumentParser.validate
/**
* Class used to parse complete command inputs.
*/
public class CommandParser {
public final class CommandParser {
private static final CommandManager COMMAND_MANAGER = MinecraftServer.getCommandManager();
@Nullable
public static CommandQueryResult findCommand(@Nullable Command parentCommand, @NotNull String commandName, @NotNull String[] args) {
public static @Nullable CommandQueryResult findCommand(@Nullable Command parentCommand, @NotNull String commandName,
@NotNull String argsInput, @NotNull String input) {
Command command = parentCommand == null ? COMMAND_MANAGER.getDispatcher().findCommand(commandName) : parentCommand;
if (command == null) {
return null;
}
CommandQueryResult commandQueryResult = new CommandQueryResult();
commandQueryResult.command = command;
commandQueryResult.commandName = commandName;
commandQueryResult.args = args;
if (command == null) return null;
// Search for subcommand
if (args.length > 0) {
final String subCommandName = args[0];
final int nextArgIndex = argsInput.indexOf(StringUtils.SPACE);
if (nextArgIndex != -1) {
final String subCommandName = argsInput.substring(0, nextArgIndex);
for (Command subcommand : command.getSubcommands()) {
if (Command.isValidName(subcommand, subCommandName)) {
final String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
commandQueryResult.command = subcommand;
commandQueryResult.commandName = subCommandName;
commandQueryResult.args = subArgs;
return findCommand(subcommand, subCommandName, subArgs);
final String updated = StringUtils.trimLeft(argsInput.replaceFirst(commandName, ""));
return findCommand(subcommand, subCommandName, updated, input);
}
}
}
return commandQueryResult;
return new CommandQueryResult(command, argsInput, input);
}
@Nullable
public static CommandQueryResult findCommand(@NotNull String input) {
final String[] parts = input.split(StringUtils.SPACE);
final String commandName = parts[0];
String[] args = new String[parts.length - 1];
System.arraycopy(parts, 1, args, 0, args.length);
return CommandParser.findCommand(null, commandName, args);
public static @Nullable CommandQueryResult findCommand(@NotNull String input) {
final String commandName;
final String content;
final int commandNameEnd = input.indexOf(StringUtils.SPACE);
if (commandNameEnd != -1) {
commandName = input.substring(0, commandNameEnd);
content = StringUtils.trimLeft(input.replaceFirst(commandName, ""));
} else {
commandName = input;
content = "";
}
return CommandParser.findCommand(null, commandName, content, input);
}
public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument<?>[] commandArguments, @NotNull String[] inputArguments,
@NotNull String commandString,
public static void parse(@Nullable CommandSyntax syntax, @NotNull Argument<?>[] commandArguments,
@NotNull CommandQueryResult queryResult,
@Nullable List<ValidSyntaxHolder> validSyntaxes,
@Nullable Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions) {
final Map<Argument<?>, ArgumentParser.ArgumentResult> argumentValueMap = new HashMap<>();
@ -76,10 +71,8 @@ public class CommandParser {
// Check the validity of the arguments...
for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) {
final Argument<?> argument = commandArguments[argIndex];
ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, inputArguments, inputIndex);
if (argumentResult == null) {
break;
}
ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, queryResult.argsInput(), inputIndex);
if (argumentResult == null) break;
// Update local var
useRemaining = argumentResult.useRemaining;
@ -107,7 +100,7 @@ public class CommandParser {
if (commandArguments.length == argumentValueMap.size() || useRemaining) {
if (validSyntaxes != null) {
ValidSyntaxHolder validSyntaxHolder = new ValidSyntaxHolder();
validSyntaxHolder.commandString = commandString;
validSyntaxHolder.commandString = queryResult.input();
validSyntaxHolder.syntax = syntax;
validSyntaxHolder.argumentResults = argumentValueMap;
@ -125,27 +118,20 @@ public class CommandParser {
* @param context the recipient of the argument parsed values
* @return the command syntax with all of its arguments correct and with the most arguments count, null if not any
*/
@Nullable
public static ValidSyntaxHolder findMostCorrectSyntax(@NotNull List<ValidSyntaxHolder> validSyntaxes,
@NotNull CommandContext context) {
if (validSyntaxes.isEmpty()) {
return null;
}
public static @Nullable ValidSyntaxHolder findMostCorrectSyntax(@NotNull List<ValidSyntaxHolder> validSyntaxes,
@NotNull CommandContext context) {
if (validSyntaxes.isEmpty()) return null;
ValidSyntaxHolder finalSyntax = null;
int maxArguments = 0;
CommandContext finalContext = null;
for (ValidSyntaxHolder validSyntaxHolder : validSyntaxes) {
final Map<Argument<?>, ArgumentParser.ArgumentResult> argsValues = validSyntaxHolder.argumentResults;
final int argsSize = argsValues.size();
// Check if the syntax has more valid arguments
if (argsSize > maxArguments) {
finalSyntax = validSyntaxHolder;
maxArguments = argsSize;
// Fill arguments map
finalContext = new CommandContext(validSyntaxHolder.commandString);
for (var entry : argsValues.entrySet()) {
@ -155,30 +141,21 @@ public class CommandParser {
}
}
}
// Get the arguments values
if (finalSyntax != null) {
context.copy(finalContext);
}
if (finalSyntax != null) context.copy(finalContext);
return finalSyntax;
}
@Nullable
public static ArgumentQueryResult findEligibleArgument(@NotNull Command command, String[] args, String commandString,
boolean trailingSpace, boolean forceCorrect,
Predicate<CommandSyntax> syntaxPredicate,
Predicate<Argument<?>> argumentPredicate) {
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
public static @Nullable ArgumentQueryResult findEligibleArgument(@NotNull CommandQueryResult result,
boolean trailingSpace, boolean forceCorrect,
Predicate<CommandSyntax> syntaxPredicate,
Predicate<Argument<?>> argumentPredicate) {
final String[] args = result.argsInput().split(StringUtils.SPACE);
Int2ObjectRBTreeMap<ArgumentQueryResult> suggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
for (CommandSyntax syntax : result.command().getSyntaxes()) {
if (!syntaxPredicate.test(syntax)) continue;
for (CommandSyntax syntax : syntaxes) {
if (!syntaxPredicate.test(syntax)) {
continue;
}
final CommandContext context = new CommandContext(commandString);
final CommandContext context = new CommandContext(result.input());
final Argument<?>[] commandArguments = syntax.getArguments();
int inputIndex = 0;
@ -187,7 +164,7 @@ public class CommandParser {
int maxArgIndex = 0;
for (int argIndex = 0; argIndex < commandArguments.length; argIndex++) {
Argument<?> argument = commandArguments[argIndex];
ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, args, inputIndex);
ArgumentParser.ArgumentResult argumentResult = validate(argument, commandArguments, argIndex, result.argsInput(), inputIndex);
if (argumentResult == null) {
// Nothing to analyze, create a dummy object
argumentResult = new ArgumentParser.ArgumentResult();
@ -219,29 +196,17 @@ public class CommandParser {
}
// Don't compute following arguments if the syntax is incorrect
if (!argumentResult.correct) {
break;
}
if (!argumentResult.correct) break;
// Don't compute unrelated arguments
final boolean isLast = inputIndex == args.length;
if (isLast && !trailingSpace) {
break;
}
}
if (maxArg != null) {
suggestions.put(maxArgIndex, maxArg);
if (isLast && !trailingSpace) break;
}
if (maxArg != null) suggestions.put(maxArgIndex, maxArg);
}
if (suggestions.isEmpty()) {
// No suggestion
return null;
}
if (suggestions.isEmpty()) return null;
final int max = suggestions.firstIntKey();
return suggestions.get(max);
}
}

View File

@ -2,8 +2,26 @@ package net.minestom.server.command.builder.parser;
import net.minestom.server.command.builder.Command;
public class CommandQueryResult {
public Command command;
public String commandName;
public String[] args;
public final class CommandQueryResult {
private final Command command;
private final String argsInput;
private final String input;
public CommandQueryResult(Command command, String argsInput, String input) {
this.command = command;
this.argsInput = argsInput;
this.input = input;
}
public Command command() {
return command;
}
public String argsInput() {
return argsInput;
}
public String input() {
return input;
}
}

View File

@ -27,13 +27,8 @@ public class TabCompleteListener {
String args = commandString.replaceFirst(Pattern.quote(commandName), "");
final CommandQueryResult commandQueryResult = CommandParser.findCommand(commandString);
if (commandQueryResult == null) {
// Command not found
return;
}
final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult.command,
commandQueryResult.args, commandString, text.endsWith(StringUtils.SPACE), false,
if (commandQueryResult == null) return;
final ArgumentQueryResult queryResult = CommandParser.findEligibleArgument(commandQueryResult, text.endsWith(StringUtils.SPACE), false,
CommandSyntax::hasSuggestion, Argument::hasSuggestion);
if (queryResult == null) {
// Suggestible argument not found

View File

@ -21,6 +21,12 @@ public class StringUtils {
return count;
}
public static String trimLeft(String input) {
int i = 0;
while (i < input.length() && Character.isWhitespace(input.charAt(i))) i++;
return input.substring(i);
}
/**
* Applies the Jaro-Winkler distance algorithm to the given strings, providing information about the
* similarity of them.