diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..4bc3533d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,75 @@
+
+
+ 4.0.0
+
+ co.aikar
+ command
+ 1.0-SNAPSHOT
+
+
+ UTF-8
+
+
+
+
+ aikar
+ http://ci.emc.gs/nexus/content/groups/aikar/
+
+
+ paper
+ https://repo.destroystokyo.com/repository/maven-public/
+
+
+
+
+ clean install
+ ${project.artifactId}
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.2
+
+ 1.8
+ 1.8
+ false
+ false
+
+ -parameters
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.0.1
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+
+
+
+
+ org.jetbrains
+ annotations
+ 13.0
+
+
+ com.destroystokyo.paper
+ paper-api
+ 1.10-R0.1-SNAPSHOT
+ provided
+
+
+
+
diff --git a/src/main/java/co/aikar/commands/BaseCommand.java b/src/main/java/co/aikar/commands/BaseCommand.java
new file mode 100644
index 00000000..ff617783
--- /dev/null
+++ b/src/main/java/co/aikar/commands/BaseCommand.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+import co.aikar.commands.annotation.CommandAlias;
+import co.aikar.commands.annotation.CommandPermission;
+import co.aikar.commands.annotation.Default;
+import co.aikar.commands.annotation.Subcommand;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.SetMultimap;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.util.StringUtil;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public abstract class BaseCommand extends Command {
+
+ private final SetMultimap subCommands = HashMultimap.create();
+
+ protected String execLabel;
+ protected String execSubcommand;
+ protected String[] origArgs;
+
+ public BaseCommand() {
+ this(null);
+ }
+
+ public BaseCommand(String cmd) {
+ super(cmd);
+ final Class extends BaseCommand> self = this.getClass();
+ CommandAlias rootCmdAlias = self.getAnnotation(CommandAlias.class);
+ if (cmd == null) {
+ if (rootCmdAlias == null) {
+ cmd = "__" + self.getSimpleName();
+ } else {
+ cmd = Patterns.PIPE.split(rootCmdAlias.value())[0];
+ }
+ cmd = cmd.toLowerCase();
+ setName(cmd);
+ setLabel(cmd);
+ }
+ this.description = cmd + " commands";
+ this.usageMessage = "/" + cmd;
+
+ final CommandPermission perm = self.getAnnotation(CommandPermission.class);
+ if (perm != null) {
+ this.setPermission(perm.value());
+ }
+
+ boolean foundDefault = false;
+ for (Method method : self.getDeclaredMethods()) {
+ method.setAccessible(true);
+ String sublist = null;
+ final Subcommand sub = method.getAnnotation(Subcommand.class);
+ final Default def = method.getAnnotation(Default.class);
+
+ final CommandAlias commandAliases = method.getAnnotation(CommandAlias.class);
+
+ if (def != null) {
+ if (!foundDefault) {
+ registerSubcommand(method, "__default");
+ foundDefault = true;
+ } else {
+ SneakyThrow.sneaky(new InvalidConfigurationException("Multiple @Default commands"));
+ }
+ }
+ if (sub != null) {
+ sublist = sub.value();
+ } else if (commandAliases != null) {
+ sublist = commandAliases.value();
+ }
+ if (sublist != null) {
+ registerSubcommand(method, sublist);
+ }
+ }
+
+ try {
+ Method unknown = self.getMethod("onUnknown", CommandSender.class, String.class, String[].class);
+ unknown.setAccessible(true);
+ registerSubcommand(unknown, "__unknown");
+ } catch (NoSuchMethodException ignored) {}
+
+
+ if (rootCmdAlias != null) {
+ List cmdList = new ArrayList<>();
+ Collections.addAll(cmdList, Patterns.PIPE.split(rootCmdAlias.value().toLowerCase()));
+ cmdList.remove(cmd);
+ for (String cmdAlias : cmdList) {
+ register(cmdAlias, new ForwardingCommand(this));
+ }
+ }
+
+ register(getName(), this);
+ }
+
+ private boolean register(String name, Command cmd) {
+ return Bukkit.getServer().getCommandMap().register(name.toLowerCase(), "empire", cmd);
+ }
+
+ private void registerSubcommand(Method method, String subCommand) {
+ subCommand = subCommand.toLowerCase();
+ final String[] subCommandParts = Patterns.SPACE.split(subCommand);
+ // Must run getSubcommandPossibility BEFORE we rewrite it just after this.
+ List cmdList = getSubCommandPossibilityList(subCommandParts);
+
+ // Strip pipes off for auto complete addition
+ for (int i = 0; i < subCommandParts.length; i++) {
+ subCommandParts[i] = Patterns.PIPE.split(subCommandParts[i])[0];
+ }
+ String prefSubCommand = StringUtils.join(subCommandParts, " ");
+ final CommandAlias cmdAlias = method.getAnnotation(CommandAlias.class);
+
+ final String[] aliasNames = cmdAlias != null ? Patterns.PIPE.split(cmdAlias.value().toLowerCase()) : null;
+ String cmdName = aliasNames != null ? aliasNames[0] : getLabel() + " ";
+ RegisteredCommand cmd = new RegisteredCommand(this, cmdName, method, prefSubCommand);
+
+ for (String subcmd : cmdList) {
+ subCommands.put(subcmd, cmd);
+ }
+
+ if (aliasNames != null) {
+ for (String name : aliasNames) {
+ register(name, new ForwardingCommand(this, subCommandParts));
+ }
+ }
+ }
+
+ /**
+ * Takes a string like "foo|bar baz|qux" and generates a list of
+ * - foo baz
+ * - foo qux
+ * - bar baz
+ * - bar qux
+ *
+ * For every possible sub command combination
+ *
+ * @param subCommandParts
+ * @return List of all sub command possibilities
+ */
+ private static List getSubCommandPossibilityList(String[] subCommandParts) {
+ int i = 0;
+ ArrayList current = null;
+ while (true) {
+ ArrayList newList = new ArrayList<>();
+
+ if (i < subCommandParts.length) {
+ for (String s1 : Patterns.PIPE.split(subCommandParts[i])) {
+ if (current != null) {
+ newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList()));
+ } else {
+ newList.add(s1);
+ }
+ }
+ }
+
+ if (i + 1 < subCommandParts.length) {
+ current = newList;
+ i = i + 1;
+ continue;
+ }
+
+ return newList;
+ }
+ }
+
+ @Override
+ public final boolean execute(CommandSender sender, String commandLabel, String[] args) {
+ if (!testPermission(sender)) {
+ return true;
+ }
+ commandLabel = commandLabel.toLowerCase();
+ if (preCommand(sender, commandLabel, args)) {
+ return true;
+ }
+
+ execSubcommand = null;
+ execLabel = commandLabel;
+ origArgs = args;
+
+ if (args.length == 0) {
+ onDefault(sender, commandLabel);
+ return true;
+ }
+
+ CommandSearch cmd = findSubCommand(args);
+ if (cmd != null) {
+ execSubcommand = cmd.getCheckSub();
+ final String[] execargs = Arrays.copyOfRange(args, cmd.argIndex, args.length);
+ executeCommand(sender, execargs, cmd.cmd);
+ return true;
+ }
+
+ if (!onUnknown(sender, commandLabel, args)) {
+ help(sender, args);
+ }
+ return true;
+ }
+
+ private CommandSearch findSubCommand(String[] args) {
+ return findSubCommand(args, false);
+ }
+ private CommandSearch findSubCommand(String[] args, boolean completion) {
+ for (int i = args.length; i >= 0; i--) {
+ String checkSub = StringUtils.join(args, " ", 0, i).toLowerCase();
+ Set cmds = subCommands.get(checkSub);
+
+ final int extraArgs = args.length - i;
+ if (!cmds.isEmpty()) {
+ RegisteredCommand cmd = null;
+ if (cmds.size() == 1) {
+ cmd = Iterables.getOnlyElement(cmds);
+ } else {
+ Optional optCmd = cmds.stream().filter(c -> {
+ int nonSender = c.nonSenderAwareResolvers;
+ int partialSender = c.optionalResolvers;
+ return extraArgs <= nonSender + partialSender && (completion || extraArgs >= nonSender);
+ }).sorted((c1, c2) -> {
+ int a = c1.nonSenderAwareResolvers + c1.optionalResolvers;
+ int b = c2.nonSenderAwareResolvers + c2.optionalResolvers;
+
+ if (a == b) {
+ return 0;
+ }
+ return a < b ? 1 : -1;
+ }).findFirst();
+ if (optCmd.isPresent()) {
+ cmd = optCmd.get();
+ }
+ }
+ if (cmd != null) {
+ return new CommandSearch(cmd, i, checkSub);
+ }
+ }
+ }
+ return null;
+ }
+
+ private static void executeCommand(CommandSender sender, String[] args, RegisteredCommand cmd) {
+ if (cmd.hasPermission(sender)) {
+ List sargs = Lists.newArrayList(args);
+ cmd.invoke(sender, sargs);
+ } else {
+ CommandUtil.sendMsg(sender, "&cI'm sorry, but you do not have permission to perform this command.");
+ }
+ }
+
+ public boolean canExecute(CommandSender sender, RegisteredCommand cmd) {
+ return true;
+ }
+
+ @Override
+ public List tabComplete(CommandSender sender, String commandLabel, String[] args)
+ throws IllegalArgumentException {
+
+ commandLabel = commandLabel.toLowerCase();
+
+
+ final CommandSearch search = findSubCommand(args, true);
+
+ if (search != null) {
+ args = Arrays.copyOfRange(args, search.argIndex, args.length);
+ return completeCommand(sender, search.cmd, args, commandLabel);
+ }
+
+ String argString = StringUtils.join(args, " ").toLowerCase();
+
+ final List cmds = new ArrayList<>();
+
+ for (Map.Entry entry : subCommands.entries()) {
+ final String key = entry.getKey();
+ if (key.startsWith(argString) && !"__unknown".equals(key)) {
+ final RegisteredCommand value = entry.getValue();
+ if (!value.hasPermission(sender)) {
+ continue;
+ }
+ String prefCommand = value.prefSubCommand;
+
+ final String[] psplit = Patterns.SPACE.split(prefCommand);
+ cmds.add(psplit[args.length - 1]);
+ }
+ }
+
+ final Set unknownCmds = subCommands.get("__unknown");
+ if (cmds.isEmpty() && !unknownCmds.isEmpty()) {
+ RegisteredCommand unknownCommand = null;
+ if (unknownCmds.size() == 1) {
+ unknownCommand = Iterables.getOnlyElement(unknownCmds);
+ }
+ if (unknownCommand != null) {
+ return completeCommand(sender, unknownCommand, args, commandLabel);
+ }
+ }
+
+ return filterTabComplete(args[args.length-1], cmds);
+ }
+
+ private List completeCommand(CommandSender sender, RegisteredCommand cmd, String[] args, String commandLabel) {
+ if (args.length > cmd.nonSenderAwareResolvers + cmd.optionalResolvers) {
+ return ImmutableList.of();
+ }
+ if (args.length == 0 || cmd.complete == null) {
+ return args.length < 2 ? super.tabComplete(sender, commandLabel, args) : ImmutableList.of();
+ }
+
+ String[] completions = Patterns.SPACE.split(cmd.complete.value());
+ final int argIndex = args.length - 1;
+
+ String input = args[argIndex];
+ final String completion = argIndex < completions.length ? completions[argIndex] : null;
+
+ if ("@players".equals(completion)) {
+ return super.tabComplete(sender, commandLabel, args);
+ }
+ List cmds = CommandCompletions.of(sender, completion, input);
+ if (cmds.isEmpty()) {
+ cmds = ImmutableList.of(input);
+ }
+ return filterTabComplete(args[(argIndex)], cmds);
+ }
+
+ private static List filterTabComplete(String arg, List cmds) {
+ return cmds.stream()
+ .distinct()
+ .filter(cmd -> cmd != null && (arg.isEmpty() || StringUtil.startsWithIgnoreCase(cmd, arg)))
+ .collect(Collectors.toList());
+ }
+
+ public void help(CommandSender sender, String[] args) {
+ CommandUtil.sendMsg(sender, "&cUnknown Command, please type &f/help");
+ }
+
+ public void onDefault(CommandSender sender, String commandLabel) {
+ executeDefault(sender);
+ }
+ public boolean onUnknown(CommandSender sender, String commandLabel, String[] args) {
+ help(sender, args);
+ return true;
+ }
+ public boolean executeDefault(CommandSender sender, String... args) {
+ final Set defs = subCommands.get("__default");
+ RegisteredCommand def = null;
+ if (!defs.isEmpty()) {
+ if (defs.size() == 1) {
+ def = Iterables.getOnlyElement(defs);
+ }
+ if (def != null) {
+ executeCommand(sender, args, def);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean preCommand(CommandSender sender, String commandLabel, String[] args) {
+ return false;
+ }
+
+ public void doHelp(CommandSender sender, String... args) {
+ help(sender, args);
+ }
+
+ public void showSyntax(CommandSender sender, RegisteredCommand cmd) {
+ CommandUtil.sendMsg(sender, "&cUsage: /" + cmd.command + " " + cmd.syntax);
+ }
+
+ /*@Data*/ /*@AllArgsConstructor*/
+ private static class CommandSearch { RegisteredCommand cmd; int argIndex; String checkSub;
+
+ public CommandSearch(RegisteredCommand cmd, int argIndex, String checkSub) {
+ this.cmd = cmd;
+ this.argIndex = argIndex;
+ this.checkSub = checkSub;
+ }
+
+ public RegisteredCommand getCmd() {
+ return this.cmd;
+ }
+
+ public int getArgIndex() {
+ return this.argIndex;
+ }
+
+ public String getCheckSub() {
+ return this.checkSub;
+ }
+
+ public void setCmd(RegisteredCommand cmd) {
+ this.cmd = cmd;
+ }
+
+ public void setArgIndex(int argIndex) {
+ this.argIndex = argIndex;
+ }
+
+ public void setCheckSub(String checkSub) {
+ this.checkSub = checkSub;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof CommandSearch)) {
+ return false;
+ }
+ final CommandSearch other = (CommandSearch) o;
+ if (!other.canEqual(this)) {
+ return false;
+ }
+ final Object this$cmd = this.getCmd();
+ final Object other$cmd = other.getCmd();
+ if (this$cmd == null ? other$cmd != null : !this$cmd.equals(other$cmd)) {
+ return false;
+ }
+ if (this.getArgIndex() != other.getArgIndex()) {
+ return false;
+ }
+ final Object this$checkSub = this.getCheckSub();
+ final Object other$checkSub = other.getCheckSub();
+ if (this$checkSub == null ? other$checkSub != null : !this$checkSub.equals(other$checkSub)) {
+ return false;
+ }
+ return true;
+ }
+
+ public int hashCode() {
+ final int PRIME = 59;
+ int result = 1;
+ final Object $cmd = this.getCmd();
+ result = result * PRIME + ($cmd == null ? 43 : $cmd.hashCode());
+ result = result * PRIME + this.getArgIndex();
+ final Object $checkSub = this.getCheckSub();
+ result = result * PRIME + ($checkSub == null ? 43 : $checkSub.hashCode());
+ return result;
+ }
+
+ protected boolean canEqual(Object other) {
+ return other instanceof CommandSearch;
+ }
+
+ public String toString() {
+ return "com.empireminecraft.commands.EmpireCommand.CommandSearch(cmd=" + this.getCmd() + ", argIndex=" + this.getArgIndex() + ", checkSub=" +
+ this.getCheckSub() + ")";
+ }
+ }
+}
diff --git a/src/main/java/co/aikar/commands/CommandCompletions.java b/src/main/java/co/aikar/commands/CommandCompletions.java
new file mode 100644
index 00000000..b6deeaeb
--- /dev/null
+++ b/src/main/java/co/aikar/commands/CommandCompletions.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.EntityType;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+
+public final class CommandCompletions {
+ private CommandCompletions() {}
+
+ private static final List EMPTY = ImmutableList.of();
+ public static List of(CommandSender sender, String completion, String input) {
+ if (completion == null) {
+ return ImmutableList.of();
+ }
+ String[] complete = Patterns.COLON.split(completion, 2);
+
+ switch (complete[0]) {
+ case "@range":
+ if (complete.length == 1) {
+ return ImmutableList.of();
+ }
+ final String[] ranges = Patterns.DASH.split(complete[1]);
+ int start;
+ int end;
+ if (ranges.length != 2) {
+ start = 0;
+ end = CommandUtil.parseInt(ranges[0], 0);
+ } else {
+ start = CommandUtil.parseInt(ranges[0], 0);
+ end = CommandUtil.parseInt(ranges[1], 0);
+ }
+ return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList());
+ case "@timeunits":
+ return ImmutableList.of("hours", "days", "weeks", "months", "minutes");
+
+ case "@mobs":
+ final Stream normal = Stream.of(EntityType.values())
+ .map(entityType -> CommandUtil.simplifyString(entityType.getName()));
+ return normal.collect(Collectors.toList());
+ default:
+ return Lists.newArrayList(Patterns.PIPE.split(completion));
+ }
+ }
+}
diff --git a/src/main/java/co/aikar/commands/CommandUtil.java b/src/main/java/co/aikar/commands/CommandUtil.java
new file mode 100644
index 00000000..6b01beb3
--- /dev/null
+++ b/src/main/java/co/aikar/commands/CommandUtil.java
@@ -0,0 +1,684 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.WordUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.math.BigDecimal;
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@SuppressWarnings("WeakerAccess")
+public final class CommandUtil {
+
+ public static final Random RANDOM = new Random();
+
+ private CommandUtil() {}
+
+ public static String padRight(String s, int n) {
+ return String.format("%1$-" + n + "s", s);
+ }
+
+ public static String padLeft(String s, int n) {
+ return String.format("%1$" + n + "s", s);
+ }
+
+ public static String formatNumber(Integer balance) {
+ return NumberFormat.getInstance().format(balance);
+ }
+
+ public static String formatLocation(Location loc) {
+ if (loc == null) {
+ return null;
+ }
+ return loc.getWorld().getName() +
+ ":" +
+ loc.getBlockX() +
+ "," +
+ loc.getBlockY() +
+ "," +
+ loc.getBlockZ();
+ }
+
+ public static String color(String message) {
+ return ChatColor.translateAlternateColorCodes('&', message);
+ }
+
+ public static void sendMsg(CommandSender player, String message) {
+ message = color(message);
+ if (player == null) {
+ for (String msg : Patterns.NEWLINE.split(message)) {
+ Log.info(msg);
+ }
+ } else {
+ for (String msg : Patterns.NEWLINE.split(message)) {
+ player.sendMessage(msg);
+ }
+ }
+ }
+
+ public static Location stringToLocation(String storedLoc) {
+ return stringToLocation(storedLoc, null);
+ }
+ public static Location stringToLocation(String storedLoc, World forcedWorld) {
+ if (storedLoc == null) {
+ return null;
+ }
+ String[] args = Patterns.COLON.split(storedLoc);
+ if (args.length >= 4 || (args.length == 3 && forcedWorld != null)) {
+ String world = forcedWorld != null ? forcedWorld.getName() : args[0];
+ int i = args.length == 3 ? 0 : 1;
+ double x = Double.parseDouble(args[i]);
+ double y = Double.parseDouble(args[i + 1]);
+ double z = Double.parseDouble(args[i + 2]);
+ Location loc = new Location(Bukkit.getWorld(world), x, y, z);
+ if (args.length >= 6) {
+ loc.setPitch(Float.parseFloat(args[4]));
+ loc.setYaw(Float.parseFloat(args[5]));
+ }
+ return loc;
+ } else if (args.length == 2) {
+ String[] args2 = Patterns.COMMA.split(args[1]);
+ if (args2.length == 3) {
+ String world = forcedWorld != null ? forcedWorld.getName() : args[0];
+ double x = Double.parseDouble(args2[0]);
+ double y = Double.parseDouble(args2[1]);
+ double z = Double.parseDouble(args2[2]);
+ return new Location(Bukkit.getWorld(world), x, y, z);
+ }
+ }
+ return null;
+ }
+
+ public static String fullLocationToString(Location loc) {
+ if (loc == null) {
+ return null;
+ }
+ return (new StringBuilder(64))
+ .append(loc.getWorld().getName())
+ .append(':')
+ .append(NumUtil.precision(loc.getX(), 4))
+ .append(':')
+ .append(NumUtil.precision(loc.getY(), 4))
+ .append(':')
+ .append(NumUtil.precision(loc.getZ(), 4))
+ .append(':')
+ .append(NumUtil.precision(loc.getPitch(), 4))
+ .append(':')
+ .append(NumUtil.precision(loc.getYaw(), 4))
+ .toString();
+ }
+
+ public static String fullBlockLocationToString(Location loc) {
+ if (loc == null) {
+ return null;
+ }
+ return (new StringBuilder(64))
+ .append(loc.getWorld().getName())
+ .append(':')
+ .append(loc.getBlockX())
+ .append(':')
+ .append(loc.getBlockY())
+ .append(':')
+ .append(loc.getBlockZ())
+ .append(':')
+ .append(NumUtil.precision(loc.getPitch(), 4))
+ .append(':')
+ .append(NumUtil.precision(loc.getYaw(), 4))
+ .toString();
+ }
+
+ public static String blockLocationToString(Location loc) {
+ if (loc == null) {
+ return null;
+ }
+
+ return (new StringBuilder(32))
+ .append(loc.getWorld().getName())
+ .append(':')
+ .append(loc.getBlockX())
+ .append(':')
+ .append(loc.getBlockY())
+ .append(':')
+ .append(loc.getBlockZ())
+ .toString();
+ }
+
+ public static T getEnumFromName(T[] types, String name) {
+ return getEnumFromName(types, name, null);
+ }
+ public static T getEnumFromName(T[] types, String name, T def) {
+ for (T type : types) {
+ if (type.name().equalsIgnoreCase(name)) {
+ return type;
+ }
+ }
+ return def;
+ }
+ public static T getEnumFromOrdinal(T[] types, int ordinal) {
+ for (T type : types) {
+ if (type.ordinal() == ordinal) {
+ return type;
+ }
+ }
+ return null;
+ }
+
+ public static String ucfirst(String str) {
+ return WordUtils.capitalizeFully(str);
+ }
+
+ public static Double parseDouble(String var) {
+ return parseDouble(var, null);
+ }
+
+ public static Double parseDouble(String var, Double def) {
+ if (var == null) {
+ return def;
+ }
+ try {
+ return Double.parseDouble(var);
+ } catch (NumberFormatException ignored) {}
+ return def;
+ }
+
+ public static Float parseFloat(String var) {
+ return parseFloat(var, null);
+ }
+ public static Float parseFloat(String var, Float def) {
+ if (var == null) {
+ return def;
+ }
+ try {
+ return Float.parseFloat(var);
+ } catch (NumberFormatException ignored) {}
+ return def;
+ }
+ public static Long parseLong(String var) {
+ return parseLong(var, null);
+ }
+ public static Long parseLong(String var, Long def) {
+ if (var == null) {
+ return def;
+ }
+ try {
+ return Long.parseLong(var);
+ } catch (NumberFormatException ignored) {}
+ return def;
+ }
+
+ public static Integer parseInt(String var) {
+ return parseInt(var, null);
+ }
+ public static Integer parseInt(String var, Integer def) {
+ if (var == null) {
+ return def;
+ }
+ try {
+ return Integer.parseInt(var);
+ } catch (NumberFormatException ignored) {}
+ return def;
+ }
+
+ public static boolean randBool() {
+ return RANDOM.nextBoolean();
+ }
+
+ public static T nullDefault(Object val, Object def) {
+ return (T) (val != null ? val : def);
+ }
+
+ public static String join(Collection args) {
+ return StringUtils.join(args, " ");
+ }
+ public static String join(Collection args, String sep) {
+ return StringUtils.join(args, sep);
+ }
+ public static String join(String[] args) {
+ return join(args, 0, ' ');
+ }
+
+ public static String join(String[] args, String sep) {
+ return StringUtils.join(args, sep);
+ }
+ public static String join(String[] args, char sep) {
+ return join(args, 0, sep);
+ }
+
+ public static String join(String[] args, int index) {
+ return join(args, index, ' ');
+ }
+
+ public static String join(String[] args, int index, char sep) {
+ return StringUtils.join(args, sep, index, args.length);
+ }
+
+ public static String simplifyString(String str) {
+ if (str == null) {
+ return null;
+ }
+ return Patterns.NON_ALPHA_NUMERIC.matcher(str.toLowerCase()).replaceAll("");
+ }
+
+ public static double round(double x, int scale) {
+ try {
+ return (new BigDecimal
+ (Double.toString(x))
+ .setScale(scale, BigDecimal.ROUND_HALF_UP))
+ .doubleValue();
+ } catch (NumberFormatException ex) {
+ if (Double.isInfinite(x)) {
+ return x;
+ } else {
+ return Double.NaN;
+ }
+ }
+ }
+ public static int roundUp(int num, int multiple) {
+ if(multiple == 0) {
+ return num;
+ }
+
+ int remainder = num % multiple;
+ if (remainder == 0) {
+ return num;
+ }
+ return num + multiple - remainder;
+
+ }
+
+ public static String removeColors(String msg) {
+ return ChatColor.stripColor(CommandUtil.color(msg));
+
+ }
+
+ public static String replaceChatString(String message, String replace, String with) {
+ return replaceChatString(message, Pattern.compile(Pattern.quote(replace), Pattern.CASE_INSENSITIVE), with);
+ }
+ public static String replaceChatString(String message, Pattern replace, String with) {
+ final String[] split = replace.split(message + "1");
+
+ if (split.length < 2) {
+ return replace.matcher(message).replaceAll(with);
+ }
+ message = split[0];
+
+ for (int i = 1; i < split.length; i++) {
+ final String prev = ChatColor.getLastColors(message);
+ message += with + prev + split[i];
+ }
+ return message.substring(0, message.length() - 1);
+ }
+
+ public static boolean isWithinDistance(@NotNull Player p1, @NotNull Player p2, int dist) {
+ return isWithinDistance(p1.getLocation(), p2.getLocation(), dist);
+ }
+ public static boolean isWithinDistance(@NotNull Location loc1, @NotNull Location loc2, int dist) {
+ return loc1.getWorld() == loc2.getWorld() && loc1.distance(loc2) <= dist;
+ }
+
+ public static String limit(String str, int limit) {
+ return str.length() > limit ? str.substring(0, limit) : str;
+ }
+
+ /**
+ * Plain string replacement, escapes replace value.
+ * @param string
+ * @param pattern
+ * @param repl
+ * @return
+ */
+ public static String replace(String string, Pattern pattern, String repl) {
+ return pattern.matcher(string).replaceAll(Matcher.quoteReplacement(repl));
+ }
+
+ /**
+ * Regex version of {@link #replace(String, Pattern, String)}
+ * @param string
+ * @param pattern
+ * @param repl
+ * @return
+ */
+ public static String replacePattern(String string, Pattern pattern, String repl) {
+ return pattern.matcher(string).replaceAll(repl);
+ }
+
+ /**
+ * Plain String replacement. If you need regex patterns, see {@link #replacePattern(String, String, String)}
+ * @param string
+ * @param pattern
+ * @param repl
+ * @return
+ */
+ public static String replace(String string, String pattern, String repl) {
+ return replace(string, Patterns.getPattern(Pattern.quote(pattern)), repl);
+ }
+
+ /**
+ * Regex version of {@link #replace(String, String, String)}
+ * @param string
+ * @param pattern
+ * @param repl
+ * @return
+ */
+ public static String replacePattern(String string, String pattern, String repl) {
+ return replace(string, Patterns.getPattern(pattern), repl);
+ }
+ /**
+ * Pure Regex Pattern matching and replacement, no escaping
+ * @param string
+ * @param pattern
+ * @param repl
+ * @return
+ */
+ public static String replacePatternMatch(String string, Pattern pattern, String repl) {
+ return pattern.matcher(string).replaceAll(repl);
+ }
+
+ /**
+ * Pure Regex Pattern matching and replacement, no escaping
+ * @param string
+ * @param pattern
+ * @param repl
+ * @return
+ */
+ public static String replacePatternMatch(String string, String pattern, String repl) {
+ return replacePatternMatch(string, Patterns.getPattern(pattern), repl);
+ }
+
+ public static String replaceStrings(String string, String... replacements) {
+ if (replacements.length < 2 || replacements.length % 2 != 0) {
+ throw new IllegalArgumentException("Invalid Replacements");
+ }
+ for (int i = 0; i < replacements.length; i += 2) {
+ String key = replacements[i];
+ String value = replacements[i+1];
+ if (value == null) value = "";
+ string = replace(string, key, value);
+ }
+ return string;
+ }
+ public static String replacePatterns(String string, String... replacements) {
+ if (replacements.length < 2 || replacements.length % 2 != 0) {
+ throw new IllegalArgumentException("Invalid Replacements");
+ }
+ for (int i = 0; i < replacements.length; i += 2) {
+ String key = replacements[i];
+ String value = replacements[i+1];
+ if (value == null) value = "";
+ string = replacePattern(string, key, value);
+ }
+ return string;
+ }
+
+
+ /**
+ * Copied from Apache Commons WordUtils, with an exception to skip spaces after delimiters.
+ *
+ * @see org.apache.commons.lang.WordUtils#capitalize(String, char[])
+ * @param str
+ * @param delimiters
+ * @return
+ */
+ public static String capitalize(String str, char[] delimiters) {
+ int delimLen = (delimiters == null ? -1 : delimiters.length);
+ if (str == null || str.isEmpty() || delimLen == 0) {
+ return str;
+ }
+ int strLen = str.length();
+ StringBuilder builder = new StringBuilder(strLen);
+ boolean capitalizeNext = true;
+ for (int i = 0; i < strLen; i++) {
+ char ch = str.charAt(i);
+
+ if (isDelimiter(ch, delimiters)) {
+ builder.append(ch);
+ capitalizeNext = true;
+ } else if (ch != ' ' && capitalizeNext) {
+ builder.append(Character.toTitleCase(ch));
+ capitalizeNext = false;
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+ private static boolean isDelimiter(char ch, char[] delimiters) {
+ if (delimiters == null) {
+ return Character.isWhitespace(ch);
+ }
+ for (char delimiter : delimiters) {
+ if (ch == delimiter) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static T random(List arr) {
+ if (arr == null || arr.isEmpty()) {
+ return null;
+ }
+ return arr.get(RANDOM.nextInt(arr.size()));
+ }
+ public static T random(T[] arr) {
+ if (arr == null || arr.length == 0) {
+ return null;
+ }
+ return arr[RANDOM.nextInt(arr.length)];
+ }
+
+ /**
+ * Added as im sure we will try to "Find this" again. This is no different than Enum.values() passed to above method logically
+ * but the array version is slightly faster.
+ * @param enm
+ * @param
+ * @return
+ */
+ @Deprecated
+ public static > T random(Class extends T> enm) {
+ return random(enm.getEnumConstants());
+ }
+
+ public static String normalize(String s) {
+ if (s == null) {
+ return null;
+ }
+ return Patterns.NON_PRINTABLE_CHARACTERS.matcher(Normalizer.normalize(s, Form.NFD)).replaceAll("");
+ }
+
+ public static int indexOf(String arg, String[] split) {
+ for (int i = 0; i < split.length; i++) {
+ if (arg == null) {
+ if (split[i] == null) {
+ return i;
+ }
+ } else if (arg.equals(split[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static String capitalizeFirst(String name) {
+ return capitalizeFirst(name, '_');
+ }
+
+ public static String capitalizeFirst(String name, char separator) {
+ name = name.toLowerCase();
+ String[] split = name.split(Character.toString(separator));
+ StringBuilder total = new StringBuilder(3);
+ for (String s : split) {
+ total.append(Character.toUpperCase(s.charAt(0))).append(s.substring(1)).append(' ');
+ }
+
+ return total.toString().trim();
+ }
+
+ public static List enumNames(Enum>[] values) {
+ return Stream.of(values).map(Enum::name).collect(Collectors.toList());
+ }
+
+ public static List enumNames(Class extends Enum>> cls) {
+ return enumNames(cls.getEnumConstants());
+ }
+
+ public static String combine(String[] args) {
+ return combine(args, 0);
+ }
+ public static String combine(String[] args, int start) {
+ int size = 0;
+ for (int i = start; i < args.length; i++) {
+ size += args[i].length();
+ }
+ StringBuilder sb = new StringBuilder(size);
+ for (int i = start; i < args.length; i++) {
+ sb.append(args[i]);
+ }
+ return sb.toString();
+ }
+
+ public static double distance(@NotNull Entity e1, @NotNull Entity e2) {
+ return distance(e1.getLocation(), e2.getLocation());
+ }
+ public static double distance2d(@NotNull Entity e1, @NotNull Entity e2) {
+ return distance2d(e1.getLocation(), e2.getLocation());
+ }
+ public static double distance2d(@NotNull Location loc1, @NotNull Location loc2) {
+ loc1 = loc1.clone();
+ loc1.setY(loc2.getY());
+ return distance(loc1, loc2);
+ }
+ public static double distance(@NotNull Location loc1, @NotNull Location loc2) {
+ if (loc1.getWorld() != loc2.getWorld()) {
+ return 0;
+ }
+ return loc1.distance(loc2);
+ }
+
+ public static Location getTargetLoc(Player player) {
+ return getTargetLoc(player, 128);
+ }
+ public static Location getTargetLoc(Player player, int maxDist) {
+ return getTargetLoc(player, maxDist, 1.5);
+ }
+ public static Location getTargetLoc(Player player, int maxDist, double addY) {
+ try {
+ Location target = player.getTargetBlock((Set) null, maxDist).getLocation();
+ target.setY(target.getY() + addY);
+ return target;
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+
+ public static Location getRandLoc(Location loc, int radius) {
+ return getRandLoc(loc, radius, radius, radius);
+ }
+ public static Location getRandLoc(Location loc, int xzRadius, int yRadius) {
+ return getRandLoc(loc, xzRadius, yRadius, xzRadius);
+ }
+ @NotNull public static Location getRandLoc(Location loc, int xRadius, int yRadius, int zRadius) {
+ Location newLoc = loc.clone();
+ newLoc.setX(NumUtil.rand(loc.getX()-xRadius, loc.getX()+xRadius));
+ newLoc.setY(NumUtil.rand(loc.getY()-yRadius, loc.getY()+yRadius));
+ newLoc.setZ(NumUtil.rand(loc.getZ()-zRadius, loc.getZ()+zRadius));
+ return newLoc;
+ }
+
+ @Nullable public static Enum> simpleMatch(Class extends Enum>> list, String item) {
+ if (item == null) {
+ return null;
+ }
+ item = CommandUtil.simplifyString(item);
+ for (Enum> s : list.getEnumConstants()) {
+ String simple = CommandUtil.simplifyString(s.name());
+ if (item.equals(simple)) {
+ return s;
+ }
+ }
+
+ return null;
+ }
+
+ @NotNull public static Boolean isTruthy(String test) {
+ switch (test) {
+ case "t":
+ case "true":
+ case "on":
+ case "y":
+ case "yes":
+ case "1":
+ return true;
+ }
+ return false;
+ }
+
+
+ public static Number parseNumber(String num, boolean suffixes) {
+ double mod = 1;
+ if (suffixes) {
+ switch (num.charAt(num.length()-1)) {
+ case 'M':
+ case 'm':
+ mod = 1000000D;
+ num = num.substring(0, num.length()-1);
+ break;
+ case 'K':
+ case 'k':
+ mod = 1000D;
+ num = num.substring(0, num.length()-1);
+ }
+ }
+
+ return Double.parseDouble(num) * mod;
+ }
+
+ public static boolean hasIntersection(Collection list1, Collection list2) {
+ for (T t : list1) {
+ if (list2.contains(t)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static Collection intersection(Collection list1, Collection list2) {
+ List list = new ArrayList<>();
+
+ for (T t : list1) {
+ if(list2.contains(t)) {
+ list.add(t);
+ }
+ }
+
+ return list;
+ }
+}
diff --git a/src/main/java/co/aikar/commands/ForwardingCommand.java b/src/main/java/co/aikar/commands/ForwardingCommand.java
new file mode 100644
index 00000000..7433a4d7
--- /dev/null
+++ b/src/main/java/co/aikar/commands/ForwardingCommand.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import java.util.List;
+
+public class ForwardingCommand extends Command {
+ private final Command command;
+ private final String[] baseArgs;
+ private static final String[] NO_ARGS = new String[0];
+
+ public ForwardingCommand(Command command) {
+ this(command, NO_ARGS);
+ }
+
+ public ForwardingCommand(Command command, String[] baseArgs) {
+ super(command.getName());
+ this.command = command;
+ this.baseArgs = baseArgs;
+ }
+
+ @Override
+ public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
+ return command.tabComplete(sender, alias, (String[]) ArrayUtils.addAll(baseArgs, args));
+ }
+
+ @Override
+ public boolean execute(CommandSender sender, String commandLabel, String[] args) {
+ return command.execute(sender, commandLabel, (String[]) ArrayUtils.addAll(baseArgs, args));
+ }
+}
diff --git a/src/main/java/co/aikar/commands/InvalidCommandArgument.java b/src/main/java/co/aikar/commands/InvalidCommandArgument.java
new file mode 100644
index 00000000..806e1960
--- /dev/null
+++ b/src/main/java/co/aikar/commands/InvalidCommandArgument.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+public class InvalidCommandArgument extends Exception {
+ public boolean showSyntax = true;
+ public InvalidCommandArgument() {
+ this(null);
+ }
+ public InvalidCommandArgument(boolean showSyntax) {
+ this(null, showSyntax);
+ }
+ public InvalidCommandArgument(String message) {
+ this(message, true);
+ }
+ public InvalidCommandArgument(String message, boolean showSyntax) {
+ super(message, null, false, false);
+ this.showSyntax = showSyntax;
+ }
+}
diff --git a/src/main/java/co/aikar/commands/Log.java b/src/main/java/co/aikar/commands/Log.java
new file mode 100644
index 00000000..24f277c2
--- /dev/null
+++ b/src/main/java/co/aikar/commands/Log.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+
+import java.util.logging.Logger;
+
+public final class Log {
+ public static Logger LOGGER;
+
+ private Log() {}
+
+
+ public static void log(String message) {
+ info(message);
+ }
+
+
+ public static void info(String message) {
+ for (String s : Patterns.NEWLINE.split(message)) {
+ LOGGER.info(s);
+ }
+ }
+
+ public static void warn(String message) {
+ for (String s : Patterns.NEWLINE.split(message)) {
+ LOGGER.warning(s);
+ }
+ }
+
+ public static void severe(String message) {
+ for (String s : Patterns.NEWLINE.split(message)) {
+ LOGGER.severe(s);
+ }
+ }
+
+ public static void error(String message) {
+ severe(message);
+ }
+
+
+ public static void exception(String msg) {
+ exception(new Throwable(msg));
+ }
+
+ public static void exception(Throwable e) {
+ exception(e.getMessage(), e);
+ }
+
+ public static void exception(String msg, Throwable e) {
+ if (msg != null) {
+ severe(msg);
+ }
+ severe(ExceptionUtils.getFullStackTrace(e));
+ }
+
+ public static void exception(Throwable dbg, int lines) {
+ if (dbg == null) {
+ return;
+ }
+ severe(dbg.getMessage());
+ final StackTraceElement current = new Throwable().getStackTrace()[1];
+ severe("c: "+ current.getClassName()+":" + current.getLineNumber());
+
+ final StackTraceElement[] stack = dbg.getStackTrace();
+ for (int i = 0; i < lines && i < stack.length; i++) {
+ final StackTraceElement cur = stack[i];
+ Logger.getGlobal().severe(" " + cur);
+ }
+ }
+}
diff --git a/src/main/java/co/aikar/commands/NumUtil.java b/src/main/java/co/aikar/commands/NumUtil.java
new file mode 100644
index 00000000..c640fc26
--- /dev/null
+++ b/src/main/java/co/aikar/commands/NumUtil.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+import org.apache.commons.lang.StringUtils;
+
+public final class NumUtil {
+ private NumUtil() {}
+
+ public static int rand(int min, int max) {
+ return min + CommandUtil.RANDOM.nextInt(max - min + 1);
+ }
+
+ /**
+ * Calculate random between 2 points, excluding a center
+ * ex: Util.rand(-12, -6, 6, 12) would not return -5 to 5
+ * @param min1
+ * @param max1
+ * @param min2
+ * @param max2
+ * @return
+ */
+ public static int rand(int min1, int max1, int min2, int max2) {
+ return CommandUtil.randBool() ? rand(min1, max1) : rand(min2, max2);
+ }
+
+ public static double rand(double min, double max) {
+ return CommandUtil.RANDOM.nextDouble() * (max - min) + min;
+ }
+
+ public static boolean isNumber(String str) {
+ return StringUtils.isNumeric(str);
+ }
+
+ public static String intToRoman(int integer) {
+ if (integer == 1) {
+ return "I";
+ }
+ if (integer == 2) {
+ return "II";
+ }
+ if (integer == 3) {
+ return "III";
+ }
+ if (integer == 4) {
+ return "IV";
+ }
+ if (integer == 5) {
+ return "V";
+ }
+ if (integer == 6) {
+ return "VI";
+ }
+ if (integer == 7) {
+ return "VII";
+ }
+ if (integer == 8) {
+ return "VIII";
+ }
+ if (integer == 9) {
+ return "IX";
+ }
+ if (integer == 10) {
+ return "X";
+ }
+ return null;
+ }
+
+ public static boolean isInteger(String string) {
+ if (!Patterns.INTEGER.matcher(string).matches()) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isFloat(String string) {
+ try {
+ Float.parseFloat(string);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public static boolean isDouble(String string) {
+ try {
+ Double.parseDouble(string);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public static boolean isBetween(float num, double min, double max) {
+ if (num >= min && num <= max){
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static double precision(double x, int p) {
+ double pow = Math.pow(10, p);
+ return Math.round(x * pow) / pow;
+ }
+}
diff --git a/src/main/java/co/aikar/commands/Patterns.java b/src/main/java/co/aikar/commands/Patterns.java
new file mode 100644
index 00000000..c0257024
--- /dev/null
+++ b/src/main/java/co/aikar/commands/Patterns.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+@SuppressWarnings("WeakerAccess")
+public final class Patterns {
+ public static final Pattern COMMA = Pattern.compile(",");
+ public static final Pattern NEWLINE = Pattern.compile("\n");
+ public static final Pattern DASH = Pattern.compile("-");
+ public static final Pattern SPACE = Pattern.compile(" ");
+ public static final Pattern SEMICOLON = Pattern.compile(";");
+ public static final Pattern COLON = Pattern.compile(":");
+ public static final Pattern PIPE = Pattern.compile("\\|");
+ public static final Pattern NON_ALPHA_NUMERIC = Pattern.compile("[^a-zA-Z0-9]");
+ public static final Pattern INTEGER = Pattern.compile("^[0-9]+$");
+ public static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]{2,16}$");
+ public static final Pattern NON_PRINTABLE_CHARACTERS = Pattern.compile("[^\\x20-\\x7F]");
+
+ public static final Pattern EQUALS = Pattern.compile("=");
+
+
+
+ private Patterns() {}
+ @SuppressWarnings("Convert2MethodRef")
+ static final LoadingCache patternCache =
+ CacheBuilder.newBuilder()
+ .expireAfterAccess(90, TimeUnit.DAYS)
+ .maximumSize(1024)
+ // has to be this or fails to compile
+ .build(CacheLoader.from((pattern) -> Pattern.compile(pattern)));
+ public static Pattern getPattern(String pattern) {
+ try {
+ return patternCache.get(pattern);
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ return Pattern.compile(pattern);
+ }
+ }
+}
diff --git a/src/main/java/co/aikar/commands/RegisteredCommand.java b/src/main/java/co/aikar/commands/RegisteredCommand.java
new file mode 100644
index 00000000..f90170d6
--- /dev/null
+++ b/src/main/java/co/aikar/commands/RegisteredCommand.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands;
+
+import co.aikar.commands.annotation.CommandAlias;
+import co.aikar.commands.annotation.CommandCompletion;
+import co.aikar.commands.annotation.CommandPermission;
+import co.aikar.commands.annotation.Default;
+import co.aikar.commands.annotation.Optional;
+import co.aikar.commands.annotation.Syntax;
+import co.aikar.commands.annotation.Values;
+import co.aikar.commands.contexts.CommandContexts;
+import co.aikar.commands.contexts.CommandExecutionContext;
+import co.aikar.commands.contexts.ContextResolver;
+import co.aikar.commands.contexts.SenderAwareContextResolver;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.bukkit.World;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.entity.Player;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class RegisteredCommand {
+ public final BaseCommand scope;
+ public final String command;
+ public final Method method;
+ public final String prefSubCommand;
+ public final Parameter[] parameters;
+ public final ContextResolver>[] resolvers;
+ public final String syntax;
+
+ public final CommandPermission perm;
+ public final CommandCompletion complete;
+ public final int nonSenderAwareResolvers;
+ public final int optionalResolvers;
+
+ RegisteredCommand(BaseCommand scope, String command, Method method, String prefSubCommand) {
+ this.scope = scope;
+ if ("__unknown".equals(prefSubCommand)) {
+ prefSubCommand = "";
+ }
+ this.command = command + (method.getAnnotation(CommandAlias.class) == null && !prefSubCommand.isEmpty() ? prefSubCommand : "");
+ this.method = method;
+ this.prefSubCommand = prefSubCommand;
+ this.perm = method.getAnnotation(CommandPermission.class);
+ this.complete = method.getAnnotation(CommandCompletion.class);
+ this.parameters = method.getParameters();
+ this.resolvers = new ContextResolver[this.parameters.length];
+ final Syntax syntaxStr = method.getAnnotation(Syntax.class);
+
+ int nonSenderAwareResolvers = 0;
+ int optionalResolvers = 0;
+ StringBuilder syntaxB = new StringBuilder(64);
+ for (int i = 0; i < parameters.length; i++) {
+ final Parameter parameter = parameters[i];
+ final Class> type = parameter.getType();
+ final ContextResolver> resolver = CommandContexts.getResolver(type);
+ if (resolver != null) {
+ resolvers[i] = resolver;
+
+ if (type == World.class || resolver instanceof SenderAwareContextResolver || parameter.getAnnotation(Optional.class) != null
+ || parameter.getAnnotation(Default.class) != null) {
+ optionalResolvers++;
+ } else {
+ nonSenderAwareResolvers++;
+ }
+ if (!CommandSender.class.isAssignableFrom(parameter.getType())) {
+ if (parameter.getAnnotation(Default.class) != null ||
+ parameter.getAnnotation(Optional.class) != null ||
+ resolver instanceof SenderAwareContextResolver) {
+ syntaxB.append('[').append(parameter.getName()).append("] ");
+ } else {
+ syntaxB.append('<').append(parameter.getName()).append("> ");
+ }
+ }
+ } else {
+ SneakyThrow.sneaky(new InvalidConfigurationException(
+ "Parameter " + type.getSimpleName() + " of " + this.command + " has no resolver"
+ ));
+ }
+ }
+ if (syntaxStr != null) {
+ this.syntax = syntaxStr.value();
+ } else {
+ this.syntax = syntaxB.toString();
+ }
+ this.nonSenderAwareResolvers = nonSenderAwareResolvers;
+ this.optionalResolvers = optionalResolvers;
+ }
+
+ public void invoke(CommandSender sender, List args) {
+ if (!scope.canExecute(sender, this)) {
+ return;
+ }
+ try {
+ Map passedArgs = Maps.newLinkedHashMap();
+ for (int i = 0; i < parameters.length; i++) {
+ boolean isLast = i == parameters.length - 1;
+ final Parameter parameter = parameters[i];
+ final String parameterName = parameter.getName();
+ final Class> type = parameter.getType();
+ final ContextResolver> resolver = resolvers[i];
+ if (resolver != null) {
+ CommandExecutionContext context = new CommandExecutionContext(this, parameter, sender, args, i, passedArgs);
+ if (args.isEmpty() && !(isLast && type == String[].class)) {
+ Default def = parameter.getAnnotation(Default.class);
+ Optional opt = parameter.getAnnotation(Optional.class);
+ if (isLast && def != null) {
+ args.add(def.value());
+ } else if (isLast && opt != null) {
+ passedArgs.put(parameterName, resolver instanceof SenderAwareContextResolver ? resolver.getContext(context) : null);
+ continue;
+ } else if (!(resolver instanceof SenderAwareContextResolver)) {
+ scope.showSyntax(sender, this);
+ return;
+ }
+ } else {
+ final Values values = parameter.getAnnotation(Values.class);
+ if (values != null) {
+ String arg = args.get(0);
+
+ final String[] split = Patterns.PIPE.split(values.value());
+ Set possible = Sets.newHashSet();
+ for (String s : split) {
+ List check = CommandCompletions.of(sender, s, arg);
+ if (!check.isEmpty()) {
+ possible.addAll(check.stream().map(String::toLowerCase).collect(Collectors.toList()));
+ } else {
+ possible.add(s.toLowerCase());
+ }
+ }
+
+ if (!possible.contains(arg.toLowerCase())) {
+ throw new InvalidCommandArgument("Must be one of: " + CommandUtil.join(possible, ", "));
+ }
+ }
+ }
+ passedArgs.put(parameterName, resolver.getContext(context));
+ } else {
+ CommandUtil.sendMsg(sender, "&cUnexpected Error. Staff have been notified of the bug.");
+ return;
+ }
+ }
+
+ method.invoke(scope, passedArgs.values().toArray());
+ } catch (Exception e) {
+ if (e instanceof InvocationTargetException && e.getCause() instanceof InvalidCommandArgument) {
+ e = (Exception) e.getCause();
+ }
+ if (e instanceof InvalidCommandArgument) {
+
+ if (e.getMessage() != null && !e.getMessage().isEmpty()) {
+ CommandUtil.sendMsg(sender, "&cError: " + e.getMessage());
+ }
+ if (((InvalidCommandArgument) e).showSyntax) {
+ scope.showSyntax(sender, this);
+ }
+ } else {
+ CommandUtil.sendMsg(sender, "&cI'm sorry, but there was an error performing this command.");
+ Log.exception("Exception in command: " + command + " " + CommandUtil.join(args), e.getCause());
+ }
+ }
+ }
+
+ public boolean hasPermission(CommandSender check) {
+ return perm == null || !(check instanceof Player) || check.hasPermission(perm.value());
+ }
+}
diff --git a/src/main/java/co/aikar/commands/SneakyThrow.java b/src/main/java/co/aikar/commands/SneakyThrow.java
new file mode 100644
index 00000000..1bdcb284
--- /dev/null
+++ b/src/main/java/co/aikar/commands/SneakyThrow.java
@@ -0,0 +1,16 @@
+package co.aikar.commands;
+
+public class SneakyThrow
+{
+
+ public static void sneaky(Throwable t)
+ {
+ throw SneakyThrow.superSneaky( t );
+ }
+
+ private static T superSneaky(Throwable t) throws T
+ {
+ throw (T) t;
+ }
+}
+
diff --git a/src/main/java/co/aikar/commands/UserUtil.java b/src/main/java/co/aikar/commands/UserUtil.java
new file mode 100644
index 00000000..71bcad12
--- /dev/null
+++ b/src/main/java/co/aikar/commands/UserUtil.java
@@ -0,0 +1,68 @@
+package co.aikar.commands;
+
+import com.google.common.collect.Iterables;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class UserUtil {
+ public static Player findPlayerSmart(CommandSender requester, String origname) {
+ String name = CommandUtil.replace(origname, ":confirm", "");
+ if (name.length() < 3) {
+ requester.sendMessage("§cUsername too short, must be at least three characters");
+ return null;
+ }
+ if (!isValidName(name)) {
+ requester.sendMessage("§c'" + name + "' is not a valid username");
+ return null;
+ }
+
+ List matches = Bukkit.getServer().matchPlayer(name);
+ List confirmList = new ArrayList<>();
+
+ // Remove confirmList players from smart matching.
+ Iterator iter = matches.iterator();
+ while (iter.hasNext()) {
+ Player player = iter.next();
+ if (requester instanceof Player && !((Player) requester).canSee(player)) {
+ if (requester.hasPermission("command.seevanish")) {
+ if (!origname.endsWith(":confirm")) {
+ confirmList.add(player);
+ iter.remove();
+ }
+ } else {
+ iter.remove();
+ }
+ }
+ }
+
+ if (matches.size() > 1 || confirmList.size() > 1) {
+ requester.sendMessage("§cMultiple players matched '" + name + "', please be more specific");
+ return null;
+ }
+
+ if (matches.isEmpty()) {
+ if (confirmList.isEmpty()) {
+ requester.sendMessage("§cNo player matching '" + name + "' is connected to this server");
+ return null;
+ } else {
+ Player player = Iterables.getOnlyElement(confirmList);
+ CommandUtil.sendMsg(requester,
+ "&cWarning: " + player.getDisplayName() + "&c is confirmList. Do not blow their cover!\n" +
+ "&cTo confirm your action, add &f:confirm&c to the end of their name. \n" +
+ "&bEx: &e/g " + player.getName() + ":confirm");
+ return null;
+ }
+ }
+
+ return matches.get(0);
+ }
+
+ public static boolean isValidName(String name) {
+ return name != null && !name.isEmpty() && Patterns.VALID_NAME_PATTERN.matcher(name).matches();
+ }
+}
diff --git a/src/main/java/co/aikar/commands/annotation/CommandAlias.java b/src/main/java/co/aikar/commands/annotation/CommandAlias.java
new file mode 100644
index 00000000..69d2a690
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/CommandAlias.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CommandAlias {
+ String value();
+}
diff --git a/src/main/java/co/aikar/commands/annotation/CommandCompletion.java b/src/main/java/co/aikar/commands/annotation/CommandCompletion.java
new file mode 100644
index 00000000..faacd0ba
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/CommandCompletion.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CommandCompletion {
+ String value();
+}
diff --git a/src/main/java/co/aikar/commands/annotation/CommandPermission.java b/src/main/java/co/aikar/commands/annotation/CommandPermission.java
new file mode 100644
index 00000000..1de1db6f
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/CommandPermission.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CommandPermission {
+ String value();
+}
diff --git a/src/main/java/co/aikar/commands/annotation/Default.java b/src/main/java/co/aikar/commands/annotation/Default.java
new file mode 100644
index 00000000..38252e47
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/Default.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Default {
+ String value() default "";
+}
diff --git a/src/main/java/co/aikar/commands/annotation/Flags.java b/src/main/java/co/aikar/commands/annotation/Flags.java
new file mode 100644
index 00000000..e2c8dcae
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/Flags.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Flags {
+ String value();
+}
diff --git a/src/main/java/co/aikar/commands/annotation/Optional.java b/src/main/java/co/aikar/commands/annotation/Optional.java
new file mode 100644
index 00000000..098c892a
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/Optional.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Optional {
+}
diff --git a/src/main/java/co/aikar/commands/annotation/Single.java b/src/main/java/co/aikar/commands/annotation/Single.java
new file mode 100644
index 00000000..21f026b0
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/Single.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Don't join remaining arguments
+ */
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Single {}
diff --git a/src/main/java/co/aikar/commands/annotation/Split.java b/src/main/java/co/aikar/commands/annotation/Split.java
new file mode 100644
index 00000000..35975336
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/Split.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Split {
+ String value() default ",";
+}
diff --git a/src/main/java/co/aikar/commands/annotation/Subcommand.java b/src/main/java/co/aikar/commands/annotation/Subcommand.java
new file mode 100644
index 00000000..0b961aa6
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/Subcommand.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Subcommand {
+ String value();
+}
diff --git a/src/main/java/co/aikar/commands/annotation/Syntax.java b/src/main/java/co/aikar/commands/annotation/Syntax.java
new file mode 100644
index 00000000..1f740d7a
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/Syntax.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Syntax {
+ String value();
+}
+
diff --git a/src/main/java/co/aikar/commands/annotation/Values.java b/src/main/java/co/aikar/commands/annotation/Values.java
new file mode 100644
index 00000000..7ec6073d
--- /dev/null
+++ b/src/main/java/co/aikar/commands/annotation/Values.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Values {
+ String value();
+}
diff --git a/src/main/java/co/aikar/commands/contexts/CommandContexts.java b/src/main/java/co/aikar/commands/contexts/CommandContexts.java
new file mode 100644
index 00000000..86fdcac7
--- /dev/null
+++ b/src/main/java/co/aikar/commands/contexts/CommandContexts.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.contexts;
+
+import co.aikar.commands.InvalidCommandArgument;
+import co.aikar.commands.annotation.Single;
+import co.aikar.commands.annotation.Split;
+import co.aikar.commands.annotation.Optional;
+import co.aikar.commands.annotation.Values;
+import co.aikar.commands.Log;
+import co.aikar.commands.Patterns;
+import co.aikar.commands.SneakyThrow;
+import co.aikar.commands.UserUtil;
+import co.aikar.commands.CommandUtil;
+import com.google.common.collect.Maps;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.List;
+import java.util.Map;
+
+public final class CommandContexts {
+ private static final Map, ContextResolver>> contextMap = Maps.newHashMap();
+
+ private CommandContexts() {}
+
+ public static void initialize() {
+ registerContext(Integer.class, (c) -> {
+ try {
+ return CommandUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).intValue();
+ } catch (NumberFormatException e) {
+ throw new InvalidCommandArgument("Must be a number");
+ }
+ });
+ registerContext(Long.class, (c) -> {
+ try {
+ return CommandUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).longValue();
+ } catch (NumberFormatException e) {
+ throw new InvalidCommandArgument("Must be a number");
+ }
+
+ });
+ registerContext(Float.class, (c) -> {
+ try {
+ return CommandUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).floatValue();
+ } catch (NumberFormatException e) {
+ throw new InvalidCommandArgument("Must be a number");
+ }
+ });
+ registerContext(Double.class, (c) -> {
+ try {
+ return CommandUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes")).doubleValue();
+ } catch (NumberFormatException e) {
+ throw new InvalidCommandArgument("Must be a number");
+ }
+ });
+ registerContext(Number.class, (c) -> {
+ try {
+ return CommandUtil.parseNumber(c.popFirstArg(), c.hasFlag("suffixes"));
+ } catch (NumberFormatException e) {
+ throw new InvalidCommandArgument("Must be a number");
+ }
+ });
+ registerContext(Boolean.class, (c) -> CommandUtil.isTruthy(c.popFirstArg()));
+ registerContext(String.class, (c) -> {
+ final Values values = c.getParam().getAnnotation(Values.class);
+ if (values != null) {
+ return c.popFirstArg();
+ }
+ if (c.isLastArg() && c.getParam().getAnnotation(Single.class) == null) {
+ return CommandUtil.join(c.getArgs());
+ }
+ return c.popFirstArg();
+ });
+ registerContext(String[].class, (c) -> {
+ String val;
+ if (c.isLastArg() && c.getParam().getAnnotation(Single.class) == null) {
+ val = CommandUtil.join(c.getArgs());
+ } else {
+ val = c.popFirstArg();
+ }
+ Split split = c.getParam().getAnnotation(Split.class);
+ if (split != null) {
+ if (val.isEmpty()) {
+ throw new InvalidCommandArgument();
+ }
+ return Patterns.getPattern(split.value()).split(val);
+ } else if (!c.isLastArg()) {
+ SneakyThrow.sneaky(new InvalidConfigurationException("Weird Command signature... String[] should be last or @Split"));
+ }
+
+ String[] result = c.getArgs().toArray(new String[c.getArgs().size()]);
+ c.getArgs().clear();
+ return result;
+ });
+
+ registerContext(OnlinePlayer.class, (c) -> {
+ final String playercheck = c.popFirstArg();
+ Player player = UserUtil.findPlayerSmart(c.getSender(), playercheck);
+ if (player == null) {
+ CommandUtil.sendMsg(c.getSender(), "&cCould not find a player by the name " + playercheck);
+ throw new InvalidCommandArgument(false);
+ }
+ return new OnlinePlayer(player);
+ });
+ registerSenderAwareContext(World.class, (c) -> {
+ String firstArg = c.getFirstArg();
+ World world = firstArg != null ? Bukkit.getWorld(firstArg) : null;
+ if (world != null) {
+ c.popFirstArg();
+ }
+ if (world == null && c.getSender() instanceof Player) {
+ world = ((Entity) c.getSender()).getWorld();
+ }
+ if (world == null) {
+ throw new InvalidCommandArgument("Invalid World");
+ }
+ return world;
+ });
+ registerSenderAwareContext(CommandSender.class, CommandExecutionContext::getSender);
+ registerSenderAwareContext(Player.class, (c) -> {
+ Player player = c.getSender() instanceof Player ? (Player) c.getSender() : null;
+ if (player == null && !c.hasAnnotation(Optional.class)) {
+ throw new InvalidCommandArgument("Requires a player to run this command", false);
+ }
+ if (player != null && c.hasFlag("itemheld") && !isValidItem(player.getInventory().getItemInMainHand())) {
+ throw new InvalidCommandArgument("You must be holding an item in your main hand.", false);
+ }
+ return player;
+ });
+ registerContext(Enum.class, (c) -> {
+ final String first = c.popFirstArg();
+ Class extends Enum>> enumCls = (Class extends Enum>>) c.getParam().getType();
+ Enum> match = CommandUtil.simpleMatch(enumCls, first);
+ if (match == null) {
+ List names = CommandUtil.enumNames(enumCls);
+ throw new InvalidCommandArgument("Please specify one of: " + CommandUtil.join(names));
+ }
+ return match;
+ });
+ }
+
+ public static void registerSenderAwareContext(Class context, SenderAwareContextResolver supplier) {
+ contextMap.put(context, supplier);
+ }
+ public static void registerContext(Class context, ContextResolver supplier) {
+ contextMap.put(context, supplier);
+ }
+
+ public static ContextResolver> getResolver(Class> type) {
+ Class> rootType = type;
+ do {
+ if (type == Object.class) {
+ break;
+ }
+
+ final ContextResolver> resolver = contextMap.get(type);
+ if (resolver != null) {
+ return resolver;
+ }
+ } while ((type = type.getSuperclass()) != null);
+
+ Log.exception(new InvalidConfigurationException("No context resolver defined for " + rootType.getName()));
+ return null;
+ }
+ private static boolean isValidItem(ItemStack item) {
+ return item != null && item.getType() != Material.AIR && item.getAmount() > 0;
+ }
+}
diff --git a/src/main/java/co/aikar/commands/contexts/CommandExecutionContext.java b/src/main/java/co/aikar/commands/contexts/CommandExecutionContext.java
new file mode 100644
index 00000000..d02c9ebf
--- /dev/null
+++ b/src/main/java/co/aikar/commands/contexts/CommandExecutionContext.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.contexts;
+
+import co.aikar.commands.RegisteredCommand;
+import co.aikar.commands.annotation.Default;
+import co.aikar.commands.annotation.Flags;
+import co.aikar.commands.annotation.Optional;
+import co.aikar.commands.Patterns;
+import co.aikar.commands.CommandUtil;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.bukkit.command.CommandSender;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Parameter;
+import java.util.List;
+import java.util.Map;
+
+/*@Data*/ public class CommandExecutionContext {
+ private final RegisteredCommand cmd;
+ private final Parameter param;
+ private final CommandSender sender;
+ private final List args;
+ private final int index;
+ private final Map passedArgs;
+ private final Map flags;
+
+ public CommandExecutionContext(RegisteredCommand cmd, Parameter param, CommandSender sender, List args,
+ int index, Map passedArgs) {
+ this.cmd = cmd;
+ this.param = param;
+ this.sender = sender;
+ this.args = args;
+ this.index = index;
+ this.passedArgs = passedArgs;
+ Flags flags = param.getAnnotation(Flags.class);
+ if (flags != null) {
+ this.flags = Maps.newHashMap();
+ for (String s : Patterns.COMMA.split(flags.value())) {
+ String[] v = Patterns.EQUALS.split(s, 2);
+ this.flags.put(v[0], v.length > 1 ? v[1] : null);
+ }
+ } else {
+ this.flags = ImmutableMap.of();
+ }
+ }
+
+ public String popFirstArg() {
+ return !args.isEmpty() ? args.remove(0) : null;
+ }
+
+ public String getFirstArg() {
+ return !args.isEmpty() ? args.get(0) : null;
+ }
+
+ public boolean isLastArg() {
+ return cmd.parameters.length -1 == index;
+ }
+
+ public int getNumParams() {
+ return cmd.parameters.length;
+ }
+
+ public boolean canOverridePlayerContext() {
+ int numRequired = getNumParams();
+ for (int i = 0; i < cmd.resolvers.length; i++) {
+ Parameter parameter = cmd.parameters[i];
+ ContextResolver> resolver = cmd.resolvers[i];
+ if (parameter.getAnnotation(Optional.class) != null || parameter.getAnnotation(Default.class) != null) {
+ numRequired--;
+ } else if (resolver instanceof SenderAwareContextResolver) {
+ numRequired--;
+ }
+ }
+
+ return numRequired >= args.size();
+ }
+
+ public Object getResolvedArg(String arg) {
+ return passedArgs.get(arg);
+ }
+
+ public Object getResolvedArg(Class>... classes) {
+ for (Class> clazz : classes) {
+ for (Object passedArg : passedArgs.values()) {
+ if (clazz.isInstance(passedArg)) {
+ return passedArg;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public T getResolvedArg(String key, Class>... classes) {
+ final Object o = passedArgs.get(key);
+ for (Class> clazz : classes) {
+ if (clazz.isInstance(o)) {
+ return (T) o;
+ }
+ }
+
+ return null;
+ }
+
+ public boolean isOptional() {
+ return param.getAnnotation(Optional.class) != null;
+ }
+ public boolean hasFlag(String flag) {
+ return flags.containsKey(flag);
+ }
+
+ public String getFlagValue(String flag, String def) {
+ return flags.containsKey(flag) ? flags.get(flag) : def;
+ }
+
+ public Integer getFlagValue(String flag, Integer def) {
+ return CommandUtil.parseInt(this.flags.get(flag), def);
+ }
+
+ public T getAnnotation(Class cls) {
+ return param.getAnnotation(cls);
+ }
+
+ public boolean hasAnnotation(Class cls) {
+ return param.getAnnotation(cls) != null;
+ }
+
+ public RegisteredCommand getCmd() {
+ return this.cmd;
+ }
+
+ public Parameter getParam() {
+ return this.param;
+ }
+
+ public CommandSender getSender() {
+ return this.sender;
+ }
+
+ public List getArgs() {
+ return this.args;
+ }
+
+ public int getIndex() {
+ return this.index;
+ }
+
+ public Map getPassedArgs() {
+ return this.passedArgs;
+ }
+
+ public Map getFlags() {
+ return this.flags;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof CommandExecutionContext)) {
+ return false;
+ }
+ final CommandExecutionContext other = (CommandExecutionContext) o;
+ if (!other.canEqual(this)) {
+ return false;
+ }
+ final Object this$cmd = this.getCmd();
+ final Object other$cmd = other.getCmd();
+ if (this$cmd == null ? other$cmd != null : !this$cmd.equals(other$cmd)) {
+ return false;
+ }
+ final Object this$param = this.getParam();
+ final Object other$param = other.getParam();
+ if (this$param == null ? other$param != null : !this$param.equals(other$param)) {
+ return false;
+ }
+ final Object this$sender = this.getSender();
+ final Object other$sender = other.getSender();
+ if (this$sender == null ? other$sender != null : !this$sender.equals(other$sender)) {
+ return false;
+ }
+ final Object this$args = this.getArgs();
+ final Object other$args = other.getArgs();
+ if (this$args == null ? other$args != null : !this$args.equals(other$args)) {
+ return false;
+ }
+ if (this.getIndex() != other.getIndex()) {
+ return false;
+ }
+ final Object this$passedArgs = this.getPassedArgs();
+ final Object other$passedArgs = other.getPassedArgs();
+ if (this$passedArgs == null ? other$passedArgs != null : !this$passedArgs.equals(other$passedArgs)) {
+ return false;
+ }
+ final Object this$flags = this.getFlags();
+ final Object other$flags = other.getFlags();
+ if (this$flags == null ? other$flags != null : !this$flags.equals(other$flags)) {
+ return false;
+ }
+ return true;
+ }
+
+ public int hashCode() {
+ final int PRIME = 59;
+ int result = 1;
+ final Object $cmd = this.getCmd();
+ result = result * PRIME + ($cmd == null ? 43 : $cmd.hashCode());
+ final Object $param = this.getParam();
+ result = result * PRIME + ($param == null ? 43 : $param.hashCode());
+ final Object $sender = this.getSender();
+ result = result * PRIME + ($sender == null ? 43 : $sender.hashCode());
+ final Object $args = this.getArgs();
+ result = result * PRIME + ($args == null ? 43 : $args.hashCode());
+ result = result * PRIME + this.getIndex();
+ final Object $passedArgs = this.getPassedArgs();
+ result = result * PRIME + ($passedArgs == null ? 43 : $passedArgs.hashCode());
+ final Object $flags = this.getFlags();
+ result = result * PRIME + ($flags == null ? 43 : $flags.hashCode());
+ return result;
+ }
+
+ protected boolean canEqual(Object other) {
+ return other instanceof CommandExecutionContext;
+ }
+
+ public String toString() {
+ return "com.empireminecraft.commands.contexts.CommandExecutionContext(cmd=" + this.getCmd() + ", param=" + this.getParam() + ", sender=" +
+ this.getSender() + ", args=" + this.getArgs() + ", index=" + this.getIndex() + ", passedArgs=" + this.getPassedArgs() + ", flags=" +
+ this.getFlags() + ")";
+ }
+}
diff --git a/src/main/java/co/aikar/commands/contexts/ContextResolver.java b/src/main/java/co/aikar/commands/contexts/ContextResolver.java
new file mode 100644
index 00000000..48946ec7
--- /dev/null
+++ b/src/main/java/co/aikar/commands/contexts/ContextResolver.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.contexts;
+
+import co.aikar.commands.InvalidCommandArgument;
+
+@FunctionalInterface
+public interface ContextResolver {
+ C getContext(CommandExecutionContext c) throws InvalidCommandArgument;
+}
diff --git a/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java b/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java
new file mode 100644
index 00000000..d8d69e96
--- /dev/null
+++ b/src/main/java/co/aikar/commands/contexts/OnlinePlayer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.contexts;
+
+import org.bukkit.entity.Player;
+
+/*@Data*/ public class OnlinePlayer {
+ public final Player player;
+
+ public OnlinePlayer(Player player) {
+ this.player = player;
+ }
+
+ public Player getPlayer() {
+ return this.player;
+ }
+
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof OnlinePlayer)) {
+ return false;
+ }
+ final OnlinePlayer other = (OnlinePlayer) o;
+ if (!other.canEqual(this)) {
+ return false;
+ }
+ final Object this$player = this.getPlayer();
+ final Object other$player = other.getPlayer();
+ if (this$player == null ? other$player != null : !this$player.equals(other$player)) {
+ return false;
+ }
+ return true;
+ }
+
+ public int hashCode() {
+ final int PRIME = 59;
+ int result = 1;
+ final Object $player = this.getPlayer();
+ result = result * PRIME + ($player == null ? 43 : $player.hashCode());
+ return result;
+ }
+
+ protected boolean canEqual(Object other) {
+ return other instanceof OnlinePlayer;
+ }
+
+ public String toString() {
+ return "com.empireminecraft.commands.contexts.OnlinePlayer(player=" + this.getPlayer() + ")";
+ }
+}
diff --git a/src/main/java/co/aikar/commands/contexts/SenderAwareContextResolver.java b/src/main/java/co/aikar/commands/contexts/SenderAwareContextResolver.java
new file mode 100644
index 00000000..3edfa4b3
--- /dev/null
+++ b/src/main/java/co/aikar/commands/contexts/SenderAwareContextResolver.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2016. Starlis LLC / dba Empire Minecraft
+ *
+ * This source code is proprietary software and must not be redistributed without Starlis LLC's approval
+ *
+ */
+
+package co.aikar.commands.contexts;
+
+public interface SenderAwareContextResolver extends ContextResolver {}