From 6b5b8c91141847f771e2a471579fb08f67110cc2 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Mon, 16 Nov 2020 05:06:09 +0100 Subject: [PATCH 01/16] Initial non-functional WIP for new commands --- .../components/commands/Command.java | 791 ------------------ .../components/commands/CommandException.java | 98 --- .../components/commands/CommandGroup.java | 346 -------- .../components/commands/CommandInfo.java | 55 -- .../components/commands/CommandWorkers.java | 41 - .../components/commands/Commands.java | 141 ---- .../components/commands/HelpCommand.java | 203 ----- .../components/commands/WithFlags.java | 54 -- .../commands/attributes/Sender.java | 4 + .../commands/internal/CommandEndpoint.java | 16 + .../commands/internal/CommandGroup.java | 47 ++ .../commands/internal/CommandMethod.java | 17 + .../commands/internal/CommandNode.java | 13 + .../components/rawtext/RawTextPart.java | 23 +- .../components/commands/CommandFlagsTest.java | 473 ----------- .../commands/internal/CommandGraphTests.java | 20 + .../fr/zcraft/ztoaster/ToastCommands.java | 64 ++ .../main/java/fr/zcraft/ztoaster/Toaster.java | 8 +- .../zcraft/ztoaster/commands/AddCommand.java | 60 -- .../zcraft/ztoaster/commands/ListCommand.java | 73 -- .../zcraft/ztoaster/commands/OpenCommand.java | 46 - 21 files changed, 195 insertions(+), 2398 deletions(-) delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/Command.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandException.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandInfo.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandWorkers.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/Commands.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/HelpCommand.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/WithFlags.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java delete mode 100644 src/test/java/fr/zcraft/quartzlib/components/commands/CommandFlagsTest.java create mode 100644 src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java create mode 100644 src/test/java/fr/zcraft/ztoaster/ToastCommands.java delete mode 100644 ztoaster/src/main/java/fr/zcraft/ztoaster/commands/AddCommand.java delete mode 100644 ztoaster/src/main/java/fr/zcraft/ztoaster/commands/ListCommand.java delete mode 100644 ztoaster/src/main/java/fr/zcraft/ztoaster/commands/OpenCommand.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/Command.java b/src/main/java/fr/zcraft/quartzlib/components/commands/Command.java deleted file mode 100644 index df37413d..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/Command.java +++ /dev/null @@ -1,791 +0,0 @@ -/* - * Copyright or © or Copr. QuartzLib contributors (2015 - 2020) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.quartzlib.components.commands; - -import fr.zcraft.quartzlib.components.commands.CommandException.Reason; -import fr.zcraft.quartzlib.components.rawtext.RawText; -import fr.zcraft.quartzlib.core.QuartzLib; -import fr.zcraft.quartzlib.tools.text.RawMessage; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.regex.Pattern; -import org.apache.commons.lang.StringUtils; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public abstract class Command { - private static final Pattern FLAG_PATTERN = Pattern.compile("(--?)[a-zA-Z0-9-]+"); - - protected CommandGroup commandGroup; - protected String commandName; - protected String usageParameters; - protected String commandDescription; - protected String[] aliases; - - protected boolean flagsEnabled; - protected Set acceptedFlags; - - protected CommandSender sender; - protected String[] args; - protected Set flags; - - /** - * Parses arguments to extract flags. - * - *

This method is made static and with all data as argument to be able to - * be unit tested.

- * - * @param args The raw arguments. - * @param acceptedFlags A set with lowercase accepted flags. - * @param realArgs An initially empty list filled with the real - * arguments, ordered. - * @param flags An initially empty set filled with flags found in - * the raw arguments. - */ - private static void parseArgs(final String[] args, final Set acceptedFlags, List realArgs, - Set flags) { - for (final String arg : args) { - if (!FLAG_PATTERN.matcher(arg).matches()) { - realArgs.add(arg); - continue; - } - - final Set flagsInArg; - if (arg.startsWith("--")) { - final String flatFlag = arg.replace("--", "").trim().toLowerCase(); - if (isValidFlag(acceptedFlags, flatFlag)) { - flagsInArg = Collections.singleton(flatFlag); - } else { - realArgs.add(arg); - continue; - } - } else { - final String flatFlags = arg.replace("-", "").trim().toLowerCase(); - flagsInArg = new HashSet<>(flatFlags.length()); - - for (char c : flatFlags.toCharArray()) { - final String flag = String.valueOf(c); - if (isValidFlag(acceptedFlags, flag)) { - flagsInArg.add(flag); - } - } - - // If there is no valid flag at all in the argument, we ignore it and - // add it back to args - if (flagsInArg.isEmpty()) { - realArgs.add(arg); - continue; - } - } - - flags.addAll(flagsInArg); - } - } - - /** - * Parses arguments to extract flags (if enabled). - * - * @param args The raw arguments passed to the command. - */ - private void parseArgs(String[] args) { - if (!flagsEnabled) { - this.args = args; - this.flags = null; - return; - } - - final List argsList = new ArrayList<>(args.length); - flags = new HashSet<>(); - - parseArgs(args, acceptedFlags, argsList, flags); - - this.args = argsList.toArray(new String[0]); - } - - /** - * Checks if a flag is accepted. - * - * @param acceptedFlags A list of accepted flags. Can be empty or {@code - * null} accepts all flags while empty accept no one. - * @param flag The flag to test. - * @return {@code true} if this flag is valid. - */ - private static boolean isValidFlag(Set acceptedFlags, String flag) { - return acceptedFlags != null && (acceptedFlags.size() == 0 || acceptedFlags.contains(flag.toLowerCase())); - } - - /** - * Displays a gray informational message. - * - * @param sender The receiver of the message. - * @param message The message to display. - */ - protected static void info(CommandSender sender, String message) { - sender.sendMessage("§7" + message); - } - - /** - * Displays a gray informational message to the sender. - * - * @param message The message to display. - */ - protected void info(String message) { - info(sender, message); - } - - - /** - * Displays a green success message. - * - * @param sender The receiver of the message. - * @param message The message to display. - */ - protected static void success(CommandSender sender, String message) { - sender.sendMessage("§a" + message); - } - - /** - * Displays a green success message to the sender. - * - * @param message The message to display. - */ - protected void success(String message) { - success(sender, message); - } - - /** - * Displays a red warning message. - * - * @param sender The receiver of the message. - * @param message The message to display. - */ - protected static void warning(CommandSender sender, String message) { - sender.sendMessage("§c" + message); - } - - /** - * Displays a red warning message to the sender. - * - * @param message The message to display. - */ - protected void warning(String message) { - warning(sender, message); - } - - private static String invalidParameterString(int index, final String expected) { - return "Argument #" + (index + 1) + " invalid: expected " + expected; - } - - private static String invalidParameterString(int index, final Object[] expected) { - String[] expectedStrings = new String[expected.length]; - - for (int i = expected.length; i-- > 0; ) { - expectedStrings[i] = expected[i].toString().toLowerCase(); - } - - String expectedString = StringUtils.join(expectedStrings, ','); - - return "Argument #" + (index + 1) + " invalid: expected " + expectedString; - } - - /** - * Runs the command. - * - *

Use protected fields to access data (like {@link #args}).

- * - * @throws CommandException If something bad happens. - */ - protected abstract void run() throws CommandException; - - /** - * Initializes the command. Internal use. - * - * @param commandGroup The group this command instance belongs to. - */ - void init(CommandGroup commandGroup) { - this.commandGroup = commandGroup; - - CommandInfo commandInfo = this.getClass().getAnnotation(CommandInfo.class); - - if (commandInfo == null) { - throw new IllegalArgumentException("Command has no CommandInfo annotation"); - } - - commandName = commandInfo.name().toLowerCase(); - usageParameters = commandInfo.usageParameters(); - commandDescription = commandGroup.getDescription(commandName); - aliases = commandInfo.aliases(); - - WithFlags withFlags = this.getClass().getAnnotation(WithFlags.class); - flagsEnabled = withFlags != null; - if (flagsEnabled) { - acceptedFlags = new HashSet<>(); - for (final String flag : withFlags.value()) { - acceptedFlags.add(flag.toLowerCase()); - } - } else { - acceptedFlags = Collections.emptySet(); - } - } - - /** - * Checks if a given sender is allowed to execute this command. - * - * @param sender The sender. - * @return {@code true} if the sender can execute the command. - */ - public boolean canExecute(CommandSender sender) { - String permissionPrefix = QuartzLib.getPlugin().getName().toLowerCase() + "."; - String globalPermission = Commands.getGlobalPermission(); - - if (globalPermission != null) { - if (sender.hasPermission(permissionPrefix + globalPermission)) { - return true; - } - } - - return sender.hasPermission(permissionPrefix + commandGroup.getUsualName()); - } - - /** - * Checks if a given sender is allowed to execute this command. - * - * @param sender The sender. - * @param args The arguments passed to the command. - * @return {@code true} if the sender can execute the command. - */ - public boolean canExecute(CommandSender sender, String[] args) { - return canExecute(sender); - } - - /** - * Tab-completes the command. This command should be overridden. - * - *

Use protected fields to access data (like {@link #args}).

- * - * @return A list with suggestions, or {@code null} without suggestions. - * @throws CommandException If something bad happens. - */ - protected List complete() throws CommandException { - return null; - } - - /** - * Executes this command. - * - * @param sender The sender. - * @param args The raw arguments passed to the command. - */ - public void execute(CommandSender sender, String[] args) { - this.sender = sender; - parseArgs(args); - - try { - if (!canExecute(sender, args)) { - throw new CommandException(this, Reason.SENDER_NOT_AUTHORIZED); - } - run(); - } catch (CommandException ex) { - warning(ex.getReasonString()); - } - - this.sender = null; - this.args = null; - this.flags = null; - } - - /** - * Tab-completes this command. - * - * @param sender The sender. - * @param args The raw arguments passed to the command. - */ - public List tabComplete(CommandSender sender, String[] args) { - List result = null; - - this.sender = sender; - parseArgs(args); - - try { - if (canExecute(sender, args)) { - result = complete(); - } - } catch (CommandException ignored) { } - - this.sender = null; - this.args = null; - this.flags = null; - - if (result == null) { - result = new ArrayList<>(); - } - return result; - } - - /** - * Returns this command's usage parameters. - * @return This command's usage parameters. - */ - public String getUsageParameters() { - return usageParameters; - } - - /** - * Returns this command's usage string. - * @return This command's usage string, formatted like this: {@code - * /{command} {sub-command} {usage parameters}}. - */ - public String getUsageString() { - return "/" + commandGroup.getUsualName() + " " + commandName + " " + usageParameters; - } - - /** - * Returns the name of this command. - * @return The name of this command. - */ - public String getName() { - return commandName; - } - - - ///////////// Common methods for commands ///////////// - - /** - * Get the command group. - * @return The command group this command belongs to. - */ - CommandGroup getCommandGroup() { - return commandGroup; - } - - /** - * Get the aliases. - * @return The aliases of this command. - */ - public String[] getAliases() { - return aliases; - } - - /** - * Checks if the given name matches this command's, or any of its aliases. - * @param name A command name. - * @return {@code true} if this command can be called like that, - * checking (without case) the command name then aliases. - */ - public boolean matches(String name) { - if (commandName.equals(name.toLowerCase())) { - return true; - } - - for (String alias : aliases) { - if (alias.equals(name)) { - return true; - } - } - - return false; - } - - - ///////////// Methods for command execution ///////////// - - /** - * Builds a command usage string. - * @param args Some arguments. - * @return A ready-to-be-executed command string with the passed arguments. - */ - public String build(String... args) { - StringBuilder command = new StringBuilder("/" + commandGroup.getUsualName() + " " + commandName); - - for (String arg : args) { - command.append(" ").append(arg); - } - - return command.toString(); - } - - /** - * Stops the command execution because an argument is invalid, and displays - * an error message. - * - * @param reason The error. - * @throws CommandException the thrown exception. - */ - protected void throwInvalidArgument(String reason) throws CommandException { - throw new CommandException(this, Reason.INVALID_PARAMETERS, reason); - } - - /** - * Stops the command execution because the command usage is disallowed, and - * displays an error message. - * - * @throws CommandException the thrown exception. - */ - protected void throwNotAuthorized() throws CommandException { - throw new CommandException(this, Reason.SENDER_NOT_AUTHORIZED); - } - - /** - * Retrieves the {@link Player} who executed this command. If the command is - * not executed by a player, aborts the execution and displays an error - * message. - * - * @return The player executing this command. - * @throws CommandException If the sender is not a player. - */ - protected Player playerSender() throws CommandException { - if (!(sender instanceof Player)) { - throw new CommandException(this, Reason.COMMANDSENDER_EXPECTED_PLAYER); - } - return (Player) sender; - } - - /** - * Aborts the execution and displays an error message. - * - * @param message The message. - * @throws CommandException the thrown exception. - */ - protected void error(String message) throws CommandException { - throw new CommandException(this, Reason.COMMAND_ERROR, message); - } - - /** - * Aborts the execution and displays a generic error message. - * - * @throws CommandException the thrown exception. - */ - protected void error() throws CommandException { - error(""); - } - - - ///////////// Methods for autocompletion ///////////// - - /** - * Sends a JSON-formatted message to the sender. - * - * @param rawMessage The JSON message. - * @throws CommandException if the command sender is not a player. - */ - protected void tellRaw(String rawMessage) throws CommandException { - RawMessage.send(playerSender(), rawMessage); - } - - /** - * Sends a {@linkplain RawText raw JSON text} to the sender. - * - * @param text The JSON message. - */ - protected void send(RawText text) { - RawMessage.send(sender, text); - } - - /** - * Returns the strings of the list starting with the given prefix. - * - * @param prefix The prefix. - * @param list The strings. - * @return A sub-list containing the strings starting with prefix. - */ - protected List getMatchingSubset(String prefix, String... list) { - return getMatchingSubset(Arrays.asList(list), prefix); - } - - /** - * Returns the strings of the list starting with the given prefix. - * - * @param list The strings. - * @param prefix The prefix. - * @return A sub-list containing the strings starting with prefix. - */ - protected List getMatchingSubset(Iterable list, String prefix) { - List matches = new ArrayList<>(); - - for (String item : list) { - if (item.startsWith(prefix)) { - matches.add(item); - } - } - - return matches; - } - - - ///////////// Methods for parameters ///////////// - - /** - * Returns a list of player names starting by the given prefix, among all - * logged in players. - * - * @param prefix The prefix. - * @return A sub-list containing the players names starting with prefix. - */ - protected List getMatchingPlayerNames(String prefix) { - return getMatchingPlayerNames(Bukkit.getOnlinePlayers(), prefix); - } - - /** - * Returns a list of player names starting by the given prefix, among the - * given players. - * - * @param players A list of players. - * @param prefix The prefix. - * @return A sub-list containing the players names starting with prefix. - */ - protected List getMatchingPlayerNames(Iterable players, String prefix) { - List matches = new ArrayList(); - - for (Player player : players) { - if (player.getName().startsWith(prefix)) { - matches.add(player.getName()); - } - } - - return matches; - } - - /** - * Retrieves an integer at the given index, or aborts the execution if none - * can be found. - * - * @param index The index. - * @return The retrieved integer. - * @throws CommandException If the value is invalid. - */ - protected int getIntegerParameter(int index) throws CommandException { - try { - return Integer.parseInt(args[index]); - } catch (NumberFormatException e) { - throw new CommandException(this, Reason.INVALID_PARAMETERS, invalidParameterString(index, "integer")); - } - } - - /** - * Retrieves a double at the given index, or aborts the execution if none - * can be found. - * - * @param index The index. - * @return The retrieved double. - * @throws CommandException If the value is invalid. - */ - protected double getDoubleParameter(int index) throws CommandException { - try { - return Double.parseDouble(args[index]); - } catch (NumberFormatException e) { - throw new CommandException(this, Reason.INVALID_PARAMETERS, - invalidParameterString(index, "integer or decimal value")); - } - } - - /** - * Retrieves a float at the given index, or aborts the execution if none can - * be found. - * - * @param index The index. - * @return The retrieved float. - * @throws CommandException If the value is invalid. - */ - protected float getFloatParameter(int index) throws CommandException { - try { - return Float.parseFloat(args[index]); - } catch (NumberFormatException e) { - throw new CommandException(this, Reason.INVALID_PARAMETERS, - invalidParameterString(index, "integer or decimal value")); - } - } - - /** - * Retrieves a long at the given index, or aborts the execution if none can - * be found. - * - * @param index The index. - * @return The retrieved long. - * @throws CommandException If the value is invalid. - */ - protected long getLongParameter(int index) throws CommandException { - try { - return Long.parseLong(args[index]); - } catch (NumberFormatException e) { - throw new CommandException(this, Reason.INVALID_PARAMETERS, invalidParameterString(index, "integer")); - } - } - - /** - * Retrieves aa boolean at the given index, or aborts the execution if none - * can be found. - * - *

Accepts yes, y, on, true, 1, no, n, off, false, and 0.

- * - * @param index The index. - * @return The retrieved boolean. - * @throws CommandException If the value is invalid. - */ - protected boolean getBooleanParameter(int index) throws CommandException { - switch (args[index].toLowerCase().trim()) { - case "yes": - case "y": - case "on": - case "true": - case "1": - return true; - - case "no": - case "n": - case "off": - case "false": - case "0": - return false; - - default: - throw new CommandException(this, Reason.INVALID_PARAMETERS, - invalidParameterString(index, "boolean (yes/no)")); - } - } - - /** - * Retrieves an enum value at the given index, or aborts the execution if - * none can be found. - * - *

Checks against the enum values without case, but does not converts - * spaces to underscores or things like that.

- * - * @param index The index. - * @param enumType The enum to search into. - * @return The retrieved enum value. - * @throws CommandException If the value cannot be found in the enum. - */ - protected > T getEnumParameter(int index, Class enumType) throws CommandException { - Enum[] enumValues = enumType.getEnumConstants(); - String parameter = args[index].toLowerCase(); - - for (Enum value : enumValues) { - if (value.toString().toLowerCase().equals(parameter)) { - return (T) value; - } - } - - throw new CommandException(this, Reason.INVALID_PARAMETERS, invalidParameterString(index, enumValues)); - } - - /** - * Retrieves a player from its name at the given index, or aborts the - * execution if none can be found. - * - * @param index The index. - * @return The retrieved player. - * @throws CommandException If the value is invalid. - */ - protected Player getPlayerParameter(int index) throws CommandException { - String parameter = args[index]; - - for (Player player : Bukkit.getOnlinePlayers()) { - if (player.getName().equals(parameter)) { - return player; - } - } - - throw new CommandException(this, Reason.INVALID_PARAMETERS, invalidParameterString(index, "player name")); - } - - /** - * Retrieves a player from its name at the given index, or aborts the - * execution if none can be found. - * - * @param parameter The string containing the name. - * @param callback A consumer that will use the offline player's UUID - */ - public void offlinePlayerParameter(final String parameter, final Consumer callback) { - CommandWorkers cw = new CommandWorkers(); - cw.offlineNameFetch(parameter, callback); - } - - /** - * Retrieves a player from its name at the given index, or aborts the - * execution if none can be found. - * - * @param index The index. - * @param callback A consumer that will use the offline player's UUID - */ - public void offlinePlayerParameter(int index, final Consumer callback) { - final String parameter = args[index]; - offlinePlayerParameter(parameter, callback); - } - - - ///////////// Methods for flags ///////////// - - /** - * Checks if a flag is set. - * - *

To use this functionality, your command class must be annotated by - * {@link WithFlags}.

- * - *

A flag is a value precessed by one or two dashes, and composed of - * alphanumerical characters, and dashes.
Flags are not - * case-sensitive.

- * - *

One-letter flags are passed using the syntax {@code -f} (for the - * {@code f} flag). Multiple one-letter flags can be passed at once, like - * this: {@code -fcrx} (for the {@code f}, {@code c}, {@code r}, and {@code - * x} flags).

- * - *

Multiple-letter flags are passed using the syntax {@code --flag} (for - * the {@code flag} flag). To pass multiple multiple-letter flags, you must - * repeat the {@code --}: {@code --flag --other-flag} (for the flags {@code - * flag} and {@code other-flag}).

- * - *

With the {@link WithFlags} annotation alone, all flags are caught. - * You can constrain the flags retrieved by passing an array of flags to the - * annotation, like this: - * - *

-     *     \@WithFlags({"flag", "f"})
-     * 
- * - *

If a flag-like argument is passed but not in the flags whitelist, it will - * be left in the {@link #args} parameters like any other arguments. Else, - * the retrieved flags are removed from the arguments list.

- * - * @param flag The flag. - * @return {@code true} if the flag was passed by the player. - */ - protected boolean hasFlag(String flag) { - return flags != null && flags.contains(flag.toLowerCase()); - } -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandException.java deleted file mode 100644 index ccb21023..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandException.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright or © or Copr. QuartzLib contributors (2015 - 2020) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.quartzlib.components.commands; - -import fr.zcraft.quartzlib.components.gui.GuiUtils; -import fr.zcraft.quartzlib.tools.PluginLogger; -import org.bukkit.ChatColor; - - -public class CommandException extends Exception { - private final Reason reason; - private final Command command; - private final String extra; - - /** - * A new CommandException. - * @param command The command that raised the exception. - * @param reason The reason code. - * @param extra Any extra message. - */ - public CommandException(Command command, Reason reason, String extra) { - this.command = command; - this.reason = reason; - this.extra = extra; - } - - public CommandException(Command command, Reason reason) { - this(command, reason, ""); - } - - public Reason getReason() { - return reason; - } - - /** - * Builds the "reason" string for this command exception. - * @return The reason string. - */ - public String getReasonString() { - switch (reason) { - case COMMANDSENDER_EXPECTED_PLAYER: - return "You must be a player to use this command."; - case INVALID_PARAMETERS: - final String prefix = ChatColor.GOLD + Commands.CHAT_PREFIX + " " + ChatColor.RESET; - return "\n" - + ChatColor.RED + Commands.CHAT_PREFIX + ' ' + ChatColor.BOLD + "Invalid argument" + '\n' - + GuiUtils.generatePrefixedFixedLengthString(ChatColor.RED + Commands.CHAT_PREFIX + " ", extra) - + '\n' - + GuiUtils.generatePrefixedFixedLengthString(prefix, "Usage: " + command.getUsageString()) - + '\n' - + GuiUtils.generatePrefixedFixedLengthString(prefix, - "For more information, use /" + command.getCommandGroup().getUsualName() - + " help " + command.getName()); - case COMMAND_ERROR: - return extra.isEmpty() ? "An unknown error suddenly happened." : extra; - case SENDER_NOT_AUTHORIZED: - return "You do not have the permission to use this command."; - default: - PluginLogger.warning("Unknown CommandException caught", this); - return "An unknown error suddenly happened."; - } - } - - public enum Reason { - COMMANDSENDER_EXPECTED_PLAYER, - INVALID_PARAMETERS, - COMMAND_ERROR, - SENDER_NOT_AUTHORIZED - } -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java deleted file mode 100644 index f0f984fa..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright or © or Copr. QuartzLib contributors (2015 - 2020) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.quartzlib.components.commands; - -import fr.zcraft.quartzlib.tools.PluginLogger; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Scanner; -import org.apache.commons.lang.StringUtils; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.command.TabCompleter; -import org.bukkit.plugin.java.JavaPlugin; - - -public class CommandGroup implements TabCompleter, CommandExecutor { - private final CommandGroup shortcutCommandGroup; - private final String[] names; - private final Class[] commandsClasses; - private final ArrayList commands = new ArrayList<>(); - private final HashMap commandsDescriptions = new HashMap<>(); - private String description = ""; - - CommandGroup(CommandGroup shortcutCommandGroup, Class commandClass, String... names) { - this.names = names; - this.commandsClasses = new Class[] {commandClass}; - this.shortcutCommandGroup = shortcutCommandGroup; - initCommands(); - } - - CommandGroup(String[] names, Class... commandsClasses) { - this.names = names; - this.commandsClasses = commandsClasses; - this.shortcutCommandGroup = null; - initDescriptions(); - initCommands(); - } - - /** - * Gets the command args from the given group args. - * @param args The group args. - * @return The command args. - */ - public static String[] getCommandArgsFromGroupArgs(String[] args) { - String[] commandArgs = new String[args.length - 1]; - - for (int i = 0; i < commandArgs.length; i++) { - commandArgs[i] = args[i + 1]; - } - - return commandArgs; - } - - private void initDescriptions() { - String fileName = "help/" + getUsualName() + ".txt"; - InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName); - if (stream == null) { - PluginLogger.warning("Could not load description file for the " + getUsualName() + " command"); - return; - } - - Scanner scanner = new Scanner(stream); - StringBuilder builder = new StringBuilder(); - - //Getting the group's description - //And then each command's description - int colonIndex; - int firstSpaceIndex; - boolean isGroupDescription = true; - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - colonIndex = line.indexOf(':'); - if (isGroupDescription) { - firstSpaceIndex = line.indexOf(' '); - if (colonIndex > 0 && firstSpaceIndex > colonIndex) { - isGroupDescription = false; - } - } - - if (isGroupDescription) { - builder.append(line).append('\n'); - } else { - commandsDescriptions.put(line.substring(0, colonIndex).trim(), - line.substring(colonIndex + 1).trim()); - } - } - - scanner.close(); - description = builder.toString().trim(); - - } - - private void initCommands() { - for (Class commandClass : commandsClasses) { - addCommand(commandClass); - } - - if (!isShortcutCommand()) { - addCommand(HelpCommand.class); - } - } - - private void addCommand(Class commandClass) { - Constructor constructor; - Command newCommand; - try { - constructor = commandClass.getConstructor(); - newCommand = constructor.newInstance(); - newCommand.init(isShortcutCommand() ? shortcutCommandGroup : this); - commands.add(newCommand); - } catch (Exception ex) { - PluginLogger.warning("Exception while initializing command", ex); - } - } - - /** - * Execute the command matching the args. - * @param sender The command's sender. - * @param args The command args. - * @return true if command ran successfuly. - */ - public boolean executeMatchingCommand(CommandSender sender, String[] args) { - if (isShortcutCommand()) { - commands.get(0).execute(sender, args); - return true; - } - - if (args.length <= 0) { - sender.sendMessage(getUsage()); - return false; - } - - String commandName = args[0]; - String[] commandArgs = getCommandArgsFromGroupArgs(args); - - return executeMatchingCommand(sender, commandName, commandArgs); - } - - private boolean executeMatchingCommand(CommandSender sender, String commandName, String[] args) { - Command command = getMatchingCommand(commandName); - if (command != null) { - command.execute(sender, args); - } else { - sender.sendMessage(getUsage()); - } - return command != null; - } - - @Override - public List onTabComplete(CommandSender sender, org.bukkit.command.Command cmd, String label, - String[] args) { - return tabComplete(sender, args); - } - - @Override - public boolean onCommand(CommandSender sender, org.bukkit.command.Command cmd, String label, String[] args) { - return executeMatchingCommand(sender, args); - } - - /** - * Computes a list of possible autocomplete suggestions for the given partial arguments. - * @param sender The sender of the command. - * @param args The partial arguments. - * @return A list of suggestions. - */ - public List tabComplete(CommandSender sender, String[] args) { - if (isShortcutCommand()) { - return commands.get(0).tabComplete(sender, args); - } - if (args.length <= 1) { - return tabComplete(sender, args.length == 1 ? args[0] : null); - } - String commandName = args[0]; - String[] commandArgs = getCommandArgsFromGroupArgs(args); - return tabCompleteMatching(sender, commandName, commandArgs); - } - - /** - * Computes a list of possible autocomplete suggestions for the given command. - * @param sender The sender of the command. - * @param commandName The name of the command - * @return A list of suggestions. - */ - public List tabComplete(CommandSender sender, String commandName) { - ArrayList matchingCommands = new ArrayList(); - for (Command command : commands) { - if (!command.canExecute(sender)) { - continue; - } - if (commandName == null || command.getName().startsWith(commandName.toLowerCase())) { - matchingCommands.add(command.getName()); - } - } - return matchingCommands; - } - - private List tabCompleteMatching(CommandSender sender, String commandName, String[] args) { - Command command = getMatchingCommand(commandName); - if (command != null) { - return command.tabComplete(sender, args); - } else { - return new ArrayList<>(); - } - } - - /** - * Gets the command matching the given name. - * @param commandName The command name. - * @return The matching command, or null if none were found. - */ - public Command getMatchingCommand(String commandName) { - for (Command command : commands) { - if (command.matches(commandName)) { - return command; - } - } - return null; - } - - /** - * Gets the command matching the given class. - * @param commandClass The command class. - * @return The matching gommand, or null if none were found. - */ - public Command getCommandInfo(Class commandClass) { - for (Command command : commands) { - if (command.getClass().equals(commandClass)) { - return command; - } - } - return null; - } - - /** - * Return if this command matches the given name. - * @param name The name of the command to test for. - * @return if this command matches the given name. - */ - public boolean matches(String name) { - name = name.toLowerCase(); - for (String commandName : names) { - if (commandName.equals(name)) { - return true; - } - } - return false; - } - - /** - * Returns an array of all subcommands. - * @return an array of all subcommands. - */ - public String[] getCommandsNames() { - String[] commandsNames = new String[commands.size()]; - - for (int i = 0; i < commands.size(); i++) { - commandsNames[i] = commands.get(i).getName(); - } - - return commandsNames; - } - - void register(JavaPlugin plugin) { - PluginCommand bukkitCommand = plugin.getCommand(getUsualName()); - if (bukkitCommand == null) { - throw new IllegalStateException("Command " + getUsualName() + " is not correctly registered in plugin.yml"); - } - bukkitCommand.setAliases(getAliases()); - bukkitCommand.setExecutor(this); - bukkitCommand.setTabCompleter(this); - } - - protected String getUsage() { - if (isShortcutCommand()) { - return "§cUsage: " + commands.get(0).getUsageString(); - } - return "§cUsage: /" + getUsualName() - + " <" + StringUtils.join(getCommandsNames(), "|") + ">"; - } - - public String getUsualName() { - return names[0]; - } - - public String[] getNames() { - return names.clone(); - } - - public List getAliases() { - return Arrays.asList(names).subList(1, names.length); - } - - public Command[] getCommands() { - return commands.toArray(new Command[commands.size()]); - } - - public String getDescription() { - return description; - } - - public String getDescription(String commandName) { - return commandsDescriptions.get(commandName); - } - - public boolean isShortcutCommand() { - return shortcutCommandGroup != null; - } - - public CommandGroup getShortcutCommandGroup() { - return shortcutCommandGroup; - } - -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandInfo.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandInfo.java deleted file mode 100644 index d2060863..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandInfo.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright or © or Copr. QuartzLib contributors (2015 - 2020) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.quartzlib.components.commands; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -public @interface CommandInfo { - /** - * The name of the command. - */ - String name(); - - /** - * The "usage" description. - */ - String usageParameters() default ""; - - /** - * Aliases for this command. - */ - String[] aliases() default {}; -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandWorkers.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandWorkers.java deleted file mode 100644 index b1b596c6..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandWorkers.java +++ /dev/null @@ -1,41 +0,0 @@ -package fr.zcraft.quartzlib.components.commands; - -import fr.zcraft.quartzlib.components.i18n.I; -import fr.zcraft.quartzlib.components.worker.Worker; -import fr.zcraft.quartzlib.components.worker.WorkerAttributes; -import fr.zcraft.quartzlib.components.worker.WorkerCallback; -import fr.zcraft.quartzlib.components.worker.WorkerRunnable; -import fr.zcraft.quartzlib.tools.PluginLogger; -import fr.zcraft.quartzlib.tools.mojang.UUIDFetcher; -import java.util.UUID; -import java.util.function.Consumer; - -@WorkerAttributes(name = "Command's worker", queriesMainThread = true) -public class CommandWorkers extends Worker { - - /** - * Fetches an offline player's UUID by name. - */ - public void offlineNameFetch(final String playerName, final Consumer callback) { - final WorkerCallback wCallback = new WorkerCallback() { - @Override - public void finished(UUID result) { - callback.accept(result); // Si tout va bien on passe l'UUID au callback - } - - @Override - public void errored(Throwable exception) { - PluginLogger.warning(I.t("Error while getting player UUID")); - callback.accept(null); // En cas d'erreur on envoie `null` au callback - } - }; - WorkerRunnable wr = new WorkerRunnable() { - @Override - public UUID run() throws Throwable { - return UUIDFetcher.fetch(playerName); - } - }; - submitQuery(wr, wCallback); - } - -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/Commands.java b/src/main/java/fr/zcraft/quartzlib/components/commands/Commands.java deleted file mode 100644 index bf7c7ea8..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/Commands.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright or © or Copr. QuartzLib contributors (2015 - 2020) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.quartzlib.components.commands; - -import fr.zcraft.quartzlib.core.QuartzComponent; -import fr.zcraft.quartzlib.core.QuartzLib; -import java.util.ArrayList; -import java.util.List; -import org.bukkit.command.CommandSender; - -public class Commands extends QuartzComponent { - public static final String CHAT_PREFIX = "┃"; - - private static final List commandGroups = new ArrayList<>(); - private static String globalPermission; - - /** - * Registers a shortcut command. - */ - public static void registerShortcut(String commandGroupName, Class commandClass, - String... shortcutNames) { - CommandGroup group = getMatchingCommandGroup(commandGroupName); - if (group == null) { - throw new IllegalArgumentException("Invalid command group name: " + commandGroupName); - } - CommandGroup newCommandGroup = new CommandGroup(group, commandClass, shortcutNames); - - newCommandGroup.register(QuartzLib.getPlugin()); - commandGroups.add(newCommandGroup); - } - - /** - * Registers many new commands. - * @param names The names of the commands - * @param commandsClasses The matching classes for the commands - */ - public static void register(String[] names, Class... commandsClasses) { - final CommandGroup commandGroup = new CommandGroup(names, commandsClasses); - commandGroup.register(QuartzLib.getPlugin()); - - commandGroups.add(commandGroup); - } - - public static void register(String name, Class... commandsClasses) { - register(new String[] {name}, commandsClasses); - } - - /** - * Executes a registered command. - * @param sender The command sender. - * @param commandName The name of the command. - * @param args The command's arguments. - * @return Whether the command was found. - */ - public static boolean execute(CommandSender sender, String commandName, String[] args) { - CommandGroup commandGroup = getMatchingCommandGroup(commandName); - if (commandGroup == null) { - return false; - } - commandGroup.executeMatchingCommand(sender, args); - return true; - } - - - /** - * Computes a list of possible autocomplete suggestions for the given command. - * @param sender The sender of the command. - * @param commandName The name of the command. - * @param args The partial arguments for the command. - * @return A list of suggestions. - */ - public static List tabComplete(CommandSender sender, String commandName, String[] args) { - CommandGroup commandGroup = getMatchingCommandGroup(commandName); - if (commandGroup == null) { - return new ArrayList<>(); - } - return commandGroup.tabComplete(sender, args); - } - - /** - * Gets the command matching the given class. - * @param commandClass The command class. - * @return The matching gommand, or null if none were found. - */ - public static Command getCommandInfo(Class commandClass) { - Command command = null; - for (CommandGroup commandGroup : commandGroups) { - command = commandGroup.getCommandInfo(commandClass); - if (command != null) { - break; - } - } - return command; - } - - private static CommandGroup getMatchingCommandGroup(String commandName) { - for (CommandGroup commandGroup : commandGroups) { - if (commandGroup.matches(commandName)) { - return commandGroup; - } - } - return null; - } - - - public static String getGlobalPermission() { - return globalPermission; - } - - public static void setGlobalPermission(String permission) { - globalPermission = permission; - } -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/HelpCommand.java b/src/main/java/fr/zcraft/quartzlib/components/commands/HelpCommand.java deleted file mode 100644 index 1ec391b0..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/HelpCommand.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright or © or Copr. QuartzLib contributors (2015 - 2020) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.quartzlib.components.commands; - -import fr.zcraft.quartzlib.components.gui.GuiUtils; -import fr.zcraft.quartzlib.components.rawtext.RawText; -import fr.zcraft.quartzlib.core.QuartzLib; -import fr.zcraft.quartzlib.tools.PluginLogger; -import fr.zcraft.quartzlib.tools.commands.PaginatedTextView; -import fr.zcraft.quartzlib.tools.text.RawMessage; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -@CommandInfo(name = "help", usageParameters = "") -public class HelpCommand extends Command { - @Override - protected void run() throws CommandException { - if (args.length < 1) { - groupHelp(1); - } else { - if (args.length == 1 && args[0].startsWith("--page=")) { - try { - groupHelp(Integer.valueOf(args[0].split("=")[1])); - return; - } catch (NumberFormatException ignored) { - } - } - - commandHelp(); - } - } - - private void groupHelp(int page) throws CommandException { - final List displayedCommands = new ArrayList<>(); - - for (Command subCommands : commandGroup.getCommands()) { - if (subCommands.canExecute(sender, args)) { - displayedCommands.add(subCommands); - } - } - - if (sender instanceof Player) { - info(""); - } - - new GroupHelpPagination() - .setData(displayedCommands.toArray(new Command[displayedCommands.size()])) - .setCurrentPage(page) - .display(sender); - } - - private void commandHelp() throws CommandException { - Command command = commandGroup.getMatchingCommand(args[0]); - if (command == null) { - error("The specified command does not exist."); - return; - } - - if (!command.canExecute(sender, args)) { - warning("You do not have the permission to use this command."); - } - - String message = "\n"; - message += GuiUtils.generatePrefixedFixedLengthString("§6" + Commands.CHAT_PREFIX + "§l ", - QuartzLib.getPlugin().getName() + " help for /" + command.getCommandGroup().getUsualName() + " " - + command.getName()) + "\n"; - message += GuiUtils.generatePrefixedFixedLengthString("§6" + Commands.CHAT_PREFIX + " ", - "Usage: §r" + command.getUsageString()) + "\n"; - - try { - String help = getHelpText(command); - if (help.isEmpty()) { - message += "§c" + Commands.CHAT_PREFIX + " There is no help message for this command."; - } else { - message += help; - } - } catch (IOException ex) { - message += "§c" + Commands.CHAT_PREFIX + " Could not read help for this command."; - PluginLogger.warning("Could not read help for the command: " + command.getName(), ex); - } - - sender.sendMessage(message); - } - - private String getHelpText(Command command) throws IOException { - String fileName = "help/" + commandGroup.getUsualName() - + "/" + command.getName() + ".txt"; - - StringBuilder result = new StringBuilder(); - - InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName); - if (stream == null) { - return ""; - } - - Scanner scanner = new Scanner(stream); - - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - result.append("§l§9" + Commands.CHAT_PREFIX + " §r").append(line).append("\n"); - } - - scanner.close(); - - return result.toString().trim(); - } - - - @Override - protected List complete() throws CommandException { - if (args.length != 1) { - return null; - } - - ArrayList matches = new ArrayList<>(); - - for (Command command : commandGroup.getCommands()) { - if (command.getName().startsWith(args[0])) { - matches.add(command.getName()); - } - } - - return matches; - } - - - private class GroupHelpPagination extends PaginatedTextView { - @Override - protected void displayHeader(CommandSender receiver) { - final String header = ChatColor.BOLD + (commandGroup.getDescription().isEmpty() - ? QuartzLib.getPlugin().getName() + " help for /" + commandGroup.getUsualName() - : commandGroup.getDescription()); - - receiver.sendMessage(receiver instanceof Player - ? GuiUtils.generatePrefixedFixedLengthString( - ChatColor.BLUE + Commands.CHAT_PREFIX + " " + ChatColor.RESET, header) - : header - ); - } - - @Override - protected void displayItem(CommandSender receiver, Command command) { - final String commandName = "/" + commandGroup.getUsualName() + " " + command.getName(); - final String description = commandGroup.getDescription(command.getName()); - - String helpMessage = ChatColor.GOLD + commandName; - if (description != null) { - helpMessage += ChatColor.GOLD + ": " + ChatColor.WHITE + description; - } - - final String formattedHelpMessage = receiver instanceof Player - ? - GuiUtils.generatePrefixedFixedLengthString(ChatColor.GOLD + Commands.CHAT_PREFIX + " ", helpMessage) - : helpMessage; - - RawText helpLine = RawText.fromFormattedString( - formattedHelpMessage, - new RawText().suggest(commandName + " ").hover(new RawText(command.getUsageString())) - ); - - RawMessage.send(receiver, helpLine); - } - - @Override - protected String getCommandToPage(int page) { - return build("--page=" + page); - } - } -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/WithFlags.java b/src/main/java/fr/zcraft/quartzlib/components/commands/WithFlags.java deleted file mode 100644 index b8ab047c..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/WithFlags.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright or © or Copr. AmauryCarrade (2015) - * - * http://amaury.carrade.eu - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.quartzlib.components.commands; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - - -/** - * Adds this annotation to a command class to make it accept flags, - * i.e. parameters prefixed with - or -- extracted from the args - * array and made available through {@link Command#hasFlag(String)}. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -public @interface WithFlags { - - /** - * The name of the flags. - */ - String[] value() default {}; -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java b/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java new file mode 100644 index 00000000..0606e25e --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java @@ -0,0 +1,4 @@ +package fr.zcraft.quartzlib.components.commands.attributes; + +public @interface Sender { +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java new file mode 100644 index 00000000..87cdaf02 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java @@ -0,0 +1,16 @@ +package fr.zcraft.quartzlib.components.commands.internal; + +import java.util.ArrayList; +import java.util.List; + +class CommandEndpoint extends CommandNode { + private final List methods = new ArrayList<>(); + + CommandEndpoint(String name) { + super(name); + } + + void addMethod(CommandMethod method) { + methods.add(method); + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java new file mode 100644 index 00000000..c0dfc935 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java @@ -0,0 +1,47 @@ +package fr.zcraft.quartzlib.components.commands.internal; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +class CommandGroup extends CommandNode { + private final Class commandGroupClass; + private final CommandGroup parent; + + private final Map subCommands = new HashMap<>(); + + public CommandGroup(Class commandGroupClass, String name) { + super(name); + this.commandGroupClass = commandGroupClass; + this.parent = null; + getCommandMethods(commandGroupClass).forEach(this::addMethod); + } + + public CommandGroup(Class commandGroupClass, String name, CommandGroup parent) { + super(name); + this.commandGroupClass = commandGroupClass; + this.parent = parent; + } + + public Iterable getSubCommands () { + return this.subCommands.values(); + } + + + private void addMethod(CommandMethod method) { + // TODO: handle adding to non-endpoints + CommandEndpoint endpoint = (CommandEndpoint) subCommands.get(method.getName()); + if (endpoint == null) { + endpoint = new CommandEndpoint(method.getName()); + subCommands.put(endpoint.getName(), endpoint); + } + endpoint.addMethod(method); + } + + // Private utils TODO: move to DiscoveryUtils? + + private static Stream getCommandMethods(Class commandGroupClass) { + return Arrays.stream(commandGroupClass.getDeclaredMethods()).map(CommandMethod::new); + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java new file mode 100644 index 00000000..fcaa99fa --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java @@ -0,0 +1,17 @@ +package fr.zcraft.quartzlib.components.commands.internal; + +import java.lang.reflect.Method; + +class CommandMethod { + private final Method method; + private final String name; + + CommandMethod(Method method) { + this.method = method; + this.name = method.getName(); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java new file mode 100644 index 00000000..c6d38306 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java @@ -0,0 +1,13 @@ +package fr.zcraft.quartzlib.components.commands.internal; + +abstract class CommandNode { + private final String name; + + protected CommandNode(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/rawtext/RawTextPart.java b/src/main/java/fr/zcraft/quartzlib/components/rawtext/RawTextPart.java index c71faee4..c8d56216 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/rawtext/RawTextPart.java +++ b/src/main/java/fr/zcraft/quartzlib/components/rawtext/RawTextPart.java @@ -32,8 +32,6 @@ package fr.zcraft.quartzlib.components.rawtext; import com.google.common.base.CaseFormat; -import fr.zcraft.quartzlib.components.commands.Command; -import fr.zcraft.quartzlib.components.commands.Commands; import fr.zcraft.quartzlib.tools.PluginLogger; import fr.zcraft.quartzlib.tools.items.ItemUtils; import fr.zcraft.quartzlib.tools.reflection.NMSException; @@ -353,20 +351,21 @@ public T command(String command) { return click(ActionClick.RUN_COMMAND, command); } - /** + /* * Adds a command executed when this text component is clicked. * * @param command The command class to execute on click. * @param args The arguments to pass to the command. * @return The current raw text component, for method chaining. */ - public T command(Class command, String... args) { + // TODO + /*public T command(Class command, String... args) { Command commandInfo = Commands.getCommandInfo(command); if (commandInfo == null) { throw new IllegalArgumentException("Unknown command"); } return command(commandInfo.build(args)); - } + }*/ /** * Adds an URI to be opened when this text component is clicked. @@ -399,7 +398,7 @@ public T suggest(String suggestion) { return click(ActionClick.SUGGEST_COMMAND, suggestion); } - /** + /* * Adds a command to be suggested on click, i.e. the command will be placed * into the player's chat when clicked. * @@ -407,13 +406,14 @@ public T suggest(String suggestion) { * @param args The arguments to pass to the command. * @return The current raw text component, for method chaining. */ - public T suggest(Class command, String... args) { + // TODO + /*public T suggest(Class command, String... args) { Command commandInfo = Commands.getCommandInfo(command); if (commandInfo == null) { throw new IllegalArgumentException("Unknown command"); } return click(ActionClick.SUGGEST_COMMAND, commandInfo.build(args)); - } + }*/ /** * Adds a text to be inserted on shift-click, i.e. the text will be appended @@ -428,7 +428,7 @@ public T insert(String insertion) { return (T) this; } - /** + /* * Adds a command to be inserted on shift-click, i.e. the command will be * appended to the player's chat when shift-clicked. * @@ -436,13 +436,14 @@ public T insert(String insertion) { * @param args The arguments to pass to the command. * @return The current raw text component, for method chaining. */ - public T insert(Class command, String... args) { + // TODO + /*public T insert(Class command, String... args) { Command commandInfo = Commands.getCommandInfo(command); if (commandInfo == null) { throw new IllegalArgumentException("Unknown command"); } return insert(commandInfo.build(args)); - } + }*/ /** * Builds this chain of components into a {@link RawText} ready to be used. diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandFlagsTest.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandFlagsTest.java deleted file mode 100644 index f94662f8..00000000 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandFlagsTest.java +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright or © or Copr. AmauryCarrade (2015) - * - * http://amaury.carrade.eu - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.quartzlib.components.commands; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import junit.framework.Assert; -import org.apache.commons.lang.StringUtils; -import org.junit.jupiter.api.Test; - -public class CommandFlagsTest { - private final Method parseArgsMethod; - - public CommandFlagsTest() throws ReflectiveOperationException { - parseArgsMethod = - Command.class.getDeclaredMethod("parseArgs", String[].class, Set.class, List.class, Set.class); - parseArgsMethod.setAccessible(true); - } - - private void parseArgs(final String[] args, final Set acceptedFlags, List realArgs, - Set flags) { - try { - parseArgsMethod.invoke(null, args, acceptedFlags, realArgs, flags); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - Assert.fail("Cannot invoke parseArgs method"); - } - } - - private void assertArgs(final String[] args, final String[] acceptedFlags, final String[] expectedArgs, - final String[] expectedFlags) { - final Set acceptedFlagsSet = acceptedFlags != null ? new HashSet<>(Arrays.asList(acceptedFlags)) : null; - - final List actualArgs = new ArrayList<>(args.length); - final Set actualFlags = new HashSet<>(); - - parseArgs(args, acceptedFlagsSet, actualArgs, actualFlags); - - final TreeSet expectedFlagsSorted = new TreeSet<>(Arrays.asList(expectedFlags)); - final TreeSet actualFlagsSorted = new TreeSet<>(actualFlags); - - Assert.assertEquals("Expected and actual arguments differs", StringUtils.join(expectedArgs, ","), - StringUtils.join(actualArgs, ",")); - Assert.assertEquals("Expected and actual flags differs", StringUtils.join(expectedFlagsSorted, ","), - StringUtils.join(actualFlagsSorted, ",")); - } - - @Test - public void flagsDisabledTest() { - assertArgs( - new String[] {"arg0", "arg1", "arg2"}, - null, - new String[] {"arg0", "arg1", "arg2"}, - new String[] {} - ); - } - - @Test - public void flagsDisabledWithFlagLikeTest() { - assertArgs( - new String[] {"arg0", "arg1", "-flag", "arg2", "--flag2"}, - null, - new String[] {"arg0", "arg1", "-flag", "arg2", "--flag2"}, - new String[] {} - ); - } - - @Test - public void simpleFlagsTest() { - assertArgs( - new String[] {"arg0", "arg1", "-f"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "-l"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"f", "l"} - ); - } - - @Test - public void simpleMultipleFlagsTest() { - assertArgs( - new String[] {"arg0", "arg1", "-fl"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"f", "l"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-flop"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"f", "l", "o", "p"} - ); - } - - @Test - public void longFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--flop"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "--flop", "--pomf"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop", "pomf"} - ); - } - - @Test - public void longFlagWithDashTest() { - assertArgs( - new String[] {"arg0", "arg1", "--flop-pomf"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop-pomf"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "--flop-pomf", "--pomf"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop-pomf", "pomf"} - ); - } - - @Test - public void mixedFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--flop", "-fl", "--pomf"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop", "pomf", "f", "l"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "--flop", "--pomf", "-l"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop", "pomf", "f", "l"} - ); - } - - @Test - public void mixedCaseFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--FLOP", "-fL", "--poMf"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop", "pomf", "f", "l"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-F", "--flop", "--POMf", "-L"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop", "pomf", "f", "l"} - ); - } - - @Test - public void middleFlagTest() { - assertArgs( - new String[] {"arg0", "--flop", "-fl", "arg1", "--pomf"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop", "pomf", "f", "l"} - ); - - assertArgs( - new String[] {"arg0", "-f", "--flop", "arg1", "--pomf", "-l"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"flop", "pomf", "f", "l"} - ); - } - - @Test - public void duplicatedFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--pomf", "--pomf"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"pomf"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "-f"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-ff"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - } - - @Test - public void duplicatedMixedCaseFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--pomf", "--POMF"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"pomf"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "-F"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-fF"}, - new String[] {}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - } - - - // Constrained - - - @Test - public void simpleConstrainedFlagsTest() { - assertArgs( - new String[] {"arg0", "arg1", "-f"}, - new String[] {"f"}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "-l"}, - new String[] {"f"}, - new String[] {"arg0", "arg1", "-l"}, - new String[] {"f"} - ); - } - - @Test - public void simpleMultipleConstrainedFlagsTest() { - assertArgs( - new String[] {"arg0", "arg1", "-fl"}, - new String[] {"f"}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-flop"}, - new String[] {"f", "o"}, - new String[] {"arg0", "arg1"}, - new String[] {"f", "o"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-fl"}, - new String[] {"k"}, - new String[] {"arg0", "arg1", "-fl"}, - new String[] {} - ); - } - - @Test - public void longConstrainedFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--flop"}, - new String[] {"flop"}, - new String[] {"arg0", "arg1"}, - new String[] {"flop"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "--flop", "--pomf"}, - new String[] {"pomf"}, - new String[] {"arg0", "arg1", "--flop"}, - new String[] {"pomf"} - ); - } - - @Test - public void mixedConstrainedFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--flop", "-fl", "--pomf"}, - new String[] {"flop", "f"}, - new String[] {"arg0", "arg1", "--pomf"}, - new String[] {"flop", "f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "--flop", "--pomf", "-l"}, - new String[] {"flop", "f"}, - new String[] {"arg0", "arg1", "--pomf", "-l"}, - new String[] {"flop", "f"} - ); - } - - @Test - public void mixedCaseConstrainedFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--FLOP", "-fL", "--poMf"}, - new String[] {"flop", "l"}, - new String[] {"arg0", "arg1", "--poMf"}, - new String[] {"flop", "l"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-F", "--flop", "--POMf", "-L"}, - new String[] {"flop", "l"}, - new String[] {"arg0", "arg1", "-F", "--POMf"}, - new String[] {"flop", "l"} - ); - } - - @Test - public void middleConstrainedFlagTest() { - assertArgs( - new String[] {"arg0", "--flop", "-fl", "arg1", "--pomf"}, - new String[] {"pomf", "l"}, - new String[] {"arg0", "--flop", "arg1"}, - new String[] {"pomf", "l"} - ); - - assertArgs( - new String[] {"arg0", "-f", "--flop", "arg1", "--pomf", "-l"}, - new String[] {"pomf", "l"}, - new String[] {"arg0", "-f", "--flop", "arg1"}, - new String[] {"pomf", "l"} - ); - } - - @Test - public void duplicatedConstrainedFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--pomf", "--pomf"}, - new String[] {"pomf"}, - new String[] {"arg0", "arg1"}, - new String[] {"pomf"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "-f"}, - new String[] {"f"}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-ff"}, - new String[] {"f"}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "--pomf", "--pomf"}, - new String[] {"flop"}, - new String[] {"arg0", "arg1", "--pomf", "--pomf"}, - new String[] {} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "-f"}, - new String[] {"l"}, - new String[] {"arg0", "arg1", "-f", "-f"}, - new String[] {} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-ff"}, - new String[] {"l"}, - new String[] {"arg0", "arg1", "-ff"}, - new String[] {} - ); - } - - @Test - public void duplicatedMixedCaseConstrainedFlagTest() { - assertArgs( - new String[] {"arg0", "arg1", "--pomf", "--POMF"}, - new String[] {"pomf"}, - new String[] {"arg0", "arg1"}, - new String[] {"pomf"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "-F"}, - new String[] {"f"}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-fF"}, - new String[] {"f"}, - new String[] {"arg0", "arg1"}, - new String[] {"f"} - ); - - assertArgs( - new String[] {"arg0", "arg1", "--pomf", "--POMF"}, - new String[] {"flop"}, - new String[] {"arg0", "arg1", "--pomf", "--POMF"}, - new String[] {} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-f", "-F"}, - new String[] {"l"}, - new String[] {"arg0", "arg1", "-f", "-F"}, - new String[] {} - ); - - assertArgs( - new String[] {"arg0", "arg1", "-fF"}, - new String[] {"l"}, - new String[] {"arg0", "arg1", "-fF"}, - new String[] {} - ); - } -} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java new file mode 100644 index 00000000..c5c72dd4 --- /dev/null +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java @@ -0,0 +1,20 @@ +package fr.zcraft.quartzlib.components.commands.internal; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.stream.StreamSupport; + +public class CommandGraphTests { + @Test public void canDiscoverBasicSubcommands() { + class FooCommand { + public void add () {} + public void get () {} + public void list () {} + } + + CommandGroup commandGroup = new CommandGroup(FooCommand.class, "foo"); + String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); + Assertions.assertArrayEquals(new String[] {"add", "get", "list"}, commandNames); + } +} diff --git a/src/test/java/fr/zcraft/ztoaster/ToastCommands.java b/src/test/java/fr/zcraft/ztoaster/ToastCommands.java new file mode 100644 index 00000000..46333ab4 --- /dev/null +++ b/src/test/java/fr/zcraft/ztoaster/ToastCommands.java @@ -0,0 +1,64 @@ +package fr.zcraft.ztoaster; + +import fr.zcraft.quartzlib.components.commands.attributes.Sender; +import fr.zcraft.quartzlib.components.gui.Gui; +import fr.zcraft.quartzlib.components.i18n.I; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +public class ToastCommands { + + // public ToastSubCommand subCommand = new ToastSubCommand(); // ... + + public void add(@Sender Player cook) { + Toast toast = ToasterWorker.addToast(cook); + cook.sendMessage(I.t("Toast {0} added.", toast.getToastId())); + } + + public void add(@Sender Player cook, int toastCount) { + for(int i = toastCount; i --> 0;) + { + ToasterWorker.addToast(cook); + } + + cook.sendMessage(I.tn("One toast added.", "{0} toasts added.", toastCount, toastCount)); + } + + public void list(@Sender CommandSender sender) { + showToasts(sender, Arrays.asList(Toaster.getToasts())); + } + + public void list(@Sender CommandSender sender, Toast.CookingStatus cookingStatus) { + ArrayList toasts = new ArrayList(); + + for(Toast toast : Toaster.getToasts()) + { + if(toast.getStatus().equals(cookingStatus)) + toasts.add(toast); + } + + showToasts(sender, toasts); + } + + private void showToasts(CommandSender sender, Collection toasts) + { + if(toasts.isEmpty()) + { + // Output of the command /toaster list, without toasts. + sender.sendMessage("§7" + I.t("There are no toasts here ...")); + } + + for(Toast toast : toasts) + { + sender.sendMessage(I.t(" Toast #{0}", toast.getToastId())); + } + } + + public void open(@Sender Player player) { + Gui.open(player, new ToastExplorer()); + } +} diff --git a/ztoaster/src/main/java/fr/zcraft/ztoaster/Toaster.java b/ztoaster/src/main/java/fr/zcraft/ztoaster/Toaster.java index 199b3a20..3c1d8df0 100644 --- a/ztoaster/src/main/java/fr/zcraft/ztoaster/Toaster.java +++ b/ztoaster/src/main/java/fr/zcraft/ztoaster/Toaster.java @@ -30,16 +30,12 @@ package fr.zcraft.ztoaster; -import fr.zcraft.quartzlib.components.commands.Commands; import fr.zcraft.quartzlib.components.gui.Gui; import fr.zcraft.quartzlib.components.i18n.I18n; import fr.zcraft.quartzlib.components.scoreboard.Sidebar; import fr.zcraft.quartzlib.components.scoreboard.SidebarScoreboard; import fr.zcraft.quartzlib.core.QuartzPlugin; import fr.zcraft.quartzlib.tools.PluginLogger; -import fr.zcraft.ztoaster.commands.AddCommand; -import fr.zcraft.ztoaster.commands.ListCommand; -import fr.zcraft.ztoaster.commands.OpenCommand; import java.io.File; import java.util.ArrayList; import java.util.Locale; @@ -108,9 +104,9 @@ public void onEnable() { toasts = new ArrayList<>(); toastCounter = 0; - loadComponents(Gui.class, Commands.class, ToasterWorker.class, SidebarScoreboard.class, I18n.class); + loadComponents(Gui.class, ToasterWorker.class, SidebarScoreboard.class, I18n.class); - Commands.register("toaster", AddCommand.class, OpenCommand.class, ListCommand.class); + // Commands.register("toaster", AddCommand.class, OpenCommand.class, ListCommand.class); I18n.useDefaultPrimaryLocale(); I18n.setFallbackLocale(Locale.US); diff --git a/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/AddCommand.java b/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/AddCommand.java deleted file mode 100644 index 639c6c1a..00000000 --- a/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/AddCommand.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright or © or Copr. ZLib contributors (2015) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.ztoaster.commands; - -import fr.zcraft.quartzlib.components.commands.Command; -import fr.zcraft.quartzlib.components.commands.CommandException; -import fr.zcraft.quartzlib.components.commands.CommandInfo; -import fr.zcraft.quartzlib.components.i18n.I; -import fr.zcraft.ztoaster.Toast; -import fr.zcraft.ztoaster.ToasterWorker; -import org.bukkit.entity.Player; - - -@CommandInfo(name = "add", usageParameters = "[toast count]") -public class AddCommand extends Command { - @Override - protected void run() throws CommandException { - Player cook = playerSender(); - - if (args.length == 0) { - Toast toast = ToasterWorker.addToast(cook); - cook.sendMessage(I.t("Toast {0} added.", toast.getToastId())); - } else { - int toastCount = getIntegerParameter(0); - for (int i = toastCount; i-- > 0; ) { - ToasterWorker.addToast(cook); - } - - cook.sendMessage(I.tn("One toast added.", "{0} toasts added.", toastCount, toastCount)); - } - } -} diff --git a/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/ListCommand.java b/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/ListCommand.java deleted file mode 100644 index 9e73fcd4..00000000 --- a/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/ListCommand.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright or © or Copr. ZLib contributors (2015) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - -package fr.zcraft.ztoaster.commands; - -import fr.zcraft.quartzlib.components.commands.Command; -import fr.zcraft.quartzlib.components.commands.CommandException; -import fr.zcraft.quartzlib.components.commands.CommandInfo; -import fr.zcraft.quartzlib.components.i18n.I; -import fr.zcraft.ztoaster.Toast; -import fr.zcraft.ztoaster.Toaster; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; - -@CommandInfo(name = "list", usageParameters = "[cooked|not_cooked]") -public class ListCommand extends Command { - @Override - protected void run() throws CommandException { - if (args.length == 0) { - showToasts(Arrays.asList(Toaster.getToasts())); - } else { - ArrayList toasts = new ArrayList(); - Toast.CookingStatus status = getEnumParameter(0, Toast.CookingStatus.class); - - for (Toast toast : Toaster.getToasts()) { - if (toast.getStatus().equals(status)) { - toasts.add(toast); - } - } - - showToasts(toasts); - } - } - - private void showToasts(Collection toasts) { - if (toasts.isEmpty()) { - // Output of the command /toaster list, without toasts. - info(I.t("There are no toasts here ...")); - } - - for (Toast toast : toasts) { - sender.sendMessage(I.t(" Toast #{0}", toast.getToastId())); - } - } -} diff --git a/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/OpenCommand.java b/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/OpenCommand.java deleted file mode 100644 index ee088321..00000000 --- a/ztoaster/src/main/java/fr/zcraft/ztoaster/commands/OpenCommand.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright or © or Copr. ZLib contributors (2015) - * - * This software is governed by the CeCILL-B license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/ or redistribute the software under the terms of the CeCILL-B - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-B license and that you accept its terms. - */ - - -package fr.zcraft.ztoaster.commands; - -import fr.zcraft.quartzlib.components.commands.Command; -import fr.zcraft.quartzlib.components.commands.CommandException; -import fr.zcraft.quartzlib.components.commands.CommandInfo; -import fr.zcraft.quartzlib.components.gui.Gui; -import fr.zcraft.ztoaster.ToastExplorer; - -@CommandInfo(name = "open") -public class OpenCommand extends Command { - @Override - protected void run() throws CommandException { - Gui.open(playerSender(), new ToastExplorer()); - } -} From 7b6454534cb847c553183e57d608cbe2f16a743b Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Tue, 17 Nov 2020 04:42:54 +0100 Subject: [PATCH 02/16] Can now run basic commands without parameters --- .../commands/internal/CommandEndpoint.java | 9 ++++- .../commands/internal/CommandGroup.java | 30 +++++++++-------- .../commands/internal/CommandMethod.java | 9 +++++ .../commands/internal/CommandNode.java | 10 +++++- .../commands/internal/DiscoveryUtils.java | 27 +++++++++++++++ .../commands/internal/CommandGraphTests.java | 33 ++++++++++++++++++- 6 files changed, 102 insertions(+), 16 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java index 87cdaf02..8e48fffe 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands.internal; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + import java.util.ArrayList; import java.util.List; @@ -7,7 +9,12 @@ class CommandEndpoint extends CommandNode { private final List methods = new ArrayList<>(); CommandEndpoint(String name) { - super(name); + super(name, null); + } + + @Override + void run(Object instance, String[] args) { + this.methods.get(0).run(instance, args); } void addMethod(CommandMethod method) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java index c0dfc935..d6260695 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java @@ -3,25 +3,23 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import java.util.stream.Stream; +import java.util.function.Supplier; class CommandGroup extends CommandNode { private final Class commandGroupClass; - private final CommandGroup parent; + private final Supplier classInstanceSupplier; private final Map subCommands = new HashMap<>(); - public CommandGroup(Class commandGroupClass, String name) { - super(name); - this.commandGroupClass = commandGroupClass; - this.parent = null; - getCommandMethods(commandGroupClass).forEach(this::addMethod); + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name) { + this(commandGroupClass, classInstanceSupplier, name, null); } - public CommandGroup(Class commandGroupClass, String name, CommandGroup parent) { - super(name); + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, CommandGroup parent) { + super(name, parent); this.commandGroupClass = commandGroupClass; - this.parent = parent; + this.classInstanceSupplier = classInstanceSupplier; + DiscoveryUtils.getCommandMethods(commandGroupClass).forEach(this::addMethod); } public Iterable getSubCommands () { @@ -39,9 +37,15 @@ private void addMethod(CommandMethod method) { endpoint.addMethod(method); } - // Private utils TODO: move to DiscoveryUtils? + void run(String... args) { + Object commandObject = classInstanceSupplier.get(); + run(commandObject, args); + } - private static Stream getCommandMethods(Class commandGroupClass) { - return Arrays.stream(commandGroupClass.getDeclaredMethods()).map(CommandMethod::new); + @Override + void run(Object instance, String[] args) { + String commandName = args[0]; + CommandNode subCommand = subCommands.get(commandName); + subCommand.run(instance, Arrays.copyOfRange(args, 1, args.length)); } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java index fcaa99fa..d97e03bc 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java @@ -1,5 +1,6 @@ package fr.zcraft.quartzlib.components.commands.internal; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; class CommandMethod { @@ -14,4 +15,12 @@ class CommandMethod { public String getName() { return name; } + + public void run(Object target, String[] args) { + try { + this.method.invoke(target, (Object[]) args); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); // TODO + } + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java index c6d38306..75b69d13 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java @@ -2,12 +2,20 @@ abstract class CommandNode { private final String name; + private final CommandGroup parent; - protected CommandNode(String name) { + protected CommandNode(String name, CommandGroup parent) { this.name = name; + this.parent = parent; } public String getName() { return name; } + + public CommandGroup getParent() { + return parent; + } + + abstract void run(Object instance, String[] args); } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java new file mode 100644 index 00000000..09a30f1e --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java @@ -0,0 +1,27 @@ +package fr.zcraft.quartzlib.components.commands.internal; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.function.Supplier; +import java.util.stream.Stream; + +abstract class DiscoveryUtils { + public static Stream getCommandMethods(Class commandGroupClass) { + return Arrays.stream(commandGroupClass.getDeclaredMethods()) + .filter(m -> Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) + .map(CommandMethod::new); + } + + public static Supplier getClassConstructorSupplier (Class commandGroupClass) { + Constructor constructor = commandGroupClass.getDeclaredConstructors()[0]; + return () -> { + try { + return constructor.newInstance(); + } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new RuntimeException(e); // TODO + } + }; + } +} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java index c5c72dd4..fa0210b4 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java @@ -5,6 +5,16 @@ import java.util.stream.StreamSupport; +// This is outside because inner classes cannot have statics +class CommandWithStatics { + public void add () {} + private void get () {} + protected void list () {} + public void delete () {} + void update () {} + static public void staticMethod () {} +} + public class CommandGraphTests { @Test public void canDiscoverBasicSubcommands() { class FooCommand { @@ -13,8 +23,29 @@ public void get () {} public void list () {} } - CommandGroup commandGroup = new CommandGroup(FooCommand.class, "foo"); + CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo"); String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); Assertions.assertArrayEquals(new String[] {"add", "get", "list"}, commandNames); } + + @Test public void onlyDiscoversPublicMethods() { + CommandGroup commandGroup = new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo"); + String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); + Assertions.assertArrayEquals(new String[] {"add", "delete"}, commandNames); + } + + @Test public void canRunBasicSubcommands() { + final boolean[] ran = {false, false, false}; + + class FooCommand { + public void add () { ran[0] = true; } + public void get () { ran[1] = true; } + public void list () { ran[2] = true; } + } + + FooCommand f = new FooCommand(); + CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(),"foo"); + commandGroup.run("get"); + Assertions.assertArrayEquals(new boolean[] { false, true, false }, ran); + } } From 91ce326333b72ba85be3889c4b43e7c1fa8294c4 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Wed, 18 Nov 2020 19:48:50 +0100 Subject: [PATCH 03/16] Can now parse basic parameter types --- .../components/commands/ArgumentType.java | 6 +++ .../commands/ArgumentTypeHandler.java | 21 +++++++++ .../ArgumentTypeHandlerCollection.java | 30 ++++++++++++ .../{internal => }/CommandEndpoint.java | 4 +- .../commands/{internal => }/CommandGroup.java | 10 ++-- .../components/commands/CommandManager.java | 19 ++++++++ .../components/commands/CommandMethod.java | 47 +++++++++++++++++++ .../commands/CommandMethodArgument.java | 17 +++++++ .../commands/{internal => }/CommandNode.java | 2 +- .../{internal => }/DiscoveryUtils.java | 7 +-- .../primitive/IntegerTypeHandler.java | 10 ++++ .../commands/internal/CommandMethod.java | 26 ---------- .../{internal => }/CommandGraphTests.java | 43 ++++++++++++++--- 13 files changed, 198 insertions(+), 44 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java rename src/main/java/fr/zcraft/quartzlib/components/commands/{internal => }/CommandEndpoint.java (76%) rename src/main/java/fr/zcraft/quartzlib/components/commands/{internal => }/CommandGroup.java (76%) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java rename src/main/java/fr/zcraft/quartzlib/components/commands/{internal => }/CommandNode.java (87%) rename src/main/java/fr/zcraft/quartzlib/components/commands/{internal => }/DiscoveryUtils.java (79%) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java rename src/test/java/fr/zcraft/quartzlib/components/commands/{internal => }/CommandGraphTests.java (56%) diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java new file mode 100644 index 00000000..9cff7a1b --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java @@ -0,0 +1,6 @@ +package fr.zcraft.quartzlib.components.commands; + +@FunctionalInterface +public interface ArgumentType { + T parse(String raw); +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java new file mode 100644 index 00000000..6d693652 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java @@ -0,0 +1,21 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.components.commands.ArgumentType; + +public class ArgumentTypeHandler { + private final Class resultType; + private final ArgumentType typeHandler; + + public ArgumentTypeHandler(Class resultType, ArgumentType typeHandler) { + this.resultType = resultType; + this.typeHandler = typeHandler; + } + + public ArgumentType getTypeHandler() { + return typeHandler; + } + + public Class getResultType() { + return resultType; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java new file mode 100644 index 00000000..ff33a655 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java @@ -0,0 +1,30 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerTypeHandler; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +class ArgumentTypeHandlerCollection { + private final Map, ArgumentTypeHandler> argumentTypeHandlerMap = new HashMap<>(); + + public ArgumentTypeHandlerCollection () { + this.registerNativeTypes(); + } + + public void register(ArgumentTypeHandler typeHandler) + { + argumentTypeHandlerMap.put(typeHandler.getResultType(), typeHandler); + } + + public Optional> findTypeHandler(Class resultType) { + return Optional.ofNullable(argumentTypeHandlerMap.get(resultType)); + } + + private void registerNativeTypes () { + register(new ArgumentTypeHandler<>(Integer.class, new IntegerTypeHandler())); + + register(new ArgumentTypeHandler<>(String.class, s -> s)); + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java similarity index 76% rename from src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index 8e48fffe..aa3961bc 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -1,6 +1,4 @@ -package fr.zcraft.quartzlib.components.commands.internal; - -import sun.reflect.generics.reflectiveObjects.NotImplementedException; +package fr.zcraft.quartzlib.components.commands; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java similarity index 76% rename from src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index d6260695..c0c6eed5 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -1,4 +1,4 @@ -package fr.zcraft.quartzlib.components.commands.internal; +package fr.zcraft.quartzlib.components.commands; import java.util.Arrays; import java.util.HashMap; @@ -11,15 +11,15 @@ class CommandGroup extends CommandNode { private final Map subCommands = new HashMap<>(); - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name) { - this(commandGroupClass, classInstanceSupplier, name, null); + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, ArgumentTypeHandlerCollection typeHandlerCollection) { + this(commandGroupClass, classInstanceSupplier, name, typeHandlerCollection, null); } - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, CommandGroup parent) { + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, ArgumentTypeHandlerCollection typeHandlerCollection, CommandGroup parent) { super(name, parent); this.commandGroupClass = commandGroupClass; this.classInstanceSupplier = classInstanceSupplier; - DiscoveryUtils.getCommandMethods(commandGroupClass).forEach(this::addMethod); + DiscoveryUtils.getCommandMethods(commandGroupClass, typeHandlerCollection).forEach(this::addMethod); } public Iterable getSubCommands () { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java new file mode 100644 index 00000000..a24c44e4 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java @@ -0,0 +1,19 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public class CommandManager { + private final Map rootCommands = new HashMap<>(); + private final ArgumentTypeHandlerCollection typeHandlerCollection = new ArgumentTypeHandlerCollection(); + + public void registerCommand(String name, Class commandType, Supplier commandClassSupplier) { + CommandGroup group = new CommandGroup(commandType, commandClassSupplier, name, typeHandlerCollection); + rootCommands.put(name, group); + } + + public void run(String commandName, String... args) { + ((CommandGroup) rootCommands.get(commandName)).run(args); // TODO + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java new file mode 100644 index 00000000..1ecbaf14 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -0,0 +1,47 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + +class CommandMethod { + private final Method method; + private final String name; + private final CommandMethodArgument[] arguments; + + CommandMethod(Method method, ArgumentTypeHandlerCollection typeHandlerCollection) { + this.method = method; + this.name = method.getName(); + + arguments = Arrays.stream(method.getParameters()) + .map(p -> new CommandMethodArgument(p, typeHandlerCollection)) + .toArray(CommandMethodArgument[]::new); + } + + public String getName() { + return name; + } + + public void run(Object target, String[] args) { + Object[] parsedArgs = parseArguments(args); + try { + this.method.invoke(target, parsedArgs); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); // TODO + } + } + + private Object[] parseArguments(String[] args) { + Object[] parsed = new Object[args.length]; + + for (int i = 0; i < args.length; i++) { + parsed[i] = arguments[i].parse(args[i]); + } + + return parsed; + } + + public CommandMethodArgument[] getArguments() { + return arguments; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java new file mode 100644 index 00000000..0ea633bb --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java @@ -0,0 +1,17 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.lang.reflect.Parameter; + +public class CommandMethodArgument { + private final Parameter parameter; + private final ArgumentTypeHandler typeHandler; + + public CommandMethodArgument(Parameter parameter, ArgumentTypeHandlerCollection typeHandlerCollection) { + this.parameter = parameter; + this.typeHandler = typeHandlerCollection.findTypeHandler(parameter.getType()).get(); // FIXME: handle unknown types + } + + public Object parse(String raw) { + return this.typeHandler.getTypeHandler().parse(raw); + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java similarity index 87% rename from src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java index 75b69d13..58a350a9 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandNode.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java @@ -1,4 +1,4 @@ -package fr.zcraft.quartzlib.components.commands.internal; +package fr.zcraft.quartzlib.components.commands; abstract class CommandNode { private final String name; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java similarity index 79% rename from src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java index 09a30f1e..023b23da 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/DiscoveryUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java @@ -1,17 +1,18 @@ -package fr.zcraft.quartzlib.components.commands.internal; +package fr.zcraft.quartzlib.components.commands; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.function.Supplier; import java.util.stream.Stream; abstract class DiscoveryUtils { - public static Stream getCommandMethods(Class commandGroupClass) { + public static Stream getCommandMethods(Class commandGroupClass, ArgumentTypeHandlerCollection typeHandlerCollection) { return Arrays.stream(commandGroupClass.getDeclaredMethods()) .filter(m -> Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) - .map(CommandMethod::new); + .map((Method method) -> new CommandMethod(method, typeHandlerCollection)); } public static Supplier getClassConstructorSupplier (Class commandGroupClass) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java new file mode 100644 index 00000000..84cba16e --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java @@ -0,0 +1,10 @@ +package fr.zcraft.quartzlib.components.commands.arguments.primitive; + +import fr.zcraft.quartzlib.components.commands.ArgumentType; + +public class IntegerTypeHandler implements ArgumentType { + @Override + public Integer parse(String raw) { + return Integer.parseInt(raw, 10); // TODO: handle exceptions + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java deleted file mode 100644 index d97e03bc..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/internal/CommandMethod.java +++ /dev/null @@ -1,26 +0,0 @@ -package fr.zcraft.quartzlib.components.commands.internal; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -class CommandMethod { - private final Method method; - private final String name; - - CommandMethod(Method method) { - this.method = method; - this.name = method.getName(); - } - - public String getName() { - return name; - } - - public void run(Object target, String[] args) { - try { - this.method.invoke(target, (Object[]) args); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); // TODO - } - } -} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java similarity index 56% rename from src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java rename to src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index fa0210b4..921fa8ca 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/internal/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -1,6 +1,7 @@ -package fr.zcraft.quartzlib.components.commands.internal; +package fr.zcraft.quartzlib.components.commands; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.stream.StreamSupport; @@ -16,6 +17,13 @@ static public void staticMethod () {} } public class CommandGraphTests { + private CommandManager commands; + + @BeforeEach + public void beforeEach () { + commands = new CommandManager(); + } + @Test public void canDiscoverBasicSubcommands() { class FooCommand { public void add () {} @@ -23,13 +31,13 @@ public void get () {} public void list () {} } - CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo"); + CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo", new ArgumentTypeHandlerCollection()); String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); Assertions.assertArrayEquals(new String[] {"add", "get", "list"}, commandNames); } @Test public void onlyDiscoversPublicMethods() { - CommandGroup commandGroup = new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo"); + CommandGroup commandGroup = new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo", new ArgumentTypeHandlerCollection()); String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); Assertions.assertArrayEquals(new String[] {"add", "delete"}, commandNames); } @@ -43,9 +51,32 @@ class FooCommand { public void list () { ran[2] = true; } } - FooCommand f = new FooCommand(); - CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(),"foo"); - commandGroup.run("get"); + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run("foo", "get"); Assertions.assertArrayEquals(new boolean[] { false, true, false }, ran); } + + @Test public void canReceiveStringArguments() { + final String[] argValue = {""}; + + class FooCommand { + public void add (String arg) { argValue[0] = arg; } + } + + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run("foo", "add", "pomf"); + Assertions.assertArrayEquals(new String[] { "pomf" }, argValue); + } + + @Test public void canReceiveParsedArguments() { + final int[] argValue = {0}; + + class FooCommand { + public void add (Integer arg) { argValue[0] = arg; } + } + + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run("foo", "add", "42"); + Assertions.assertArrayEquals(new int[] { 42 }, argValue); + } } From ed0eb15b4a5fb86d27579a02d2ff82d5e9540a06 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Tue, 24 Nov 2020 10:22:43 +0100 Subject: [PATCH 04/16] Can now parse all enums by default, and add infrastructure to parse any generic type --- .../components/commands/ArgumentType.java | 4 +- .../commands/ArgumentTypeHandler.java | 4 +- .../ArgumentTypeHandlerCollection.java | 36 ++++++++--- .../components/commands/CommandEndpoint.java | 4 +- .../components/commands/CommandGroup.java | 6 +- .../components/commands/CommandManager.java | 4 +- .../components/commands/CommandMethod.java | 7 ++- .../commands/CommandMethodArgument.java | 4 +- .../components/commands/CommandNode.java | 4 +- .../commands/GenericArgumentType.java | 7 +++ .../arguments/generic/EnumArgumentType.java | 59 +++++++++++++++++++ ...eHandler.java => IntegerArgumentType.java} | 2 +- .../exceptions/ArgumentParseException.java | 4 ++ .../commands/exceptions/CommandException.java | 4 ++ .../commands/CommandGraphTests.java | 22 ++++++- .../generic/EnumArgumentTypeTests.java | 22 +++++++ 16 files changed, 170 insertions(+), 23 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/GenericArgumentType.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java rename src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/{IntegerTypeHandler.java => IntegerArgumentType.java} (79%) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java create mode 100644 src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java index 9cff7a1b..f97c2c0c 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java @@ -1,6 +1,8 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; + @FunctionalInterface public interface ArgumentType { - T parse(String raw); + T parse(String raw) throws ArgumentParseException; } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java index 6d693652..d79503d4 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java @@ -1,8 +1,6 @@ package fr.zcraft.quartzlib.components.commands; -import fr.zcraft.quartzlib.components.commands.ArgumentType; - -public class ArgumentTypeHandler { +class ArgumentTypeHandler { private final Class resultType; private final ArgumentType typeHandler; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java index ff33a655..b1e3ca66 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java @@ -1,13 +1,14 @@ package fr.zcraft.quartzlib.components.commands; -import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerTypeHandler; +import fr.zcraft.quartzlib.components.commands.arguments.generic.EnumArgumentType; +import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerArgumentType; +import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; +import java.util.*; class ArgumentTypeHandlerCollection { private final Map, ArgumentTypeHandler> argumentTypeHandlerMap = new HashMap<>(); + private final List> genericArgumentTypes = new ArrayList<>(); public ArgumentTypeHandlerCollection () { this.registerNativeTypes(); @@ -18,13 +19,34 @@ public void register(ArgumentTypeHandler typeHandler) argumentTypeHandlerMap.put(typeHandler.getResultType(), typeHandler); } + public void register(GenericArgumentType genericArgumentType) { + genericArgumentTypes.add(genericArgumentType); + } + public Optional> findTypeHandler(Class resultType) { - return Optional.ofNullable(argumentTypeHandlerMap.get(resultType)); + ArgumentTypeHandler typeHandler = argumentTypeHandlerMap.get(resultType); + if (typeHandler != null) return Optional.of(typeHandler); + return this.findGenericTypeHandler(resultType); } - private void registerNativeTypes () { - register(new ArgumentTypeHandler<>(Integer.class, new IntegerTypeHandler())); + private Optional> findGenericTypeHandler(Class resultType) { + for (GenericArgumentType t : genericArgumentTypes) { + Optional> matchingArgumentType = t.getMatchingArgumentType(resultType); + if (matchingArgumentType.isPresent()) { + ArgumentTypeHandler typeHandler = new ArgumentTypeHandler<>(resultType, (ArgumentType) matchingArgumentType.get()); + return Optional.of(typeHandler); + } + } + return Optional.empty(); + } + + private void registerNativeTypes () { + // Primitive types + register(new ArgumentTypeHandler<>(Integer.class, new IntegerArgumentType())); register(new ArgumentTypeHandler<>(String.class, s -> s)); + + // Generic types + register(new EnumArgumentType()); } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index aa3961bc..061d5b49 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + import java.util.ArrayList; import java.util.List; @@ -11,7 +13,7 @@ class CommandEndpoint extends CommandNode { } @Override - void run(Object instance, String[] args) { + void run(Object instance, String[] args) throws CommandException { this.methods.get(0).run(instance, args); } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index c0c6eed5..1b87d313 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -37,13 +39,13 @@ private void addMethod(CommandMethod method) { endpoint.addMethod(method); } - void run(String... args) { + void run(String... args) throws CommandException { Object commandObject = classInstanceSupplier.get(); run(commandObject, args); } @Override - void run(Object instance, String[] args) { + void run(Object instance, String[] args) throws CommandException { String commandName = args[0]; CommandNode subCommand = subCommands.get(commandName); subCommand.run(instance, Arrays.copyOfRange(args, 1, args.length)); diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java index a24c44e4..8845ed31 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; @@ -13,7 +15,7 @@ public void registerCommand(String name, Class commandType, Supplier c rootCommands.put(name, group); } - public void run(String commandName, String... args) { + public void run(String commandName, String... args) throws CommandException { ((CommandGroup) rootCommands.get(commandName)).run(args); // TODO } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java index 1ecbaf14..5f878900 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -1,5 +1,8 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; @@ -22,7 +25,7 @@ public String getName() { return name; } - public void run(Object target, String[] args) { + public void run(Object target, String[] args) throws CommandException { Object[] parsedArgs = parseArguments(args); try { this.method.invoke(target, parsedArgs); @@ -31,7 +34,7 @@ public void run(Object target, String[] args) { } } - private Object[] parseArguments(String[] args) { + private Object[] parseArguments(String[] args) throws ArgumentParseException { Object[] parsed = new Object[args.length]; for (int i = 0; i < args.length; i++) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java index 0ea633bb..06b06aad 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; + import java.lang.reflect.Parameter; public class CommandMethodArgument { @@ -11,7 +13,7 @@ public CommandMethodArgument(Parameter parameter, ArgumentTypeHandlerCollection this.typeHandler = typeHandlerCollection.findTypeHandler(parameter.getType()).get(); // FIXME: handle unknown types } - public Object parse(String raw) { + public Object parse(String raw) throws ArgumentParseException { return this.typeHandler.getTypeHandler().parse(raw); } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java index 58a350a9..8fa1538d 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; + abstract class CommandNode { private final String name; private final CommandGroup parent; @@ -17,5 +19,5 @@ public CommandGroup getParent() { return parent; } - abstract void run(Object instance, String[] args); + abstract void run(Object instance, String[] args) throws CommandException; } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/GenericArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/GenericArgumentType.java new file mode 100644 index 00000000..48dc320f --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/GenericArgumentType.java @@ -0,0 +1,7 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.util.Optional; + +public interface GenericArgumentType { + Optional> getMatchingArgumentType(Class type); +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java new file mode 100644 index 00000000..3baeb45a --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java @@ -0,0 +1,59 @@ +package fr.zcraft.quartzlib.components.commands.arguments.generic; + +import fr.zcraft.quartzlib.components.commands.ArgumentType; +import fr.zcraft.quartzlib.components.commands.GenericArgumentType; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class EnumArgumentType implements GenericArgumentType> { + @Override + public Optional>> getMatchingArgumentType(Class type) { + if (type.isEnum()) { + return Optional.of(new DiscreteEnumArgumentType(type)); + } + return Optional.empty(); + } + + static private class DiscreteEnumArgumentType implements ArgumentType> { + private final Map> enumValues; + + public DiscreteEnumArgumentType(Class enumClass) { + enumValues = getEnumValues(enumClass); + } + + @Override + public Enum parse(String raw) throws ArgumentParseException { + Enum value = enumValues.get(raw); + if (value == null) throw new EnumParseException(); + return value; + } + } + + static private class EnumParseException extends ArgumentParseException { + + } + + static private Map> getEnumValues (Class enumClass) { + Map> enumValues = new HashMap<>(); + + Arrays.stream(enumClass.getDeclaredFields()) + .filter(f -> Modifier.isPublic(f.getModifiers()) + && Modifier.isStatic(f.getModifiers()) + && enumClass.isAssignableFrom(f.getType())) + .forEach(f -> { + try { + f.setAccessible(true); + enumValues.put(f.getName().toLowerCase(), (Enum)f.get(null)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + + return enumValues; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java similarity index 79% rename from src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java index 84cba16e..8d2a2c57 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerTypeHandler.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java @@ -2,7 +2,7 @@ import fr.zcraft.quartzlib.components.commands.ArgumentType; -public class IntegerTypeHandler implements ArgumentType { +public class IntegerArgumentType implements ArgumentType { @Override public Integer parse(String raw) { return Integer.parseInt(raw, 10); // TODO: handle exceptions diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java new file mode 100644 index 00000000..b6ff6416 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java @@ -0,0 +1,4 @@ +package fr.zcraft.quartzlib.components.commands.exceptions; + +public class ArgumentParseException extends CommandException { +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java new file mode 100644 index 00000000..ea6c97d8 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java @@ -0,0 +1,4 @@ +package fr.zcraft.quartzlib.components.commands.exceptions; + +public abstract class CommandException extends Exception { +} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index 921fa8ca..53e27dc3 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -1,5 +1,6 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -42,7 +43,7 @@ public void list () {} Assertions.assertArrayEquals(new String[] {"add", "delete"}, commandNames); } - @Test public void canRunBasicSubcommands() { + @Test public void canRunBasicSubcommands() throws CommandException { final boolean[] ran = {false, false, false}; class FooCommand { @@ -56,7 +57,7 @@ class FooCommand { Assertions.assertArrayEquals(new boolean[] { false, true, false }, ran); } - @Test public void canReceiveStringArguments() { + @Test public void canReceiveStringArguments() throws CommandException { final String[] argValue = {""}; class FooCommand { @@ -68,7 +69,7 @@ class FooCommand { Assertions.assertArrayEquals(new String[] { "pomf" }, argValue); } - @Test public void canReceiveParsedArguments() { + @Test public void canReceiveParsedArguments() throws CommandException { final int[] argValue = {0}; class FooCommand { @@ -79,4 +80,19 @@ class FooCommand { commands.run("foo", "add", "42"); Assertions.assertArrayEquals(new int[] { 42 }, argValue); } + + enum FooEnum { FOO, BAR } + @Test public void canReceiveEnumArguments() throws CommandException { + final FooEnum[] argValue = {null}; + + class FooCommand { + public void add (FooEnum arg) { argValue[0] = arg; } + } + + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run("foo", "add", "foo"); + Assertions.assertArrayEquals(new FooEnum[] { FooEnum.FOO }, argValue); + commands.run("foo", "add", "bar"); + Assertions.assertArrayEquals(new FooEnum[] { FooEnum.BAR }, argValue); + } } diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java new file mode 100644 index 00000000..6041e0ce --- /dev/null +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java @@ -0,0 +1,22 @@ +package fr.zcraft.quartzlib.components.commands.arguments.generic; + +import fr.zcraft.quartzlib.components.commands.ArgumentType; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class EnumArgumentTypeTests { + private final EnumArgumentType enumArgumentType = new EnumArgumentType(); + + private enum SimpleEnum { FOO, BAR } + + @Test + public void worksOnSimpleEnum() throws ArgumentParseException { + ArgumentType argumentType = enumArgumentType.getMatchingArgumentType(SimpleEnum.class).get(); + + Assertions.assertEquals(SimpleEnum.FOO, argumentType.parse("foo")); + Assertions.assertEquals(SimpleEnum.BAR, argumentType.parse("bar")); + + Assertions.assertThrows(ArgumentParseException.class, () -> argumentType.parse("blah")); + } +} From 95c32ca391d962a279545d3f04e8dfb58cc2c88b Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Tue, 8 Dec 2020 08:58:23 +0100 Subject: [PATCH 05/16] Added basic support for command sender arguments --- .../ArgumentTypeHandlerCollection.java | 52 ----------- ...eHandler.java => ArgumentTypeWrapper.java} | 4 +- .../components/commands/CommandEndpoint.java | 5 +- .../components/commands/CommandGroup.java | 17 ++-- .../components/commands/CommandManager.java | 9 +- .../components/commands/CommandMethod.java | 44 +++++++--- .../commands/CommandMethodArgument.java | 12 ++- .../commands/CommandMethodSenderArgument.java | 27 ++++++ .../components/commands/CommandNode.java | 3 +- .../components/commands/DiscoveryUtils.java | 4 +- .../commands/GenericSenderType.java | 7 ++ .../components/commands/SenderType.java | 9 ++ .../commands/SenderTypeWrapper.java | 19 ++++ .../components/commands/TypeCollection.java | 86 +++++++++++++++++++ .../commands/attributes/Sender.java | 7 ++ .../exceptions/InvalidSenderException.java | 4 + .../senders/GenericCommandSender.java | 40 +++++++++ .../commands/CommandGraphTests.java | 35 ++++++-- 18 files changed, 291 insertions(+), 93 deletions(-) delete mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java rename src/main/java/fr/zcraft/quartzlib/components/commands/{ArgumentTypeHandler.java => ArgumentTypeWrapper.java} (80%) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodSenderArgument.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/GenericSenderType.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/SenderType.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/SenderTypeWrapper.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/InvalidSenderException.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/senders/GenericCommandSender.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java deleted file mode 100644 index b1e3ca66..00000000 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandlerCollection.java +++ /dev/null @@ -1,52 +0,0 @@ -package fr.zcraft.quartzlib.components.commands; - -import fr.zcraft.quartzlib.components.commands.arguments.generic.EnumArgumentType; -import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerArgumentType; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -class ArgumentTypeHandlerCollection { - private final Map, ArgumentTypeHandler> argumentTypeHandlerMap = new HashMap<>(); - private final List> genericArgumentTypes = new ArrayList<>(); - - public ArgumentTypeHandlerCollection () { - this.registerNativeTypes(); - } - - public void register(ArgumentTypeHandler typeHandler) - { - argumentTypeHandlerMap.put(typeHandler.getResultType(), typeHandler); - } - - public void register(GenericArgumentType genericArgumentType) { - genericArgumentTypes.add(genericArgumentType); - } - - public Optional> findTypeHandler(Class resultType) { - ArgumentTypeHandler typeHandler = argumentTypeHandlerMap.get(resultType); - if (typeHandler != null) return Optional.of(typeHandler); - return this.findGenericTypeHandler(resultType); - } - - private Optional> findGenericTypeHandler(Class resultType) { - for (GenericArgumentType t : genericArgumentTypes) { - Optional> matchingArgumentType = t.getMatchingArgumentType(resultType); - - if (matchingArgumentType.isPresent()) { - ArgumentTypeHandler typeHandler = new ArgumentTypeHandler<>(resultType, (ArgumentType) matchingArgumentType.get()); - return Optional.of(typeHandler); - } - } - return Optional.empty(); - } - - private void registerNativeTypes () { - // Primitive types - register(new ArgumentTypeHandler<>(Integer.class, new IntegerArgumentType())); - register(new ArgumentTypeHandler<>(String.class, s -> s)); - - // Generic types - register(new EnumArgumentType()); - } -} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeWrapper.java similarity index 80% rename from src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeWrapper.java index d79503d4..e253b3de 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeHandler.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentTypeWrapper.java @@ -1,10 +1,10 @@ package fr.zcraft.quartzlib.components.commands; -class ArgumentTypeHandler { +class ArgumentTypeWrapper { private final Class resultType; private final ArgumentType typeHandler; - public ArgumentTypeHandler(Class resultType, ArgumentType typeHandler) { + public ArgumentTypeWrapper(Class resultType, ArgumentType typeHandler) { this.resultType = resultType; this.typeHandler = typeHandler; } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index 061d5b49..39064bf2 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -1,6 +1,7 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import org.bukkit.command.CommandSender; import java.util.ArrayList; import java.util.List; @@ -13,8 +14,8 @@ class CommandEndpoint extends CommandNode { } @Override - void run(Object instance, String[] args) throws CommandException { - this.methods.get(0).run(instance, args); + void run(Object instance, CommandSender sender, String[] args) throws CommandException { + this.methods.get(0).run(instance, sender, args); } void addMethod(CommandMethod method) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index 1b87d313..f8401835 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -1,6 +1,7 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import org.bukkit.command.CommandSender; import java.util.Arrays; import java.util.HashMap; @@ -13,15 +14,15 @@ class CommandGroup extends CommandNode { private final Map subCommands = new HashMap<>(); - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, ArgumentTypeHandlerCollection typeHandlerCollection) { - this(commandGroupClass, classInstanceSupplier, name, typeHandlerCollection, null); + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, TypeCollection typeCollection) { + this(commandGroupClass, classInstanceSupplier, name, typeCollection, null); } - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, ArgumentTypeHandlerCollection typeHandlerCollection, CommandGroup parent) { + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, TypeCollection typeCollection, CommandGroup parent) { super(name, parent); this.commandGroupClass = commandGroupClass; this.classInstanceSupplier = classInstanceSupplier; - DiscoveryUtils.getCommandMethods(commandGroupClass, typeHandlerCollection).forEach(this::addMethod); + DiscoveryUtils.getCommandMethods(commandGroupClass, typeCollection).forEach(this::addMethod); } public Iterable getSubCommands () { @@ -39,15 +40,15 @@ private void addMethod(CommandMethod method) { endpoint.addMethod(method); } - void run(String... args) throws CommandException { + void run(CommandSender sender, String... args) throws CommandException { Object commandObject = classInstanceSupplier.get(); - run(commandObject, args); + run(commandObject, sender, args); } @Override - void run(Object instance, String[] args) throws CommandException { + void run(Object instance, CommandSender sender, String[] args) throws CommandException { String commandName = args[0]; CommandNode subCommand = subCommands.get(commandName); - subCommand.run(instance, Arrays.copyOfRange(args, 1, args.length)); + subCommand.run(instance, sender, Arrays.copyOfRange(args, 1, args.length)); } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java index 8845ed31..7777ec08 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java @@ -1,6 +1,7 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import org.bukkit.command.CommandSender; import java.util.HashMap; import java.util.Map; @@ -8,14 +9,14 @@ public class CommandManager { private final Map rootCommands = new HashMap<>(); - private final ArgumentTypeHandlerCollection typeHandlerCollection = new ArgumentTypeHandlerCollection(); + private final TypeCollection typeCollection = new TypeCollection(); public void registerCommand(String name, Class commandType, Supplier commandClassSupplier) { - CommandGroup group = new CommandGroup(commandType, commandClassSupplier, name, typeHandlerCollection); + CommandGroup group = new CommandGroup(commandType, commandClassSupplier, name, typeCollection); rootCommands.put(name, group); } - public void run(String commandName, String... args) throws CommandException { - ((CommandGroup) rootCommands.get(commandName)).run(args); // TODO + public void run(CommandSender sender, String commandName, String... args) throws CommandException { + ((CommandGroup) rootCommands.get(commandName)).run(sender, args); // TODO } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java index 5f878900..ad1d217d 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -1,32 +1,49 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.attributes.Sender; import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import fr.zcraft.quartzlib.components.commands.exceptions.InvalidSenderException; +import org.bukkit.command.CommandSender; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Arrays; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; class CommandMethod { private final Method method; private final String name; private final CommandMethodArgument[] arguments; + private final int parameterCount; + private CommandMethodSenderArgument senderArgument = null; - CommandMethod(Method method, ArgumentTypeHandlerCollection typeHandlerCollection) { + CommandMethod(Method method, TypeCollection typeCollection) { this.method = method; this.name = method.getName(); - arguments = Arrays.stream(method.getParameters()) - .map(p -> new CommandMethodArgument(p, typeHandlerCollection)) - .toArray(CommandMethodArgument[]::new); + Parameter[] parameters = method.getParameters(); + List arguments = new ArrayList<>(); + for (int i = 0; i < parameters.length; i++) { + Parameter parameter = parameters[i]; + if (parameter.isAnnotationPresent(Sender.class)) { // TODO: check for multiple sender arguments + senderArgument = new CommandMethodSenderArgument(parameter, i, typeCollection); + } else { + arguments.add(new CommandMethodArgument(parameter, i, typeCollection)); + } + } + + this.arguments = arguments.toArray(new CommandMethodArgument[]{}); + this.parameterCount = parameters.length; } public String getName() { return name; } - public void run(Object target, String[] args) throws CommandException { - Object[] parsedArgs = parseArguments(args); + public void run(Object target, CommandSender sender, String[] args) throws CommandException { + Object[] parsedArgs = parseArguments(sender, args); try { this.method.invoke(target, parsedArgs); } catch (IllegalAccessException | InvocationTargetException e) { @@ -34,11 +51,16 @@ public void run(Object target, String[] args) throws CommandException { } } - private Object[] parseArguments(String[] args) throws ArgumentParseException { - Object[] parsed = new Object[args.length]; + private Object[] parseArguments(CommandSender sender, String[] args) throws ArgumentParseException, InvalidSenderException { + Object[] parsed = new Object[parameterCount]; + + for (int i = 0; i < arguments.length; i++) { + CommandMethodArgument argument = arguments[i]; + parsed[argument.getPosition()] = argument.parse(args[i]); + } - for (int i = 0; i < args.length; i++) { - parsed[i] = arguments[i].parse(args[i]); + if (this.senderArgument != null) { + parsed[this.senderArgument.getPosition()] = this.senderArgument.parse(sender); } return parsed; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java index 06b06aad..f432f26a 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java @@ -6,14 +6,20 @@ public class CommandMethodArgument { private final Parameter parameter; - private final ArgumentTypeHandler typeHandler; + private final int position; + private final ArgumentTypeWrapper typeHandler; - public CommandMethodArgument(Parameter parameter, ArgumentTypeHandlerCollection typeHandlerCollection) { + public CommandMethodArgument(Parameter parameter, int position, TypeCollection typeCollection) { this.parameter = parameter; - this.typeHandler = typeHandlerCollection.findTypeHandler(parameter.getType()).get(); // FIXME: handle unknown types + this.position = position; + this.typeHandler = typeCollection.findArgumentType(parameter.getType()).get(); // FIXME: handle unknown types } public Object parse(String raw) throws ArgumentParseException { return this.typeHandler.getTypeHandler().parse(raw); } + + public int getPosition() { + return position; + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodSenderArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodSenderArgument.java new file mode 100644 index 00000000..62a0bb7f --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodSenderArgument.java @@ -0,0 +1,27 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; +import fr.zcraft.quartzlib.components.commands.exceptions.InvalidSenderException; +import org.bukkit.command.CommandSender; + +import java.lang.reflect.Parameter; + +public class CommandMethodSenderArgument { + private final Parameter parameter; + private final int position; + private final SenderTypeWrapper typeHandler; + + public CommandMethodSenderArgument(Parameter parameter, int position, TypeCollection typeCollection) { + this.parameter = parameter; + this.position = position; + this.typeHandler = typeCollection.findSenderType(parameter.getType()).get(); // FIXME: handle unknown types + } + + public Object parse(CommandSender raw) throws InvalidSenderException { + return this.typeHandler.getTypeHandler().parse(raw); + } + + public int getPosition() { + return position; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java index 8fa1538d..77bb4e36 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java @@ -1,6 +1,7 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import org.bukkit.command.CommandSender; abstract class CommandNode { private final String name; @@ -19,5 +20,5 @@ public CommandGroup getParent() { return parent; } - abstract void run(Object instance, String[] args) throws CommandException; + abstract void run(Object instance, CommandSender sender, String[] args) throws CommandException; } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java index 023b23da..a9f4463c 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java @@ -9,10 +9,10 @@ import java.util.stream.Stream; abstract class DiscoveryUtils { - public static Stream getCommandMethods(Class commandGroupClass, ArgumentTypeHandlerCollection typeHandlerCollection) { + public static Stream getCommandMethods(Class commandGroupClass, TypeCollection typeCollection) { return Arrays.stream(commandGroupClass.getDeclaredMethods()) .filter(m -> Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) - .map((Method method) -> new CommandMethod(method, typeHandlerCollection)); + .map((Method method) -> new CommandMethod(method, typeCollection)); } public static Supplier getClassConstructorSupplier (Class commandGroupClass) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/GenericSenderType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/GenericSenderType.java new file mode 100644 index 00000000..b633d320 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/GenericSenderType.java @@ -0,0 +1,7 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.util.Optional; + +public interface GenericSenderType { + Optional> getMatchingSenderType(Class type); +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/SenderType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/SenderType.java new file mode 100644 index 00000000..dc741693 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/SenderType.java @@ -0,0 +1,9 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.components.commands.exceptions.InvalidSenderException; +import org.bukkit.command.CommandSender; + +@FunctionalInterface +public interface SenderType { + T parse(CommandSender raw) throws InvalidSenderException; +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/SenderTypeWrapper.java b/src/main/java/fr/zcraft/quartzlib/components/commands/SenderTypeWrapper.java new file mode 100644 index 00000000..ff7ff326 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/SenderTypeWrapper.java @@ -0,0 +1,19 @@ +package fr.zcraft.quartzlib.components.commands; + +class SenderTypeWrapper { + private final Class resultType; + private final SenderType typeHandler; + + public SenderTypeWrapper(Class resultType, SenderType typeHandler) { + this.resultType = resultType; + this.typeHandler = typeHandler; + } + + public SenderType getTypeHandler() { + return typeHandler; + } + + public Class getResultType() { + return resultType; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java b/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java new file mode 100644 index 00000000..d01c8f03 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java @@ -0,0 +1,86 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.components.commands.arguments.generic.EnumArgumentType; +import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerArgumentType; +import fr.zcraft.quartzlib.components.commands.senders.GenericCommandSender; + +import java.util.*; + +class TypeCollection { + private final Map, ArgumentTypeWrapper> argumentTypeMap = new HashMap<>(); + private final List> genericArgumentTypes = new ArrayList<>(); + + private final Map, SenderTypeWrapper> senderTypeMap = new HashMap<>(); + private final List> genericSenderTypes = new ArrayList<>(); + + public TypeCollection() { + this.registerNativeTypes(); + } + + public void register(ArgumentTypeWrapper typeHandler) + { + argumentTypeMap.put(typeHandler.getResultType(), typeHandler); + } + + public void register(GenericArgumentType genericArgumentType) { + genericArgumentTypes.add(genericArgumentType); + } + + public void register(SenderTypeWrapper typeHandler) + { + senderTypeMap.put(typeHandler.getResultType(), typeHandler); + } + + public void register(GenericSenderType genericSenderType) { + genericSenderTypes.add(genericSenderType); + } + + public Optional> findArgumentType(Class resultType) { + ArgumentTypeWrapper typeHandler = argumentTypeMap.get(resultType); + if (typeHandler != null) return Optional.of(typeHandler); + return this.findGenericArgumentType(resultType); + } + + private Optional> findGenericArgumentType(Class resultType) { + for (GenericArgumentType t : genericArgumentTypes) { + Optional> matchingArgumentType = t.getMatchingArgumentType(resultType); + + if (matchingArgumentType.isPresent()) { + ArgumentTypeWrapper typeHandler = new ArgumentTypeWrapper<>(resultType, (ArgumentType) matchingArgumentType.get()); + return Optional.of(typeHandler); + } + } + return Optional.empty(); + } + + private void registerNativeTypes () { + // Primitive types + register(new ArgumentTypeWrapper<>(Integer.class, new IntegerArgumentType())); + register(new ArgumentTypeWrapper<>(String.class, s -> s)); + + // Generic types + register(new EnumArgumentType()); + + // Generic sender types + register(new GenericCommandSender()); + } + + + public Optional> findSenderType(Class resultType) { + SenderTypeWrapper typeHandler = senderTypeMap.get(resultType); + if (typeHandler != null) return Optional.of(typeHandler); + return this.findGenericSenderType(resultType); + } + + private Optional> findGenericSenderType(Class resultType) { + for (GenericSenderType t : genericSenderTypes) { + Optional> matchingSenderType = t.getMatchingSenderType(resultType); + + if (matchingSenderType.isPresent()) { + SenderTypeWrapper typeHandler = new SenderTypeWrapper<>(resultType, (SenderType) matchingSenderType.get()); + return Optional.of(typeHandler); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java b/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java index 0606e25e..4f4f66ef 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java @@ -1,4 +1,11 @@ package fr.zcraft.quartzlib.components.commands.attributes; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) public @interface Sender { } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/InvalidSenderException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/InvalidSenderException.java new file mode 100644 index 00000000..68787e5e --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/InvalidSenderException.java @@ -0,0 +1,4 @@ +package fr.zcraft.quartzlib.components.commands.exceptions; + +public class InvalidSenderException extends CommandException { +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/senders/GenericCommandSender.java b/src/main/java/fr/zcraft/quartzlib/components/commands/senders/GenericCommandSender.java new file mode 100644 index 00000000..c93a7c36 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/senders/GenericCommandSender.java @@ -0,0 +1,40 @@ +package fr.zcraft.quartzlib.components.commands.senders; + +import fr.zcraft.quartzlib.components.commands.GenericSenderType; +import fr.zcraft.quartzlib.components.commands.SenderType; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; +import fr.zcraft.quartzlib.components.commands.exceptions.InvalidSenderException; +import org.bukkit.command.CommandSender; + +import java.util.Optional; + +public class GenericCommandSender implements GenericSenderType { + @Override + public Optional> getMatchingSenderType(Class type) { + if (CommandSender.class.isAssignableFrom(type)) { + return Optional.of(new CommandSenderSubType(type)); + } + return Optional.empty(); + } + + static private class InvalidSenderTypeException extends InvalidSenderException { + + } + + static private class CommandSenderSubType implements SenderType { + private final Class subtype; + + private CommandSenderSubType(Class subtype) { + this.subtype = subtype; + } + + @Override + public CommandSender parse(CommandSender raw) throws InvalidSenderException { + if (subtype.isAssignableFrom(raw.getClass())) { + return raw; + } + + throw new InvalidSenderTypeException(); + } + } +} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index 53e27dc3..55fa3f20 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -1,6 +1,10 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.MockedBukkitTest; +import fr.zcraft.quartzlib.components.commands.attributes.Sender; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,7 +21,7 @@ void update () {} static public void staticMethod () {} } -public class CommandGraphTests { +public class CommandGraphTests extends MockedBukkitTest { private CommandManager commands; @BeforeEach @@ -32,13 +36,13 @@ public void get () {} public void list () {} } - CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo", new ArgumentTypeHandlerCollection()); + CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo", new TypeCollection()); String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); Assertions.assertArrayEquals(new String[] {"add", "get", "list"}, commandNames); } @Test public void onlyDiscoversPublicMethods() { - CommandGroup commandGroup = new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo", new ArgumentTypeHandlerCollection()); + CommandGroup commandGroup = new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo", new TypeCollection()); String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); Assertions.assertArrayEquals(new String[] {"add", "delete"}, commandNames); } @@ -53,7 +57,7 @@ class FooCommand { } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); - commands.run("foo", "get"); + commands.run(server.addPlayer(), "foo", "get"); Assertions.assertArrayEquals(new boolean[] { false, true, false }, ran); } @@ -65,7 +69,7 @@ class FooCommand { } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); - commands.run("foo", "add", "pomf"); + commands.run(server.addPlayer(), "foo", "add", "pomf"); Assertions.assertArrayEquals(new String[] { "pomf" }, argValue); } @@ -77,7 +81,7 @@ class FooCommand { } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); - commands.run("foo", "add", "42"); + commands.run(server.addPlayer(), "foo", "add", "42"); Assertions.assertArrayEquals(new int[] { 42 }, argValue); } @@ -90,9 +94,24 @@ class FooCommand { } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); - commands.run("foo", "add", "foo"); + commands.run(server.addPlayer(), "foo", "add", "foo"); Assertions.assertArrayEquals(new FooEnum[] { FooEnum.FOO }, argValue); - commands.run("foo", "add", "bar"); + commands.run(server.addPlayer(), "foo", "add", "bar"); Assertions.assertArrayEquals(new FooEnum[] { FooEnum.BAR }, argValue); } + + @Test public void canReceiveCommandSender() throws CommandException { + final CommandSender[] senders = {null}; + Player player = server.addPlayer(); + + class FooCommand { + public void add(@Sender CommandSender sender) { + senders[0] = sender; + } + } + + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run(player,"foo", "add"); + Assertions.assertArrayEquals(new CommandSender[] { player }, senders); + } } From 5297efeea71ecc75068ad8791bddf98c0c67c890 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Sat, 12 Dec 2020 07:00:56 +0100 Subject: [PATCH 06/16] Fix checkstyle and tests --- .../components/commands/CommandEndpoint.java | 3 +- .../components/commands/CommandGroup.java | 11 +- .../components/commands/CommandManager.java | 3 +- .../components/commands/CommandMethod.java | 8 +- .../commands/CommandMethodArgument.java | 1 - .../commands/CommandMethodSenderArgument.java | 4 +- .../components/commands/DiscoveryUtils.java | 2 +- .../components/commands/TypeCollection.java | 29 +++-- .../arguments/generic/EnumArgumentType.java | 47 +++---- .../senders/GenericCommandSender.java | 8 +- .../commands/CommandGraphTests.java | 118 +++++++++++------- .../commands/CommandWithStatics.java | 22 ++++ .../generic/EnumArgumentTypeTests.java | 4 +- .../fr/zcraft/ztoaster/ToastCommands.java | 23 ++-- 14 files changed, 164 insertions(+), 119 deletions(-) create mode 100644 src/test/java/fr/zcraft/quartzlib/components/commands/CommandWithStatics.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index 39064bf2..51ebf771 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -1,10 +1,9 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; -import org.bukkit.command.CommandSender; - import java.util.ArrayList; import java.util.List; +import org.bukkit.command.CommandSender; class CommandEndpoint extends CommandNode { private final List methods = new ArrayList<>(); diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index f8401835..8aba0b8e 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -1,12 +1,11 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; -import org.bukkit.command.CommandSender; - import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; +import org.bukkit.command.CommandSender; class CommandGroup extends CommandNode { private final Class commandGroupClass; @@ -14,18 +13,20 @@ class CommandGroup extends CommandNode { private final Map subCommands = new HashMap<>(); - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, TypeCollection typeCollection) { + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, + TypeCollection typeCollection) { this(commandGroupClass, classInstanceSupplier, name, typeCollection, null); } - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, TypeCollection typeCollection, CommandGroup parent) { + public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, + TypeCollection typeCollection, CommandGroup parent) { super(name, parent); this.commandGroupClass = commandGroupClass; this.classInstanceSupplier = classInstanceSupplier; DiscoveryUtils.getCommandMethods(commandGroupClass, typeCollection).forEach(this::addMethod); } - public Iterable getSubCommands () { + public Iterable getSubCommands() { return this.subCommands.values(); } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java index 7777ec08..d496c506 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java @@ -1,11 +1,10 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; -import org.bukkit.command.CommandSender; - import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; +import org.bukkit.command.CommandSender; public class CommandManager { private final Map rootCommands = new HashMap<>(); diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java index ad1d217d..d0edd920 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -4,13 +4,12 @@ import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; import fr.zcraft.quartzlib.components.commands.exceptions.InvalidSenderException; -import org.bukkit.command.CommandSender; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.List; +import org.bukkit.command.CommandSender; class CommandMethod { private final Method method; @@ -34,7 +33,7 @@ class CommandMethod { } } - this.arguments = arguments.toArray(new CommandMethodArgument[]{}); + this.arguments = arguments.toArray(new CommandMethodArgument[] {}); this.parameterCount = parameters.length; } @@ -51,7 +50,8 @@ public void run(Object target, CommandSender sender, String[] args) throws Comma } } - private Object[] parseArguments(CommandSender sender, String[] args) throws ArgumentParseException, InvalidSenderException { + private Object[] parseArguments(CommandSender sender, String[] args) + throws ArgumentParseException, InvalidSenderException { Object[] parsed = new Object[parameterCount]; for (int i = 0; i < arguments.length; i++) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java index f432f26a..1a151e10 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java @@ -1,7 +1,6 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; - import java.lang.reflect.Parameter; public class CommandMethodArgument { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodSenderArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodSenderArgument.java index 62a0bb7f..6c773508 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodSenderArgument.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodSenderArgument.java @@ -1,10 +1,8 @@ package fr.zcraft.quartzlib.components.commands; -import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; import fr.zcraft.quartzlib.components.commands.exceptions.InvalidSenderException; -import org.bukkit.command.CommandSender; - import java.lang.reflect.Parameter; +import org.bukkit.command.CommandSender; public class CommandMethodSenderArgument { private final Parameter parameter; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java index a9f4463c..4d357f1b 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java @@ -15,7 +15,7 @@ public static Stream getCommandMethods(Class commandGroupClass .map((Method method) -> new CommandMethod(method, typeCollection)); } - public static Supplier getClassConstructorSupplier (Class commandGroupClass) { + public static Supplier getClassConstructorSupplier(Class commandGroupClass) { Constructor constructor = commandGroupClass.getDeclaredConstructors()[0]; return () -> { try { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java b/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java index d01c8f03..19600349 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java @@ -3,8 +3,11 @@ import fr.zcraft.quartzlib.components.commands.arguments.generic.EnumArgumentType; import fr.zcraft.quartzlib.components.commands.arguments.primitive.IntegerArgumentType; import fr.zcraft.quartzlib.components.commands.senders.GenericCommandSender; - -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; class TypeCollection { private final Map, ArgumentTypeWrapper> argumentTypeMap = new HashMap<>(); @@ -17,8 +20,7 @@ public TypeCollection() { this.registerNativeTypes(); } - public void register(ArgumentTypeWrapper typeHandler) - { + public void register(ArgumentTypeWrapper typeHandler) { argumentTypeMap.put(typeHandler.getResultType(), typeHandler); } @@ -26,8 +28,7 @@ public void register(GenericArgumentType genericArgumentType) { genericArgumentTypes.add(genericArgumentType); } - public void register(SenderTypeWrapper typeHandler) - { + public void register(SenderTypeWrapper typeHandler) { senderTypeMap.put(typeHandler.getResultType(), typeHandler); } @@ -37,7 +38,9 @@ public void register(GenericSenderType genericSenderType) { public Optional> findArgumentType(Class resultType) { ArgumentTypeWrapper typeHandler = argumentTypeMap.get(resultType); - if (typeHandler != null) return Optional.of(typeHandler); + if (typeHandler != null) { + return Optional.of(typeHandler); + } return this.findGenericArgumentType(resultType); } @@ -46,14 +49,15 @@ private Optional> findGenericArgumentType(Class re Optional> matchingArgumentType = t.getMatchingArgumentType(resultType); if (matchingArgumentType.isPresent()) { - ArgumentTypeWrapper typeHandler = new ArgumentTypeWrapper<>(resultType, (ArgumentType) matchingArgumentType.get()); + ArgumentTypeWrapper typeHandler = + new ArgumentTypeWrapper<>(resultType, (ArgumentType) matchingArgumentType.get()); return Optional.of(typeHandler); } } return Optional.empty(); } - private void registerNativeTypes () { + private void registerNativeTypes() { // Primitive types register(new ArgumentTypeWrapper<>(Integer.class, new IntegerArgumentType())); register(new ArgumentTypeWrapper<>(String.class, s -> s)); @@ -68,7 +72,9 @@ private void registerNativeTypes () { public Optional> findSenderType(Class resultType) { SenderTypeWrapper typeHandler = senderTypeMap.get(resultType); - if (typeHandler != null) return Optional.of(typeHandler); + if (typeHandler != null) { + return Optional.of(typeHandler); + } return this.findGenericSenderType(resultType); } @@ -77,7 +83,8 @@ private Optional> findGenericSenderType(Class result Optional> matchingSenderType = t.getMatchingSenderType(resultType); if (matchingSenderType.isPresent()) { - SenderTypeWrapper typeHandler = new SenderTypeWrapper<>(resultType, (SenderType) matchingSenderType.get()); + SenderTypeWrapper typeHandler = + new SenderTypeWrapper<>(resultType, (SenderType) matchingSenderType.get()); return Optional.of(typeHandler); } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java index 3baeb45a..77cd208d 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentType.java @@ -3,7 +3,6 @@ import fr.zcraft.quartzlib.components.commands.ArgumentType; import fr.zcraft.quartzlib.components.commands.GenericArgumentType; import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; - import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; @@ -11,6 +10,25 @@ import java.util.Optional; public class EnumArgumentType implements GenericArgumentType> { + private static Map> getEnumValues(Class enumClass) { + Map> enumValues = new HashMap<>(); + + Arrays.stream(enumClass.getDeclaredFields()) + .filter(f -> Modifier.isPublic(f.getModifiers()) + && Modifier.isStatic(f.getModifiers()) + && enumClass.isAssignableFrom(f.getType())) + .forEach(f -> { + try { + f.setAccessible(true); + enumValues.put(f.getName().toLowerCase(), (Enum) f.get(null)); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + + return enumValues; + } + @Override public Optional>> getMatchingArgumentType(Class type) { if (type.isEnum()) { @@ -19,7 +37,7 @@ public Optional>> getMatchingArgumentType(Class type) { return Optional.empty(); } - static private class DiscreteEnumArgumentType implements ArgumentType> { + private static class DiscreteEnumArgumentType implements ArgumentType> { private final Map> enumValues; public DiscreteEnumArgumentType(Class enumClass) { @@ -29,31 +47,14 @@ public DiscreteEnumArgumentType(Class enumClass) { @Override public Enum parse(String raw) throws ArgumentParseException { Enum value = enumValues.get(raw); - if (value == null) throw new EnumParseException(); + if (value == null) { + throw new EnumParseException(); + } return value; } } - static private class EnumParseException extends ArgumentParseException { + private static class EnumParseException extends ArgumentParseException { } - - static private Map> getEnumValues (Class enumClass) { - Map> enumValues = new HashMap<>(); - - Arrays.stream(enumClass.getDeclaredFields()) - .filter(f -> Modifier.isPublic(f.getModifiers()) - && Modifier.isStatic(f.getModifiers()) - && enumClass.isAssignableFrom(f.getType())) - .forEach(f -> { - try { - f.setAccessible(true); - enumValues.put(f.getName().toLowerCase(), (Enum)f.get(null)); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - }); - - return enumValues; - } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/senders/GenericCommandSender.java b/src/main/java/fr/zcraft/quartzlib/components/commands/senders/GenericCommandSender.java index c93a7c36..ed8d1c68 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/senders/GenericCommandSender.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/senders/GenericCommandSender.java @@ -2,11 +2,9 @@ import fr.zcraft.quartzlib.components.commands.GenericSenderType; import fr.zcraft.quartzlib.components.commands.SenderType; -import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; import fr.zcraft.quartzlib.components.commands.exceptions.InvalidSenderException; -import org.bukkit.command.CommandSender; - import java.util.Optional; +import org.bukkit.command.CommandSender; public class GenericCommandSender implements GenericSenderType { @Override @@ -17,11 +15,11 @@ public Optional> getMatchingSenderType(Class type) return Optional.empty(); } - static private class InvalidSenderTypeException extends InvalidSenderException { + private static class InvalidSenderTypeException extends InvalidSenderException { } - static private class CommandSenderSubType implements SenderType { + private static class CommandSenderSubType implements SenderType { private final Class subtype; private CommandSenderSubType(Class subtype) { diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index 55fa3f20..765341e3 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -3,104 +3,124 @@ import fr.zcraft.quartzlib.MockedBukkitTest; import fr.zcraft.quartzlib.components.commands.attributes.Sender; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import java.util.stream.StreamSupport; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.stream.StreamSupport; - -// This is outside because inner classes cannot have statics -class CommandWithStatics { - public void add () {} - private void get () {} - protected void list () {} - public void delete () {} - void update () {} - static public void staticMethod () {} -} +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; public class CommandGraphTests extends MockedBukkitTest { private CommandManager commands; - @BeforeEach - public void beforeEach () { + @Before + public void beforeEach() { commands = new CommandManager(); } - @Test public void canDiscoverBasicSubcommands() { + @Test + public void canDiscoverBasicSubcommands() { class FooCommand { - public void add () {} - public void get () {} - public void list () {} + public void add() { + } + + public void get() { + } + + public void list() { + } } - CommandGroup commandGroup = new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo", new TypeCollection()); - String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); - Assertions.assertArrayEquals(new String[] {"add", "get", "list"}, commandNames); + CommandGroup commandGroup = + new CommandGroup(FooCommand.class, () -> new FooCommand(), "foo", new TypeCollection()); + String[] commandNames = + StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName) + .toArray(String[]::new); + Assert.assertArrayEquals(new String[] {"add", "get", "list"}, commandNames); } - @Test public void onlyDiscoversPublicMethods() { - CommandGroup commandGroup = new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo", new TypeCollection()); - String[] commandNames = StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName).toArray(String[]::new); - Assertions.assertArrayEquals(new String[] {"add", "delete"}, commandNames); + @Test + public void onlyDiscoversPublicMethods() { + CommandGroup commandGroup = + new CommandGroup(CommandWithStatics.class, () -> new CommandWithStatics(), "foo", new TypeCollection()); + String[] commandNames = + StreamSupport.stream(commandGroup.getSubCommands().spliterator(), false).map(CommandNode::getName) + .toArray(String[]::new); + Assert.assertArrayEquals(new String[] {"add", "delete"}, commandNames); } - @Test public void canRunBasicSubcommands() throws CommandException { + @Test + public void canRunBasicSubcommands() throws CommandException { final boolean[] ran = {false, false, false}; class FooCommand { - public void add () { ran[0] = true; } - public void get () { ran[1] = true; } - public void list () { ran[2] = true; } + public void add() { + ran[0] = true; + } + + public void get() { + ran[1] = true; + } + + public void list() { + ran[2] = true; + } } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(server.addPlayer(), "foo", "get"); - Assertions.assertArrayEquals(new boolean[] { false, true, false }, ran); + Assert.assertArrayEquals(new boolean[] {false, true, false}, ran); } - @Test public void canReceiveStringArguments() throws CommandException { + @Test + public void canReceiveStringArguments() throws CommandException { final String[] argValue = {""}; class FooCommand { - public void add (String arg) { argValue[0] = arg; } + public void add(String arg) { + argValue[0] = arg; + } } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(server.addPlayer(), "foo", "add", "pomf"); - Assertions.assertArrayEquals(new String[] { "pomf" }, argValue); + Assert.assertArrayEquals(new String[] {"pomf"}, argValue); } - @Test public void canReceiveParsedArguments() throws CommandException { + @Test + public void canReceiveParsedArguments() throws CommandException { final int[] argValue = {0}; class FooCommand { - public void add (Integer arg) { argValue[0] = arg; } + public void add(Integer arg) { + argValue[0] = arg; + } } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(server.addPlayer(), "foo", "add", "42"); - Assertions.assertArrayEquals(new int[] { 42 }, argValue); + Assert.assertArrayEquals(new int[] {42}, argValue); } - enum FooEnum { FOO, BAR } - @Test public void canReceiveEnumArguments() throws CommandException { + @Test + public void canReceiveEnumArguments() throws CommandException { final FooEnum[] argValue = {null}; class FooCommand { - public void add (FooEnum arg) { argValue[0] = arg; } + public void add(FooEnum arg) { + argValue[0] = arg; + } } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(server.addPlayer(), "foo", "add", "foo"); - Assertions.assertArrayEquals(new FooEnum[] { FooEnum.FOO }, argValue); + Assert.assertArrayEquals(new FooEnum[] {FooEnum.FOO}, argValue); commands.run(server.addPlayer(), "foo", "add", "bar"); - Assertions.assertArrayEquals(new FooEnum[] { FooEnum.BAR }, argValue); + Assert.assertArrayEquals(new FooEnum[] {FooEnum.BAR}, argValue); } - @Test public void canReceiveCommandSender() throws CommandException { + @Test + public void canReceiveCommandSender() throws CommandException { final CommandSender[] senders = {null}; Player player = server.addPlayer(); @@ -111,7 +131,11 @@ public void add(@Sender CommandSender sender) { } commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); - commands.run(player,"foo", "add"); - Assertions.assertArrayEquals(new CommandSender[] { player }, senders); + commands.run(player, "foo", "add"); + Assert.assertArrayEquals(new CommandSender[] {player}, senders); + } + + enum FooEnum { + FOO, BAR } } diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandWithStatics.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandWithStatics.java new file mode 100644 index 00000000..1fda8472 --- /dev/null +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandWithStatics.java @@ -0,0 +1,22 @@ +package fr.zcraft.quartzlib.components.commands; + +// This is outside because inner classes cannot have statics +class CommandWithStatics { + public static void staticMethod() { + } + + public void add() { + } + + private void get() { + } + + protected void list() { + } + + public void delete() { + } + + void update() { + } +} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java index 6041e0ce..699cfa5c 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/arguments/generic/EnumArgumentTypeTests.java @@ -8,7 +8,9 @@ public class EnumArgumentTypeTests { private final EnumArgumentType enumArgumentType = new EnumArgumentType(); - private enum SimpleEnum { FOO, BAR } + private enum SimpleEnum { + FOO, BAR + } @Test public void worksOnSimpleEnum() throws ArgumentParseException { diff --git a/src/test/java/fr/zcraft/ztoaster/ToastCommands.java b/src/test/java/fr/zcraft/ztoaster/ToastCommands.java index 46333ab4..773ae30e 100644 --- a/src/test/java/fr/zcraft/ztoaster/ToastCommands.java +++ b/src/test/java/fr/zcraft/ztoaster/ToastCommands.java @@ -3,12 +3,11 @@ import fr.zcraft.quartzlib.components.commands.attributes.Sender; import fr.zcraft.quartzlib.components.gui.Gui; import fr.zcraft.quartzlib.components.i18n.I; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; public class ToastCommands { @@ -20,8 +19,7 @@ public void add(@Sender Player cook) { } public void add(@Sender Player cook, int toastCount) { - for(int i = toastCount; i --> 0;) - { + for (int i = toastCount; i-- > 0; ) { ToasterWorker.addToast(cook); } @@ -35,25 +33,22 @@ public void list(@Sender CommandSender sender) { public void list(@Sender CommandSender sender, Toast.CookingStatus cookingStatus) { ArrayList toasts = new ArrayList(); - for(Toast toast : Toaster.getToasts()) - { - if(toast.getStatus().equals(cookingStatus)) + for (Toast toast : Toaster.getToasts()) { + if (toast.getStatus().equals(cookingStatus)) { toasts.add(toast); + } } showToasts(sender, toasts); } - private void showToasts(CommandSender sender, Collection toasts) - { - if(toasts.isEmpty()) - { + private void showToasts(CommandSender sender, Collection toasts) { + if (toasts.isEmpty()) { // Output of the command /toaster list, without toasts. sender.sendMessage("§7" + I.t("There are no toasts here ...")); } - for(Toast toast : toasts) - { + for (Toast toast : toasts) { sender.sendMessage(I.t(" Toast #{0}", toast.getToastId())); } } From fc0b7fc4473fa19406474318c4cf6b18eb287ce1 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Sat, 12 Dec 2020 07:05:01 +0100 Subject: [PATCH 07/16] Add subcommand support --- .../components/commands/CommandEndpoint.java | 4 +- .../components/commands/CommandGroup.java | 61 ++++++++++++++++--- .../components/commands/CommandNode.java | 9 +-- .../components/commands/DiscoveryUtils.java | 19 +++++- .../commands/GroupClassInstanceSupplier.java | 17 ++++++ .../commands/attributes/SubCommand.java | 11 ++++ .../commands/CommandGraphTests.java | 26 ++++++++ 7 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/GroupClassInstanceSupplier.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/attributes/SubCommand.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index 51ebf771..9ff6a167 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -13,8 +13,8 @@ class CommandEndpoint extends CommandNode { } @Override - void run(Object instance, CommandSender sender, String[] args) throws CommandException { - this.methods.get(0).run(instance, sender, args); + void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException { + this.methods.get(0).run(parentInstance, sender, args); } void addMethod(CommandMethod method) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index 8aba0b8e..a030a666 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -1,29 +1,55 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Nullable; class CommandGroup extends CommandNode { private final Class commandGroupClass; + + @Nullable private final Supplier classInstanceSupplier; + @Nullable + private final GroupClassInstanceSupplier groupClassInstanceSupplier; private final Map subCommands = new HashMap<>(); - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, - TypeCollection typeCollection) { - this(commandGroupClass, classInstanceSupplier, name, typeCollection, null); + CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, + TypeCollection typeCollection) { + this(commandGroupClass, classInstanceSupplier, null, name, typeCollection, null); + } + + CommandGroup(Class commandGroupClass, GroupClassInstanceSupplier classInstanceSupplier, String name, + CommandGroup parent, TypeCollection typeCollection) { + this(commandGroupClass, null, classInstanceSupplier, name, typeCollection, parent); + } + + CommandGroup(CommandGroup parent, Field backingField, TypeCollection typeCollection) { + this( + backingField.getType(), + GroupClassInstanceSupplier.backingField(backingField), + backingField.getName(), + parent, + typeCollection + ); } - public CommandGroup(Class commandGroupClass, Supplier classInstanceSupplier, String name, - TypeCollection typeCollection, CommandGroup parent) { + private CommandGroup( + Class commandGroupClass, + @Nullable Supplier classInstanceSupplier, + @Nullable GroupClassInstanceSupplier groupClassInstanceSupplier, String name, + TypeCollection typeCollection, CommandGroup parent) { super(name, parent); this.commandGroupClass = commandGroupClass; this.classInstanceSupplier = classInstanceSupplier; + this.groupClassInstanceSupplier = groupClassInstanceSupplier; DiscoveryUtils.getCommandMethods(commandGroupClass, typeCollection).forEach(this::addMethod); + DiscoveryUtils.getSubCommands(this, typeCollection).forEach(this::addSubCommand); } public Iterable getSubCommands() { @@ -41,15 +67,36 @@ private void addMethod(CommandMethod method) { endpoint.addMethod(method); } + private void addSubCommand(CommandGroup commandGroup) { + subCommands.put(commandGroup.getName(), commandGroup); + } + void run(CommandSender sender, String... args) throws CommandException { + if (classInstanceSupplier == null) { + throw new IllegalStateException("This command group comes from a parent and cannot instanciate itself."); + } + Object commandObject = classInstanceSupplier.get(); - run(commandObject, sender, args); + runSelf(commandObject, sender, args); } @Override - void run(Object instance, CommandSender sender, String[] args) throws CommandException { + void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException { + if (this.groupClassInstanceSupplier == null) { + throw new IllegalStateException("This command group cannot be ran from a parent"); + } + + Object instance = this.groupClassInstanceSupplier.supply(parentInstance); + runSelf(instance, sender, args); + } + + private void runSelf(Object instance, CommandSender sender, String[] args) throws CommandException { String commandName = args[0]; CommandNode subCommand = subCommands.get(commandName); subCommand.run(instance, sender, Arrays.copyOfRange(args, 1, args.length)); } + + public Class getCommandGroupClass() { + return commandGroupClass; + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java index 77bb4e36..365da497 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java @@ -2,12 +2,13 @@ import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Nullable; abstract class CommandNode { private final String name; - private final CommandGroup parent; + @Nullable private final CommandGroup parent; - protected CommandNode(String name, CommandGroup parent) { + protected CommandNode(String name, @Nullable CommandGroup parent) { this.name = name; this.parent = parent; } @@ -16,9 +17,9 @@ public String getName() { return name; } - public CommandGroup getParent() { + @Nullable public CommandGroup getParent() { return parent; } - abstract void run(Object instance, CommandSender sender, String[] args) throws CommandException; + abstract void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException; } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java index 4d357f1b..a2fc7424 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.attributes.SubCommand; +import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -11,10 +13,21 @@ abstract class DiscoveryUtils { public static Stream getCommandMethods(Class commandGroupClass, TypeCollection typeCollection) { return Arrays.stream(commandGroupClass.getDeclaredMethods()) - .filter(m -> Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) + .filter(m -> hasRunnableModifiers(m.getModifiers())) .map((Method method) -> new CommandMethod(method, typeCollection)); } + public static Stream getSubCommands(CommandGroup commandGroup, TypeCollection typeCollection) { + return Arrays.stream(commandGroup.getCommandGroupClass().getDeclaredFields()) + .filter(m -> hasRunnableModifiers(m.getModifiers())) + .filter(DiscoveryUtils::isSubcommand) + .map(field -> new CommandGroup(commandGroup, field, typeCollection)); + } + + private static boolean isSubcommand(AccessibleObject field) { + return field.isAnnotationPresent(SubCommand.class); + } + public static Supplier getClassConstructorSupplier(Class commandGroupClass) { Constructor constructor = commandGroupClass.getDeclaredConstructors()[0]; return () -> { @@ -25,4 +38,8 @@ public static Supplier getClassConstructorSupplier(Class commandGroupClass } }; } + + private static boolean hasRunnableModifiers(int modifiers) { + return Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers); + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/GroupClassInstanceSupplier.java b/src/main/java/fr/zcraft/quartzlib/components/commands/GroupClassInstanceSupplier.java new file mode 100644 index 00000000..78497219 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/GroupClassInstanceSupplier.java @@ -0,0 +1,17 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.lang.reflect.Field; + +public interface GroupClassInstanceSupplier { + static GroupClassInstanceSupplier backingField(Field field) { + return (instance) -> { + try { + return field.get(instance); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); // TODO + } + }; + } + + Object supply(Object parent); +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/SubCommand.java b/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/SubCommand.java new file mode 100644 index 00000000..cb38f922 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/SubCommand.java @@ -0,0 +1,11 @@ +package fr.zcraft.quartzlib.components.commands.attributes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface SubCommand { +} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index 765341e3..e3fb96c2 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -2,6 +2,7 @@ import fr.zcraft.quartzlib.MockedBukkitTest; import fr.zcraft.quartzlib.components.commands.attributes.Sender; +import fr.zcraft.quartzlib.components.commands.attributes.SubCommand; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; import java.util.stream.StreamSupport; import org.bukkit.command.CommandSender; @@ -135,6 +136,31 @@ public void add(@Sender CommandSender sender) { Assert.assertArrayEquals(new CommandSender[] {player}, senders); } + @Test + public void canCallSubcommand() throws CommandException { + final boolean[] ran = {false}; + + class SubFooCommand { + public void add() { + ran[0] = true; + } + } + + class FooCommand { + @SubCommand + public final SubFooCommand sub = new SubFooCommand(); + + public void add() { + throw new RuntimeException("This shouldn't run!"); + } + } + + Player player = server.addPlayer(); + commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run(player, "foo", "sub", "add"); + Assert.assertArrayEquals(new boolean[] {true}, ran); + } + enum FooEnum { FOO, BAR } From 42addf09ddb21c75b8c616f250c5c6d213b8022f Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Sat, 12 Dec 2020 09:55:07 +0100 Subject: [PATCH 08/16] Can now register commands to be ran by Bukkit --- .../components/commands/CommandGroup.java | 1 + .../components/commands/CommandManager.java | 15 +++++++++ .../commands/QuartzCommandExecutor.java | 26 +++++++++++++++ .../commands/CommandGraphTests.java | 15 ++++----- .../commands/CommandRegistrationTests.java | 33 +++++++++++++++++++ 5 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/QuartzCommandExecutor.java create mode 100644 src/test/java/fr/zcraft/quartzlib/components/commands/CommandRegistrationTests.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index a030a666..77db87b5 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -93,6 +93,7 @@ void run(Object parentInstance, CommandSender sender, String[] args) throws Comm private void runSelf(Object instance, CommandSender sender, String[] args) throws CommandException { String commandName = args[0]; CommandNode subCommand = subCommands.get(commandName); + // TODO: handle null subCommand.run(instance, sender, Arrays.copyOfRange(args, 1, args.length)); } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java index d496c506..6e7933f9 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java @@ -1,18 +1,33 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import fr.zcraft.quartzlib.core.QuartzLib; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; public class CommandManager { private final Map rootCommands = new HashMap<>(); private final TypeCollection typeCollection = new TypeCollection(); + public void addCommand(String name, Class commandType, Supplier commandClassSupplier) { + CommandGroup group = new CommandGroup(commandType, commandClassSupplier, name, typeCollection); + rootCommands.put(name, group); + } + public void registerCommand(String name, Class commandType, Supplier commandClassSupplier) { CommandGroup group = new CommandGroup(commandType, commandClassSupplier, name, typeCollection); rootCommands.put(name, group); + registerCommand(group); + } + + private void registerCommand(CommandGroup group) { + PluginCommand command = QuartzLib.getPlugin().getCommand(group.getName()); + // TODO: handle null here + Objects.requireNonNull(command).setExecutor(new QuartzCommandExecutor(group)); } public void run(CommandSender sender, String commandName, String... args) throws CommandException { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/QuartzCommandExecutor.java b/src/main/java/fr/zcraft/quartzlib/components/commands/QuartzCommandExecutor.java new file mode 100644 index 00000000..9c2fa779 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/QuartzCommandExecutor.java @@ -0,0 +1,26 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class QuartzCommandExecutor implements CommandExecutor { + private final CommandGroup group; + + public QuartzCommandExecutor(CommandGroup group) { + this.group = group; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, + @NotNull String[] args) { + try { + group.run(sender, args); + } catch (CommandException e) { + throw new RuntimeException(e); // TODO + } + return true; + } +} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index e3fb96c2..1462f251 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -68,7 +68,7 @@ public void list() { } } - commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(server.addPlayer(), "foo", "get"); Assert.assertArrayEquals(new boolean[] {false, true, false}, ran); } @@ -83,7 +83,7 @@ public void add(String arg) { } } - commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(server.addPlayer(), "foo", "add", "pomf"); Assert.assertArrayEquals(new String[] {"pomf"}, argValue); } @@ -98,7 +98,7 @@ public void add(Integer arg) { } } - commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(server.addPlayer(), "foo", "add", "42"); Assert.assertArrayEquals(new int[] {42}, argValue); } @@ -113,7 +113,7 @@ public void add(FooEnum arg) { } } - commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(server.addPlayer(), "foo", "add", "foo"); Assert.assertArrayEquals(new FooEnum[] {FooEnum.FOO}, argValue); commands.run(server.addPlayer(), "foo", "add", "bar"); @@ -131,7 +131,7 @@ public void add(@Sender CommandSender sender) { } } - commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); commands.run(player, "foo", "add"); Assert.assertArrayEquals(new CommandSender[] {player}, senders); } @@ -155,9 +155,8 @@ public void add() { } } - Player player = server.addPlayer(); - commands.registerCommand("foo", FooCommand.class, () -> new FooCommand()); - commands.run(player, "foo", "sub", "add"); + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); + commands.run(server.addPlayer(), "foo", "sub", "add"); Assert.assertArrayEquals(new boolean[] {true}, ran); } diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandRegistrationTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandRegistrationTests.java new file mode 100644 index 00000000..04adafde --- /dev/null +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandRegistrationTests.java @@ -0,0 +1,33 @@ +package fr.zcraft.quartzlib.components.commands; + +import fr.zcraft.quartzlib.MockedToasterTest; +import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class CommandRegistrationTests extends MockedToasterTest { + private CommandManager commands; + + @Before + public void beforeEach() { + commands = new CommandManager(); + } + + @Test + public void canRegisterAndRunCommand() throws CommandException { + + final boolean[] ran = {false}; + + class FooCommand { + public void get() { + ran[0] = true; + } + } + + commands.registerCommand("toaster", FooCommand.class, () -> new FooCommand()); + boolean success = server.dispatchCommand(server.addPlayer(), "toaster get"); + Assert.assertTrue(success); + Assert.assertArrayEquals(new boolean[] {true}, ran); + } +} From dff2c1d36987ec415c099c6a10ace38191e715cb Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Thu, 17 Dec 2020 16:32:26 +0100 Subject: [PATCH 09/16] Added MissingSubcommandException, plus some more internal changes --- .../components/commands/CommandGroup.java | 10 ++- .../components/commands/CommandMethod.java | 6 +- .../commands/CommandMethodArgument.java | 11 +++- .../components/commands/CommandNode.java | 2 +- .../components/commands/ExecutionContext.java | 13 ++++ .../commands/QuartzCommandExecutor.java | 3 +- .../components/commands/TypeCollection.java | 2 + .../exceptions/ArgumentParseException.java | 7 +++ .../commands/exceptions/CommandException.java | 4 ++ .../exceptions/InvalidSenderException.java | 7 +++ .../MissingSubcommandException.java | 63 +++++++++++++++++++ .../UnknownArgumentTypeException.java | 15 +++++ .../quartzlib/tools/text/RawMessage.java | 2 +- ...nTests.java => CommandExecutionTests.java} | 2 +- .../fr/zcraft/ztoaster/ToastCommands.java | 0 .../main/java/fr/zcraft/ztoaster/Toaster.java | 9 ++- 16 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/ExecutionContext.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/MissingSubcommandException.java create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownArgumentTypeException.java rename src/test/java/fr/zcraft/quartzlib/components/commands/{CommandRegistrationTests.java => CommandExecutionTests.java} (93%) rename {src/test => ztoaster/src/main}/java/fr/zcraft/ztoaster/ToastCommands.java (100%) diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index 77db87b5..a805d904 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -1,15 +1,17 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import fr.zcraft.quartzlib.components.commands.exceptions.MissingSubcommandException; import java.lang.reflect.Field; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.Nullable; -class CommandGroup extends CommandNode { +public class CommandGroup extends CommandNode { private final Class commandGroupClass; @Nullable @@ -52,7 +54,7 @@ private CommandGroup( DiscoveryUtils.getSubCommands(this, typeCollection).forEach(this::addSubCommand); } - public Iterable getSubCommands() { + public Collection getSubCommands() { return this.subCommands.values(); } @@ -91,6 +93,10 @@ void run(Object parentInstance, CommandSender sender, String[] args) throws Comm } private void runSelf(Object instance, CommandSender sender, String[] args) throws CommandException { + if (args.length == 0) { + throw new MissingSubcommandException(this); + } + String commandName = args[0]; CommandNode subCommand = subCommands.get(commandName); // TODO: handle null diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java index d0edd920..6f0e52f9 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -29,7 +29,7 @@ class CommandMethod { if (parameter.isAnnotationPresent(Sender.class)) { // TODO: check for multiple sender arguments senderArgument = new CommandMethodSenderArgument(parameter, i, typeCollection); } else { - arguments.add(new CommandMethodArgument(parameter, i, typeCollection)); + arguments.add(new CommandMethodArgument(this, parameter, i, typeCollection)); } } @@ -69,4 +69,8 @@ private Object[] parseArguments(CommandSender sender, String[] args) public CommandMethodArgument[] getArguments() { return arguments; } + + public Method getMethod() { + return method; + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java index 1a151e10..d9c35483 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java @@ -1,6 +1,7 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; +import fr.zcraft.quartzlib.components.commands.exceptions.UnknownArgumentTypeException; import java.lang.reflect.Parameter; public class CommandMethodArgument { @@ -8,10 +9,16 @@ public class CommandMethodArgument { private final int position; private final ArgumentTypeWrapper typeHandler; - public CommandMethodArgument(Parameter parameter, int position, TypeCollection typeCollection) { + public CommandMethodArgument( + CommandMethod parent, + Parameter parameter, + int position, + TypeCollection typeCollection + ) { this.parameter = parameter; this.position = position; - this.typeHandler = typeCollection.findArgumentType(parameter.getType()).get(); // FIXME: handle unknown types + this.typeHandler = typeCollection.findArgumentType(parameter.getType()) + .orElseThrow(() -> new UnknownArgumentTypeException(parent.getMethod(), parameter.getType())); } public Object parse(String raw) throws ArgumentParseException { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java index 365da497..d5371bd0 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandNode.java @@ -4,7 +4,7 @@ import org.bukkit.command.CommandSender; import org.jetbrains.annotations.Nullable; -abstract class CommandNode { +public abstract class CommandNode { private final String name; @Nullable private final CommandGroup parent; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ExecutionContext.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ExecutionContext.java new file mode 100644 index 00000000..c350bc21 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ExecutionContext.java @@ -0,0 +1,13 @@ +package fr.zcraft.quartzlib.components.commands; + +import org.bukkit.command.CommandSender; + +public class ExecutionContext { + private final CommandSender sender; + private final String[] fullArgs; + + public ExecutionContext(CommandSender sender, String[] fullArgs) { + this.sender = sender; + this.fullArgs = fullArgs; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/QuartzCommandExecutor.java b/src/main/java/fr/zcraft/quartzlib/components/commands/QuartzCommandExecutor.java index 9c2fa779..b51c52af 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/QuartzCommandExecutor.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/QuartzCommandExecutor.java @@ -1,6 +1,7 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import fr.zcraft.quartzlib.tools.text.RawMessage; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -19,7 +20,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command try { group.run(sender, args); } catch (CommandException e) { - throw new RuntimeException(e); // TODO + RawMessage.send(sender, e.display(sender).build()); } return true; } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java b/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java index 19600349..c914798b 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/TypeCollection.java @@ -62,6 +62,8 @@ private void registerNativeTypes() { register(new ArgumentTypeWrapper<>(Integer.class, new IntegerArgumentType())); register(new ArgumentTypeWrapper<>(String.class, s -> s)); + register(new ArgumentTypeWrapper<>(int.class, new IntegerArgumentType())); + // Generic types register(new EnumArgumentType()); diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java index b6ff6416..c2e20753 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/ArgumentParseException.java @@ -1,4 +1,11 @@ package fr.zcraft.quartzlib.components.commands.exceptions; +import fr.zcraft.quartzlib.components.rawtext.RawText; +import org.bukkit.command.CommandSender; + public class ArgumentParseException extends CommandException { + @Override + public RawText display(CommandSender sender) { + return null; + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java index ea6c97d8..baa218af 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/CommandException.java @@ -1,4 +1,8 @@ package fr.zcraft.quartzlib.components.commands.exceptions; +import fr.zcraft.quartzlib.components.rawtext.RawText; +import org.bukkit.command.CommandSender; + public abstract class CommandException extends Exception { + public abstract RawText display(CommandSender sender); } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/InvalidSenderException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/InvalidSenderException.java index 68787e5e..ca07d99d 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/InvalidSenderException.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/InvalidSenderException.java @@ -1,4 +1,11 @@ package fr.zcraft.quartzlib.components.commands.exceptions; +import fr.zcraft.quartzlib.components.rawtext.RawText; +import org.bukkit.command.CommandSender; + public class InvalidSenderException extends CommandException { + @Override + public RawText display(CommandSender sender) { + return null; + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/MissingSubcommandException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/MissingSubcommandException.java new file mode 100644 index 00000000..61d32686 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/MissingSubcommandException.java @@ -0,0 +1,63 @@ +package fr.zcraft.quartzlib.components.commands.exceptions; + +import fr.zcraft.quartzlib.components.commands.CommandGroup; +import fr.zcraft.quartzlib.components.commands.CommandNode; +import fr.zcraft.quartzlib.components.i18n.I; +import fr.zcraft.quartzlib.components.rawtext.RawText; +import fr.zcraft.quartzlib.components.rawtext.RawTextPart; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; + +public class MissingSubcommandException extends CommandException { + private final CommandGroup commandGroup; + + public MissingSubcommandException(CommandGroup commandGroup) { + this.commandGroup = commandGroup; + } + + @Override + public RawText display(CommandSender sender) { + RawTextPart text = new RawText(I.t("Missing subcommand: ")) + .color(ChatColor.RED) + .then("/").color(ChatColor.WHITE) + .then(getParents()).color(ChatColor.AQUA) + .then(" <").style(ChatColor.GRAY) + .then(I.t("sub-command")) + .style(ChatColor.GRAY, ChatColor.UNDERLINE) + .hover(appendSubCommandList(new RawText())) + .then(">").style(ChatColor.GRAY); + + return text.build(); + } + + private String getParents() { + StringBuilder builder = new StringBuilder(); + + CommandGroup group = commandGroup; + + do { + if (builder.length() > 0) { + builder.append(' '); + } + builder.append(group.getName()); + group = group.getParent(); + } while (group != null); + + return builder.toString(); + } + + private RawTextPart appendSubCommandList(RawTextPart text) { + boolean first = true; + text = text.then(I.t("One of the following:\n ")); + for (CommandNode subCommand : commandGroup.getSubCommands()) { + if (!first) { + text = text.then(", ").color(ChatColor.GRAY); + } + first = false; + + text = text.then(subCommand.getName()).color(ChatColor.AQUA); + } + + return text; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownArgumentTypeException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownArgumentTypeException.java new file mode 100644 index 00000000..bb0fc275 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownArgumentTypeException.java @@ -0,0 +1,15 @@ +package fr.zcraft.quartzlib.components.commands.exceptions; + +import java.lang.reflect.Method; + +public class UnknownArgumentTypeException extends RuntimeException { + public UnknownArgumentTypeException(Method method, Class foundType) { + super(getErrorMessage(method, foundType)); + } + + private static String getErrorMessage(Method method, Class foundType) { + return "Found unknown command argument type: '" + foundType + + "' (found in '" + method.toString() + "'). " + + "Did you forget to register it to the CommandManager?"; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/tools/text/RawMessage.java b/src/main/java/fr/zcraft/quartzlib/tools/text/RawMessage.java index 302338aa..73ba86f8 100644 --- a/src/main/java/fr/zcraft/quartzlib/tools/text/RawMessage.java +++ b/src/main/java/fr/zcraft/quartzlib/tools/text/RawMessage.java @@ -41,7 +41,7 @@ * Utility to send JSON messages. * *

This tool uses the /tellraw command to send the messages. If the JSON is not correctly - * formatted, the message will not be sent and a Runtime exception containing the exception throw by + * formatted, the message will not be sent and a Runtime exception containing the exception thrown by * the vanilla /tellraw command will be thrown.

*/ public final class RawMessage { diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandRegistrationTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandExecutionTests.java similarity index 93% rename from src/test/java/fr/zcraft/quartzlib/components/commands/CommandRegistrationTests.java rename to src/test/java/fr/zcraft/quartzlib/components/commands/CommandExecutionTests.java index 04adafde..c1dc1569 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandRegistrationTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandExecutionTests.java @@ -6,7 +6,7 @@ import org.junit.Before; import org.junit.Test; -public class CommandRegistrationTests extends MockedToasterTest { +public class CommandExecutionTests extends MockedToasterTest { private CommandManager commands; @Before diff --git a/src/test/java/fr/zcraft/ztoaster/ToastCommands.java b/ztoaster/src/main/java/fr/zcraft/ztoaster/ToastCommands.java similarity index 100% rename from src/test/java/fr/zcraft/ztoaster/ToastCommands.java rename to ztoaster/src/main/java/fr/zcraft/ztoaster/ToastCommands.java diff --git a/ztoaster/src/main/java/fr/zcraft/ztoaster/Toaster.java b/ztoaster/src/main/java/fr/zcraft/ztoaster/Toaster.java index 3c1d8df0..7b221da3 100644 --- a/ztoaster/src/main/java/fr/zcraft/ztoaster/Toaster.java +++ b/ztoaster/src/main/java/fr/zcraft/ztoaster/Toaster.java @@ -30,6 +30,7 @@ package fr.zcraft.ztoaster; +import fr.zcraft.quartzlib.components.commands.CommandManager; import fr.zcraft.quartzlib.components.gui.Gui; import fr.zcraft.quartzlib.components.i18n.I18n; import fr.zcraft.quartzlib.components.scoreboard.Sidebar; @@ -64,7 +65,8 @@ public class Toaster extends QuartzPlugin implements Listener { */ private Sidebar toasterSidebar; - public Toaster () {} + public Toaster() { + } protected Toaster(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { super(loader, description, dataFolder, file); @@ -72,6 +74,7 @@ protected Toaster(JavaPluginLoader loader, PluginDescriptionFile description, Fi /** * . + * * @return The id for a new toast. */ public static int newToastId() { @@ -80,6 +83,7 @@ public static int newToastId() { /** * . + * * @return an array of all the toasts ever created (until toaster restart). */ public static Toast[] getToasts() { @@ -106,7 +110,8 @@ public void onEnable() { loadComponents(Gui.class, ToasterWorker.class, SidebarScoreboard.class, I18n.class); - // Commands.register("toaster", AddCommand.class, OpenCommand.class, ListCommand.class); + new CommandManager() + .registerCommand("toaster", ToastCommands.class, ToastCommands::new); I18n.useDefaultPrimaryLocale(); I18n.setFallbackLocale(Locale.US); From 07a080a5cfbdac5d9ffdb03fd41e55fa996d71e0 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Thu, 17 Dec 2020 18:01:25 +0100 Subject: [PATCH 10/16] Added UnknownSubcommandException --- .../components/commands/CommandGroup.java | 9 +- .../UnknownSubcommandException.java | 95 +++++++++++++++++++ .../quartzlib/tools/text/StringUtils.java | 62 ++++++++++++ 3 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownSubcommandException.java create mode 100644 src/main/java/fr/zcraft/quartzlib/tools/text/StringUtils.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index a805d904..fe8fd16b 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -2,6 +2,7 @@ import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; import fr.zcraft.quartzlib.components.commands.exceptions.MissingSubcommandException; +import fr.zcraft.quartzlib.components.commands.exceptions.UnknownSubcommandException; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collection; @@ -97,9 +98,11 @@ private void runSelf(Object instance, CommandSender sender, String[] args) throw throw new MissingSubcommandException(this); } - String commandName = args[0]; - CommandNode subCommand = subCommands.get(commandName); - // TODO: handle null + CommandNode subCommand = subCommands.get(args[0]); + if (subCommand == null) { + throw new UnknownSubcommandException(this, args[0]); + } + subCommand.run(instance, sender, Arrays.copyOfRange(args, 1, args.length)); } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownSubcommandException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownSubcommandException.java new file mode 100644 index 00000000..7966c29f --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownSubcommandException.java @@ -0,0 +1,95 @@ +package fr.zcraft.quartzlib.components.commands.exceptions; + +import fr.zcraft.quartzlib.components.commands.CommandGroup; +import fr.zcraft.quartzlib.components.commands.CommandNode; +import fr.zcraft.quartzlib.components.i18n.I; +import fr.zcraft.quartzlib.components.rawtext.RawText; +import fr.zcraft.quartzlib.components.rawtext.RawTextPart; +import fr.zcraft.quartzlib.tools.text.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Nullable; + +public class UnknownSubcommandException extends CommandException { + public static final String CHAT_PREFIX = "┃ "; + private final CommandGroup commandGroup; + private final String attemptedSubcommand; + + public UnknownSubcommandException(CommandGroup commandGroup, String attemptedSubcommand) { + this.commandGroup = commandGroup; + this.attemptedSubcommand = attemptedSubcommand; + } + + @Override + public RawText display(CommandSender sender) { + RawTextPart text = new RawText(CHAT_PREFIX).color(ChatColor.DARK_RED) + .then(I.t("Unknown subcommand: ")).color(ChatColor.RED) + .then("/").color(ChatColor.WHITE) + .then(getParents() + " ").color(ChatColor.AQUA) + .then(attemptedSubcommand).style(ChatColor.RED, ChatColor.UNDERLINE, ChatColor.BOLD) + .hover(appendSubCommandList(new RawText())); + + String nearest = getNearestCommand(); + + if (nearest != null) { + text = text.then("\n" + CHAT_PREFIX).color(ChatColor.AQUA) + .then(" " + I.t("Did you mean") + ": ").color(ChatColor.GRAY) + .then("/").color(ChatColor.WHITE) + .then(getParents() + " ").color(ChatColor.AQUA) + .then(nearest).color(ChatColor.DARK_AQUA); + } + + return text.build(); + } + + private static final int MAX_DISTANCE = 10; + + @Nullable + private String getNearestCommand() { + String nearest = null; + int nearestDistance = MAX_DISTANCE; + + for (CommandNode subCommand : commandGroup.getSubCommands()) { + String name = subCommand.getName(); + int distance = StringUtils.levenshteinDistance(attemptedSubcommand, name); + + if (distance < nearestDistance) { + nearest = name; + nearestDistance = distance; + } + } + + return nearest; + } + + private String getParents() { + StringBuilder builder = new StringBuilder(); + + CommandGroup group = commandGroup; + + do { + if (builder.length() > 0) { + builder.append(' '); + } + builder.append(group.getName()); + group = group.getParent(); + } while (group != null); + + return builder.toString(); + } + + private RawTextPart appendSubCommandList(RawTextPart text) { + boolean first = true; + text = text.then(I.t("Should be one of the following:\n ")); + for (CommandNode subCommand : commandGroup.getSubCommands()) { + if (!first) { + text = text.then(", ").color(ChatColor.GRAY); + } + first = false; + + text = text.then(subCommand.getName()).color(ChatColor.AQUA); + } + + return text; + } +} diff --git a/src/main/java/fr/zcraft/quartzlib/tools/text/StringUtils.java b/src/main/java/fr/zcraft/quartzlib/tools/text/StringUtils.java new file mode 100644 index 00000000..b9ec3a17 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/tools/text/StringUtils.java @@ -0,0 +1,62 @@ +package fr.zcraft.quartzlib.tools.text; + +/** + * Various string-related utilities. + */ +public final class StringUtils { + private StringUtils() { + } + + /** + * Compute the distance of Levenshtein Distance between two strings. + * + *

Implementation is from: + * https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Java

+ * @param lhs The first string + * @param rhs The second string + * @return The distance between the two strings + */ + public static int levenshteinDistance(CharSequence lhs, CharSequence rhs) { + int len0 = lhs.length() + 1; + int len1 = rhs.length() + 1; + + // the array of distances + int[] cost = new int[len0]; + int[] newcost = new int[len0]; + + // initial cost of skipping prefix in String s0 + for (int i = 0; i < len0; i++) { + cost[i] = i; + } + + // dynamically computing the array of distances + + // transformation cost for each letter in s1 + for (int j = 1; j < len1; j++) { + // initial cost of skipping prefix in String s1 + newcost[0] = j; + + // transformation cost for each letter in s0 + for (int i = 1; i < len0; i++) { + // matching current letters in both strings + int match = (lhs.charAt(i - 1) == rhs.charAt(j - 1)) ? 0 : 1; + + // computing cost for each transformation + int costReplace = cost[i - 1] + match; + int costInsert = cost[i] + 1; + int costDelete = newcost[i - 1] + 1; + + // keep minimum cost + newcost[i] = Math.min(Math.min(costInsert, costDelete), costReplace); + } + + // swap cost/newcost arrays + int[] swap = cost; + cost = newcost; + newcost = swap; + } + + // the distance is the cost for transforming all letters in both strings + return cost[len0 - 1]; + } +} From a02314ed969c55d75fb87a82a4dc1efcbc00f355 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Fri, 18 Dec 2020 05:54:34 +0100 Subject: [PATCH 11/16] Improved UnknownSubcommandException, added StringUtils.levenshteinNearest utility method. --- .../UnknownSubcommandException.java | 30 +++++++++---------- .../quartzlib/tools/text/StringUtils.java | 29 ++++++++++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownSubcommandException.java b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownSubcommandException.java index 7966c29f..f417be01 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownSubcommandException.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/exceptions/UnknownSubcommandException.java @@ -6,6 +6,8 @@ import fr.zcraft.quartzlib.components.rawtext.RawText; import fr.zcraft.quartzlib.components.rawtext.RawTextPart; import fr.zcraft.quartzlib.tools.text.StringUtils; +import java.util.List; +import java.util.stream.Collectors; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.Nullable; @@ -33,10 +35,14 @@ public RawText display(CommandSender sender) { if (nearest != null) { text = text.then("\n" + CHAT_PREFIX).color(ChatColor.AQUA) - .then(" " + I.t("Did you mean") + ": ").color(ChatColor.GRAY) + .then(" " + I.t("Did you mean: ")).color(ChatColor.GRAY) .then("/").color(ChatColor.WHITE) - .then(getParents() + " ").color(ChatColor.AQUA) - .then(nearest).color(ChatColor.DARK_AQUA); + .then(getParents() + " ").style(ChatColor.AQUA, ChatColor.UNDERLINE) + .hover(I.t("Click to insert this command")) + .suggest("/" + getParents() + " " + nearest) + .then(nearest).style(ChatColor.DARK_AQUA, ChatColor.UNDERLINE) + .hover(I.t("Click to insert this command")) + .suggest("/" + getParents() + " " + nearest); } return text.build(); @@ -46,20 +52,12 @@ public RawText display(CommandSender sender) { @Nullable private String getNearestCommand() { - String nearest = null; - int nearestDistance = MAX_DISTANCE; + List names = commandGroup.getSubCommands() + .stream() + .map(CommandNode::getName) + .collect(Collectors.toList()); - for (CommandNode subCommand : commandGroup.getSubCommands()) { - String name = subCommand.getName(); - int distance = StringUtils.levenshteinDistance(attemptedSubcommand, name); - - if (distance < nearestDistance) { - nearest = name; - nearestDistance = distance; - } - } - - return nearest; + return StringUtils.levenshteinNearest(attemptedSubcommand, names, MAX_DISTANCE); } private String getParents() { diff --git a/src/main/java/fr/zcraft/quartzlib/tools/text/StringUtils.java b/src/main/java/fr/zcraft/quartzlib/tools/text/StringUtils.java index b9ec3a17..2475b3dc 100644 --- a/src/main/java/fr/zcraft/quartzlib/tools/text/StringUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/tools/text/StringUtils.java @@ -1,5 +1,7 @@ package fr.zcraft.quartzlib.tools.text; +import org.jetbrains.annotations.Nullable; + /** * Various string-related utilities. */ @@ -7,6 +9,33 @@ public final class StringUtils { private StringUtils() { } + /** + * Find the nearest from a given string among the given list of candidates, + * computed based on a Levenshtein Distance. + *

The string must be from at most a given maximum distance of all candidates, else null is returned.

+ * @param toTest The string to test. + * @param candidates The list of candidates. + * @param maxDistance The maximum distance. + * @param The type of CharSequence to test (usually String) + * @return The nearest candidate to the string to test, if found within maxDistance. + */ + @Nullable + public static T levenshteinNearest(T toTest, Iterable candidates, int maxDistance) { + T nearest = null; + int nearestDistance = maxDistance; + + for (T subCommand : candidates) { + int distance = StringUtils.levenshteinDistance(toTest, subCommand); + + if (distance < nearestDistance) { + nearest = subCommand; + nearestDistance = distance; + } + } + + return nearest; + } + /** * Compute the distance of Levenshtein Distance between two strings. * From bfc151f5a7a26e14593503a971c730c331bc016b Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Fri, 18 Dec 2020 06:43:00 +0100 Subject: [PATCH 12/16] Added tests for StringUtils --- .../tools/text/StringUtilsTests.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/test/java/fr/zcraft/quartzlib/tools/text/StringUtilsTests.java diff --git a/src/test/java/fr/zcraft/quartzlib/tools/text/StringUtilsTests.java b/src/test/java/fr/zcraft/quartzlib/tools/text/StringUtilsTests.java new file mode 100644 index 00000000..0456389b --- /dev/null +++ b/src/test/java/fr/zcraft/quartzlib/tools/text/StringUtilsTests.java @@ -0,0 +1,29 @@ +package fr.zcraft.quartzlib.tools.text; + +import java.util.Arrays; +import java.util.List; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; + +public class StringUtilsTests { + @Test + public void canComputeLevenshteinDistance() { + Assertions.assertEquals(0, StringUtils.levenshteinDistance("foo", "foo")); + Assertions.assertEquals(1, StringUtils.levenshteinDistance("fooa", "foo")); + Assertions.assertEquals(1, StringUtils.levenshteinDistance("foo", "fooa")); + Assertions.assertEquals(1, StringUtils.levenshteinDistance("fao", "foo")); + Assertions.assertEquals(6, StringUtils.levenshteinDistance("fooaaaaaa", "foo")); + Assertions.assertEquals(2, StringUtils.levenshteinDistance("f", "foo")); + Assertions.assertEquals(3, StringUtils.levenshteinDistance("a", "foo")); + } + + @Test + public void canFindLevenshteinNearest() { + List candidates = Arrays.asList("add", "list", "open"); + + Assertions.assertEquals("add", StringUtils.levenshteinNearest("foo", candidates, 10)); + Assertions.assertEquals("add", StringUtils.levenshteinNearest("adf", candidates, 10)); + Assertions.assertEquals("open", StringUtils.levenshteinNearest("openn", candidates, 10)); + Assertions.assertNull(StringUtils.levenshteinNearest("kkkkkkkkkkkkkkkk", candidates, 10)); + } +} From c2a72c381b305983b31a016747b83dd5907f0719 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Fri, 18 Dec 2020 08:24:32 +0100 Subject: [PATCH 13/16] Add basic support for overriden methods --- .../components/commands/CommandEndpoint.java | 8 ++++++- .../components/commands/CommandMethod.java | 16 +++++++------ .../commands/CommandGraphTests.java | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index 9ff6a167..cfad4eab 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.Nullable; class CommandEndpoint extends CommandNode { private final List methods = new ArrayList<>(); @@ -14,7 +15,12 @@ class CommandEndpoint extends CommandNode { @Override void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException { - this.methods.get(0).run(parentInstance, sender, args); + CommandMethod method = findMatchingMethod(args); + method.run(parentInstance, sender, args); + } + + @Nullable CommandMethod findMatchingMethod(String[] args) { + return this.methods.stream().filter(m -> m.getArguments().length == args.length).findFirst().orElse(null); } void addMethod(CommandMethod method) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java index 6f0e52f9..d6f2abca 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -10,13 +10,15 @@ import java.util.ArrayList; import java.util.List; import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; class CommandMethod { - private final Method method; - private final String name; - private final CommandMethodArgument[] arguments; + @NotNull private final Method method; + @NotNull private final String name; + @NotNull private final CommandMethodArgument[] arguments; private final int parameterCount; - private CommandMethodSenderArgument senderArgument = null; + @Nullable private CommandMethodSenderArgument senderArgument = null; CommandMethod(Method method, TypeCollection typeCollection) { this.method = method; @@ -37,7 +39,7 @@ class CommandMethod { this.parameterCount = parameters.length; } - public String getName() { + public @NotNull String getName() { return name; } @@ -66,11 +68,11 @@ private Object[] parseArguments(CommandSender sender, String[] args) return parsed; } - public CommandMethodArgument[] getArguments() { + public @NotNull CommandMethodArgument[] getArguments() { return arguments; } - public Method getMethod() { + public @NotNull Method getMethod() { return method; } } diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index 1462f251..5d069f91 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -160,6 +160,29 @@ public void add() { Assert.assertArrayEquals(new boolean[] {true}, ran); } + @Test + public void canHandleOverrides() throws CommandException { + final String[] argValue = {""}; + + class FooCommand { + public void add(String arg) { + argValue[0] = arg; + } + + public void add() { + argValue[0] = "bar"; + } + } + + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); + + commands.run(server.addPlayer(), "foo", "add", "pomf"); + Assert.assertArrayEquals(new String[] {"pomf"}, argValue); + + commands.run(server.addPlayer(), "foo", "add"); + Assert.assertArrayEquals(new String[] {"bar"}, argValue); + } + enum FooEnum { FOO, BAR } From fe71502ff08a696f70c6fefe4dcf89b24130ba3c Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Sat, 19 Dec 2020 03:22:15 +0100 Subject: [PATCH 14/16] Add basic support for overriden methods with same argument counts (but different types) --- .../components/commands/ArgumentType.java | 9 ++++++++ .../components/commands/CommandEndpoint.java | 20 ++++++++++++---- .../components/commands/CommandMethod.java | 7 +++--- .../primitive/IntegerArgumentType.java | 13 +++++++++-- .../commands/CommandGraphTests.java | 23 +++++++++++++++++++ 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java index f97c2c0c..636ef090 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/ArgumentType.java @@ -5,4 +5,13 @@ @FunctionalInterface public interface ArgumentType { T parse(String raw) throws ArgumentParseException; + + default boolean isValid(String raw) { + try { + parse(raw); + return true; + } catch (ArgumentParseException ignored) { + return false; + } + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index cfad4eab..5de99407 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -15,12 +15,22 @@ class CommandEndpoint extends CommandNode { @Override void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException { - CommandMethod method = findMatchingMethod(args); - method.run(parentInstance, sender, args); - } + for (CommandMethod method : this.methods) { + if (method.getArguments().length != args.length) { + continue; // TODO + } + + try { + Object[] parsedArgs; + parsedArgs = method.parseArguments(sender, args); + method.run(parentInstance, parsedArgs); + return; + } catch (CommandException ignored) { // TODO + + } + } - @Nullable CommandMethod findMatchingMethod(String[] args) { - return this.methods.stream().filter(m -> m.getArguments().length == args.length).findFirst().orElse(null); + throw new RuntimeException("No matching command found"); // TODO } void addMethod(CommandMethod method) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java index d6f2abca..2a85e9a8 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -43,16 +43,15 @@ class CommandMethod { return name; } - public void run(Object target, CommandSender sender, String[] args) throws CommandException { - Object[] parsedArgs = parseArguments(sender, args); + public void run(Object target, Object[] parsedArgs) throws CommandException { try { this.method.invoke(target, parsedArgs); } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); // TODO + throw new RuntimeException(e); // TODO } } - private Object[] parseArguments(CommandSender sender, String[] args) + public Object[] parseArguments(CommandSender sender, String[] args) throws ArgumentParseException, InvalidSenderException { Object[] parsed = new Object[parameterCount]; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java index 8d2a2c57..a01fa989 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/arguments/primitive/IntegerArgumentType.java @@ -1,10 +1,19 @@ package fr.zcraft.quartzlib.components.commands.arguments.primitive; import fr.zcraft.quartzlib.components.commands.ArgumentType; +import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; public class IntegerArgumentType implements ArgumentType { @Override - public Integer parse(String raw) { - return Integer.parseInt(raw, 10); // TODO: handle exceptions + public Integer parse(String raw) throws ArgumentParseException { + try { + return Integer.parseInt(raw, 10); + } catch (NumberFormatException ignored) { + throw new IntegerParseException(); + } + } + + private static class IntegerParseException extends ArgumentParseException { + } } diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index 5d069f91..502c2f03 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -183,6 +183,29 @@ public void add() { Assert.assertArrayEquals(new String[] {"bar"}, argValue); } + @Test + public void canHandleOverridesWithSameArgumentCount() throws CommandException { + final Object[] argValue = {null}; + + class FooCommand { + public void add(Integer arg) { + argValue[0] = arg; + } + + public void add(String arg) { + argValue[0] = arg; + } + } + + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); + + commands.run(server.addPlayer(), "foo", "add", "pomf"); + Assert.assertArrayEquals(new Object[] {"pomf"}, argValue); + + commands.run(server.addPlayer(), "foo", "add", "42"); + Assert.assertArrayEquals(new Object[] {42}, argValue); + } + enum FooEnum { FOO, BAR } From 5fa3fb926220235c5ca3d18aea49c119eb1d21b1 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Sat, 19 Dec 2020 07:33:48 +0100 Subject: [PATCH 15/16] Can now handle priorities in method overrides --- .../components/commands/CommandEndpoint.java | 7 ++- .../components/commands/CommandMethod.java | 34 ++++++++++++-- .../components/commands/DiscoveryUtils.java | 8 +++- .../commands/annotations/CommandMethod.java | 12 +++++ .../{attributes => annotations}/Sender.java | 2 +- .../SubCommand.java | 2 +- .../commands/CommandGraphTests.java | 47 ++++++++++++++++++- .../fr/zcraft/ztoaster/ToastCommands.java | 2 +- 8 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/annotations/CommandMethod.java rename src/main/java/fr/zcraft/quartzlib/components/commands/{attributes => annotations}/Sender.java (81%) rename src/main/java/fr/zcraft/quartzlib/components/commands/{attributes => annotations}/SubCommand.java (82%) diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index 5de99407..9a1f0bca 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -1,13 +1,12 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; -import java.util.ArrayList; -import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.Nullable; class CommandEndpoint extends CommandNode { - private final List methods = new ArrayList<>(); + private final SortedSet methods = new TreeSet<>(); CommandEndpoint(String name) { super(name, null); diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java index 2a85e9a8..8d7f05a9 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -1,6 +1,6 @@ package fr.zcraft.quartzlib.components.commands; -import fr.zcraft.quartzlib.components.commands.attributes.Sender; +import fr.zcraft.quartzlib.components.commands.annotations.Sender; import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; import fr.zcraft.quartzlib.components.commands.exceptions.InvalidSenderException; @@ -13,16 +13,20 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -class CommandMethod { +class CommandMethod implements Comparable { @NotNull private final Method method; @NotNull private final String name; @NotNull private final CommandMethodArgument[] arguments; private final int parameterCount; @Nullable private CommandMethodSenderArgument senderArgument = null; - CommandMethod(Method method, TypeCollection typeCollection) { + private final int declarationIndex; + private final int priority; + + CommandMethod(Method method, TypeCollection typeCollection, int declarationIndex) { this.method = method; this.name = method.getName(); + this.declarationIndex = declarationIndex; Parameter[] parameters = method.getParameters(); List arguments = new ArrayList<>(); @@ -37,6 +41,15 @@ class CommandMethod { this.arguments = arguments.toArray(new CommandMethodArgument[] {}); this.parameterCount = parameters.length; + + fr.zcraft.quartzlib.components.commands.annotations.CommandMethod annotation = + method.getAnnotation(fr.zcraft.quartzlib.components.commands.annotations.CommandMethod.class); + + if (annotation != null) { + priority = annotation.priority(); + } else { + priority = 0; + } } public @NotNull String getName() { @@ -74,4 +87,19 @@ public Object[] parseArguments(CommandSender sender, String[] args) public @NotNull Method getMethod() { return method; } + + @Override + public int compareTo(@NotNull CommandMethod other) { + if (priority == other.priority) { + if (declarationIndex == other.declarationIndex) { + // This is needed to differentiate between methods with the same declaration index and priority + return method.toString().compareTo(other.method.toString()); + } + + return Integer.compare(declarationIndex, other.declarationIndex); + } + + // Higher priority = first in natural order + return Integer.compare(priority, other.priority) * -1; + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java index a2fc7424..3d2b5d2f 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java @@ -1,20 +1,24 @@ package fr.zcraft.quartzlib.components.commands; -import fr.zcraft.quartzlib.components.commands.attributes.SubCommand; +import fr.zcraft.quartzlib.components.commands.annotations.SubCommand; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Stream; abstract class DiscoveryUtils { public static Stream getCommandMethods(Class commandGroupClass, TypeCollection typeCollection) { + // Yay java "lambdas" + AtomicInteger declarationIndex = new AtomicInteger(); + return Arrays.stream(commandGroupClass.getDeclaredMethods()) .filter(m -> hasRunnableModifiers(m.getModifiers())) - .map((Method method) -> new CommandMethod(method, typeCollection)); + .map((Method method) -> new CommandMethod(method, typeCollection, declarationIndex.getAndIncrement())); } public static Stream getSubCommands(CommandGroup commandGroup, TypeCollection typeCollection) { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/CommandMethod.java new file mode 100644 index 00000000..d5dd5930 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/CommandMethod.java @@ -0,0 +1,12 @@ +package fr.zcraft.quartzlib.components.commands.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface CommandMethod { + int priority() default 0; +} diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java b/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/Sender.java similarity index 81% rename from src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/annotations/Sender.java index 4f4f66ef..f3b2a95b 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/Sender.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/Sender.java @@ -1,4 +1,4 @@ -package fr.zcraft.quartzlib.components.commands.attributes; +package fr.zcraft.quartzlib.components.commands.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/SubCommand.java b/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/SubCommand.java similarity index 82% rename from src/main/java/fr/zcraft/quartzlib/components/commands/attributes/SubCommand.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/annotations/SubCommand.java index cb38f922..bbdb490f 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/attributes/SubCommand.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/SubCommand.java @@ -1,4 +1,4 @@ -package fr.zcraft.quartzlib.components.commands.attributes; +package fr.zcraft.quartzlib.components.commands.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index 502c2f03..a3677363 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -1,8 +1,9 @@ package fr.zcraft.quartzlib.components.commands; import fr.zcraft.quartzlib.MockedBukkitTest; -import fr.zcraft.quartzlib.components.commands.attributes.Sender; -import fr.zcraft.quartzlib.components.commands.attributes.SubCommand; +import fr.zcraft.quartzlib.components.commands.annotations.CommandMethod; +import fr.zcraft.quartzlib.components.commands.annotations.Sender; +import fr.zcraft.quartzlib.components.commands.annotations.SubCommand; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; import java.util.stream.StreamSupport; import org.bukkit.command.CommandSender; @@ -206,6 +207,48 @@ public void add(String arg) { Assert.assertArrayEquals(new Object[] {42}, argValue); } + @Test + public void canHandleOverridesWithPriorities() throws CommandException { + final Object[] argValue = {null}; + + class FooCommand { + public void add(String arg) { + argValue[0] = arg; + } + + public void add(Integer arg) { + argValue[0] = arg; + } + } + + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); + + commands.run(server.addPlayer(), "foo", "add", "pomf"); + Assert.assertArrayEquals(new Object[] {"pomf"}, argValue); + + commands.run(server.addPlayer(), "foo", "add", "42"); + Assert.assertArrayEquals(new Object[] {"42"}, argValue); + + class FooCommand2 { + public void add(String arg) { + argValue[0] = arg; + } + + @CommandMethod(priority = 2) + public void add(Integer arg) { + argValue[0] = arg; + } + } + + commands.addCommand("foo", FooCommand2.class, () -> new FooCommand2()); + + commands.run(server.addPlayer(), "foo", "add", "pomf"); + Assert.assertArrayEquals(new Object[] {"pomf"}, argValue); + + commands.run(server.addPlayer(), "foo", "add", "42"); + Assert.assertArrayEquals(new Object[] {42}, argValue); + } + enum FooEnum { FOO, BAR } diff --git a/ztoaster/src/main/java/fr/zcraft/ztoaster/ToastCommands.java b/ztoaster/src/main/java/fr/zcraft/ztoaster/ToastCommands.java index 773ae30e..d38baea1 100644 --- a/ztoaster/src/main/java/fr/zcraft/ztoaster/ToastCommands.java +++ b/ztoaster/src/main/java/fr/zcraft/ztoaster/ToastCommands.java @@ -1,6 +1,6 @@ package fr.zcraft.ztoaster; -import fr.zcraft.quartzlib.components.commands.attributes.Sender; +import fr.zcraft.quartzlib.components.commands.annotations.Sender; import fr.zcraft.quartzlib.components.gui.Gui; import fr.zcraft.quartzlib.components.i18n.I; import java.util.ArrayList; From b923aefe42415df65df219e42ec848c41f5dcc36 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz Date: Sat, 19 Dec 2020 12:15:35 +0100 Subject: [PATCH 16/16] Can now get parameter names, or generate them as a fallback --- .../components/commands/CommandEndpoint.java | 7 +++- .../components/commands/CommandGroup.java | 4 +++ .../components/commands/CommandManager.java | 9 +++-- .../components/commands/CommandMethod.java | 36 +++++++++---------- ...ument.java => CommandMethodParameter.java} | 26 ++++++++++++-- .../components/commands/DiscoveryUtils.java | 30 +++++++++++++++- .../commands/annotations/Param.java | 12 +++++++ .../commands/CommandExecutionTests.java | 3 +- .../commands/CommandGraphTests.java | 22 ++++++++++++ .../commands/DiscoveryUtilsTests.java | 32 +++++++++++++++++ 10 files changed, 155 insertions(+), 26 deletions(-) rename src/main/java/fr/zcraft/quartzlib/components/commands/{CommandMethodArgument.java => CommandMethodParameter.java} (56%) create mode 100644 src/main/java/fr/zcraft/quartzlib/components/commands/annotations/Param.java create mode 100644 src/test/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtilsTests.java diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java index 9a1f0bca..b400c4f4 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandEndpoint.java @@ -4,6 +4,7 @@ import java.util.SortedSet; import java.util.TreeSet; import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; class CommandEndpoint extends CommandNode { private final SortedSet methods = new TreeSet<>(); @@ -15,7 +16,7 @@ class CommandEndpoint extends CommandNode { @Override void run(Object parentInstance, CommandSender sender, String[] args) throws CommandException { for (CommandMethod method : this.methods) { - if (method.getArguments().length != args.length) { + if (method.getParameters().length != args.length) { continue; // TODO } @@ -35,4 +36,8 @@ void run(Object parentInstance, CommandSender sender, String[] args) throws Comm void addMethod(CommandMethod method) { methods.add(method); } + + @NotNull public Iterable getMethods() { + return methods; + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java index fe8fd16b..f3ac7a45 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandGroup.java @@ -59,6 +59,10 @@ public Collection getSubCommands() { return this.subCommands.values(); } + @Nullable public CommandNode getSubCommand(String subCommandName) { + return this.subCommands.get(subCommandName); + } + private void addMethod(CommandMethod method) { // TODO: handle adding to non-endpoints diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java index 6e7933f9..f48c7a4f 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandManager.java @@ -8,9 +8,10 @@ import java.util.function.Supplier; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; +import org.jetbrains.annotations.Nullable; public class CommandManager { - private final Map rootCommands = new HashMap<>(); + private final Map rootCommands = new HashMap<>(); private final TypeCollection typeCollection = new TypeCollection(); public void addCommand(String name, Class commandType, Supplier commandClassSupplier) { @@ -31,6 +32,10 @@ private void registerCommand(CommandGroup group) { } public void run(CommandSender sender, String commandName, String... args) throws CommandException { - ((CommandGroup) rootCommands.get(commandName)).run(sender, args); // TODO + rootCommands.get(commandName).run(sender, args); // TODO + } + + @Nullable public CommandGroup getCommand(String commandName) { + return rootCommands.get(commandName); } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java index 8d7f05a9..eeeb0816 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethod.java @@ -13,12 +13,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -class CommandMethod implements Comparable { +public class CommandMethod implements Comparable { @NotNull private final Method method; @NotNull private final String name; - @NotNull private final CommandMethodArgument[] arguments; + @NotNull private final CommandMethodParameter[] parameters; private final int parameterCount; - @Nullable private CommandMethodSenderArgument senderArgument = null; + @Nullable private CommandMethodSenderArgument senderParameter = null; private final int declarationIndex; private final int priority; @@ -28,19 +28,19 @@ class CommandMethod implements Comparable { this.name = method.getName(); this.declarationIndex = declarationIndex; - Parameter[] parameters = method.getParameters(); - List arguments = new ArrayList<>(); - for (int i = 0; i < parameters.length; i++) { - Parameter parameter = parameters[i]; - if (parameter.isAnnotationPresent(Sender.class)) { // TODO: check for multiple sender arguments - senderArgument = new CommandMethodSenderArgument(parameter, i, typeCollection); + Parameter[] javaParameters = method.getParameters(); + List parameters = new ArrayList<>(); + for (int i = 0; i < javaParameters.length; i++) { + Parameter parameter = javaParameters[i]; + if (parameter.isAnnotationPresent(Sender.class)) { // TODO: check for multiple sender parameters + senderParameter = new CommandMethodSenderArgument(parameter, i, typeCollection); } else { - arguments.add(new CommandMethodArgument(this, parameter, i, typeCollection)); + parameters.add(new CommandMethodParameter(this, parameter, i, typeCollection)); } } - this.arguments = arguments.toArray(new CommandMethodArgument[] {}); - this.parameterCount = parameters.length; + this.parameters = parameters.toArray(new CommandMethodParameter[] {}); + this.parameterCount = javaParameters.length; fr.zcraft.quartzlib.components.commands.annotations.CommandMethod annotation = method.getAnnotation(fr.zcraft.quartzlib.components.commands.annotations.CommandMethod.class); @@ -68,20 +68,20 @@ public Object[] parseArguments(CommandSender sender, String[] args) throws ArgumentParseException, InvalidSenderException { Object[] parsed = new Object[parameterCount]; - for (int i = 0; i < arguments.length; i++) { - CommandMethodArgument argument = arguments[i]; + for (int i = 0; i < parameters.length; i++) { + CommandMethodParameter argument = parameters[i]; parsed[argument.getPosition()] = argument.parse(args[i]); } - if (this.senderArgument != null) { - parsed[this.senderArgument.getPosition()] = this.senderArgument.parse(sender); + if (this.senderParameter != null) { + parsed[this.senderParameter.getPosition()] = this.senderParameter.parse(sender); } return parsed; } - public @NotNull CommandMethodArgument[] getArguments() { - return arguments; + public @NotNull CommandMethodParameter[] getParameters() { + return parameters; } public @NotNull Method getMethod() { diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodParameter.java similarity index 56% rename from src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java rename to src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodParameter.java index d9c35483..53d2d4f1 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodArgument.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/CommandMethodParameter.java @@ -1,15 +1,19 @@ package fr.zcraft.quartzlib.components.commands; +import fr.zcraft.quartzlib.components.commands.annotations.Param; import fr.zcraft.quartzlib.components.commands.exceptions.ArgumentParseException; import fr.zcraft.quartzlib.components.commands.exceptions.UnknownArgumentTypeException; +import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import org.jetbrains.annotations.NotNull; -public class CommandMethodArgument { +public class CommandMethodParameter { private final Parameter parameter; private final int position; private final ArgumentTypeWrapper typeHandler; + private final String name; - public CommandMethodArgument( + public CommandMethodParameter( CommandMethod parent, Parameter parameter, int position, @@ -19,6 +23,7 @@ public CommandMethodArgument( this.position = position; this.typeHandler = typeCollection.findArgumentType(parameter.getType()) .orElseThrow(() -> new UnknownArgumentTypeException(parent.getMethod(), parameter.getType())); + this.name = findName(parent.getMethod(), parameter); } public Object parse(String raw) throws ArgumentParseException { @@ -28,4 +33,21 @@ public Object parse(String raw) throws ArgumentParseException { public int getPosition() { return position; } + + @NotNull public String getName() { + return name; + } + + private static String findName(Method declaringMethod, Parameter param) { + Param annotation = param.getAnnotation(Param.class); + if (annotation != null) { + return annotation.value(); + } + + if (param.isNamePresent()) { + return param.getName(); + } + + return DiscoveryUtils.generateArgumentName(declaringMethod, param); + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java index 3d2b5d2f..9cfeeedc 100644 --- a/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtils.java @@ -6,15 +6,17 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import java.util.stream.IntStream; import java.util.stream.Stream; abstract class DiscoveryUtils { public static Stream getCommandMethods(Class commandGroupClass, TypeCollection typeCollection) { // Yay java "lambdas" - AtomicInteger declarationIndex = new AtomicInteger(); + AtomicInteger declarationIndex = new AtomicInteger(0); return Arrays.stream(commandGroupClass.getDeclaredMethods()) .filter(m -> hasRunnableModifiers(m.getModifiers())) @@ -46,4 +48,30 @@ public static Supplier getClassConstructorSupplier(Class commandGroupClass private static boolean hasRunnableModifiers(int modifiers) { return Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers); } + + public static String generateArgumentName(Method declaringMethod, Parameter parameter) { + String parameterTypeName = generateArgumentName(parameter.getType()); + Parameter[] parametersWithSameTypeName = Arrays.stream(declaringMethod.getParameters()) + .filter(p -> parameterTypeName.equals(generateArgumentName(p.getType()))) + .toArray(Parameter[]::new); + + if (parametersWithSameTypeName.length <= 1) { + return parameterTypeName; + } + + int index = IntStream.range(0, parametersWithSameTypeName.length) + .filter(i -> parameter == parametersWithSameTypeName[i]) + .findFirst() // first occurrence + .orElse(-1) + 1; + + return parameterTypeName + index; + } + + private static String generateArgumentName(Class type) { + String parameterTypeName = type.getSimpleName(); + if ("".equals(parameterTypeName)) { + return "arg"; + } + return Character.toLowerCase(parameterTypeName.charAt(0)) + parameterTypeName.substring(1); + } } diff --git a/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/Param.java b/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/Param.java new file mode 100644 index 00000000..ab379ee7 --- /dev/null +++ b/src/main/java/fr/zcraft/quartzlib/components/commands/annotations/Param.java @@ -0,0 +1,12 @@ +package fr.zcraft.quartzlib.components.commands.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface Param { + String value(); +} diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandExecutionTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandExecutionTests.java index c1dc1569..86cd9c3b 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandExecutionTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandExecutionTests.java @@ -15,8 +15,7 @@ public void beforeEach() { } @Test - public void canRegisterAndRunCommand() throws CommandException { - + public void canRegisterAndRunCommand() { final boolean[] ran = {false}; class FooCommand { diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java index a3677363..bb9fb28e 100644 --- a/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/CommandGraphTests.java @@ -2,9 +2,11 @@ import fr.zcraft.quartzlib.MockedBukkitTest; import fr.zcraft.quartzlib.components.commands.annotations.CommandMethod; +import fr.zcraft.quartzlib.components.commands.annotations.Param; import fr.zcraft.quartzlib.components.commands.annotations.Sender; import fr.zcraft.quartzlib.components.commands.annotations.SubCommand; import fr.zcraft.quartzlib.components.commands.exceptions.CommandException; +import java.util.Objects; import java.util.stream.StreamSupport; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -252,4 +254,24 @@ public void add(Integer arg) { enum FooEnum { FOO, BAR } + + @Test + public void canSpecifyParam() throws CommandException { + class FooCommand { + public void add(String arg) { + } + + public void list(@Param("myInt") Integer arg) { + } + } + + commands.addCommand("foo", FooCommand.class, () -> new FooCommand()); + CommandGroup fooCommand = Objects.requireNonNull(commands.getCommand("foo")); + + CommandEndpoint addCommand = (CommandEndpoint) Objects.requireNonNull(fooCommand.getSubCommand("add")); + Assert.assertEquals("string", addCommand.getMethods().iterator().next().getParameters()[0].getName()); + + CommandEndpoint listCommand = (CommandEndpoint) Objects.requireNonNull(fooCommand.getSubCommand("list")); + Assert.assertEquals("myInt", listCommand.getMethods().iterator().next().getParameters()[0].getName()); + } } diff --git a/src/test/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtilsTests.java b/src/test/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtilsTests.java new file mode 100644 index 00000000..7f21e3ef --- /dev/null +++ b/src/test/java/fr/zcraft/quartzlib/components/commands/DiscoveryUtilsTests.java @@ -0,0 +1,32 @@ +package fr.zcraft.quartzlib.components.commands; + +import java.lang.reflect.Method; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class DiscoveryUtilsTests { + @Test + public void canGenerateArgumentNames() throws NoSuchMethodException { + class Foo { + public void add(int foo1, String foo2) { + + } + + public void add(String foo1, String foo2) { + + } + } + + Method addMethod = Foo.class.getMethod("add", int.class, String.class); + Assertions.assertEquals("int", + DiscoveryUtils.generateArgumentName(addMethod, addMethod.getParameters()[0])); + Assertions.assertEquals("string", + DiscoveryUtils.generateArgumentName(addMethod, addMethod.getParameters()[1])); + + Method add2Method = Foo.class.getMethod("add", String.class, String.class); + Assertions.assertEquals("string1", + DiscoveryUtils.generateArgumentName(add2Method, add2Method.getParameters()[0])); + Assertions.assertEquals("string2", + DiscoveryUtils.generateArgumentName(add2Method, add2Method.getParameters()[1])); + } +}