2020-07-10 16:37:18 +02:00
|
|
|
package net.minestom.server.command.builder;
|
|
|
|
|
2021-06-20 22:32:06 +02:00
|
|
|
import com.github.benmanes.caffeine.cache.Cache;
|
|
|
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
2021-01-15 22:04:57 +01:00
|
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
|
2020-07-14 13:35:07 +02:00
|
|
|
import net.minestom.server.command.CommandSender;
|
2020-07-10 16:37:18 +02:00
|
|
|
import net.minestom.server.command.builder.arguments.Argument;
|
2021-01-08 03:07:37 +01:00
|
|
|
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
|
2021-02-11 04:18:19 +01:00
|
|
|
import net.minestom.server.command.builder.parser.CommandParser;
|
2021-03-10 19:14:24 +01:00
|
|
|
import net.minestom.server.command.builder.parser.CommandQueryResult;
|
2021-02-11 04:18:19 +01:00
|
|
|
import net.minestom.server.command.builder.parser.CommandSuggestionHolder;
|
|
|
|
import net.minestom.server.command.builder.parser.ValidSyntaxHolder;
|
2021-05-15 08:31:24 +02:00
|
|
|
import net.minestom.server.utils.StringUtils;
|
2020-10-29 19:51:10 +01:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
2020-10-28 01:29:05 +01:00
|
|
|
import org.jetbrains.annotations.Nullable;
|
2020-07-10 16:37:18 +02:00
|
|
|
|
|
|
|
import java.util.*;
|
2021-02-22 11:49:28 +01:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2020-07-10 16:37:18 +02:00
|
|
|
|
2020-10-15 14:36:21 +02:00
|
|
|
/**
|
|
|
|
* Class responsible for parsing {@link Command}.
|
|
|
|
*/
|
2020-07-14 13:35:07 +02:00
|
|
|
public class CommandDispatcher {
|
2020-07-10 16:37:18 +02:00
|
|
|
|
2020-10-15 14:36:21 +02:00
|
|
|
private final Map<String, Command> commandMap = new HashMap<>();
|
2021-02-10 00:24:23 +01:00
|
|
|
private final Set<Command> commands = new HashSet<>();
|
2020-07-10 16:37:18 +02:00
|
|
|
|
2021-06-20 22:32:06 +02:00
|
|
|
private final Cache<String, CommandResult> cache = Caffeine.newBuilder()
|
2021-02-22 11:49:28 +01:00
|
|
|
.expireAfterWrite(30, TimeUnit.SECONDS)
|
|
|
|
.build();
|
|
|
|
|
2020-11-14 01:39:51 +01:00
|
|
|
/**
|
|
|
|
* Registers a command,
|
|
|
|
* be aware that registering a command name or alias will override the previous entry.
|
|
|
|
*
|
|
|
|
* @param command the command to register
|
|
|
|
*/
|
2020-10-29 19:51:10 +01:00
|
|
|
public void register(@NotNull Command command) {
|
2020-08-04 06:14:42 +02:00
|
|
|
this.commandMap.put(command.getName().toLowerCase(), command);
|
2020-10-29 19:51:10 +01:00
|
|
|
|
|
|
|
// Register aliases
|
|
|
|
final String[] aliases = command.getAliases();
|
|
|
|
if (aliases != null) {
|
2021-02-09 18:24:50 +01:00
|
|
|
for (String alias : aliases) {
|
2020-10-29 19:51:10 +01:00
|
|
|
this.commandMap.put(alias.toLowerCase(), command);
|
|
|
|
}
|
2020-07-10 16:37:18 +02:00
|
|
|
}
|
2021-02-10 00:24:23 +01:00
|
|
|
|
|
|
|
this.commands.add(command);
|
2020-07-10 16:37:18 +02:00
|
|
|
}
|
|
|
|
|
2020-11-24 22:56:12 +01:00
|
|
|
public void unregister(@NotNull Command command) {
|
2020-12-12 06:13:50 +01:00
|
|
|
this.commandMap.remove(command.getName().toLowerCase());
|
|
|
|
|
|
|
|
final String[] aliases = command.getAliases();
|
|
|
|
if (aliases != null) {
|
|
|
|
for (String alias : aliases) {
|
|
|
|
this.commandMap.remove(alias.toLowerCase());
|
|
|
|
}
|
2020-11-06 16:03:08 +01:00
|
|
|
}
|
2021-02-10 00:24:23 +01:00
|
|
|
|
|
|
|
this.commands.remove(command);
|
2021-04-12 22:45:45 +02:00
|
|
|
|
|
|
|
// Clear cache
|
|
|
|
this.cache.invalidateAll();
|
2020-11-06 16:03:08 +01:00
|
|
|
}
|
|
|
|
|
2021-08-19 09:06:24 +02:00
|
|
|
public @NotNull Set<Command> getCommands() {
|
2021-02-13 00:34:25 +01:00
|
|
|
return Collections.unmodifiableSet(commands);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the command class associated with the name.
|
|
|
|
*
|
|
|
|
* @param commandName the command name
|
|
|
|
* @return the {@link Command} associated with the name, null if not any
|
|
|
|
*/
|
2021-08-19 09:06:24 +02:00
|
|
|
public @Nullable Command findCommand(@NotNull String commandName) {
|
2021-02-13 00:34:25 +01:00
|
|
|
commandName = commandName.toLowerCase();
|
|
|
|
return commandMap.getOrDefault(commandName, null);
|
|
|
|
}
|
|
|
|
|
2021-02-11 00:04:42 +01:00
|
|
|
/**
|
|
|
|
* Checks if the command exists, and execute it.
|
|
|
|
*
|
|
|
|
* @param source the command source
|
|
|
|
* @param commandString the command with the argument(s)
|
|
|
|
* @return the command result
|
|
|
|
*/
|
2021-08-19 09:06:24 +02:00
|
|
|
public @NotNull CommandResult execute(@NotNull CommandSender source, @NotNull String commandString) {
|
2021-02-11 00:04:42 +01:00
|
|
|
CommandResult commandResult = parse(commandString);
|
|
|
|
ParsedCommand parsedCommand = commandResult.parsedCommand;
|
|
|
|
if (parsedCommand != null) {
|
2021-03-17 05:06:09 +01:00
|
|
|
commandResult.commandData = parsedCommand.execute(source);
|
2021-02-11 00:04:42 +01:00
|
|
|
}
|
|
|
|
return commandResult;
|
|
|
|
}
|
|
|
|
|
2020-08-03 06:36:42 +02:00
|
|
|
/**
|
2020-10-29 19:51:10 +01:00
|
|
|
* Parses the given command.
|
2020-08-03 06:36:42 +02:00
|
|
|
*
|
|
|
|
* @param commandString the command (containing the command name and the args if any)
|
2021-02-11 00:04:42 +01:00
|
|
|
* @return the parsing result
|
2020-08-03 06:36:42 +02:00
|
|
|
*/
|
2021-08-19 09:06:24 +02:00
|
|
|
public @NotNull CommandResult parse(@NotNull String commandString) {
|
2020-07-10 16:37:18 +02:00
|
|
|
commandString = commandString.trim();
|
2021-02-22 11:49:28 +01:00
|
|
|
// Verify if the result is cached
|
|
|
|
{
|
|
|
|
final CommandResult cachedResult = cache.getIfPresent(commandString);
|
|
|
|
if (cachedResult != null) {
|
|
|
|
return cachedResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-10 16:37:18 +02:00
|
|
|
// Split space
|
2021-01-08 03:07:37 +01:00
|
|
|
final String[] parts = commandString.split(StringUtils.SPACE);
|
2020-10-29 19:51:10 +01:00
|
|
|
final String commandName = parts[0];
|
2020-07-10 16:37:18 +02:00
|
|
|
|
2022-01-23 23:30:57 +01:00
|
|
|
final CommandQueryResult commandQueryResult = CommandParser.findCommand(this, commandString);
|
2020-08-03 06:36:42 +02:00
|
|
|
// Check if the command exists
|
2021-03-10 19:14:24 +01:00
|
|
|
if (commandQueryResult == null) {
|
2021-02-11 03:00:13 +01:00
|
|
|
return CommandResult.of(CommandResult.Type.UNKNOWN, commandName);
|
2021-02-11 00:04:42 +01:00
|
|
|
}
|
|
|
|
CommandResult result = new CommandResult();
|
2021-02-11 03:00:13 +01:00
|
|
|
result.input = commandString;
|
2021-02-22 09:42:48 +01:00
|
|
|
// Find the used syntax and fill CommandResult#type and CommandResult#parsedCommand
|
2022-01-23 23:30:57 +01:00
|
|
|
findParsedCommand( commandQueryResult, commandName, commandString, result);
|
2021-02-22 11:49:28 +01:00
|
|
|
|
|
|
|
// Cache result
|
2021-08-19 09:06:24 +02:00
|
|
|
this.cache.put(commandString, result);
|
2021-02-22 11:49:28 +01:00
|
|
|
|
2021-02-11 00:04:42 +01:00
|
|
|
return result;
|
2020-07-10 16:37:18 +02:00
|
|
|
}
|
|
|
|
|
2022-01-23 23:30:57 +01:00
|
|
|
private @NotNull ParsedCommand findParsedCommand(@NotNull CommandQueryResult commandQueryResult,
|
|
|
|
@NotNull String commandName,
|
2021-08-19 09:06:24 +02:00
|
|
|
@NotNull String commandString,
|
|
|
|
@NotNull CommandResult result) {
|
2022-01-23 23:30:57 +01:00
|
|
|
final Command command = commandQueryResult.command();
|
|
|
|
String[] args = commandQueryResult.args();
|
2021-02-20 08:59:15 +01:00
|
|
|
final boolean hasArgument = args.length > 0;
|
|
|
|
|
2021-03-10 06:38:51 +01:00
|
|
|
final String input = commandName + StringUtils.SPACE + String.join(StringUtils.SPACE, args);
|
|
|
|
|
2021-02-11 00:04:42 +01:00
|
|
|
ParsedCommand parsedCommand = new ParsedCommand();
|
2022-01-23 23:30:57 +01:00
|
|
|
parsedCommand.parents = commandQueryResult.parents();
|
2021-02-11 00:04:42 +01:00
|
|
|
parsedCommand.command = command;
|
2021-03-17 05:06:09 +01:00
|
|
|
parsedCommand.commandString = commandString;
|
2020-07-10 16:37:18 +02:00
|
|
|
|
2020-10-29 19:51:10 +01:00
|
|
|
// The default executor should be used if no argument is provided
|
2021-08-19 09:06:24 +02:00
|
|
|
if (!hasArgument) {
|
|
|
|
Optional<CommandSyntax> optionalSyntax = command.getSyntaxes()
|
|
|
|
.stream()
|
|
|
|
.filter(syntax -> syntax.getArguments().length == 0)
|
|
|
|
.findFirst();
|
|
|
|
|
|
|
|
if (optionalSyntax.isPresent()) {
|
|
|
|
// Empty syntax found
|
|
|
|
final CommandSyntax syntax = optionalSyntax.get();
|
|
|
|
parsedCommand.syntax = syntax;
|
|
|
|
parsedCommand.executor = syntax.getExecutor();
|
|
|
|
parsedCommand.context = new CommandContext(input);
|
|
|
|
|
|
|
|
result.type = CommandResult.Type.SUCCESS;
|
|
|
|
result.parsedCommand = parsedCommand;
|
|
|
|
return parsedCommand;
|
|
|
|
} else {
|
|
|
|
// No empty syntax, use default executor if any
|
|
|
|
final CommandExecutor defaultExecutor = command.getDefaultExecutor();
|
|
|
|
if (defaultExecutor != null) {
|
|
|
|
parsedCommand.executor = defaultExecutor;
|
2021-03-12 22:10:03 +01:00
|
|
|
parsedCommand.context = new CommandContext(input);
|
|
|
|
|
|
|
|
result.type = CommandResult.Type.SUCCESS;
|
|
|
|
result.parsedCommand = parsedCommand;
|
|
|
|
return parsedCommand;
|
|
|
|
}
|
2021-02-11 00:04:42 +01:00
|
|
|
}
|
2020-07-10 16:37:18 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 19:51:10 +01:00
|
|
|
// SYNTAXES PARSING
|
2020-07-10 16:37:18 +02:00
|
|
|
|
2020-10-29 19:51:10 +01:00
|
|
|
// All the registered syntaxes of the command
|
2020-08-03 06:36:42 +02:00
|
|
|
final Collection<CommandSyntax> syntaxes = command.getSyntaxes();
|
2020-10-29 19:51:10 +01:00
|
|
|
// Contains all the fully validated syntaxes (we later find the one with the most amount of arguments)
|
2021-02-10 00:24:23 +01:00
|
|
|
List<ValidSyntaxHolder> validSyntaxes = new ArrayList<>(syntaxes.size());
|
2020-10-29 19:51:10 +01:00
|
|
|
// Contains all the syntaxes that are not fully correct, used to later, retrieve the "most correct syntax"
|
|
|
|
// Number of correct argument - The data about the failing argument
|
2021-01-15 22:04:57 +01:00
|
|
|
Int2ObjectRBTreeMap<CommandSuggestionHolder> syntaxesSuggestions = new Int2ObjectRBTreeMap<>(Collections.reverseOrder());
|
2020-07-10 16:37:18 +02:00
|
|
|
|
|
|
|
for (CommandSyntax syntax : syntaxes) {
|
2021-03-10 20:19:29 +01:00
|
|
|
CommandParser.parse(syntax, syntax.getArguments(), args, commandString, validSyntaxes, syntaxesSuggestions);
|
2020-07-10 16:37:18 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 19:51:10 +01:00
|
|
|
// Check if there is at least one correct syntax
|
2020-10-28 01:02:12 +01:00
|
|
|
if (!validSyntaxes.isEmpty()) {
|
2021-03-10 06:38:51 +01:00
|
|
|
CommandContext context = new CommandContext(input);
|
2020-10-28 01:02:12 +01:00
|
|
|
// Search the syntax with all perfect args
|
2021-03-10 06:38:51 +01:00
|
|
|
final ValidSyntaxHolder finalValidSyntax = CommandParser.findMostCorrectSyntax(validSyntaxes, context);
|
2021-02-11 04:47:48 +01:00
|
|
|
if (finalValidSyntax != null) {
|
2020-10-29 19:51:10 +01:00
|
|
|
// A fully correct syntax has been found, use it
|
2022-01-23 23:30:57 +01:00
|
|
|
final CommandSyntax syntax = finalValidSyntax.syntax();
|
2021-02-11 04:47:48 +01:00
|
|
|
|
|
|
|
parsedCommand.syntax = syntax;
|
|
|
|
parsedCommand.executor = syntax.getExecutor();
|
2021-03-10 06:38:51 +01:00
|
|
|
parsedCommand.context = context;
|
2021-02-22 09:42:48 +01:00
|
|
|
|
|
|
|
result.type = CommandResult.Type.SUCCESS;
|
|
|
|
result.parsedCommand = parsedCommand;
|
2021-02-11 00:04:42 +01:00
|
|
|
return parsedCommand;
|
2020-10-28 01:02:12 +01:00
|
|
|
}
|
2020-07-10 16:37:18 +02:00
|
|
|
}
|
|
|
|
|
2020-10-29 19:51:10 +01:00
|
|
|
// No all-correct syntax, find the closest one to use the argument callback
|
2021-08-19 09:06:24 +02:00
|
|
|
if (!syntaxesSuggestions.isEmpty()) {
|
|
|
|
final int max = syntaxesSuggestions.firstIntKey(); // number of correct arguments in the most correct syntax
|
|
|
|
final CommandSuggestionHolder suggestionHolder = syntaxesSuggestions.get(max);
|
2022-01-23 23:30:57 +01:00
|
|
|
final CommandSyntax syntax = suggestionHolder.syntax();
|
|
|
|
final ArgumentSyntaxException argumentSyntaxException = suggestionHolder.argumentSyntaxException();
|
|
|
|
final int argIndex = suggestionHolder.argIndex();
|
2021-08-19 09:06:24 +02:00
|
|
|
|
|
|
|
// Found the closest syntax with at least 1 correct argument
|
|
|
|
final Argument<?> argument = syntax.getArguments()[argIndex];
|
|
|
|
if (argument.hasErrorCallback() && argumentSyntaxException != null) {
|
|
|
|
parsedCommand.callback = argument.getCallback();
|
|
|
|
parsedCommand.argumentSyntaxException = argumentSyntaxException;
|
|
|
|
|
|
|
|
result.type = CommandResult.Type.INVALID_SYNTAX;
|
|
|
|
result.parsedCommand = parsedCommand;
|
|
|
|
return parsedCommand;
|
2020-07-10 16:37:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-11 00:33:55 +01:00
|
|
|
// No syntax found
|
2021-02-22 09:42:48 +01:00
|
|
|
result.type = CommandResult.Type.INVALID_SYNTAX;
|
2021-03-10 06:38:51 +01:00
|
|
|
result.parsedCommand = ParsedCommand.withDefaultExecutor(command, input);
|
2021-02-22 09:42:48 +01:00
|
|
|
return result.parsedCommand;
|
2020-07-10 16:37:18 +02:00
|
|
|
}
|
|
|
|
}
|