001package fr.aumgn.bukkitutils.command;
002
003import java.lang.reflect.Method;
004import java.util.Locale;
005
006import org.apache.commons.lang.Validate;
007import org.bukkit.command.CommandExecutor;
008import org.bukkit.command.CommandSender;
009import org.bukkit.command.PluginCommand;
010import org.bukkit.plugin.java.JavaPlugin;
011
012import fr.aumgn.bukkitutils.command.args.CommandArgs;
013import fr.aumgn.bukkitutils.command.executor.MethodCommandExecutor;
014import fr.aumgn.bukkitutils.command.executor.NestedCommandExecutor;
015
016/**
017 * Class which handles all the mess of registering commands and nested commands
018 * using the Bukkit API.
019 */
020public class CommandsRegistration {
021
022    private static final String PREEXECUTE_METHOD_NAME = "preExecute";
023
024    private final JavaPlugin plugin;
025    private final CommandsMessages messages;
026
027    public CommandsRegistration(JavaPlugin plugin, Locale locale) {
028        this.plugin = plugin;
029        CommandsLocalization localisation =
030                new CommandsLocalization(plugin, locale);
031        this.messages = localisation.get("commands");
032    }
033
034    /**
035     * Registers all defined command in the given {@link Commands} object.
036     */
037    public void register(Commands commands) {
038        Method preExecute = getPreExecuteMethod(commands);
039
040        String cmdPrefix = "";
041        if (commands.getClass().isAnnotationPresent(NestedCommands.class)) {
042            cmdPrefix = registerNestedCommand(commands, preExecute);
043        }
044
045
046        for (Method method : commands.getClass().getDeclaredMethods()) {
047            if (!method.isAnnotationPresent(Command.class)) {
048                continue;
049            }
050
051            Command cmdAnnotation = method.getAnnotation(Command.class);
052            validateCommand(method, false);
053            Validate.notEmpty(cmdAnnotation.name());
054
055            String cmdName = cmdPrefix + cmdAnnotation.name();
056            PluginCommand command = plugin.getCommand(cmdName);
057            Validate.notNull(command,
058                    String.format("Command '%s' does not exist", cmdName));
059
060            if (command != null) {
061                CommandExecutor executor = new MethodCommandExecutor(
062                        messages, commands, preExecute, method, cmdAnnotation);
063                setCommand(command, executor);
064            }
065        }
066    }
067
068    private Method getPreExecuteMethod(Commands commands) {
069        try {
070            Method preExecute = commands.getClass().getMethod(
071                    PREEXECUTE_METHOD_NAME, CommandSender.class,
072                    CommandArgs.class);
073            validateCommand(preExecute, true);
074            return preExecute;
075        } catch (NoSuchMethodException exc) {
076            return null;
077        }
078    }
079
080    private String registerNestedCommand(Commands commands,
081            Method preExecute) {
082        NestedCommands annotation =
083                commands.getClass().getAnnotation(NestedCommands.class);
084        String[] nestedCmds = annotation.value();
085        Validate.notEmpty(nestedCmds);
086        Validate.notEmpty(nestedCmds[0]);
087
088        StringBuilder fullName = new StringBuilder();
089
090        NestedCommandExecutor nestedExecutor = null;
091        for (String name : nestedCmds) {
092            Validate.notEmpty(name);
093            fullName.append(name);
094
095            PluginCommand command = plugin.getCommand(fullName.toString());
096            Validate.notNull(command, String.format(
097                    "Command '%s' does not exist", name));
098            CommandExecutor executor = command.getExecutor();
099            if (executor instanceof NestedCommandExecutor) {
100                nestedExecutor = (NestedCommandExecutor) executor;
101            } else {
102                nestedExecutor = new NestedCommandExecutor(plugin, messages,
103                        annotation.defaultTo());
104                setCommand(command, nestedExecutor);
105            }
106            fullName.append(" ");
107        }
108
109        return fullName.toString();
110    }
111
112    private void setCommand(PluginCommand command, CommandExecutor executor) {
113        CommandExecutor oldExecutor = command.getExecutor();
114        command.setExecutor(executor);
115        if (oldExecutor instanceof MethodCommandExecutor
116                || oldExecutor instanceof MethodCommandExecutor) {
117            return;
118        }
119
120        command.setUsage(messages.usageMessage(command.getUsage()));
121        command.setPermissionMessage(messages.permissionMessage());
122    }
123
124    private void validateCommand(Method method, boolean strictLength) {
125        Class<?>[] params = method.getParameterTypes();
126        if (strictLength) {
127            Validate.isTrue(params.length == 2,
128                    "Command method must define two parameters.");
129        } else {
130            Validate.isTrue(params.length == 1 || params.length == 2,
131                    "Command method must define one or two parameter(s).");
132        }
133        Validate.isTrue(CommandSender.class.isAssignableFrom(params[0]),
134                "First parameter of command method must "
135                + "be of type CommandSender");
136        Validate.isTrue(params.length == 1
137                    || CommandArgs.class.isAssignableFrom(params[1]),
138                    "Second parameter of command method must "
139                            + "be of type CommandArgs");
140    }
141}