package fr.xephi.authme.command; import ch.jalu.injector.factory.Factory; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.help.HelpProvider; import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.util.StringUtils; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import javax.inject.Inject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * The AuthMe command handler, responsible for invoking the correct {@link ExecutableCommand} based on incoming * command labels or for displaying a help message for unknown command labels. */ 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; private final CommandMapper commandMapper; private final PermissionsManager permissionsManager; private final Messages messages; private final HelpProvider helpProvider; /** * Map with ExecutableCommand children. The key is the type of the value. */ private Map, ExecutableCommand> commands = new HashMap<>(); @Inject CommandHandler(Factory commandFactory, CommandMapper commandMapper, PermissionsManager permissionsManager, Messages messages, HelpProvider helpProvider) { this.commandMapper = commandMapper; this.permissionsManager = permissionsManager; this.messages = messages; this.helpProvider = helpProvider; initializeCommands(commandFactory, commandMapper.getCommandClasses()); } /** * Map a command that was invoked to the proper {@link CommandDescription} or return a useful error * message upon failure. * * @param sender The command sender. * @param bukkitCommandLabel The command label (Bukkit). * @param bukkitArgs The command arguments (Bukkit). * * @return True if the command was executed, false otherwise. */ public boolean processCommand(CommandSender sender, String bukkitCommandLabel, String[] bukkitArgs) { // 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 = commandMapper.mapPartsToCommand(sender, parts); handleCommandResult(sender, result); return !FoundResultStatus.MISSING_BASE_COMMAND.equals(result.getResultStatus()); } /** * Processes the given {@link FoundCommandResult} for the provided command sender. * * @param sender the command sender who executed the command * @param result the command mapping result */ private void handleCommandResult(CommandSender sender, FoundCommandResult result) { switch (result.getResultStatus()) { case SUCCESS: executeCommand(sender, result); 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: messages.send(sender, MessageKey.NO_PERMISSION); break; default: throw new IllegalStateException("Unknown result status '" + result.getResultStatus() + "'"); } } /** * Initialize all required ExecutableCommand objects. * * @param commandFactory factory to create command objects * @param commandClasses the classes to instantiate */ private void initializeCommands(Factory commandFactory, Set> commandClasses) { for (Class clazz : commandClasses) { commands.put(clazz, commandFactory.newInstance(clazz)); } } /** * Execute the command for the given command sender. * * @param sender The sender which initiated the command * @param result The mapped result */ private void executeCommand(CommandSender sender, FoundCommandResult result) { ExecutableCommand executableCommand = commands.get(result.getCommandDescription().getExecutableCommand()); List arguments = result.getArguments(); executableCommand.executeCommand(sender, arguments); } /** * Skip all entries of the given array that are simply whitespace. * * @param args The array to process * @return List of the items that are not empty */ private static List skipEmptyArguments(String[] args) { List cleanArguments = new ArrayList<>(); for (String argument : args) { if (!StringUtils.isBlank(argument)) { cleanArguments.add(argument); } } return cleanArguments; } /** * 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.getPermission())) { messages.send(sender, MessageKey.NO_PERMISSION); return; } ExecutableCommand executableCommand = commands.get(command.getExecutableCommand()); MessageKey usageMessage = executableCommand.getArgumentsMismatchMessage(); if (usageMessage == null) { showHelpForCommand(sender, result); } else { messages.send(sender, usageMessage); } } private void showHelpForCommand(CommandSender sender, FoundCommandResult result) { sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!"); helpProvider.outputHelp(sender, result, HelpProvider.SHOW_ARGUMENTS); 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); } }