From 8ef1b2ae3e3036ac61d90420ebdd0dafb45bad28 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Wed, 23 Dec 2015 14:54:45 +0100 Subject: [PATCH] #306 Add command service and set up constructor dependency injection (work in progress) - Pass all dependencies via constructor - Encapsulate command handling more (e.g. split CommandHandler with new CommandMapper) - Add help command to all base commands at one central point See AccountsCommand or HelpCommand for an example of the advantages - all necessary functions come from CommandService; objects aren't retrieved through a singleton getInstance() method anymore --- src/main/java/fr/xephi/authme/AuthMe.java | 24 +- .../xephi/authme/command/CommandHandler.java | 248 +---------------- .../authme/command/CommandInitializer.java | 136 ++------- .../xephi/authme/command/CommandMapper.java | 258 ++++++++++++++++++ .../xephi/authme/command/CommandService.java | 58 ++++ .../authme/command/ExecutableCommand.java | 10 +- .../authme/command/FoundCommandResult.java | 13 +- .../authme/command/FoundResultStatus.java | 2 + .../command/executable/HelpCommand.java | 32 +-- .../executable/authme/AccountsCommand.java | 81 ++---- .../authme/SwitchAntiBotCommand.java | 15 +- .../executable/email/EmailBaseCommand.java | 19 ++ .../authme/command/help/HelpProvider.java | 13 +- ...andlerTest.java => CommandMapperTest.java} | 79 ++++-- 14 files changed, 517 insertions(+), 471 deletions(-) create mode 100644 src/main/java/fr/xephi/authme/command/CommandMapper.java create mode 100644 src/main/java/fr/xephi/authme/command/CommandService.java create mode 100644 src/main/java/fr/xephi/authme/command/executable/email/EmailBaseCommand.java rename src/test/java/fr/xephi/authme/command/{CommandHandlerTest.java => CommandMapperTest.java} (78%) diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index 7acc03d3b..17251fef5 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -9,8 +9,12 @@ import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; +import fr.xephi.authme.command.CommandDescription; import fr.xephi.authme.command.CommandHandler; import fr.xephi.authme.command.CommandInitializer; +import fr.xephi.authme.command.CommandMapper; +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.command.help.HelpProvider; import fr.xephi.authme.converter.Converter; import fr.xephi.authme.converter.ForceFlatToSqlite; import fr.xephi.authme.datasource.CacheDataSource; @@ -68,6 +72,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; @@ -196,9 +201,12 @@ public class AuthMe extends JavaPlugin { plugin = this; setupConstants(); + // Set up messages + messages = Messages.getInstance(); + // Set up the permissions manager and command handler permsMan = initializePermissionsManager(); - commandHandler = new CommandHandler(CommandInitializer.getBaseCommands(), permsMan); + commandHandler = initializeCommandHandler(permsMan, messages); // Set up the module manager setupModuleManager(); @@ -213,8 +221,6 @@ public class AuthMe extends JavaPlugin { // Setup otherAccounts file this.otherAccounts = OtherAccounts.getInstance(); - // Setup messages - this.messages = Messages.getInstance(); // Set up Metrics setupMetrics(); @@ -405,6 +411,14 @@ public class AuthMe extends JavaPlugin { } } + private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages) { + HelpProvider helpProvider = new HelpProvider(permissionsManager); + Set baseCommands = CommandInitializer.buildCommands(); + CommandMapper mapper = new CommandMapper(baseCommands, messages, permissionsManager); + CommandService commandService = new CommandService(this, mapper, helpProvider, messages); + return new CommandHandler(commandService); + } + /** * Set up the API. This sets up the new and the old API. */ @@ -926,10 +940,6 @@ public class AuthMe extends JavaPlugin { return moduleManager; } - public CommandHandler getCommandHandler() { - return this.commandHandler; - } - /** * Handle Bukkit commands. * diff --git a/src/main/java/fr/xephi/authme/command/CommandHandler.java b/src/main/java/fr/xephi/authme/command/CommandHandler.java index fad9a2699..8a3480eec 100644 --- a/src/main/java/fr/xephi/authme/command/CommandHandler.java +++ b/src/main/java/fr/xephi/authme/command/CommandHandler.java @@ -1,21 +1,10 @@ package fr.xephi.authme.command; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.command.executable.HelpCommand; -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; import org.bukkit.command.CommandSender; import java.util.ArrayList; import java.util.List; -import java.util.Set; - -import static fr.xephi.authme.command.FoundResultStatus.INCORRECT_ARGUMENTS; -import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND; -import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; /** * The AuthMe command handler, responsible for mapping incoming commands to the correct {@link CommandDescription} @@ -23,28 +12,13 @@ import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; */ public class CommandHandler { - /** - * 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; - - /** - * The class of the help command, to which the base label should also be passed in the arguments. - */ - private static final Class HELP_COMMAND_CLASS = HelpCommand.class; - - private final Set baseCommands; - private final PermissionsManager permissionsManager; + private final CommandService commandService; /** * Create a command handler. - * - * @param baseCommands The collection of available AuthMe base commands */ - public CommandHandler(Set baseCommands, PermissionsManager permissionsManager) { - this.baseCommands = baseCommands; - this.permissionsManager = permissionsManager; + public CommandHandler(CommandService commandService) { + this.commandService = commandService; } /** @@ -61,41 +35,20 @@ public class CommandHandler { // 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); - FoundCommandResult result = mapPartsToCommand(parts); - switch (result.getResultStatus()) { - case SUCCESS: - executeCommandIfAllowed(sender, result.getCommandDescription(), result.getArguments()); - 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, result); - break; - default: - throw new IllegalStateException("Unknown result '" + result.getResultStatus() + "'"); + FoundCommandResult result = commandService.mapPartsToCommand(sender, parts); + if (FoundResultStatus.SUCCESS.equals(result.getResultStatus())) { + executeCommand(sender, result); + } else { + commandService.outputMappingError(sender, result); } - - return true; + return !FoundResultStatus.MISSING_BASE_COMMAND.equals(result.getResultStatus()); } - /** - * Check a command's permissions and execute it with the given arguments if the check succeeds. - * - * @param sender The command sender - * @param command The command to process - * @param arguments The arguments to pass to the command - */ - private void executeCommandIfAllowed(CommandSender sender, CommandDescription command, List arguments) { - if (permissionsManager.hasPermission(sender, command)) { - command.getExecutableCommand().executeCommand(sender, arguments); - } else { - sendPermissionDeniedError(sender); - } + private void executeCommand(CommandSender sender, FoundCommandResult result) { + ExecutableCommand executableCommand = result.getCommandDescription().getExecutableCommand(); + List arguments = result.getArguments(); + executableCommand.executeCommand(sender, arguments, commandService); } /** @@ -114,181 +67,6 @@ public class CommandHandler { return cleanArguments; } - /** - * 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 result The command that was found during the mapping process - */ - private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result) { - sender.sendMessage(ChatColor.DARK_RED + "Unknown command!"); - // Show a command suggestion if available and the difference isn't too big - if (result.getDifference() <= SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) { - sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD - + CommandUtils.constructCommandPath(result.getCommandDescription()) + ChatColor.YELLOW + "?"); - } - - sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + result.getLabels().get(0) - + " help" + ChatColor.YELLOW + " to view help."); - } - - private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) { - CommandDescription command = result.getCommandDescription(); - if (!permissionsManager.hasPermission(sender, command)) { - sendPermissionDeniedError(sender); - return; - } - - // Show the command argument help - sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!"); - List lines = HelpProvider.printHelp(result, HelpProvider.SHOW_ARGUMENTS); - for (String line : lines) { - sender.sendMessage(line); - } - - List labels = result.getLabels(); - String childLabel = labels.size() >= 2 ? labels.get(1) : ""; - sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE - + "/" + labels.get(0) + " help " + childLabel); - } - - // TODO ljacqu 20151212: Remove me once I am a MessageKey - private void sendPermissionDeniedError(CommandSender sender) { - sender.sendMessage(ChatColor.DARK_RED + "You don't have permission to use this command!"); - } - - /** - * Map incoming command parts to a command. This processes all parts and distinguishes the labels from arguments. - * - * @param parts The parts to map to commands and arguments - * @return The generated {@link FoundCommandResult} - */ - public FoundCommandResult mapPartsToCommand(final List parts) { - if (CollectionUtils.isEmpty(parts)) { - return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND); - } - - CommandDescription base = getBaseCommand(parts.get(0)); - if (base == null) { - return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND); - } - - // Prefer labels: /register help goes to "Help command", not "Register command" with argument 'help' - List remainingParts = parts.subList(1, parts.size()); - CommandDescription childCommand = getSuitableChild(base, remainingParts); - if (childCommand != null) { - FoundCommandResult result = new FoundCommandResult( - childCommand, parts.subList(0, 2), parts.subList(2, parts.size())); - return transformResultForHelp(result); - } else if (hasSuitableArgumentCount(base, remainingParts.size())) { - return new FoundCommandResult(base, parts.subList(0, 1), parts.subList(1, parts.size())); - } - - return getCommandWithSmallestDifference(base, parts); - } - - private FoundCommandResult getCommandWithSmallestDifference(CommandDescription base, List parts) { - // Return the base command with incorrect arg count error if we only have one part - if (parts.size() <= 1) { - return new FoundCommandResult(base, parts, new ArrayList(), 0.0, INCORRECT_ARGUMENTS); - } - - final String childLabel = parts.get(1); - double minDifference = Double.POSITIVE_INFINITY; - CommandDescription closestCommand = null; - - for (CommandDescription child : base.getChildren()) { - double difference = getLabelDifference(child, childLabel); - if (difference < minDifference) { - minDifference = difference; - closestCommand = child; - } - } - - // base command may have no children, in which case we return the base command with incorrect arguments error - if (closestCommand == null) { - return new FoundCommandResult( - base, parts.subList(0, 1), parts.subList(1, parts.size()), 0.0, INCORRECT_ARGUMENTS); - } - - FoundResultStatus status = (minDifference == 0.0) ? INCORRECT_ARGUMENTS : UNKNOWN_LABEL; - final int partsSize = parts.size(); - List labels = parts.subList(0, Math.min(closestCommand.getLabelCount(), partsSize)); - List arguments = (labels.size() == partsSize) - ? new ArrayList() - : parts.subList(labels.size(), partsSize); - - return new FoundCommandResult(closestCommand, labels, arguments, minDifference, status); - } - - private CommandDescription getBaseCommand(String label) { - String baseLabel = label.toLowerCase(); - for (CommandDescription command : baseCommands) { - if (command.hasLabel(baseLabel)) { - return command; - } - } - return null; - } - - /** - * Return a child from a base command if the label and the argument count match. - * - * @param baseCommand The base command whose children should be checked - * @param parts The command parts received from the invocation; the first item is the potential label and any - * other items are command arguments. The first initial part that led to the base command should not - * be present. - * - * @return A command if there was a complete match (including proper argument count), null otherwise - */ - private CommandDescription getSuitableChild(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) && hasSuitableArgumentCount(child, argumentCount)) { - return child; - } - } - return null; - } - - private static FoundCommandResult transformResultForHelp(FoundCommandResult result) { - if (result.getCommandDescription() != null - && HELP_COMMAND_CLASS.isAssignableFrom(result.getCommandDescription().getExecutableCommand().getClass())) { - // For "/authme help register" we have labels = [authme, help] and arguments = [register] - // But for the help command we want labels = [authme, help] and arguments = [authme, register], - // so we can use the arguments as the labels to the command to show help for - List arguments = new ArrayList<>(result.getArguments()); - arguments.add(0, result.getLabels().get(0)); - return new FoundCommandResult(result.getCommandDescription(), result.getLabels(), - arguments, result.getDifference(), result.getResultStatus()); - } - return result; - } - - private static boolean hasSuitableArgumentCount(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; - } } diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java index 40646d2a8..d2b590aa9 100644 --- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java +++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java @@ -27,6 +27,7 @@ import fr.xephi.authme.command.executable.changepassword.ChangePasswordCommand; import fr.xephi.authme.command.executable.converter.ConverterCommand; import fr.xephi.authme.command.executable.email.AddEmailCommand; import fr.xephi.authme.command.executable.email.ChangeEmailCommand; +import fr.xephi.authme.command.executable.email.EmailBaseCommand; import fr.xephi.authme.command.executable.email.RecoverEmailCommand; import fr.xephi.authme.command.executable.login.LoginCommand; import fr.xephi.authme.command.executable.logout.LogoutCommand; @@ -36,6 +37,7 @@ import fr.xephi.authme.permission.AdminPermission; import fr.xephi.authme.permission.PlayerPermission; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -47,24 +49,11 @@ import static fr.xephi.authme.permission.DefaultPermission.OP_ONLY; */ public final class CommandInitializer { - private static Set baseCommands; - private CommandInitializer() { // Helper class } - public static Set getBaseCommands() { - if (baseCommands == null) { - initializeCommands(); - } - return baseCommands; - } - - private static void initializeCommands() { - // Create a list of help command labels - final List helpCommandLabels = Arrays.asList("help", "hlp", "h", "sos", "?"); - final ExecutableCommand helpCommandExecutable = new HelpCommand(); - + public static Set buildCommands() { // Register the base AuthMe Reloaded command final CommandDescription AUTHME_BASE = CommandDescription.builder() .labels("authme") @@ -73,16 +62,6 @@ public final class CommandInitializer { .executableCommand(new AuthMeCommand()) .build(); - // Register the help command - CommandDescription.builder() - .parent(AUTHME_BASE) - .labels(helpCommandLabels) - .description("View help") - .detailedDescription("View detailed help pages about AuthMeReloaded commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) - .build(); - // Register the register command CommandDescription.builder() .parent(AUTHME_BASE) @@ -301,16 +280,6 @@ public final class CommandInitializer { .executableCommand(new LoginCommand()) .build(); - // Register the help command - CommandDescription.builder() - .parent(LOGIN_BASE) - .labels(helpCommandLabels) - .description("View Help") - .detailedDescription("View detailed help pages about AuthMeReloaded login commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) - .build(); - // Register the base logout command CommandDescription LOGOUT_BASE = CommandDescription.builder() .parent(null) @@ -321,16 +290,6 @@ public final class CommandInitializer { .executableCommand(new LogoutCommand()) .build(); - // Register the help command - CommandDescription.builder() - .parent(LOGOUT_BASE) - .labels(helpCommandLabels) - .description("View help") - .detailedDescription("View detailed help pages about AuthMeReloaded logout commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) - .build(); - // Register the base register command final CommandDescription REGISTER_BASE = CommandDescription.builder() .parent(null) @@ -343,16 +302,6 @@ public final class CommandInitializer { .executableCommand(new RegisterCommand()) .build(); - // Register the help command - CommandDescription.builder() - .parent(REGISTER_BASE) - .labels(helpCommandLabels) - .description("View help") - .detailedDescription("View detailed help pages about AuthMeReloaded register commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) - .build(); - // Register the base unregister command CommandDescription UNREGISTER_BASE = CommandDescription.builder() .parent(null) @@ -364,16 +313,6 @@ public final class CommandInitializer { .executableCommand(new UnregisterCommand()) .build(); - // Register the help command - CommandDescription.builder() - .parent(UNREGISTER_BASE) - .labels(helpCommandLabels) - .description("View help") - .detailedDescription("View detailed help pages about AuthMeReloaded unregister commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) - .build(); - // Register the base changepassword command final CommandDescription CHANGE_PASSWORD_BASE = CommandDescription.builder() .parent(null) @@ -386,33 +325,13 @@ public final class CommandInitializer { .executableCommand(new ChangePasswordCommand()) .build(); - // Register the help command - CommandDescription.builder() - .parent(CHANGE_PASSWORD_BASE) - .labels(helpCommandLabels) - .description("View help") - .detailedDescription("View detailed help pages about AuthMeReloaded changepassword commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) - .build(); - // Register the base Email command CommandDescription EMAIL_BASE = CommandDescription.builder() .parent(null) .labels("email", "mail") .description("Email command") .detailedDescription("The AuthMeReloaded Email command base.") - .executableCommand(helpCommandExecutable) - .build(); - - // Register the help command - CommandDescription.builder() - .parent(EMAIL_BASE) - .labels(helpCommandLabels) - .description("View help") - .detailedDescription("View detailed help pages about AuthMeReloaded email commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) + .executableCommand(new EmailBaseCommand()) .build(); // Register the add command @@ -462,16 +381,6 @@ public final class CommandInitializer { .executableCommand(new CaptchaCommand()) .build(); - // Register the help command - CommandDescription.builder() - .parent(CAPTCHA_BASE) - .labels(helpCommandLabels) - .description("View help") - .detailedDescription("View detailed help pages about AuthMeReloaded captcha commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) - .build(); - // Register the base converter command CommandDescription CONVERTER_BASE = CommandDescription.builder() .parent(null) @@ -484,18 +393,7 @@ public final class CommandInitializer { .executableCommand(new ConverterCommand()) .build(); - // Register the help command - CommandDescription.builder() - .parent(CONVERTER_BASE) - .labels(helpCommandLabels) - .description("View help") - .detailedDescription("View detailed help pages about AuthMeReloaded converter commands.") - .withArgument("query", "The command or query to view help for.", true) - .executableCommand(helpCommandExecutable) - .build(); - - // Add the base commands to the commands array - baseCommands = ImmutableSet.of( + Set baseCommands = ImmutableSet.of( AUTHME_BASE, LOGIN_BASE, LOGOUT_BASE, @@ -505,5 +403,29 @@ public final class CommandInitializer { EMAIL_BASE, CAPTCHA_BASE, CONVERTER_BASE); + + setHelpOnAllBases(baseCommands); + return baseCommands; + } + + /** + * Set the help command on all base commands, e.g. to register /authme help or /register help. + * + * @param commands The list of base commands to register a help child command on + */ + private static void setHelpOnAllBases(Collection commands) { + final HelpCommand helpCommandExecutable = new HelpCommand(); + final List helpCommandLabels = Arrays.asList("help", "hlp", "h", "sos", "?"); + + for (CommandDescription base : commands) { + CommandDescription.builder() + .parent(base) + .labels(helpCommandLabels) + .description("View help") + .detailedDescription("View detailed help for /" + base.getLabels().get(0) + " commands.") + .withArgument("query", "The command or query to view help for.", true) + .executableCommand(helpCommandExecutable) + .build(); + } } } diff --git a/src/main/java/fr/xephi/authme/command/CommandMapper.java b/src/main/java/fr/xephi/authme/command/CommandMapper.java new file mode 100644 index 000000000..ccf96dc6c --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/CommandMapper.java @@ -0,0 +1,258 @@ +package fr.xephi.authme.command; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.command.executable.HelpCommand; +import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.output.Messages; +import fr.xephi.authme.permission.PermissionsManager; +import fr.xephi.authme.util.CollectionUtils; +import fr.xephi.authme.util.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static fr.xephi.authme.command.FoundResultStatus.INCORRECT_ARGUMENTS; +import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND; +import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; + +/** + * The AuthMe command handler, responsible for mapping incoming command parts to the correct {@link CommandDescription} + * or to display help messages for erroneous invocations (unknown command, no permission, etc.). + */ +public class CommandMapper { + + /** + * 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; + + /** + * The class of the help command, to which the base label should also be passed in the arguments. + */ + private static final Class HELP_COMMAND_CLASS = HelpCommand.class; + + private final Set baseCommands; + private final Messages messages; + private final PermissionsManager permissionsManager; + + public CommandMapper(Set baseCommands, Messages messages, + PermissionsManager permissionsManager) { + this.baseCommands = baseCommands; + this.messages = messages; + this.permissionsManager = permissionsManager; + } + + public void outputStandardError(CommandSender sender, FoundCommandResult result) { + switch (result.getResultStatus()) { + case SUCCESS: + // Successful mapping, so no error to output + break; + case MISSING_BASE_COMMAND: + sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!"); + break; + case INCORRECT_ARGUMENTS: + sendImproperArgumentsMessage(sender, result); + break; + case UNKNOWN_LABEL: + sendUnknownCommandMessage(sender, result); + break; + case NO_PERMISSION: + sendPermissionDeniedError(sender); + break; + default: + throw new IllegalStateException("Unknown result status '" + result.getResultStatus() + "'"); + } + } + + /** + * Show an "unknown command" message to the user and suggest an existing command if its similarity is within + * the defined threshold. + * + * @param sender The command sender + * @param result The command that was found during the mapping process + */ + private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result) { + sender.sendMessage(ChatColor.DARK_RED + "Unknown command!"); + + // Show a command suggestion if available and the difference isn't too big + if (result.getDifference() <= SUGGEST_COMMAND_THRESHOLD && result.getCommandDescription() != null) { + sender.sendMessage(ChatColor.YELLOW + "Did you mean " + ChatColor.GOLD + + CommandUtils.constructCommandPath(result.getCommandDescription()) + ChatColor.YELLOW + "?"); + } + + sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + result.getLabels().get(0) + + " help" + ChatColor.YELLOW + " to view help."); + } + + private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) { + CommandDescription command = result.getCommandDescription(); + if (!permissionsManager.hasPermission(sender, command)) { + sendPermissionDeniedError(sender); + return; + } + + // Show the command argument help + sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!"); + List lines = HelpProvider.printHelp(result, HelpProvider.SHOW_ARGUMENTS); + for (String line : lines) { + sender.sendMessage(line); + } + + List labels = result.getLabels(); + String childLabel = labels.size() >= 2 ? labels.get(1) : ""; + sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + + "/" + labels.get(0) + " help " + childLabel); + } + + // TODO ljacqu 20151212: Remove me once I am a MessageKey + private static void sendPermissionDeniedError(CommandSender sender) { + sender.sendMessage(ChatColor.DARK_RED + "You don't have permission to use this command!"); + } + + /** + * Map incoming command parts to a command. This processes all parts and distinguishes the labels from arguments. + * + * @param sender The command sender (null if none applicable) + * @param parts The parts to map to commands and arguments + * @return The generated {@link FoundCommandResult} + */ + public FoundCommandResult mapPartsToCommand(CommandSender sender, final List parts) { + if (CollectionUtils.isEmpty(parts)) { + return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND); + } + + CommandDescription base = getBaseCommand(parts.get(0)); + if (base == null) { + return new FoundCommandResult(null, parts, null, 0.0, MISSING_BASE_COMMAND); + } + + // Prefer labels: /register help goes to "Help command", not "Register command" with argument 'help' + List remainingParts = parts.subList(1, parts.size()); + CommandDescription childCommand = getSuitableChild(base, remainingParts); + if (childCommand != null) { + FoundResultStatus status = getPermissionAwareStatus(sender, childCommand); + FoundCommandResult result = new FoundCommandResult( + childCommand, parts.subList(0, 2), parts.subList(2, parts.size()), 0.0, status); + return transformResultForHelp(result); + } else if (hasSuitableArgumentCount(base, remainingParts.size())) { + FoundResultStatus status = getPermissionAwareStatus(sender, base); + return new FoundCommandResult(base, parts.subList(0, 1), parts.subList(1, parts.size()), 0.0, status); + } + + return getCommandWithSmallestDifference(base, parts); + } + + private FoundCommandResult getCommandWithSmallestDifference(CommandDescription base, List parts) { + // Return the base command with incorrect arg count error if we only have one part + if (parts.size() <= 1) { + return new FoundCommandResult(base, parts, new ArrayList(), 0.0, INCORRECT_ARGUMENTS); + } + + final String childLabel = parts.get(1); + double minDifference = Double.POSITIVE_INFINITY; + CommandDescription closestCommand = null; + + for (CommandDescription child : base.getChildren()) { + double difference = getLabelDifference(child, childLabel); + if (difference < minDifference) { + minDifference = difference; + closestCommand = child; + } + } + + // base command may have no children, in which case we return the base command with incorrect arguments error + if (closestCommand == null) { + return new FoundCommandResult( + base, parts.subList(0, 1), parts.subList(1, parts.size()), 0.0, INCORRECT_ARGUMENTS); + } + + FoundResultStatus status = (minDifference == 0.0) ? INCORRECT_ARGUMENTS : UNKNOWN_LABEL; + final int partsSize = parts.size(); + List labels = parts.subList(0, Math.min(closestCommand.getLabelCount(), partsSize)); + List arguments = (labels.size() == partsSize) + ? new ArrayList() + : parts.subList(labels.size(), partsSize); + + return new FoundCommandResult(closestCommand, labels, arguments, minDifference, status); + } + + private CommandDescription getBaseCommand(String label) { + String baseLabel = label.toLowerCase(); + for (CommandDescription command : baseCommands) { + if (command.hasLabel(baseLabel)) { + return command; + } + } + return null; + } + + /** + * Return a child from a base command if the label and the argument count match. + * + * @param baseCommand The base command whose children should be checked + * @param parts The command parts received from the invocation; the first item is the potential label and any + * other items are command arguments. The first initial part that led to the base command should not + * be present. + * + * @return A command if there was a complete match (including proper argument count), null otherwise + */ + private CommandDescription getSuitableChild(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) && hasSuitableArgumentCount(child, argumentCount)) { + return child; + } + } + return null; + } + + private static FoundCommandResult transformResultForHelp(FoundCommandResult result) { + if (result.getCommandDescription() != null + && HELP_COMMAND_CLASS.isAssignableFrom(result.getCommandDescription().getExecutableCommand().getClass())) { + // For "/authme help register" we have labels = [authme, help] and arguments = [register] + // But for the help command we want labels = [authme, help] and arguments = [authme, register], + // so we can use the arguments as the labels to the command to show help for + List arguments = new ArrayList<>(result.getArguments()); + arguments.add(0, result.getLabels().get(0)); + return new FoundCommandResult(result.getCommandDescription(), result.getLabels(), + arguments, result.getDifference(), result.getResultStatus()); + } + return result; + } + + private FoundResultStatus getPermissionAwareStatus(CommandSender sender, CommandDescription command) { + if (sender != null && !permissionsManager.hasPermission(sender, command)) { + return FoundResultStatus.NO_PERMISSION; + } + return FoundResultStatus.SUCCESS; + } + + private static boolean hasSuitableArgumentCount(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; + } + +} diff --git a/src/main/java/fr/xephi/authme/command/CommandService.java b/src/main/java/fr/xephi/authme/command/CommandService.java new file mode 100644 index 000000000..58d50f035 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/CommandService.java @@ -0,0 +1,58 @@ +package fr.xephi.authme.command; + +import fr.xephi.authme.AuthMe; +import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.output.MessageKey; +import fr.xephi.authme.output.Messages; +import org.bukkit.command.CommandSender; + +import java.util.List; + +/** + * Service for implementations of {@link ExecutableCommand} to execute some common tasks. + * This service basically wraps calls, forwarding them to other classes. + */ +public class CommandService { + + private final AuthMe authMe; + private final Messages messages; + private final HelpProvider helpProvider; + private final CommandMapper commandMapper; + + public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages) { + this.authMe = authMe; + this.messages = messages; + this.helpProvider = helpProvider; + this.commandMapper = commandMapper; + } + + public void send(CommandSender sender, MessageKey messageKey) { + messages.send(sender, messageKey); + } + + public FoundCommandResult mapPartsToCommand(CommandSender sender, List commandParts) { + return commandMapper.mapPartsToCommand(sender, commandParts); + } + + public void outputMappingError(CommandSender sender, FoundCommandResult result) { + commandMapper.outputStandardError(sender, result); + } + + public void runTaskAsynchronously(Runnable task) { + authMe.getServer().getScheduler().runTaskAsynchronously(authMe, task); + } + + public DataSource getDataSource() { + // TODO ljacqu 20151222: Add getter for .database and rename the field to dataSource + return authMe.database; + } + + public void outputHelp(CommandSender sender, FoundCommandResult result, int options) { + List lines = helpProvider.printHelp(sender, result, options); + for (String line : lines) { + sender.sendMessage(line); + } + } + +} diff --git a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java index cffe25b73..9567089f5 100644 --- a/src/main/java/fr/xephi/authme/command/ExecutableCommand.java +++ b/src/main/java/fr/xephi/authme/command/ExecutableCommand.java @@ -7,13 +7,15 @@ import java.util.List; /** * Base class for AuthMe commands that can be executed. */ -public abstract class ExecutableCommand { +public interface ExecutableCommand { /** * Execute the command with the given arguments. * - * @param sender The command sender. - * @param arguments The arguments. + * @param sender The command sender. + * @param arguments The arguments. + * @param commandService The command service. */ - public abstract void executeCommand(CommandSender sender, List arguments); + void executeCommand(CommandSender sender, List arguments, CommandService commandService); + } diff --git a/src/main/java/fr/xephi/authme/command/FoundCommandResult.java b/src/main/java/fr/xephi/authme/command/FoundCommandResult.java index f9fcce7b1..53de7f4d2 100644 --- a/src/main/java/fr/xephi/authme/command/FoundCommandResult.java +++ b/src/main/java/fr/xephi/authme/command/FoundCommandResult.java @@ -14,6 +14,8 @@ import java.util.List; * count doesn't match. Guarantees that the command description field is not null; difference is 0.0 *
  • {@link FoundResultStatus#UNKNOWN_LABEL}: The labels could not be mapped to a command. The command description * may be set to the most similar command, or it may be null. Difference is above 0.0.
  • + *
  • {@link FoundResultStatus#NO_PERMISSION}: The command could be matched properly but the sender does not have + * permission to execute it.
  • *
  • {@link FoundResultStatus#MISSING_BASE_COMMAND} should never occur. All other fields may be null and any further * processing of the object should be aborted.
  • * @@ -54,17 +56,6 @@ public class FoundCommandResult { this.resultStatus = resultStatus; } - /** - * Constructor for a fully successfully matched command. - * - * @param commandDescription The matched command description. - * @param labels The labels used to access the command. - * @param arguments The command arguments. - */ - public FoundCommandResult(CommandDescription commandDescription, List labels, List arguments) { - this(commandDescription, labels, arguments, 0.0, FoundResultStatus.SUCCESS); - } - public CommandDescription getCommandDescription() { return this.commandDescription; } diff --git a/src/main/java/fr/xephi/authme/command/FoundResultStatus.java b/src/main/java/fr/xephi/authme/command/FoundResultStatus.java index 080a3447e..32ca6b7b2 100644 --- a/src/main/java/fr/xephi/authme/command/FoundResultStatus.java +++ b/src/main/java/fr/xephi/authme/command/FoundResultStatus.java @@ -11,6 +11,8 @@ public enum FoundResultStatus { UNKNOWN_LABEL, + NO_PERMISSION, + MISSING_BASE_COMMAND } diff --git a/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java b/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java index 793803e59..bbbdcf28f 100644 --- a/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/HelpCommand.java @@ -1,13 +1,11 @@ package fr.xephi.authme.command.executable; -import fr.xephi.authme.command.CommandHandler; +import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.CommandUtils; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.command.FoundCommandResult; import fr.xephi.authme.command.FoundResultStatus; import fr.xephi.authme.command.help.HelpProvider; -import fr.xephi.authme.permission.PermissionsManager; -import fr.xephi.authme.util.Wrapper; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -16,41 +14,33 @@ import java.util.List; import static fr.xephi.authme.command.FoundResultStatus.MISSING_BASE_COMMAND; import static fr.xephi.authme.command.FoundResultStatus.UNKNOWN_LABEL; -public class HelpCommand extends ExecutableCommand { +public class HelpCommand implements ExecutableCommand { // Convention: arguments is not the actual invoked arguments but the command that was invoked, // e.g. "/authme help register" would typically be arguments = [register], but here we pass [authme, register] @Override - public void executeCommand(CommandSender sender, List arguments) { - // TODO #306 ljacqu 20151213: Get command handler from non-static context - CommandHandler commandHandler = Wrapper.getInstance().getAuthMe().getCommandHandler(); - FoundCommandResult foundCommandResult = commandHandler.mapPartsToCommand(arguments); + public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + FoundCommandResult result = commandService.mapPartsToCommand(sender, arguments); - // TODO ljacqu 20151213: This is essentially the same logic as in CommandHandler and we'd like to have the same - // messages. Maybe we can have another method in CommandHandler where the end command isn't executed upon - // success. - FoundResultStatus resultStatus = foundCommandResult.getResultStatus(); + FoundResultStatus resultStatus = result.getResultStatus(); if (MISSING_BASE_COMMAND.equals(resultStatus)) { sender.sendMessage(ChatColor.DARK_RED + "Could not get base command"); return; } else if (UNKNOWN_LABEL.equals(resultStatus)) { - if (foundCommandResult.getCommandDescription() == null) { + if (result.getCommandDescription() == null) { sender.sendMessage(ChatColor.DARK_RED + "Unknown command"); return; } else { sender.sendMessage(ChatColor.GOLD + "Assuming " + ChatColor.WHITE - + CommandUtils.constructCommandPath(foundCommandResult.getCommandDescription())); + + CommandUtils.constructCommandPath(result.getCommandDescription())); } } - PermissionsManager permissionsManager = Wrapper.getInstance().getAuthMe().getPermissionsManager(); - List lines = arguments.size() == 1 - ? HelpProvider.printHelp(foundCommandResult, HelpProvider.SHOW_CHILDREN) - : HelpProvider.printHelp(foundCommandResult, sender, permissionsManager, HelpProvider.ALL_OPTIONS); - for (String line : lines) { - sender.sendMessage(line); + if (arguments.size() == 1) { + commandService.outputHelp(sender, result, HelpProvider.SHOW_CHILDREN); + } else { + commandService.outputHelp(sender, result, HelpProvider.ALL_OPTIONS); } - } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java index 2fc63e0d8..5d4d8cda8 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/AccountsCommand.java @@ -1,88 +1,65 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.output.Messages; +import fr.xephi.authme.util.StringUtils; import org.bukkit.command.CommandSender; import java.util.List; -public class AccountsCommand extends ExecutableCommand { +/** + * Shows all accounts registered by the same IP address for the given player name or IP address. + */ +public class AccountsCommand implements ExecutableCommand { @Override - public void executeCommand(final CommandSender sender, List arguments) { - final AuthMe plugin = AuthMe.getInstance(); - final Messages m = plugin.getMessages(); - + public void executeCommand(final CommandSender sender, List arguments, + final CommandService commandService) { final String playerName = arguments.isEmpty() ? sender.getName() : arguments.get(0); - // Command logic + // Assumption: a player name cannot contain '.' if (!playerName.contains(".")) { - plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { + commandService.runTaskAsynchronously(new Runnable() { @Override public void run() { - PlayerAuth auth = plugin.database.getAuth(playerName.toLowerCase()); + PlayerAuth auth = commandService.getDataSource().getAuth(playerName.toLowerCase()); if (auth == null) { - m.send(sender, MessageKey.UNKNOWN_USER); + commandService.send(sender, MessageKey.UNKNOWN_USER); return; } - StringBuilder message = new StringBuilder("[AuthMe] "); - List accountList = plugin.database.getAllAuthsByName(auth); + + List accountList = commandService.getDataSource().getAllAuthsByName(auth); if (accountList.isEmpty()) { - m.send(sender, MessageKey.USER_NOT_REGISTERED); - return; - } - if (accountList.size() == 1) { + commandService.send(sender, MessageKey.USER_NOT_REGISTERED); + } else if (accountList.size() == 1) { sender.sendMessage("[AuthMe] " + playerName + " is a single account player"); - return; + } else { + outputAccountsList(sender, playerName, accountList); } - int i = 0; - for (String account : accountList) { - i++; - message.append(account); - if (i != accountList.size()) { - message.append(", "); - } else { - message.append('.'); - } - } - sender.sendMessage("[AuthMe] " + playerName + " has " - + String.valueOf(accountList.size()) + " accounts."); - sender.sendMessage(message.toString()); } }); - return; } else { - plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { + commandService.runTaskAsynchronously(new Runnable() { @Override public void run() { - List accountList = plugin.database.getAllAuthsByIp(playerName); - StringBuilder message = new StringBuilder("[AuthMe] "); + List accountList = commandService.getDataSource().getAllAuthsByIp(playerName); if (accountList.isEmpty()) { sender.sendMessage("[AuthMe] This IP does not exist in the database."); - return; - } - if (accountList.size() == 1) { + } else if (accountList.size() == 1) { sender.sendMessage("[AuthMe] " + playerName + " is a single account player"); - return; + } else { + outputAccountsList(sender, playerName, accountList); } - int i = 0; - for (String account : accountList) { - i++; - message.append(account); - if (i != accountList.size()) { - message.append(", "); - } else { - message.append('.'); - } - } - sender.sendMessage("[AuthMe] " + playerName + " has " - + String.valueOf(accountList.size()) + " accounts."); - sender.sendMessage(message.toString()); } }); } } + + private static void outputAccountsList(CommandSender sender, String playerName, List accountList) { + sender.sendMessage("[AuthMe] " + playerName + " has " + accountList.size() + " accounts."); + String message = "[AuthMe] " + StringUtils.join(", ", accountList) + "."; + sender.sendMessage(message); + } } diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java index 73fac3fbb..b95e4cf26 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/SwitchAntiBotCommand.java @@ -1,21 +1,17 @@ package fr.xephi.authme.command.executable.authme; import fr.xephi.authme.AntiBot; -import fr.xephi.authme.AuthMe; -import fr.xephi.authme.command.CommandHandler; +import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; -import fr.xephi.authme.command.FoundCommandResult; -import fr.xephi.authme.command.help.HelpProvider; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; -import java.util.Arrays; import java.util.List; -public class SwitchAntiBotCommand extends ExecutableCommand { +public class SwitchAntiBotCommand implements ExecutableCommand { @Override - public void executeCommand(final CommandSender sender, List arguments) { + public void executeCommand(final CommandSender sender, List arguments, CommandService commandService) { if (arguments.isEmpty()) { sender.sendMessage("[AuthMe] AntiBot status: " + AntiBot.getAntiBotStatus().name()); return; @@ -32,12 +28,13 @@ public class SwitchAntiBotCommand extends ExecutableCommand { sender.sendMessage("[AuthMe] AntiBotMod Manual Override: disabled!"); } else { sender.sendMessage(ChatColor.DARK_RED + "Invalid AntiBot mode!"); - // TODO ljacqu 20151213: Fix static retrieval of command handler + // FIXME #306: Restore help showing logic + /* CommandHandler commandHandler = AuthMe.getInstance().getCommandHandler(); FoundCommandResult foundCommandResult = commandHandler.mapPartsToCommand(Arrays.asList("authme", "antibot")); HelpProvider.printHelp(foundCommandResult, HelpProvider.SHOW_ARGUMENTS); - sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/authme help antibot"); + sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/authme help antibot");*/ } } } diff --git a/src/main/java/fr/xephi/authme/command/executable/email/EmailBaseCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/EmailBaseCommand.java new file mode 100644 index 000000000..0bf8c0c87 --- /dev/null +++ b/src/main/java/fr/xephi/authme/command/executable/email/EmailBaseCommand.java @@ -0,0 +1,19 @@ +package fr.xephi.authme.command.executable.email; + +import fr.xephi.authme.command.CommandService; +import fr.xephi.authme.command.ExecutableCommand; +import org.bukkit.command.CommandSender; + +import java.util.List; + +/** + * Base command for /email, showing information about the child commands. + */ +public class EmailBaseCommand implements ExecutableCommand { + + @Override + public void executeCommand(CommandSender sender, List arguments, CommandService commandService) { + // FIXME #306 use getCommandService().getHelpProvider(); + // FIXME #306 HelpProvider.printHelp() + } +} diff --git a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java index 37bbb9e5c..3eb4045c6 100644 --- a/src/main/java/fr/xephi/authme/command/help/HelpProvider.java +++ b/src/main/java/fr/xephi/authme/command/help/HelpProvider.java @@ -24,7 +24,7 @@ import static java.util.Collections.singletonList; /** * Help syntax generator for AuthMe commands. */ -public final class HelpProvider { +public class HelpProvider { // --- Bit flags --- /** Set to not show the command. */ @@ -43,13 +43,22 @@ public final class HelpProvider { /** Shortcut for setting all options apart from {@link HelpProvider#HIDE_COMMAND}. */ public static final int ALL_OPTIONS = ~HIDE_COMMAND; - private HelpProvider() { + private final PermissionsManager permissionsManager; + + public HelpProvider(PermissionsManager permissionsManager) { + this.permissionsManager = permissionsManager; } public static List printHelp(FoundCommandResult foundCommand, int options) { return printHelp(foundCommand, null, null, options); } + public List printHelp(CommandSender sender, FoundCommandResult result, int options) { + // FIXME don't overload and pass to the static method + // FIXME remove the static methods altogether + return printHelp(result, sender, permissionsManager, options); + } + // sender and permissions manager may be null if SHOW_PERMISSIONS is not set public static List printHelp(FoundCommandResult foundCommand, CommandSender sender, PermissionsManager permissionsManager, int options) { diff --git a/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java b/src/test/java/fr/xephi/authme/command/CommandMapperTest.java similarity index 78% rename from src/test/java/fr/xephi/authme/command/CommandHandlerTest.java rename to src/test/java/fr/xephi/authme/command/CommandMapperTest.java index b333b1645..117d2ba52 100644 --- a/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandMapperTest.java @@ -1,12 +1,14 @@ package fr.xephi.authme.command; import fr.xephi.authme.command.executable.HelpCommand; +import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.DefaultPermission; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.permission.PlayerPermission; import org.bukkit.command.CommandSender; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -35,12 +37,12 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; /** - * Test for {@link CommandHandler}. + * Test for {@link CommandMapper}. */ -public class CommandHandlerTest { +public class CommandMapperTest { private static Set commands; - private static CommandHandler handler; + private static CommandMapper mapper; private static PermissionsManager permissionsManagerMock; @BeforeClass @@ -69,7 +71,7 @@ public class CommandHandlerTest { @Before public void setUpMocks() { permissionsManagerMock = mock(PermissionsManager.class); - handler = new CommandHandler(commands, permissionsManagerMock); + mapper = new CommandMapper(commands, mock(Messages.class), permissionsManagerMock); } // ----------- @@ -79,9 +81,11 @@ public class CommandHandlerTest { public void shouldMapPartsToLoginChildCommand() { // given List parts = Arrays.asList("authme", "login", "test1"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getCommandDescription(), equalTo(getChildWithLabel("login", "authme"))); @@ -96,9 +100,11 @@ public class CommandHandlerTest { public void shouldMapPartsToCommandWithNoCaseSensitivity() { // given List parts = Arrays.asList("Authme", "REG", "arg1", "arg2"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getCommandDescription(), equalTo(getChildWithLabel("register", "authme"))); @@ -112,9 +118,11 @@ public class CommandHandlerTest { public void shouldRejectCommandWithTooManyArguments() { // given List parts = Arrays.asList("authme", "register", "pass123", "pass123", "pass123"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getCommandDescription(), equalTo(getChildWithLabel("register", "authme"))); @@ -128,9 +136,11 @@ public class CommandHandlerTest { public void shouldRejectCommandWithTooFewArguments() { // given List parts = Arrays.asList("authme", "Reg"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getCommandDescription(), equalTo(getChildWithLabel("register", "authme"))); @@ -144,9 +154,11 @@ public class CommandHandlerTest { public void shouldSuggestCommandWithSimilarLabel() { // given List parts = Arrays.asList("authme", "reh", "pass123", "pass123"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getCommandDescription(), equalTo(getChildWithLabel("register", "authme"))); @@ -161,9 +173,11 @@ public class CommandHandlerTest { public void shouldSuggestMostSimilarCommand() { // given List parts = Arrays.asList("authme", "asdfawetawty4asdca"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getCommandDescription(), not(nullValue())); @@ -177,9 +191,11 @@ public class CommandHandlerTest { public void shouldHandleBaseWithWrongArguments() { // given List parts = singletonList("unregister"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getResultStatus(), equalTo(FoundResultStatus.INCORRECT_ARGUMENTS)); @@ -193,9 +209,11 @@ public class CommandHandlerTest { public void shouldHandleUnknownBase() { // given List parts = asList("bogus", "label1", "arg1"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getResultStatus(), equalTo(FoundResultStatus.MISSING_BASE_COMMAND)); @@ -205,7 +223,7 @@ public class CommandHandlerTest { @Test public void shouldHandleNullInput() { // given / when - FoundCommandResult result = handler.mapPartsToCommand(null); + FoundCommandResult result = mapper.mapPartsToCommand(mock(CommandSender.class), null); // then assertThat(result.getResultStatus(), equalTo(FoundResultStatus.MISSING_BASE_COMMAND)); @@ -216,9 +234,11 @@ public class CommandHandlerTest { public void shouldMapToBaseWithProperArguments() { // given List parts = asList("Unreg", "player1"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getResultStatus(), equalTo(FoundResultStatus.SUCCESS)); @@ -232,9 +252,11 @@ public class CommandHandlerTest { public void shouldReturnChildlessBaseCommandWithArgCountError() { // given List parts = asList("unregistER", "player1", "wrongArg"); + CommandSender sender = mock(CommandSender.class); + given(permissionsManagerMock.hasPermission(eq(sender), any(CommandDescription.class))).willReturn(true); // when - FoundCommandResult result = handler.mapPartsToCommand(parts); + FoundCommandResult result = mapper.mapPartsToCommand(sender, parts); // then assertThat(result.getResultStatus(), equalTo(FoundResultStatus.INCORRECT_ARGUMENTS)); @@ -248,6 +270,7 @@ public class CommandHandlerTest { // processCommand() tests // ---------- @Test + @Ignore public void shouldCallMappedCommandWithArgs() { // given String bukkitLabel = "Authme"; @@ -258,11 +281,13 @@ public class CommandHandlerTest { given(permissionsManagerMock.hasPermission(sender, command)).willReturn(true); // when - handler.processCommand(sender, bukkitLabel, bukkitArgs); + // FIXME #306 Move to CommandHandler test + // mapper.processCommand(sender, bukkitLabel, bukkitArgs); // then ArgumentCaptor argsCaptor = ArgumentCaptor.forClass(List.class); - verify(command.getExecutableCommand()).executeCommand(eq(sender), argsCaptor.capture()); + verify(command.getExecutableCommand()) + .executeCommand(eq(sender), argsCaptor.capture(), any(CommandService.class)); List argument = argsCaptor.getValue(); assertThat(argument, contains("myPass")); // Ensure that no error message was issued to the command sender @@ -270,6 +295,7 @@ public class CommandHandlerTest { } @Test + @Ignore public void shouldNotCallExecutableCommandIfNoPermission() { // given String bukkitLabel = "unreg"; @@ -279,13 +305,14 @@ public class CommandHandlerTest { .willReturn(false); // when - handler.processCommand(sender, bukkitLabel, bukkitArgs); + // FIXME #306 Move to CommandHandler test + // mapper.processCommand(sender, bukkitLabel, bukkitArgs); // then CommandDescription command = getCommandWithLabel("unregister", commands); verify(permissionsManagerMock).hasPermission(sender, command); verify(command.getExecutableCommand(), never()) - .executeCommand(any(CommandSender.class), anyListOf(String.class)); + .executeCommand(any(CommandSender.class), anyListOf(String.class), any(CommandService.class)); ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); verify(sender).sendMessage(messageCaptor.capture()); @@ -294,6 +321,7 @@ public class CommandHandlerTest { } @Test + @Ignore public void shouldStripWhitespace() { // given String bukkitLabel = "AuthMe"; @@ -304,17 +332,20 @@ public class CommandHandlerTest { given(permissionsManagerMock.hasPermission(sender, command)).willReturn(true); // when - handler.processCommand(sender, bukkitLabel, bukkitArgs); + // FIXME #306 Move to CommandHandler test + // mapper.processCommand(sender, bukkitLabel, bukkitArgs); // then ArgumentCaptor argsCaptor = ArgumentCaptor.forClass(List.class); - verify(command.getExecutableCommand()).executeCommand(eq(sender), argsCaptor.capture()); + verify(command.getExecutableCommand()) + .executeCommand(eq(sender), argsCaptor.capture(), any(CommandService.class)); List arguments = argsCaptor.getValue(); assertThat(arguments, contains("testArg")); verify(sender, never()).sendMessage(anyString()); } @Test + @Ignore public void shouldPassCommandPathAsArgumentsToHelpCommand() { // given String bukkitLabel = "email"; @@ -325,11 +356,13 @@ public class CommandHandlerTest { given(permissionsManagerMock.hasPermission(sender, command)).willReturn(true); // when - handler.processCommand(sender, bukkitLabel, bukkitArgs); + // FIXME #306 Move to CommandHandler test + // mapper.processCommand(sender, bukkitLabel, bukkitArgs); // then ArgumentCaptor argsCaptor = ArgumentCaptor.forClass(List.class); - verify(command.getExecutableCommand()).executeCommand(eq(sender), argsCaptor.capture()); + verify(command.getExecutableCommand()) + .executeCommand(eq(sender), argsCaptor.capture(), any(CommandService.class)); List arguments = argsCaptor.getValue(); assertThat(arguments, contains("email", "arg1")); }