diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java index 09336105c..a6a74551e 100644 --- a/src/main/java/fr/xephi/authme/command/CommandMapper.java +++ b/src/main/java/fr/xephi/authme/command/CommandMapper.java @@ -1,6 +1,7 @@ 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; @@ -10,6 +11,18 @@ import java.util.List; */ 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. * @@ -26,25 +39,41 @@ public class CommandMapper { return null; // TODO Pass on the information that base could not be mapped } - //List labels = CollectionUtils.getRange(parts, 0, 1); List remaining = parts.subList(1, parts.size()); - // Prefer labels: /register help goes to "Help command", not "Register command" with 'help' + // 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... - } - - // No child command found, check if parent is suitable - if (isSuitableArgumentCount(base, remaining.size())) { + } else if (isSuitableArgumentCount(base, remaining.size())) { // return base... it's valid } - // TODO: We don't have a suitable command for the given parts, so find the most similar one + // 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); @@ -52,13 +81,32 @@ public class CommandMapper { 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) { - // TODO: Validate list + make case-insensitive + if (CollectionUtils.isEmpty(parts)) { + return null; + } + final String label = parts.get(0).toLowerCase(); final int argumentCount = parts.size() - 1; - List args = parts.subList(1, parts.size()); for (CommandDescription child : baseCommand.getChildren()) { if (child.getLabels().contains(label) && isSuitableArgumentCount(child, argumentCount)) { return child;