From ec9009d776bda239c405028e58ac42292905d277 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Fri, 11 Dec 2015 23:16:13 +0100 Subject: [PATCH] =?UTF-8?q?Separate=20command=20handler=20into=20smaller?= =?UTF-8?q?=20parts=20(wip=20=E2=80=93=20doesn't=20compile)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move logic from CommandMapper to CommandHandler - Create "ResultStatus" on FoundCommandResult to precisely return the error case Naming and structure not final --- .../authme/command/CommandDescription.java | 353 +----------------- .../xephi/authme/command/CommandHandler.java | 273 +++++++------- .../xephi/authme/command/CommandMapper.java | 128 ------- 3 files changed, 139 insertions(+), 615 deletions(-) delete mode 100644 src/main/java/fr/xephi/authme/command/CommandMapper.java diff --git a/src/main/java/fr/xephi/authme/command/CommandDescription.java b/src/main/java/fr/xephi/authme/command/CommandDescription.java index 9114ff1a2..a523dfb4a 100644 --- a/src/main/java/fr/xephi/authme/command/CommandDescription.java +++ b/src/main/java/fr/xephi/authme/command/CommandDescription.java @@ -13,6 +13,8 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import static com.google.common.base.Objects.firstNonNull; + /** * Command description - defines which labels ("names") will lead to a command and points to the * {@link ExecutableCommand} implementation that executes the logic of the command. @@ -99,38 +101,8 @@ public class CommandDescription { return instance; } - /** - * Get the label most similar to the reference. The first label will be returned if no reference was supplied. - * - * @param reference The command reference. - * - * @return The most similar label, or the first label. An empty label will be returned if no label was set. - */ - public String getLabel(CommandParts reference) { - // Ensure there's any item in the command list - if (this.labels.size() == 0) - return ""; - - // Return the first label if we can't use the reference - if (reference == null) - return this.labels.get(0); - - // Get the correct label from the reference - String preferred = reference.get(getParentCount()); - - // Check whether the preferred label is in the label list - double currentDifference = -1; - String currentLabel = this.labels.get(0); - for (String entry : this.labels) { - double entryDifference = StringUtils.getDifference(entry, preferred); - if (entryDifference < currentDifference || currentDifference < 0) { - currentDifference = entryDifference; - currentLabel = entry; - } - } - - // Return the most similar label - return currentLabel; + private void addChild(CommandDescription command) { + children.add(command); } /** @@ -150,7 +122,7 @@ public class CommandDescription { * @return True if this command label equals to the param command. */ public boolean hasLabel(String commandLabel) { - for (String label : this.labels) { + for (String label : labels) { if (label.equalsIgnoreCase(commandLabel)) { return true; } @@ -158,83 +130,6 @@ public class CommandDescription { return false; } - /** - * Check whether this command label is applicable with a command reference. This doesn't check if the parent - * are suitable too. - * - * @param commandReference The command reference. - * - * @return True if the command reference is suitable to this command label, false otherwise. - */ - public boolean isSuitableLabel(CommandParts commandReference) { - // Get the parent count - //getParent() = getParent().getParentCount() + 1 - String element = commandReference.get(getParentCount()); - - // Check whether this command description has this command label - for (String label : labels) { - if (label.equalsIgnoreCase(element)) { - return true; - } - } - return false; - } - - /** - * Get the command reference. - * - * @param reference The reference to use as template, which is used to choose the most similar reference. - * - * @return Command reference. - */ - public CommandParts getCommandReference(CommandParts reference) { - // Build the reference - List referenceList = new ArrayList<>(); - - // Check whether this command has a parent, if so, add the absolute parent command - if (getParent() != null) { - referenceList.addAll(getParent().getCommandReference(reference).getList()); - } - - // Get the current label - referenceList.add(getLabel(reference)); - - // Return the reference - return new CommandParts(referenceList); - } - - /** - * Get the difference between this command and another command reference. - * - * @param other The other command reference. - * - * @return The command difference. Zero if there's no difference. A negative number on error. - */ - public double getCommandDifference(CommandParts other) { - return getCommandDifference(other, false); - } - - /** - * Get the difference between this command and another command reference. - * - * @param other The other command reference. - * @param fullCompare True to fully compare both command references. - * - * @return The command difference. Zero if there's no difference. A negative number on error. - */ - public double getCommandDifference(CommandParts other, boolean fullCompare) { - // Make sure the reference is valid - if (other == null) - return -1; - - // Get the command reference - CommandParts reference = getCommandReference(other); - - // Compare the two references, return the result - return CommandUtils.getDifference(reference.getList(), - CollectionUtils.getRange(other.getList(), 0, reference.getList().size()), fullCompare); - } - /** * Get the executable command. * @@ -244,29 +139,6 @@ public class CommandDescription { return this.executableCommand; } - /** - * Set the executable command. - * - * @param executableCommand The executable command. - */ - public void setExecutableCommand(ExecutableCommand executableCommand) { - this.executableCommand = executableCommand; - } - - /** - * Execute the command, if possible. - * - * @param sender The command sender that triggered the execution of this command. - * @param commandReference The command reference. - * @param commandArguments The command arguments. - * - * @return True on success, false on failure. - */ - public boolean execute(CommandSender sender, CommandParts commandReference, CommandParts commandArguments) { - // Execute the command, return the result - return getExecutableCommand().executeCommand(sender, commandReference, commandArguments); - } - /** * Get the parent command if this command description has a parent. * @@ -276,52 +148,6 @@ public class CommandDescription { return this.parent; } - /** - * Get the number of parent this description has. - * - * @return The number of parents. - */ - public int getParentCount() { - // Check whether the this description has a parent - if (!hasParent()) - return 0; - - // Get the parent count of the parent, return the result - return getParent().getParentCount() + 1; - } - - /** - * Set the parent command. - * - * @param parent Parent command. - * - * @return True on success, false on failure. - */ - public boolean setParent(CommandDescription parent) { - // Make sure the parent is different - if (this.parent == parent) - return true; - - // Set the parent - this.parent = parent; - - // Make sure the parent isn't null - if (parent == null) - return true; - - // Add this description as a child to the parent - return parent.addChild(this); - } - - /** - * Check whether the plugin description has a parent command. - * - * @return True if the description has a parent command, false otherwise. - */ - public boolean hasParent() { - return this.parent != null; - } - /** * Get all command children. * @@ -331,70 +157,6 @@ public class CommandDescription { return this.children; } - /** - * Add a child to the command description. - * - * @param commandDescription The child to add. - * - * @return True on success, false on failure. - */ - public boolean addChild(CommandDescription commandDescription) { - // Make sure the description is valid - if (commandDescription == null) - return false; - - // Make sure the child doesn't exist already - if (isChild(commandDescription)) - return true; - - // The command description to add as a child - if (!this.children.add(commandDescription)) - return false; - - // Set this description as parent on the child - return commandDescription.setParent(this); - } - - /** - * Check whether this command has any child labels. - * - * @return True if this command has any child labels. - */ - public boolean hasChildren() { - return (this.children.size() != 0); - } - - /** - * Check if this command description has a specific child. - * - * @param commandDescription The command description to check for. - * - * @return True if this command description has the specific child, false otherwise. - */ - public boolean isChild(CommandDescription commandDescription) { - // Make sure the description is valid - if (commandDescription == null) - return false; - - // Check whether this child exists, return the result - return this.children.contains(commandDescription); - } - - /** - * Add an argument. - * - * @param argument The argument to add. - * - * @return True if succeed, false if failed. - */ - public boolean addArgument(CommandArgumentDescription argument) { - // Make sure the argument is valid - if (argument == null) - return false; - - // Add the argument, return the result - return this.arguments.add(argument); - } /** * Get all command arguments. @@ -432,104 +194,6 @@ public class CommandDescription { return detailedDescription; } - /** - * Find the best suitable command for a query reference. - * - * @param queryReference The query reference to find a command for. - * - * @return The command found, or null. - */ - public FoundCommandResult findCommand(final CommandParts queryReference) { - // Make sure the command reference is valid - List queryRef = queryReference.getList(); - if (queryRef.isEmpty()) { - return null; - } - - // Check whether this description is for the last element in the command reference, if so return the current command - if (queryRef.size() <= getParentCount() + 1) { - return new FoundCommandResult( - this, - getCommandReference(queryReference), - new CommandParts(new ArrayList()), - queryReference); - } - - // Get the new command reference and arguments - CommandParts newReference = new CommandParts(CollectionUtils.getRange(queryReference.getList(), 0, getParentCount() + 1)); - CommandParts newArguments = new CommandParts(CollectionUtils.getRange(queryReference.getList(), getParentCount() + 1)); - - // Handle the child's, if this command has any - if (getChildren().size() > 0) { - // Get a new instance of the child's list, and sort them by their difference in comparison to the query reference - List commandChildren = new ArrayList<>(getChildren()); - Collections.sort(commandChildren, new Comparator() { - @Override - public int compare(CommandDescription o1, CommandDescription o2) { - return Double.compare( - o1.getCommandDifference(queryReference), - o2.getCommandDifference(queryReference)); - } - }); - - // Get the difference of the first child in the list - double firstChildDifference = commandChildren.get(0).getCommandDifference(queryReference, true); - - // Check if the reference perfectly suits the arguments of the current command if it doesn't perfectly suits a child command - if (firstChildDifference > 0.0) - if (getSuitableArgumentsDifference(queryReference) == 0) - return new FoundCommandResult(this, newReference, newArguments, queryReference); - - // Loop through each child - for (CommandDescription child : commandChildren) { - // Get the best suitable command - FoundCommandResult result = child.findCommand(queryReference); - if (result != null) - return result; - } - } - - // Check if the remaining command reference elements fit the arguments for this command - if (getSuitableArgumentsDifference(queryReference) >= 0) - return new FoundCommandResult(this, newReference, newArguments, queryReference); - - // No command found, return null - return null; - } - - /** - * Check if the remaining command reference elements are suitable with arguments of the current command description, - * and get the difference in argument count. - * - * @param commandReference The command reference. - * - * @return The difference in argument count between the reference and the actual command. - */ - public int getSuitableArgumentsDifference(CommandParts commandReference) { - // Make sure the command reference is valid - List labels = commandReference.getList(); - if (labels.isEmpty()) { - return -1; - } - - // Get the remaining command reference element count - int remainingElementCount = labels.size() - getParentCount() - 1; - - // Check if there are too few arguments - int minArguments = CommandUtils.getMinNumberOfArguments(this); - if (minArguments > remainingElementCount) { - return Math.abs(minArguments - remainingElementCount); - } - - // Check if there are too many arguments - int maxArguments = CommandUtils.getMaxNumberOfArguments(this); - if (maxArguments >= 0 && maxArguments < remainingElementCount) { - return Math.abs(remainingElementCount - maxArguments); - } - - // The argument count is the same - return 0; - } /** * Get the command permissions. Return null if the command doesn't require any permission. @@ -562,6 +226,7 @@ public class CommandDescription { * * @return The generated CommandDescription object */ + // TODO ljacqu 20151206 Move validation to the create instance method public CommandDescription build() { return createInstance( getOrThrow(labels, "labels"), @@ -570,7 +235,7 @@ public class CommandDescription { getOrThrow(executableCommand, "executableCommand"), firstNonNull(parent, null), arguments, - firstNonNull(permissions, null) + permissions ); } @@ -629,10 +294,6 @@ public class CommandDescription { return new ArrayList<>(Arrays.asList(items)); } - private static T firstNonNull(T first, T second) { - return first != null ? first : second; - } - private static T getOrThrow(T element, String elementName) { if (!isEmpty(element)) { return element; diff --git a/src/main/java/fr/xephi/authme/command/CommandHandler.java b/src/main/java/fr/xephi/authme/command/CommandHandler.java index c6cb67b29..3b1873806 100644 --- a/src/main/java/fr/xephi/authme/command/CommandHandler.java +++ b/src/main/java/fr/xephi/authme/command/CommandHandler.java @@ -2,6 +2,7 @@ package fr.xephi.authme.command; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.util.CollectionUtils; import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; @@ -17,27 +18,23 @@ import java.util.Set; */ public class CommandHandler { - /** - * The threshold for assuming an existing command. If the difference is below this value, we assume - * that the user meant the similar command and we will run it. - */ - private static final double ASSUME_COMMAND_THRESHOLD = 0.12; - /** * The threshold for suggesting a similar command. If the difference is below this value, we will * ask the player whether he meant the similar command. */ private static final double SUGGEST_COMMAND_THRESHOLD = 0.75; - private final Set commands; + private final Set baseCommands; + private final PermissionsManager permissionsManager; /** * Create a command handler. * - * @param commands The collection of available AuthMe commands + * @param baseCommands The collection of available AuthMe base commands */ - public CommandHandler(Set commands) { - this.commands = commands; + public CommandHandler(Set baseCommands, PermissionsManager permissionsManager) { + this.baseCommands = baseCommands; + this.permissionsManager = permissionsManager; } /** @@ -51,43 +48,29 @@ public class CommandHandler { * @return True if the command was executed, false otherwise. */ public boolean processCommand(CommandSender sender, String bukkitCommandLabel, String[] bukkitArgs) { - List commandArgs = skipEmptyArguments(bukkitArgs); - // Add the Bukkit command label to the front so we get a list like [authme, register, pass, passConfirm] - commandArgs.add(0, bukkitCommandLabel); + // Add the Bukkit command label to the front so we get a list like [authme, register, bobby, mysecret] + List parts = skipEmptyArguments(bukkitArgs); + parts.add(0, bukkitCommandLabel); - // TODO: remove commandParts - CommandParts commandReference = new CommandParts(commandArgs); - - // Get a suitable command for this reference, and make sure it isn't null - FoundCommandResult result = findCommand(commandReference); - if (result == null) { - // TODO ljacqu 20151204: Log more information to the console (bukkitCommandLabel) - sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!"); - return false; + // Get the base command of the result, e.g. authme for [authme, register, bobby, mysecret] + FoundCommandResult result = mapPartsToCommand(parts); + switch (result.getResultStatus()) { + case SUCCESS: + // Check perms + process + break; + case MISSING_BASE_COMMAND: + sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!"); + return false; + case INCORRECT_ARGUMENTS: + // sendImproperArgumentsMessage(sender, result); + break; + case UNKNOWN_LABEL: + // sendUnknownCommandMessage(sender); + break; + default: + throw new RuntimeException("Unknown result '" + result.getResultStatus() + "'"); } - - String baseCommand = commandArgs.get(0); - - // Make sure the difference between the command reference and the actual command isn't too big - final double commandDifference = result.getDifference(); - if (commandDifference <= ASSUME_COMMAND_THRESHOLD) { - - // Show a message when the command handler is assuming a command - if (commandDifference > 0) { - sendCommandAssumptionMessage(sender, result, commandReference); - } - - if (!result.hasPermission(sender)) { - sender.sendMessage(ChatColor.DARK_RED + "You don't have permission to use this command!"); - } else if (!result.hasProperArguments()) { - sendImproperArgumentsMessage(sender, result, commandReference, baseCommand); - } else { - return result.executeCommand(sender); - } - } else { - sendUnknownCommandMessage(sender, commandDifference, result, baseCommand); - } return true; } @@ -108,101 +91,22 @@ public class CommandHandler { } - private static CommandDescription mapToBase(String commandLabel) { - for (CommandDescription command : CommandInitializer.getBaseCommands()) { - if (command.getLabels().contains(commandLabel)) { - return command; - } - } - return null; - } - - /** - * Find the best suitable command for the specified reference. - * - * @param queryReference The query reference to find a command for. - * - * @return The command found, or null. - */ - public FoundCommandResult findCommand(CommandParts queryReference) { - // Make sure the command reference is valid - List labels = queryReference.getList(); - if (labels.isEmpty()) { - return null; - } - - for (CommandDescription commandDescription : commands) { - // Check whether there's a command description available for the - // current command - if (!commandDescription.isSuitableLabel(queryReference)) - continue; - - // Find the command reference, return the result - return commandDescription.findCommand(queryReference); - } - - // No applicable command description found, return false - return null; - } - - /** - * Find the best suitable command for the specified reference. - * - * @param commandParts The query reference to find a command for. - * - * @return The command found, or null. - */ - private CommandDescription findCommand(List commandParts) { - // Make sure the command reference is valid - if (commandParts.isEmpty()) { - return null; - } - - // TODO ljacqu 20151129: Since we only use .contains() on the CommandDescription#labels after init, change - // the type to set for faster lookup - Iterable commandsToScan = CommandInitializer.getBaseCommands(); - CommandDescription result = null; - for (String label : commandParts) { - result = findLabel(label, commandsToScan); - if (result == null) { - return null; - } - commandsToScan = result.getChildren(); - } - return result; - } - - private static CommandDescription findLabel(String label, Iterable commands) { - if (commands == null) { - return null; - } - for (CommandDescription command : commands) { - if (command.getLabels().contains(label)) { // TODO ljacqu should be case-insensitive - return command; - } - } - return null; - } - /** * Show an "unknown command" to the user and suggests an existing command if its similarity is within * the defined threshold. * * @param sender The command sender - * @param commandDifference The difference between the invoked command and the existing one * @param result The command that was found during the mapping process - * @param baseCommand The base command (TODO: This is probably already in FoundCommandResult) + * @param baseCommand The base command */ - private static void sendUnknownCommandMessage(CommandSender sender, double commandDifference, - FoundCommandResult result, String baseCommand) { - CommandParts commandReference = result.getCommandReference(); + private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result, String baseCommand) { sender.sendMessage(ChatColor.DARK_RED + "Unknown command!"); - // Show a command suggestion if available and the difference isn't too big - if (commandDifference < SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) { + if (result.getDifference() < SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) { sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD + "/" - + result.getCommandDescription().getCommandReference(commandReference) + ChatColor.YELLOW + "?"); + + result.getCommandDescription() + ChatColor.YELLOW + "?"); + // TODO: Define a proper string representation of command description } sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + baseCommand + " help" @@ -212,28 +116,115 @@ public class CommandHandler { private static void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result, CommandParts commandReference, String baseCommand) { // Get the command and the suggested command reference - List suggestedCommandReference = - result.getCommandDescription().getCommandReference(commandReference).getList(); - List helpCommandReference = CollectionUtils.getRange(suggestedCommandReference, 1); + // FIXME List suggestedCommandReference = + // result.getCommandDescription().getCommandReference(commandReference).getList(); + // List helpCommandReference = CollectionUtils.getRange(suggestedCommandReference, 1); // Show the invalid arguments warning sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!"); // Show the command argument help - HelpProvider.showHelp(sender, commandReference, new CommandParts(suggestedCommandReference), - true, false, true, false, false, false); + // HelpProvider.showHelp(sender, commandReference, new CommandParts(suggestedCommandReference), + // true, false, true, false, false, false); // Show the command to use for detailed help - sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/" + baseCommand - + " help " + CommandUtils.labelsToString(helpCommandReference)); + // sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/" + baseCommand + // + " help " + CommandUtils.labelsToString(helpCommandReference)); } - private static void sendCommandAssumptionMessage(CommandSender sender, FoundCommandResult result, - CommandParts commandReference) { - List assumedCommandParts = - result.getCommandDescription().getCommandReference(commandReference).getList(); + public FoundCommandResult mapPartsToCommand(final List parts) { + if (CollectionUtils.isEmpty(parts)) { + return new FoundCommandResult(null, parts, null, 0.0, FoundCommandResult.ResultStatus.MISSING_BASE_COMMAND); + } + + CommandDescription base = getBaseCommand(parts.get(0)); + if (base == null) { + return new FoundCommandResult(null, parts, null, 0.0, FoundCommandResult.ResultStatus.MISSING_BASE_COMMAND); + } + + // Prefer labels: /register help goes to "Help command", not "Register command" with argument 'help' + List remaining = parts.subList(1, parts.size()); + CommandDescription childCommand = returnSuitableChild(base, remaining); + if (childCommand != null) { + return new FoundCommandResult(childCommand, parts.subList(2, parts.size()), parts.subList(0, 2)); + } else if (isSuitableArgumentCount(base, remaining.size())) { + return new FoundCommandResult(base, parts.subList(1, parts.size()), parts.subList(0, 1)); + } + + // TODO: return getCommandWithSmallestDifference() + return null; - sender.sendMessage(ChatColor.DARK_RED + "Unknown command, assuming " + ChatColor.GOLD + "/" - + CommandUtils.labelsToString(assumedCommandParts) + ChatColor.DARK_RED + "!"); } + + // TODO: Return FoundCommandDescription immediately + private CommandDescription getCommandWithSmallestDifference(CommandDescription base, List parts) { + final String label = parts.get(0); + final int argumentCount = parts.size() - 1; + + double minDifference = Double.POSITIVE_INFINITY; + CommandDescription closestCommand = null; + for (CommandDescription child : base.getChildren()) { + double argumentDifference = getArgumentCountDifference(child, argumentCount); + double labelDifference = getLabelDifference(child, label); + // Weigh argument difference less + double difference = labelDifference + argumentCount / 2; + if (difference < minDifference) { + minDifference = difference; + closestCommand = child; + } + } + return closestCommand; + } + + private static boolean isSuitableArgumentCount(CommandDescription command, int argumentCount) { + int minArgs = CommandUtils.getMinNumberOfArguments(command); + int maxArgs = CommandUtils.getMaxNumberOfArguments(command); + + return argumentCount >= minArgs && argumentCount <= maxArgs; + } + + private static double getLabelDifference(CommandDescription command, String givenLabel) { + double minDifference = Double.POSITIVE_INFINITY; + for (String commandLabel : command.getLabels()) { + double difference = StringUtils.getDifference(commandLabel, givenLabel); + if (difference < minDifference) { + minDifference = difference; + } + } + return minDifference; + } + + private static int getArgumentCountDifference(CommandDescription commandDescription, int givenArgumentsCount) { + return Math.min( + Math.abs(givenArgumentsCount - CommandUtils.getMinNumberOfArguments(commandDescription)), + Math.abs(givenArgumentsCount - CommandUtils.getMaxNumberOfArguments(commandDescription))); + } + + // Is the given command a suitable match for the given parts? parts is for example [changepassword, newpw, newpw] + public CommandDescription returnSuitableChild(CommandDescription baseCommand, List parts) { + if (CollectionUtils.isEmpty(parts)) { + return null; + } + + final String label = parts.get(0).toLowerCase(); + final int argumentCount = parts.size() - 1; + + for (CommandDescription child : baseCommand.getChildren()) { + if (child.hasLabel(label) && isSuitableArgumentCount(child, argumentCount)) { + return child; + } + } + return null; + } + + private CommandDescription getBaseCommand(String label) { + String baseLabel = label.toLowerCase(); + for (CommandDescription command : baseCommands) { + if (command.hasLabel(baseLabel)) { + return command; + } + } + return null; + } + } diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java deleted file mode 100644 index a6a74551e..000000000 --- a/src/main/java/fr/xephi/authme/command/CommandMapper.java +++ /dev/null @@ -1,128 +0,0 @@ -package fr.xephi.authme.command; - -import fr.xephi.authme.util.CollectionUtils; -import fr.xephi.authme.util.StringUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Class responsible for mapping incoming arguments to a {@link CommandDescription}. - */ -public class CommandMapper { - - /** - * The threshold for assuming an existing command. If the difference is below this value, we assume - * that the user meant the similar command and we will run it. - */ - private static final double ASSUME_COMMAND_THRESHOLD = 0.12; - - /** - * The threshold for suggesting a similar command. If the difference is below this value, we will - * ask the player whether he meant the similar command. - */ - private static final double SUGGEST_COMMAND_THRESHOLD = 0.75; - - /** - * Map incoming command parts to an actual command. - * - * @param parts The parts to process - * @return The generated result - */ - public FoundCommandResult mapPartsToCommand(final List parts) { - if (CollectionUtils.isEmpty(parts)) { - return null; // TODO pass on the information that the base could not be mapped - } - - CommandDescription base = getBaseCommand(parts.get(0)); - if (base == null) { - return null; // TODO Pass on the information that base could not be mapped - } - - List remaining = parts.subList(1, parts.size()); - - // Prefer labels: /register help goes to "Help command", not "Register command" with argument 'help' - CommandDescription childCommand = returnSuitableChild(base, remaining); - if (childCommand != null) { - // return childcommand: it's valid... - } else if (isSuitableArgumentCount(base, remaining.size())) { - // return base... it's valid - } - - // TODO: return getCommandWithSmallestDifference() - return null; - - } - - // TODO: Return FoundCommandDescription immediately - private CommandDescription getCommandWithSmallestDifference(CommandDescription base, List parts) { - final String label = parts.get(0); - final int argumentCount = parts.size() - 1; - - double minDifference = Double.POSITIVE_INFINITY; - CommandDescription closestCommand = null; - for (CommandDescription child : base.getChildren()) { - double argumentDifference = getArgumentCountDifference(child, argumentCount); - double labelDifference = getLabelDifference(child, label); - // Weigh argument difference less - double difference = labelDifference + argumentCount / 2; - if (difference < minDifference) { - minDifference = difference; - closestCommand = child; - } - } - return closestCommand; - } - - private static boolean isSuitableArgumentCount(CommandDescription command, int argumentCount) { - int minArgs = CommandUtils.getMinNumberOfArguments(command); - int maxArgs = CommandUtils.getMaxNumberOfArguments(command); - - return argumentCount >= minArgs && argumentCount <= maxArgs; - } - - private static double getLabelDifference(CommandDescription command, String givenLabel) { - double minDifference = Double.POSITIVE_INFINITY; - for (String commandLabel : command.getLabels()) { - double difference = StringUtils.getDifference(commandLabel, givenLabel); - if (difference < minDifference) { - minDifference = difference; - } - } - return minDifference; - } - - private static int getArgumentCountDifference(CommandDescription commandDescription, int givenArgumentsCount) { - return Math.min( - Math.abs(givenArgumentsCount - CommandUtils.getMinNumberOfArguments(commandDescription)), - Math.abs(givenArgumentsCount - CommandUtils.getMaxNumberOfArguments(commandDescription))); - } - - // Is the given command a suitable match for the given parts? parts is for example [changepassword, newpw, newpw] - public CommandDescription returnSuitableChild(CommandDescription baseCommand, List parts) { - if (CollectionUtils.isEmpty(parts)) { - return null; - } - - final String label = parts.get(0).toLowerCase(); - final int argumentCount = parts.size() - 1; - - for (CommandDescription child : baseCommand.getChildren()) { - if (child.getLabels().contains(label) && isSuitableArgumentCount(child, argumentCount)) { - return child; - } - } - return null; - } - - public CommandDescription getBaseCommand(String label) { - String baseLabel = label.toLowerCase(); - for (CommandDescription command : CommandInitializer.getBaseCommands()) { - if (command.getLabels().contains(baseLabel)) { - return command; - } - } - return null; - } - -}