diff --git a/paper-api/src/main/java/org/bukkit/command/Command.java b/paper-api/src/main/java/org/bukkit/command/Command.java index f11655b813..5d5e2b89f5 100644 --- a/paper-api/src/main/java/org/bukkit/command/Command.java +++ b/paper-api/src/main/java/org/bukkit/command/Command.java @@ -8,14 +8,26 @@ import java.util.List; */ public abstract class Command { private final String name; + private String nextLabel; + private String label; private List aliases; + private List activeAliases; + private CommandMap commandMap = null; protected String description = ""; protected String usageMessage; protected Command(String name) { + this(name, "", "/" + name, new ArrayList()); + } + + protected Command(String name, String description, String usageMessage, List aliases) { this.name = name; - this.aliases = new ArrayList(); - this.usageMessage = "/" + name; + this.nextLabel = name; + this.label = name; + this.description = description; + this.usageMessage = usageMessage; + this.aliases = aliases; + this.activeAliases = new ArrayList(aliases); } /** @@ -38,12 +50,84 @@ public abstract class Command { } /** - * Returns a list of aliases registered to this command + * Returns the current lable for this command + * + * @return Label of this command or null if not registered + */ + public String getLabel() { + return label; + } + + /** + * Sets the label of this command + * If the command is currently registered the label change will only take effect after + * its been reregistered e.g. after a /reload + * + * @return returns true if the name change happened instantly or false if it was scheduled for reregistration + */ + public boolean setLabel(String name) { + this.nextLabel = name; + if (!isRegistered()) { + this.label = name; + return true; + } + return false; + } + + /** + * Registers this command to a CommandMap + * Once called it only allows changes the registered CommandMap + * + * @param commandMap the CommandMap to register this command to + * @return true if the registration was successful (the current registered CommandMap was the passed CommandMap or null) false otherwise + */ + public boolean register(CommandMap commandMap) { + if (allowChangesFrom(commandMap)) { + this.commandMap = commandMap; + return true; + } + + return false; + } + + /** + * Unregisters this command from the passed CommandMap applying any outstanding changes + * + * @param commandMap the CommandMap to unregister + * @return true if the unregistration was successfull (the current registered CommandMap was the passed CommandMap or null) false otherwise + */ + public boolean unregister(CommandMap commandMap) { + if (allowChangesFrom(commandMap)) { + this.commandMap = null; + this.activeAliases = new ArrayList(this.aliases); + this.label = this.nextLabel; + return true; + } + + return false; + } + + + private boolean allowChangesFrom(CommandMap commandMap) { + return (null == this.commandMap || this.commandMap == commandMap); + } + + /** + * Returns the current registered state of this command + * + * @return true if this command is currently registered false otherwise + */ + public boolean isRegistered() { + return (null != this.commandMap); + } + + /** + * Returns a list of active aliases of this command * * @return List of aliases */ public List getAliases() { - return aliases; + return activeAliases; } /** @@ -65,13 +149,16 @@ public abstract class Command { } /** - * Sets the list of aliases registered to this command + * Sets the list of aliases to request on registration for this command * * @param aliases Aliases to register to this command * @return This command object, for linking */ public Command setAliases(List aliases) { this.aliases = aliases; + if (!isRegistered()) { + this.activeAliases = new ArrayList(aliases); + } return this; } diff --git a/paper-api/src/main/java/org/bukkit/command/CommandMap.java b/paper-api/src/main/java/org/bukkit/command/CommandMap.java index e5a50467ca..079910c860 100644 --- a/paper-api/src/main/java/org/bukkit/command/CommandMap.java +++ b/paper-api/src/main/java/org/bukkit/command/CommandMap.java @@ -6,19 +6,40 @@ public interface CommandMap { /** * Registers all the commands belonging to a certain plugin. - * @param plugin - * @return + * Caller can use:- + * command.getName() to determine the label registered for this command + * command.getAliases() to determine the aliases which where registered + * + * @param fallbackPrefix a prefix which is prepended to each command with a ':' one or more times to make the command unique + * @param commands a list of commands to register */ public void registerAll(String fallbackPrefix, List commands); /** * Registers a command. Returns true on success; false if name is already taken and fallback had to be used. + * Caller can use:- + * command.getName() to determine the label registered for this command + * command.getAliases() to determine the aliases which where registered * - * @param a label for this command, without the '/'-prefix. - * @return Returns true if command was registered; false if label was already in use. + * @param label the label of the command, without the '/'-prefix. + * @param fallbackPrefix a prefix which is prepended to the command with a ':' one or more times to make the command unique + * @param command the command to register + * @return true if command was registered with the passed in label, false otherwise, which indicates the fallbackPrefix was used one or more times */ public boolean register(String label, String fallbackPrefix, Command command); + /** + * Registers a command. Returns true on success; false if name is already taken and fallback had to be used. + * Caller can use:- + * command.getName() to determine the label registered for this command + * command.getAliases() to determine the aliases which where registered + * + * @param fallbackPrefix a prefix which is prepended to the command with a ':' one or more times to make the command unique + * @param command the command to register, from which label is determined from the command name + * @return true if command was registered with the passed in label, false otherwise, which indicates the fallbackPrefix was used one or more times + */ + public boolean register(String fallbackPrefix, Command command); + /** * Looks for the requested command and executes it if found. * @@ -26,7 +47,7 @@ public interface CommandMap { * @return targetFound returns false if no target is found. * @throws CommandException Thrown when the executor for the given command fails with an unhandled exception */ - public boolean dispatch(CommandSender sender, String cmdLine); + public boolean dispatch(CommandSender sender, String cmdLine) throws CommandException; /** * Clears all registered commands. @@ -37,7 +58,7 @@ public interface CommandMap { * Gets the command registered to the specified name * * @param name Name of the command to retrieve - * @return Command with the specified name + * @return Command with the specified name or null if a command with that label doesn't exist */ public Command getCommand(String name); } diff --git a/paper-api/src/main/java/org/bukkit/command/SimpleCommandMap.java b/paper-api/src/main/java/org/bukkit/command/SimpleCommandMap.java index 77391c7593..472716b4e2 100644 --- a/paper-api/src/main/java/org/bukkit/command/SimpleCommandMap.java +++ b/paper-api/src/main/java/org/bukkit/command/SimpleCommandMap.java @@ -7,6 +7,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Iterator; import org.bukkit.ChatColor; import org.bukkit.Server; @@ -32,9 +33,7 @@ public final class SimpleCommandMap implements CommandMap { } /** - * Registers multiple commands. Returns name of first command for which fallback had to be used if any. - * @param plugin - * @return + * {@inheritDoc} */ public void registerAll(String fallbackPrefix, List commands) { if (commands != null) { @@ -44,41 +43,74 @@ public final class SimpleCommandMap implements CommandMap { } } - private void register(String fallbackPrefix, Command command) { - register(command.getName(), fallbackPrefix, command); - aliases.addAll(command.getAliases()); - aliases.remove(command.getName()); - - for (String name : command.getAliases()) { - register(name, fallbackPrefix, command); - } + /** + * {@inheritDoc} + */ + public boolean register(String fallbackPrefix, Command command) { + return register(command.getName(), fallbackPrefix, command); } /** * {@inheritDoc} */ - public boolean register(String name, String fallbackPrefix, Command command) { - boolean nameInUse = nameInUse(name); + public boolean register(String label, String fallbackPrefix, Command command) { + boolean registeredPassedLabel = register(label, fallbackPrefix, command, false); - if (nameInUse) { - name = fallbackPrefix + ":" + name; + Iterator iterator = command.getAliases().iterator(); + while (iterator.hasNext()) { + if (!register((String) iterator.next(), fallbackPrefix, command, true)) { + iterator.remove(); + } } - knownCommands.put(name.toLowerCase(), command); - return !nameInUse; + // Register to us so further updates of the commands label and aliases are postponed until its reregistered + command.register(this); + + return registeredPassedLabel; } - private boolean nameInUse(String name) { - if (getCommand(name) != null) { - return !aliases.contains(name); + /** + * Registers a command with the given name is possible, otherwise uses fallbackPrefix to create a unique name if its not an alias + * @param name the name of the command, without the '/'-prefix. + * @param fallbackPrefix a prefix which is prepended to the command with a ':' one or more times to make the command unique + * @param command the command to register + * @return true if command was registered with the passed in label, false otherwise. + * If isAlias was true a return of false indicates no command was registerd + * If isAlias was false a return of false indicates the fallbackPrefix was used one or more times to create a unique name for the command + */ + private synchronized boolean register(String label, String fallbackPrefix, Command command, boolean isAlias) { + String lowerLabel = label.trim().toLowerCase(); + + if (isAlias && knownCommands.containsKey(lowerLabel)) { + // Request is for an alias and it conflicts with a existing command or previous alias ignore it + // Note: This will mean it gets removed from the commands list of active aliases + return false; } - return false; + + boolean registerdPassedLabel = true; + + // If the command exists but is an alias we overwrite it, otherwise we rename it based on the fallbackPrefix + while (knownCommands.containsKey(lowerLabel) && !aliases.contains(lowerLabel)) { + lowerLabel = fallbackPrefix + ":" + lowerLabel; + registerdPassedLabel = false; + } + + if (isAlias) { + aliases.add(lowerLabel); + } else { + // Ensure lowerLabel isn't listed as a alias anymore and update the commands registered name + aliases.remove(lowerLabel); + command.setLabel(lowerLabel); + } + knownCommands.put(lowerLabel, command); + + return registerdPassedLabel; } /** * {@inheritDoc} */ - public boolean dispatch(CommandSender sender, String commandLine) { + public boolean dispatch(CommandSender sender, String commandLine) throws CommandException { String[] args = commandLine.split(" "); if (args.length == 0) { @@ -86,29 +118,27 @@ public final class SimpleCommandMap implements CommandMap { } String sentCommandLabel = args[0].toLowerCase(); - - args = Arrays_copyOfRange(args, 1, args.length); - Command target = getCommand(sentCommandLabel); - boolean isRegisteredCommand = (target != null); - - if (isRegisteredCommand) { - try { - target.execute(sender, sentCommandLabel, args); - } catch (CommandException ex) { - throw ex; - } catch (Throwable ex) { - throw new CommandException("Unhandled exception executing '" + commandLine + "' in " + target, ex); - } + if (target == null) { + return false; + } + + try { + return target.execute(sender, sentCommandLabel, Arrays_copyOfRange(args, 1, args.length)); + } catch (CommandException ex) { + throw ex; + } catch (Throwable ex) { + throw new CommandException("Unhandled exception executing '" + commandLine + "' in " + target, ex); } - return isRegisteredCommand; } - public void clearCommands() { - synchronized (this) { - knownCommands.clear(); - setDefaultCommands(server); + public synchronized void clearCommands() { + for (Map.Entry entry : knownCommands.entrySet()) { + entry.getValue().unregister(this); } + knownCommands.clear(); + aliases.clear(); + setDefaultCommands(server); } public Command getCommand(String name) {