diff --git a/example/src/main/java/co/aikar/acfexample/ACFExample.java b/example/src/main/java/co/aikar/acfexample/ACFExample.java index 39e0a90e..8e3b6d4c 100644 --- a/example/src/main/java/co/aikar/acfexample/ACFExample.java +++ b/example/src/main/java/co/aikar/acfexample/ACFExample.java @@ -40,6 +40,7 @@ public final class ACFExample extends JavaPlugin { private void registerCommands() { commandManager = ACF.createManager(this); + commandManager.getCommandReplacements().addReplacements("test", "foobar", "%foo", "barbaz"); commandManager.getCommandContexts().registerContext(SomeObject.class, SomeObject.getContextResolver()); commandManager.getCommandCompletions().registerCompletion("test", (sender, config, input, c) -> ( Lists.newArrayList("foo", "bar", "baz") diff --git a/example/src/main/java/co/aikar/acfexample/SomeCommand.java b/example/src/main/java/co/aikar/acfexample/SomeCommand.java index 92acd701..cbc10a37 100644 --- a/example/src/main/java/co/aikar/acfexample/SomeCommand.java +++ b/example/src/main/java/co/aikar/acfexample/SomeCommand.java @@ -31,6 +31,7 @@ import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Values; import co.aikar.commands.contexts.OnlinePlayer; import org.bukkit.World; import org.bukkit.command.CommandSender; @@ -85,13 +86,14 @@ public class SomeCommand extends BaseCommand { public class Test extends BaseSubCommand { @Subcommand("test1|td1") - @CommandCompletion("FOO") + @CommandCompletion("%foo") public void onTest1(Player player, String testX) { player.sendMessage("You got test inner test1: " + testX); } - @Subcommand("test2|td2") - @CommandCompletion("BAR") - public void onTest2(Player player, String testY) { + @Subcommand("test2|td2|%test") + @CommandCompletion("%test") + @CommandPermission("%test") + public void onTest2(Player player, @Values("%test") String testY) { player.sendMessage("You got test inner test2: " + testY); } diff --git a/src/main/java/co/aikar/commands/ACFPatterns.java b/src/main/java/co/aikar/commands/ACFPatterns.java index d0f7e2c8..1aae19f9 100644 --- a/src/main/java/co/aikar/commands/ACFPatterns.java +++ b/src/main/java/co/aikar/commands/ACFPatterns.java @@ -34,6 +34,7 @@ import java.util.regex.Pattern; @SuppressWarnings("WeakerAccess") final class ACFPatterns { public static final Pattern COMMA = Pattern.compile(","); + public static final Pattern PERCENTAGE = Pattern.compile("%", Pattern.LITERAL); public static final Pattern NEWLINE = Pattern.compile("\n"); public static final Pattern DASH = Pattern.compile("-"); public static final Pattern SPACE = Pattern.compile(" "); diff --git a/src/main/java/co/aikar/commands/BaseCommand.java b/src/main/java/co/aikar/commands/BaseCommand.java index c9662210..4770837d 100644 --- a/src/main/java/co/aikar/commands/BaseCommand.java +++ b/src/main/java/co/aikar/commands/BaseCommand.java @@ -116,7 +116,7 @@ public class BaseCommand extends Command { if (rootCmdAlias == null) { cmd = "__" + self.getSimpleName(); } else { - cmd = ACFPatterns.PIPE.split(rootCmdAlias.value())[0]; + cmd = ACFPatterns.PIPE.split(manager.getCommandReplacements().replace(rootCmdAlias.value()))[0]; } cmd = cmd.toLowerCase(); try { @@ -139,7 +139,7 @@ public class BaseCommand extends Command { final CommandPermission perm = self.getAnnotation(CommandPermission.class); if (perm != null) { - this.setPermission(perm.value()); + this.setPermission(manager.getCommandReplacements().replace(perm.value())); } boolean foundDefault = false; @@ -250,7 +250,7 @@ public class BaseCommand extends Command { } private void registerSubcommand(Method method, String subCommand) { - subCommand = subCommand.toLowerCase(); + subCommand = manager.getCommandReplacements().replace(subCommand.toLowerCase()); final String[] subCommandParts = ACFPatterns.SPACE.split(subCommand); // Must run getSubcommandPossibility BEFORE we rewrite it just after this. List cmdList = getSubCommandPossibilityList(subCommandParts); @@ -262,7 +262,7 @@ public class BaseCommand extends Command { String prefSubCommand = StringUtils.join(subCommandParts, " "); final CommandAlias cmdAlias = method.getAnnotation(CommandAlias.class); - final String[] aliasNames = cmdAlias != null ? ACFPatterns.PIPE.split(cmdAlias.value().toLowerCase()) : null; + final String[] aliasNames = cmdAlias != null ? ACFPatterns.PIPE.split(manager.getCommandReplacements().replace(cmdAlias.value().toLowerCase())) : null; String cmdName = aliasNames != null ? aliasNames[0] : getLabel() + " "; RegisteredCommand cmd = new RegisteredCommand(this, cmdName, method, prefSubCommand); @@ -443,7 +443,7 @@ public class BaseCommand extends Command { return args.length < 2 ? super.tabComplete(sender, commandLabel, args) : ImmutableList.of(); } - String[] completions = ACFPatterns.SPACE.split(cmd.complete.value()); + String[] completions = ACFPatterns.SPACE.split(cmd.complete); List cmds = manager.getCommandCompletions().of(cmd, sender, completions, args); return filterTabComplete(args[args.length-1], cmds); diff --git a/src/main/java/co/aikar/commands/BukkitCommandCompletions.java b/src/main/java/co/aikar/commands/BukkitCommandCompletions.java index 29d0cafe..7b49d28b 100644 --- a/src/main/java/co/aikar/commands/BukkitCommandCompletions.java +++ b/src/main/java/co/aikar/commands/BukkitCommandCompletions.java @@ -42,8 +42,8 @@ import java.util.stream.Stream; @SuppressWarnings("WeakerAccess") public class BukkitCommandCompletions extends CommandCompletions { - BukkitCommandCompletions() { - super(); + public BukkitCommandCompletions(BukkitCommandManager manager) { + super(manager); registerCompletion("mobs", (sender, config, input, c) -> { final Stream normal = Stream.of(EntityType.values()) .map(entityType -> ACFUtil.simplifyString(entityType.getName())); diff --git a/src/main/java/co/aikar/commands/BukkitCommandContexts.java b/src/main/java/co/aikar/commands/BukkitCommandContexts.java index ad27a650..ca3155e9 100644 --- a/src/main/java/co/aikar/commands/BukkitCommandContexts.java +++ b/src/main/java/co/aikar/commands/BukkitCommandContexts.java @@ -40,8 +40,8 @@ import java.util.stream.Stream; @SuppressWarnings("WeakerAccess") public class BukkitCommandContexts extends CommandContexts { - BukkitCommandContexts() { - super(); + public BukkitCommandContexts(BukkitCommandManager manager) { + super(manager); registerContext(OnlinePlayer.class, (c) -> { final String playercheck = c.popFirstArg(); diff --git a/src/main/java/co/aikar/commands/BukkitCommandManager.java b/src/main/java/co/aikar/commands/BukkitCommandManager.java index b64d4523..f05d35ec 100644 --- a/src/main/java/co/aikar/commands/BukkitCommandManager.java +++ b/src/main/java/co/aikar/commands/BukkitCommandManager.java @@ -25,7 +25,6 @@ package co.aikar.commands; import co.aikar.timings.lib.TimingManager; import org.bukkit.Bukkit; -import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandException; @@ -89,7 +88,7 @@ public class BukkitCommandManager extends CommandManager { @Override public synchronized CommandContexts getCommandContexts() { if (this.contexts == null) { - this.contexts = new BukkitCommandContexts(); + this.contexts = new BukkitCommandContexts(this); } return contexts; } @@ -97,27 +96,24 @@ public class BukkitCommandManager extends CommandManager { @Override public synchronized CommandCompletions getCommandCompletions() { if (this.completions == null) { - this.completions = new BukkitCommandCompletions(); + this.completions = new BukkitCommandCompletions(this); } return completions; } @Override - public boolean registerCommand(BaseCommand command) { + public void registerCommand(BaseCommand command) { final String plugin = this.plugin.getName().toLowerCase(); command.onRegister(this); - boolean allSuccess = true; for (Map.Entry entry : command.registeredCommands.entrySet()) { String key = entry.getKey().toLowerCase(); RootCommand value = entry.getValue(); - if (!value.isRegistered && !(commandMap.register(key, plugin, value))) { - allSuccess = false; + if (!value.isRegistered) { + commandMap.register(key, plugin, value); } value.isRegistered = true; registeredCommands.put(key, command); } - - return allSuccess; } public void unregisterCommand(BaseCommand command) { diff --git a/src/main/java/co/aikar/commands/CommandCompletions.java b/src/main/java/co/aikar/commands/CommandCompletions.java index ba2d5d8f..ef40f011 100644 --- a/src/main/java/co/aikar/commands/CommandCompletions.java +++ b/src/main/java/co/aikar/commands/CommandCompletions.java @@ -41,9 +41,11 @@ import java.util.stream.IntStream; @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"}) public class CommandCompletions { + private final CommandManager manager; private Map completionMap = new HashMap<>(); - public CommandCompletions() { + public CommandCompletions(CommandManager manager) { + this.manager = manager; registerCompletion("range", (sender, config, input, c) -> { if (config == null) { return ImmutableList.of(); @@ -82,6 +84,7 @@ public class CommandCompletions { @NotNull List getCompletionValues(RegisteredCommand command, CommandSender sender, String completion, String[] args) { + completion = manager.getCommandReplacements().replace(completion); final int argIndex = args.length - 1; String input = args[argIndex]; diff --git a/src/main/java/co/aikar/commands/CommandContexts.java b/src/main/java/co/aikar/commands/CommandContexts.java index c18c0b20..d995d630 100644 --- a/src/main/java/co/aikar/commands/CommandContexts.java +++ b/src/main/java/co/aikar/commands/CommandContexts.java @@ -37,8 +37,10 @@ import java.util.Map; @SuppressWarnings("WeakerAccess") public class CommandContexts { private final Map, ContextResolver> contextMap = Maps.newHashMap(); + private final CommandManager manager; - CommandContexts() { + CommandContexts(CommandManager manager) { + this.manager = manager; registerContext(Integer.class, (c) -> { try { return ACFUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).intValue(); diff --git a/src/main/java/co/aikar/commands/CommandExecutionContext.java b/src/main/java/co/aikar/commands/CommandExecutionContext.java index 2787498c..5c3ca353 100644 --- a/src/main/java/co/aikar/commands/CommandExecutionContext.java +++ b/src/main/java/co/aikar/commands/CommandExecutionContext.java @@ -58,7 +58,7 @@ public class CommandExecutionContext { Flags flags = param.getAnnotation(Flags.class); if (flags != null) { this.flags = Maps.newHashMap(); - for (String s : ACFPatterns.COMMA.split(flags.value())) { + for (String s : ACFPatterns.COMMA.split(cmd.scope.manager.getCommandReplacements().replace(flags.value()))) { String[] v = ACFPatterns.EQUALS.split(s, 2); this.flags.put(v[0], v.length > 1 ? v[1] : null); } diff --git a/src/main/java/co/aikar/commands/CommandManager.java b/src/main/java/co/aikar/commands/CommandManager.java index f96f6762..8273a9ea 100644 --- a/src/main/java/co/aikar/commands/CommandManager.java +++ b/src/main/java/co/aikar/commands/CommandManager.java @@ -28,9 +28,11 @@ import co.aikar.timings.lib.TimingManager; import java.util.HashMap; import java.util.Map; +@SuppressWarnings("WeakerAccess") public abstract class CommandManager { protected Map rootCommands = new HashMap<>(); + protected CommandReplacements replacements = new CommandReplacements(); /** * Gets the command contexts manager @@ -44,13 +46,26 @@ public abstract class CommandManager { */ public abstract CommandCompletions getCommandCompletions(); + /** + * Lets you add custom string replacements that can be applied to annotation values, + * to reduce duplication/repetition of common values such as permission nodes and command prefixes. + * + * Any replacement registered starts with a % + * + * So for ex @CommandPermission("%staff") + * @return Replacements Manager + */ + public CommandReplacements getCommandReplacements() { + return replacements; + } + /** * Registers a command with ACF * * @param command The command to register * @return boolean */ - public abstract boolean registerCommand(BaseCommand command); + public abstract void registerCommand(BaseCommand command); public abstract TimingManager getTimings(); diff --git a/src/main/java/co/aikar/commands/CommandReplacements.java b/src/main/java/co/aikar/commands/CommandReplacements.java new file mode 100644 index 00000000..aceceb04 --- /dev/null +++ b/src/main/java/co/aikar/commands/CommandReplacements.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package co.aikar.commands; + +import java.util.AbstractMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Manages replacement template strings + */ +public class CommandReplacements { + private final Map> replacements = new LinkedHashMap<>(); + + public void addReplacements(String... replacements) { + if (replacements.length == 0 || replacements.length % 2 != 0) { + throw new IllegalArgumentException("Must pass a number of arguments divisible by 2."); + } + for (int i = 0; i < replacements.length; i += 2) { + addReplacement(replacements[i], replacements[i+1]); + } + } + + public String addReplacement(String key, String val) { + key = ACFPatterns.PERCENTAGE.matcher(key.toLowerCase()).replaceAll(""); + Pattern pattern = Pattern.compile("%" + Pattern.quote(key) + "\\b", Pattern.CASE_INSENSITIVE); + + Map.Entry entry = new AbstractMap.SimpleImmutableEntry<>(pattern, val); + Map.Entry replaced = replacements.put(key, entry); + + if (replaced != null) { + return replaced.getValue(); + } + + return null; + } + public String replace(String text) { + if (text == null) { + return null; + } + + for (Map.Entry entry : replacements.values()) { + text = entry.getKey().matcher(text).replaceAll(entry.getValue()); + } + + return text; + } +} diff --git a/src/main/java/co/aikar/commands/PaperCommandCompletions.java b/src/main/java/co/aikar/commands/PaperCommandCompletions.java index 353e7800..3f90f370 100644 --- a/src/main/java/co/aikar/commands/PaperCommandCompletions.java +++ b/src/main/java/co/aikar/commands/PaperCommandCompletions.java @@ -25,4 +25,7 @@ package co.aikar.commands; @SuppressWarnings("WeakerAccess") public class PaperCommandCompletions extends BukkitCommandCompletions { + public PaperCommandCompletions(PaperCommandManager manager) { + super(manager); + } } diff --git a/src/main/java/co/aikar/commands/PaperCommandContexts.java b/src/main/java/co/aikar/commands/PaperCommandContexts.java index 9b32635d..d9ffb429 100644 --- a/src/main/java/co/aikar/commands/PaperCommandContexts.java +++ b/src/main/java/co/aikar/commands/PaperCommandContexts.java @@ -25,4 +25,7 @@ package co.aikar.commands; @SuppressWarnings("WeakerAccess") public class PaperCommandContexts extends BukkitCommandContexts { + public PaperCommandContexts(PaperCommandManager manager) { + super(manager); + } } diff --git a/src/main/java/co/aikar/commands/PaperCommandManager.java b/src/main/java/co/aikar/commands/PaperCommandManager.java index 9ae2855a..c664a846 100644 --- a/src/main/java/co/aikar/commands/PaperCommandManager.java +++ b/src/main/java/co/aikar/commands/PaperCommandManager.java @@ -36,7 +36,7 @@ public class PaperCommandManager extends BukkitCommandManager { @Override public synchronized CommandContexts getCommandContexts() { if (this.contexts == null) { - this.contexts = new PaperCommandContexts(); + this.contexts = new PaperCommandContexts(this); } return this.contexts; } @@ -44,7 +44,7 @@ public class PaperCommandManager extends BukkitCommandManager { @Override public synchronized CommandCompletions getCommandCompletions() { if (this.completions == null) { - this.completions = new PaperCommandCompletions(); + this.completions = new PaperCommandCompletions(this); } return this.completions; } diff --git a/src/main/java/co/aikar/commands/RegisteredCommand.java b/src/main/java/co/aikar/commands/RegisteredCommand.java index d61fd033..d64f2f4a 100644 --- a/src/main/java/co/aikar/commands/RegisteredCommand.java +++ b/src/main/java/co/aikar/commands/RegisteredCommand.java @@ -50,7 +50,7 @@ import java.util.Set; import java.util.stream.Collectors; public class RegisteredCommand { - private final BaseCommand scope; + final BaseCommand scope; public final String command; private final Method method; final String prefSubCommand; @@ -58,8 +58,8 @@ public class RegisteredCommand { final ContextResolver[] resolvers; final String syntaxText; - private final CommandPermission permission; - final CommandCompletion complete; + private final String permission; + final String complete; final int nonSenderAwareResolvers; final int optionalResolvers; private MCTiming timing; @@ -72,8 +72,10 @@ public class RegisteredCommand { this.command = command + (method.getAnnotation(CommandAlias.class) == null && !prefSubCommand.isEmpty() ? prefSubCommand : ""); this.method = method; this.prefSubCommand = prefSubCommand; - this.permission = method.getAnnotation(CommandPermission.class); - this.complete = method.getAnnotation(CommandCompletion.class); + CommandPermission permissionAnno = method.getAnnotation(CommandPermission.class); + this.permission = permissionAnno != null ? scope.manager.getCommandReplacements().replace(permissionAnno.value()) : null; + CommandCompletion completionAnno = method.getAnnotation(CommandCompletion.class); + this.complete = completionAnno != null ? scope.manager.getCommandReplacements().replace(completionAnno.value()) : null; this.parameters = method.getParameters(); this.resolvers = new ContextResolver[this.parameters.length]; final Syntax syntaxStr = method.getAnnotation(Syntax.class); @@ -114,9 +116,9 @@ public class RegisteredCommand { } } if (syntaxStr != null) { - this.syntaxText = syntaxStr.value(); + this.syntaxText = manager.getCommandReplacements().replace(syntaxStr.value()); } else { - this.syntaxText = syntaxB.toString(); + this.syntaxText = manager.getCommandReplacements().replace(syntaxB.toString()); } this.nonSenderAwareResolvers = nonSenderAwareResolvers; this.optionalResolvers = optionalResolvers; @@ -173,7 +175,7 @@ public class RegisteredCommand { Default def = parameter.getAnnotation(Default.class); Optional opt = parameter.getAnnotation(Optional.class); if (isLast && def != null) { - args.add(def.value()); + args.add(scope.manager.getCommandReplacements().replace(def.value())); } else if (isLast && opt != null) { passedArgs.put(parameterName, resolver instanceof SenderAwareContextResolver ? resolver.getContext(context) : null); //noinspection UnnecessaryContinue @@ -187,7 +189,7 @@ public class RegisteredCommand { if (values != null) { String arg = args.get(0); - final String[] split = ACFPatterns.PIPE.split(values.value()); + final String[] split = ACFPatterns.PIPE.split(scope.manager.getCommandReplacements().replace(values.value())); Set possible = Sets.newHashSet(); for (String s : split) { List check = this.scope.manager.getCommandCompletions().getCompletionValues(this, sender, s, origArgs); @@ -215,7 +217,11 @@ public class RegisteredCommand { } boolean hasPermission(CommandSender check) { - return permission == null || !(check instanceof Player) || check.hasPermission(permission.value()); + return permission == null || !(check instanceof Player) || check.hasPermission(permission); + } + + public String getPermission() { + return permission; } public String getPrefSubCommand() { @@ -225,8 +231,4 @@ public class RegisteredCommand { public String getSyntaxText() { return syntaxText; } - - public CommandPermission getPermission() { - return permission; - } }