Package net.aufdemrand.denizen.utilities.command

Source Code of net.aufdemrand.denizen.utilities.command.CommandManager$CommandInfo

package net.aufdemrand.denizen.utilities.command;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import net.aufdemrand.denizen.utilities.command.exceptions.*;
import net.aufdemrand.denizen.utilities.command.messaging.Messaging;
import net.aufdemrand.denizen.utilities.debugging.dB;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

public class CommandManager {

    private final Map<Class<? extends Annotation>, CommandAnnotationProcessor> annotationProcessors =
            new HashMap<Class<? extends Annotation>, CommandAnnotationProcessor>();

    /*
     * Mapping of commands (including aliases) with a description. Root commands
     * are stored under a key of null, whereas child commands are cached under
     * their respective Method. The child map has the key of the command name
     * (one for each alias) with the method.
     */
    private final Map<String, Method> commands = new HashMap<String, Method>();
    private Injector injector;
    private final Map<Method, Object> instances = new HashMap<Method, Object>();
    private final ListMultimap<Method, Annotation> registeredAnnotations = ArrayListMultimap.create();
    private final Set<Method> serverCommands = new HashSet<Method>();

    public CommandManager() {
        registerAnnotationProcessor(new RequirementsProcessor());
    }

    /**
     *
     * Attempt to execute a command using the root {@link Command} given. A list
     * of method arguments may be used when calling the command handler method.
     *
     * A command handler method should follow the form
     * <code>command(CommandContext args, CommandSender sender)</code> where
     * {@link CommandSender} can be replaced with {@link Player} to only accept
     * players. The method parameters must include the method args given, if
     * any.
     *
     * @param command
     *            The command to execute
     * @param args
     *            The arguments of the command
     * @param sender
     *            The sender of the command
     * @param methodArgs
     *            The method arguments to be used when calling the command
     *            handler
     * @throws CommandException
     *             Any exceptions caused from execution of the command
     */
    public void execute(org.bukkit.command.Command command, String[] args, CommandSender sender, Object... methodArgs)
            throws CommandException {
        // must put command into split.
        String[] newArgs = new String[args.length + 1];
        System.arraycopy(args, 0, newArgs, 1, args.length);
        newArgs[0] = command.getName().toLowerCase();

        Object[] newMethodArgs = new Object[methodArgs.length + 1];
        System.arraycopy(methodArgs, 0, newMethodArgs, 1, methodArgs.length);
        executeMethod(newArgs, sender, newMethodArgs);
    }

    private void executeHelp(String[] args, CommandSender sender) throws CommandException {
        if (!sender.hasPermission("denizen.basic"))
            throw new NoPermissionsException();
        int page = 1;
        try {
            page = args.length == 3 ? Integer.parseInt(args[2]) : page;
        } catch (NumberFormatException e) {
            sendSpecificHelp(sender, args[0], args[2]);
            return;
        }
        sendHelp(sender, args[0], page);
    }

    // Attempt to execute a command.
    private void executeMethod(String[] args, CommandSender sender, Object[] methodArgs) throws CommandException {
        String cmdName = args[0].toLowerCase();
        String modifier = args.length > 1 ? args[1] : "";
        boolean help = modifier.toLowerCase().equals("help");

        Method method = commands.get(cmdName + " " + modifier.toLowerCase());
        if (method == null && !help)
            method = commands.get(cmdName + " *");

        if (method == null && help) {
            executeHelp(args, sender);
            return;
        }

        if (method == null)
            throw new UnhandledCommandException();

        if (!serverCommands.contains(method) && sender instanceof ConsoleCommandSender)
            throw new ServerCommandException();

        if (!hasPermission(method, sender))
            throw new NoPermissionsException();

        Command cmd = method.getAnnotation(Command.class);
        CommandContext context = new CommandContext(sender, args);

        if (context.argsLength() < cmd.min())
            throw new CommandUsageException("Too few arguments.", getUsage(args, cmd));

        if (cmd.max() != -1 && context.argsLength() > cmd.max())
            throw new CommandUsageException("Too many arguments.", getUsage(args, cmd));

        if (!cmd.flags().contains("*")) {
            for (char flag : context.getFlags())
                if (cmd.flags().indexOf(String.valueOf(flag)) == -1)
                    throw new CommandUsageException("Unknown flag: " + flag, getUsage(args, cmd));
        }

        methodArgs[0] = context;

        for (Annotation annotation : registeredAnnotations.get(method)) {
            CommandAnnotationProcessor processor = annotationProcessors.get(annotation.annotationType());
            processor.process(sender, context, annotation, methodArgs);
        }

        Object instance = instances.get(method);
        try {
            method.invoke(instance, methodArgs);
        } catch (IllegalArgumentException e) {
            dB.echoError(e);
        } catch (IllegalAccessException e) {
            dB.echoError(e);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof CommandException)
                throw (CommandException) e.getCause();
            throw new WrappedCommandException(e.getCause());
        }
    }

    /**
     * A safe version of <code>execute</code> which catches and logs all errors
     * that occur. Returns whether the command handler should print usage or
     * not.
     *
     * @see #execute(org.bukkit.command.Command, String[], CommandSender, Object...)
     * @return Whether further usage should be printed
     */
    public boolean executeSafe(org.bukkit.command.Command command, String[] args, CommandSender sender,
                               Object... methodArgs) {
        try {
            try {
                execute(command, args, sender, methodArgs);
            } catch (ServerCommandException ex) {
                Messaging.send(sender, "You must be ingame to use that command.");
            } catch (CommandUsageException ex) {
                Messaging.sendError(sender, ex.getMessage());
                Messaging.sendError(sender, ex.getUsage());
            } catch (UnhandledCommandException ex) {
                return false;
            } catch (WrappedCommandException ex) {
                throw ex.getCause();
            } catch (CommandException ex) {
                Messaging.sendError(sender, ex.getMessage());
            } catch (NumberFormatException ex) {
                Messaging.sendError(sender, "That is not a valid number.");
            }
        } catch (Throwable ex) {
            ex.printStackTrace();
            if (sender instanceof Player) {
                Messaging.sendError(sender, "Please report this error: [See console]");
                Messaging.sendError(sender, ex.getClass().getName() + ": " + ex.getMessage());
            }
        }
        return true;
    }

    /**
     * Searches for the closest modifier using Levenshtein distance to the given
     * top level command and modifier.
     *
     * @param command
     *            The top level command
     * @param modifier
     *            The modifier to use as the base
     * @return The closest modifier, or empty
     */
    public String getClosestCommandModifier(String command, String modifier) {
        int minDist = Integer.MAX_VALUE;
        command = command.toLowerCase();
        String closest = "";
        for (String cmd : commands.keySet()) {
            String[] split = cmd.split(" ");
            if (split.length <= 1 || !split[0].equals(command))
                continue;
            int distance = getLevenshteinDistance(modifier, split[1]);
            if (minDist > distance) {
                minDist = distance;
                closest = split[1];
            }
        }

        return closest;
    }

    /**
     * Gets the {@link CommandInfo} for the given top level command and
     * modifier, or null if not found.
     *
     * @param rootCommand
     *            The top level command
     * @param modifier
     *            The modifier (may be empty)
     * @return The command info for the command
     */
    public CommandInfo getCommand(String rootCommand, String modifier) {
        String joined = rootCommand + ' ' + modifier;
        for (Map.Entry<String, Method> entry : commands.entrySet()) {
            if (!entry.getKey().equalsIgnoreCase(joined) || entry.getValue() == null)
                continue;
            Command commandAnnotation = entry.getValue().getAnnotation(Command.class);
            if (commandAnnotation == null)
                continue;
            return new CommandInfo(commandAnnotation);
        }
        return null;
    }

    /**
     * Gets all modified and root commands from the given root level command.
     * For example, if <code>/npc look</code> and <code>/npc jump</code> were
     * defined, calling <code>getCommands("npc")</code> would return
     * {@link CommandInfo}s for both commands.
     *
     * @param command
     *            The root level command
     * @return The list of {@link CommandInfo}s
     */
    public List<CommandInfo> getCommands(String command) {
        List<CommandInfo> cmds = new ArrayList<CommandInfo>();
        command = command.toLowerCase();
        for (Map.Entry<String, Method> entry : commands.entrySet()) {
            if (!entry.getKey().startsWith(command) || entry.getValue() == null)
                continue;
            Command commandAnnotation = entry.getValue().getAnnotation(Command.class);
            if (commandAnnotation == null)
                continue;
            cmds.add(new CommandInfo(commandAnnotation));
        }
        return cmds;
    }

    private List<String> getLines(CommandSender sender, String baseCommand) {
        // Ensures that commands with multiple modifiers are only added once
        Set<CommandInfo> processed = new HashSet<CommandInfo>();
        List<String> lines = new ArrayList<String>();
        for (CommandInfo info : getCommands(baseCommand)) {
            Command command = info.getCommandAnnotation();
            if (processed.contains(info) || !sender.hasPermission(command.permission()))
                continue;
            lines.add(format(command, baseCommand));
            if (command.modifiers().length > 1) {
                processed.add(info);
            }
        }
        return lines;
    }

    private String getUsage(String[] args, Command cmd) {
        return "/" + args[0] + " " + cmd.usage();
    }

    /**
     * Checks to see whether there is a command handler for the given command at
     * the root level. This will check aliases as well.
     *
     * @param cmd
     *            The command to check
     * @param modifier
     *            The modifier to check (may be empty)
     * @return Whether the command is handled
     */
    public boolean hasCommand(org.bukkit.command.Command cmd, String modifier) {
        String cmdName = cmd.getName().toLowerCase();
        return commands.containsKey(cmdName + " " + modifier.toLowerCase()) || commands.containsKey(cmdName + " *");
    }

    // Returns whether a CommandSender has permission.
    private boolean hasPermission(CommandSender sender, String perm) {
        return sender.hasPermission(perm);
    }

    // Returns whether a player has access to a command.
    private boolean hasPermission(Method method, CommandSender sender) {
        Command cmd = method.getAnnotation(Command.class);
        return cmd.permission().isEmpty() || hasPermission(sender, cmd.permission()) || hasPermission(sender, "admin");
    }

    /**
     * Register a class that contains commands (methods annotated with
     * {@link Command}). If no dependency {@link Injector} is specified, then
     * only static methods of the class will be registered. Otherwise, new
     * instances the command class will be created and instance methods will be
     * called.
     *
     * @see #setInjector(Injector)
     * @param clazz
     *            The class to scan
     */
    public void register(Class<?> clazz) {
        registerMethods(clazz, null);
    }

    /**
     * Registers an {@link CommandAnnotationProcessor} that can process
     * annotations before a command is executed.
     *
     * Methods with the {@link Command} annotation will have the rest of their
     * annotations scanned and stored if there is a matching
     * {@link CommandAnnotationProcessor}. Annotations that do not have a
     * processor are discarded. The scanning method uses annotations from the
     * declaring class as a base before narrowing using the method's
     * annotations.
     *
     * @param processor
     *            The annotation processor
     */
    public void registerAnnotationProcessor(CommandAnnotationProcessor processor) {
        annotationProcessors.put(processor.getAnnotationClass(), processor);
    }

    /*
     * Register the methods of a class. This will automatically construct
     * instances as necessary.
     */
    private void registerMethods(Class<?> clazz, Method parent) {
        Object obj = injector != null ? injector.getInstance(clazz) : null;
        registerMethods(clazz, parent, obj);
    }

    // Register the methods of a class.
    private void registerMethods(Class<?> clazz, Method parent, Object obj) {
        for (Method method : clazz.getMethods()) {
            if (!method.isAnnotationPresent(Command.class))
                continue;
            // We want to be able invoke with an instance
            if (!Modifier.isStatic(method.getModifiers())) {
                // Can't register this command if we don't have an instance
                if (obj == null)
                    continue;
                instances.put(method, obj);
            }

            Command cmd = method.getAnnotation(Command.class);
            // Cache the aliases too
            for (String alias : cmd.aliases()) {
                for (String modifier : cmd.modifiers()) {
                    commands.put(alias + " " + modifier, method);
                }
                if (!commands.containsKey(alias + " help")) {
                    commands.put(alias + " help", null);
                }
            }

            List<Annotation> annotations = new ArrayList<Annotation>();
            for (Annotation annotation : method.getDeclaringClass().getAnnotations()) {
                Class<? extends Annotation> annotationClass = annotation.annotationType();
                if (annotationProcessors.containsKey(annotationClass))
                    annotations.add(annotation);
            }
            for (Annotation annotation : method.getAnnotations()) {
                Class<? extends Annotation> annotationClass = annotation.annotationType();
                if (!annotationProcessors.containsKey(annotationClass))
                    continue;
                Iterator<Annotation> itr = annotations.iterator();
                while (itr.hasNext()) {
                    Annotation previous = itr.next();
                    if (previous.annotationType() == annotationClass) {
                        itr.remove();
                    }
                }
                annotations.add(annotation);
            }

            if (annotations.size() > 0)
                registeredAnnotations.putAll(method, annotations);

            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length <= 1 || parameterTypes[1] == CommandSender.class)
                serverCommands.add(method);
        }
    }

    private void sendHelp(CommandSender sender, String name, int page) throws CommandException {
        if (name.equalsIgnoreCase("npc"))
            name = "NPC";
        Paginator paginator = new Paginator().header(capitalize(name) + " Help");
        for (String line : getLines(sender, name.toLowerCase()))
            paginator.addLine(line);
        if (!paginator.sendPage(sender, page))
            throw new CommandException("The page " + page + " does not exist.");
    }

    private void sendSpecificHelp(CommandSender sender, String rootCommand, String modifier) throws CommandException {
        CommandInfo info = getCommand(rootCommand, modifier);
        if (info == null)
            throw new CommandException("Command /" + rootCommand + " " + modifier + " not found.");
        Messaging.send(sender, format(info.getCommandAnnotation(), rootCommand));
        if (info.getCommandAnnotation().help().isEmpty())
            return;
        Messaging.send(sender, "<b>" + info.getCommandAnnotation().help());
    }

    public void setInjector(Injector injector) {
        this.injector = injector;
    }

    public static class CommandInfo {
        private final Command commandAnnotation;

        public CommandInfo(Command commandAnnotation) {
            this.commandAnnotation = commandAnnotation;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            CommandInfo other = (CommandInfo) obj;
            if (commandAnnotation == null) {
                if (other.commandAnnotation != null) {
                    return false;
                }
            } else if (!commandAnnotation.equals(other.commandAnnotation)) {
                return false;
            }
            return true;
        }

        public Command getCommandAnnotation() {
            return commandAnnotation;
        }

        @Override
        public int hashCode() {
            return 31 + ((commandAnnotation == null) ? 0 : commandAnnotation.hashCode());
        }
    }

    private static String capitalize(Object string) {
        String capitalize = string.toString();
        return capitalize.length() == 0 ? "" : Character.toUpperCase(capitalize.charAt(0))
                + capitalize.substring(1, capitalize.length());
    }

    private static String format(Command command, String alias) {
        return String.format(COMMAND_FORMAT, alias,(command.usage().isEmpty() ? "" : " " + command.usage()),
                command.desc());
    }

    private static int getLevenshteinDistance(String s, String t) {
        if (s == null || t == null)
            throw new IllegalArgumentException("Strings must not be null");

        int n = s.length(); // length of s
        int m = t.length(); // length of t

        if (n == 0)
            return m;
        else if (m == 0)
            return n;

        int p[] = new int[n + 1]; // 'previous' cost array, horizontally
        int d[] = new int[n + 1]; // cost array, horizontally
        int _d[]; // placeholder to assist in swapping p and d

        // indexes into strings s and t
        int i; // iterates through s
        int j; // iterates through t

        char t_j; // jth character of t

        int cost; // cost

        for (i = 0; i <= n; i++)
            p[i] = i;

        for (j = 1; j <= m; j++) {
            t_j = t.charAt(j - 1);
            d[0] = j;

            for (i = 1; i <= n; i++) {
                cost = s.charAt(i - 1) == t_j ? 0 : 1;
                // minimum of cell to the left+1, to the top+1, diagonally left
                // and up +cost
                d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
            }

            // copy current distance counts to 'previous row' distance counts
            _d = p;
            p = d;
            d = _d;
        }

        // our last action in the above loop was to switch d and p, so p now
        // actually has the most recent cost counts
        return p[n];
    }

    private static final String COMMAND_FORMAT = "<7>/<c>%s%s <7>- <e>%s";
}
TOP

Related Classes of net.aufdemrand.denizen.utilities.command.CommandManager$CommandInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.