ch.njol.skript.command.Commands.java Source code

Java tutorial

Introduction

Here is the source code for ch.njol.skript.command.Commands.java

Source

/*
 *   This file is part of Skript.
 *
 *  Skript is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Skript is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Skript.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * 
 * Copyright 2011-2014 Peter Gttinger
 * 
 */

package ch.njol.skript.command;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Filter;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.help.HelpMap;
import org.bukkit.help.HelpTopic;
import org.bukkit.plugin.SimplePluginManager;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.ScriptLoader;
import ch.njol.skript.Skript;
import ch.njol.skript.SkriptConfig;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.classes.Parser;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.config.validate.SectionValidator;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.ParseContext;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.localization.ArgsMessage;
import ch.njol.skript.localization.Language;
import ch.njol.skript.localization.Message;
import ch.njol.skript.log.BukkitLoggerFilter;
import ch.njol.skript.log.RetainingLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.Utils;
import ch.njol.util.Callback;
import ch.njol.util.NonNullPair;
import ch.njol.util.StringUtils;

/**
 * @author Peter Gttinger
 */
@SuppressWarnings("deprecation")
public abstract class Commands {

    public final static ArgsMessage m_too_many_arguments = new ArgsMessage("commands.too many arguments");
    public final static Message m_correct_usage = new Message("commands.correct usage");
    public final static Message m_internal_error = new Message("commands.internal error");

    private final static Map<String, ScriptCommand> commands = new HashMap<String, ScriptCommand>();

    @Nullable
    private static SimpleCommandMap commandMap = null;
    @Nullable
    private static Map<String, Command> cmKnownCommands;
    @Nullable
    private static Set<String> cmAliases;
    static {
        try {
            if (Bukkit.getPluginManager() instanceof SimplePluginManager) {
                final Field commandMapField = SimplePluginManager.class.getDeclaredField("commandMap");
                commandMapField.setAccessible(true);
                commandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getPluginManager());

                final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands");
                knownCommandsField.setAccessible(true);
                cmKnownCommands = (Map<String, Command>) knownCommandsField.get(commandMap);

                try {
                    final Field aliasesField = SimpleCommandMap.class.getDeclaredField("aliases");
                    aliasesField.setAccessible(true);
                    cmAliases = (Set<String>) aliasesField.get(commandMap);
                } catch (final NoSuchFieldException e) {
                }
            }
        } catch (final SecurityException e) {
            Skript.error("Please disable the security manager");
            commandMap = null;
        } catch (final Exception e) {
            Skript.outdatedError(e);
            commandMap = null;
        }
    }

    private final static SectionValidator commandStructure = new SectionValidator().addEntry("usage", true)
            .addEntry("description", true).addEntry("permission", true).addEntry("permission message", true)
            .addEntry("aliases", true).addEntry("executable by", true).addSection("trigger", false);

    @Nullable
    public static List<Argument<?>> currentArguments = null;

    @SuppressWarnings("null")
    private final static Pattern escape = Pattern.compile("[" + Pattern.quote("(|)<>%\\") + "]");
    @SuppressWarnings("null")
    private final static Pattern unescape = Pattern.compile("\\\\[" + Pattern.quote("(|)<>%\\") + "]");

    private final static String escape(final String s) {
        return "" + escape.matcher(s).replaceAll("\\\\$0");
    }

    private final static String unescape(final String s) {
        return "" + unescape.matcher(s).replaceAll("$0");
    }

    private final static Listener commandListener = new Listener() {
        @SuppressWarnings("null")
        @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
        public void onPlayerCommand(final PlayerCommandPreprocessEvent e) {
            if (handleCommand(e.getPlayer(), e.getMessage().substring(1)))
                e.setCancelled(true);
        }

        @SuppressWarnings("null")
        @EventHandler(priority = EventPriority.HIGHEST)
        public void onServerCommand(final ServerCommandEvent e) {
            if (e.getCommand() == null || e.getCommand().isEmpty())
                return;
            if (SkriptConfig.enableEffectCommands.value()
                    && e.getCommand().startsWith(SkriptConfig.effectCommandToken.value())) {
                if (handleEffectCommand(e.getSender(), e.getCommand())) {
                    e.setCommand("");
                    suppressUnknownCommandMessage = true;
                }
                return;
            }
            if (handleCommand(e.getSender(), e.getCommand())) {
                e.setCommand("");
                suppressUnknownCommandMessage = true;
            }
        }
    };

    static boolean suppressUnknownCommandMessage = false;
    static {
        BukkitLoggerFilter.addFilter(new Filter() {
            @Override
            public boolean isLoggable(final @Nullable LogRecord record) {
                if (record == null)
                    return false;
                if (suppressUnknownCommandMessage && record.getMessage() != null
                        && record.getMessage().toLowerCase().startsWith("unknown command")) {
                    suppressUnknownCommandMessage = false;
                    return false;
                }
                return true;
            }
        });
    }

    @Nullable
    private final static Listener pre1_3chatListener = Skript.isRunningMinecraft(1, 3) ? null : new Listener() {
        @SuppressWarnings("null")
        @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
        public void onPlayerChat(final PlayerChatEvent e) {
            if (!SkriptConfig.enableEffectCommands.value()
                    || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value()))
                return;
            if (handleEffectCommand(e.getPlayer(), e.getMessage()))
                e.setCancelled(true);
        }
    };
    @Nullable
    private final static Listener post1_3chatListener = !Skript.isRunningMinecraft(1, 3) ? null : new Listener() {
        @SuppressWarnings("null")
        @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
        public void onPlayerChat(final AsyncPlayerChatEvent e) {
            if (!SkriptConfig.enableEffectCommands.value()
                    || !e.getMessage().startsWith(SkriptConfig.effectCommandToken.value()))
                return;
            if (!e.isAsynchronous()) {
                if (handleEffectCommand(e.getPlayer(), e.getMessage()))
                    e.setCancelled(true);
            } else {
                final Future<Boolean> f = Bukkit.getScheduler().callSyncMethod(Skript.getInstance(),
                        new Callable<Boolean>() {
                            @Override
                            public Boolean call() throws Exception {
                                return handleEffectCommand(e.getPlayer(), e.getMessage());
                            }
                        });
                try {
                    while (true) {
                        try {
                            if (f.get())
                                e.setCancelled(true);
                            break;
                        } catch (final InterruptedException e1) {
                        }
                    }
                } catch (final ExecutionException e1) {
                    Skript.exception(e1);
                }
            }
        }
    };

    /**
     * @param sender
     * @param command full command string without the slash
     * @return whether to cancel the event
     */
    final static boolean handleCommand(final CommandSender sender, final String command) {
        final String[] cmd = command.split("\\s+", 2);
        cmd[0] = cmd[0].toLowerCase();
        if (cmd[0].endsWith("?")) {
            final ScriptCommand c = commands.get(cmd[0].substring(0, cmd[0].length() - 1));
            if (c != null) {
                c.sendHelp(sender);
                return true;
            }
        }
        final ScriptCommand c = commands.get(cmd[0]);
        if (c != null) {
            //         if (cmd.length == 2 && cmd[1].equals("?")) {
            //            c.sendHelp(sender);
            //            return true;
            //         }
            if (SkriptConfig.logPlayerCommands.value() && !(sender instanceof ConsoleCommandSender))
                SkriptLogger.LOGGER.info(sender.getName() + ": /" + command);
            c.execute(sender, "" + cmd[0], cmd.length == 1 ? "" : "" + cmd[1]);
            return true;
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    final static boolean handleEffectCommand(final CommandSender sender, String command) {
        if (!(sender instanceof ConsoleCommandSender || sender.hasPermission("skript.effectcommands")
                || SkriptConfig.allowOpsToUseEffectCommands.value() && sender.isOp()))
            return false;
        final boolean wasLocal = Language.setUseLocal(false);
        try {
            command = "" + command.substring(SkriptConfig.effectCommandToken.value().length()).trim();
            final RetainingLogHandler log = SkriptLogger.startRetainingLog();
            try {
                ScriptLoader.setCurrentEvent("effect command", EffectCommandEvent.class);
                final Effect e = Effect.parse(command, null);
                ScriptLoader.deleteCurrentEvent();

                if (e != null) {
                    log.clear(); // ignore warnings and stuff
                    log.printLog();

                    sender.sendMessage(ChatColor.GRAY + "executing '" + ChatColor.stripColor(command) + "'");
                    if (SkriptConfig.logPlayerCommands.value() && !(sender instanceof ConsoleCommandSender))
                        Skript.info(sender.getName() + " issued effect command: " + command);
                    e.run(new EffectCommandEvent(sender, command));
                } else {
                    if (sender == Bukkit.getConsoleSender()) // log as SEVERE instead of INFO like printErrors below
                        SkriptLogger.LOGGER.severe("Error in: " + ChatColor.stripColor(command));
                    else
                        sender.sendMessage(
                                ChatColor.RED + "Error in: " + ChatColor.GRAY + ChatColor.stripColor(command));
                    log.printErrors(sender, "(No specific information is available)");
                }
            } finally {
                log.stop();
            }
            return true;
        } catch (final Exception e) {
            Skript.exception(e, "Unexpected error while executing effect command '" + command + "' by '"
                    + sender.getName() + "'");
            sender.sendMessage(ChatColor.RED
                    + "An internal error occurred while executing this effect. Please refer to the server log for details.");
            return true;
        } finally {
            Language.setUseLocal(wasLocal);
        }
    }

    @SuppressWarnings("null")
    private final static Pattern commandPattern = Pattern.compile("(?i)^command /?(\\S+)(\\s+(.+))?$"),
            argumentPattern = Pattern
                    .compile("<\\s*(?:(.+?)\\s*:\\s*)?(.+?)\\s*(?:=\\s*(" + SkriptParser.wildcard + "))?\\s*>");

    @Nullable
    public final static ScriptCommand loadCommand(final SectionNode node) {
        final String key = node.getKey();
        if (key == null)
            return null;
        final String s = ScriptLoader.replaceOptions(key);

        int level = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '[') {
                level++;
            } else if (s.charAt(i) == ']') {
                if (level == 0) {
                    Skript.error("Invalid placement of [optional brackets]");
                    return null;
                }
                level--;
            }
        }
        if (level > 0) {
            Skript.error("Invalid amount of [optional brackets]");
            return null;
        }

        Matcher m = commandPattern.matcher(s);
        final boolean a = m.matches();
        assert a;

        final String command = "" + m.group(1).toLowerCase();
        final ScriptCommand existingCommand = commands.get(command);
        if (existingCommand != null && existingCommand.getLabel().equals(command)) {
            final File f = existingCommand.getScript();
            Skript.error("A command with the name /" + command + " is already defined"
                    + (f == null ? "" : " in " + f.getName()));
            return null;
        }

        final String arguments = m.group(3) == null ? "" : m.group(3);
        final StringBuilder pattern = new StringBuilder();

        List<Argument<?>> currentArguments = new ArrayList<Argument<?>>();
        m = argumentPattern.matcher(arguments);
        int lastEnd = 0;
        int optionals = 0;
        for (int i = 0; m.find(); i++) {
            pattern.append(escape("" + arguments.substring(lastEnd, m.start())));
            optionals += StringUtils.count(arguments, '[', lastEnd, m.start());
            optionals -= StringUtils.count(arguments, ']', lastEnd, m.start());

            lastEnd = m.end();

            ClassInfo<?> c;
            c = Classes.getClassInfoFromUserInput("" + m.group(2));
            final NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + m.group(2));
            if (c == null)
                c = Classes.getClassInfoFromUserInput(p.getFirst());
            if (c == null) {
                Skript.error("Unknown type '" + m.group(2) + "'");
                return null;
            }
            final Parser<?> parser = c.getParser();
            if (parser == null || !parser.canParse(ParseContext.COMMAND)) {
                Skript.error("Can't use " + c + " as argument of a command");
                return null;
            }

            final Argument<?> arg = Argument.newInstance(m.group(1), c, m.group(3), i, !p.getSecond(),
                    optionals > 0);
            if (arg == null)
                return null;
            currentArguments.add(arg);

            if (arg.isOptional() && optionals == 0) {
                pattern.append('[');
                optionals++;
            }
            pattern.append("%" + (arg.isOptional() ? "-" : "")
                    + Utils.toEnglishPlural(c.getCodeName(), p.getSecond()) + "%");
        }

        pattern.append(escape("" + arguments.substring(lastEnd)));
        optionals += StringUtils.count(arguments, '[', lastEnd);
        optionals -= StringUtils.count(arguments, ']', lastEnd);
        for (int i = 0; i < optionals; i++)
            pattern.append(']');

        String desc = "/" + command + " ";
        final boolean wasLocal = Language.setUseLocal(true); // use localised class names in description
        try {
            desc += StringUtils.replaceAll(pattern, "(?<!\\\\)%-?(.+?)%", new Callback<String, Matcher>() {
                @Override
                public String run(final @Nullable Matcher m) {
                    assert m != null;
                    final NonNullPair<String, Boolean> p = Utils.getEnglishPlural("" + m.group(1));
                    final String s = p.getFirst();
                    return "<" + Classes.getClassInfo(s).getName().toString(p.getSecond()) + ">";
                }
            });
        } finally {
            Language.setUseLocal(wasLocal);
        }
        desc = unescape(desc);
        desc = "" + desc.trim();

        node.convertToEntries(0);
        commandStructure.validate(node);
        if (!(node.get("trigger") instanceof SectionNode))
            return null;

        final String usage = ScriptLoader.replaceOptions(node.get("usage", desc));
        final String description = ScriptLoader.replaceOptions(node.get("description", ""));
        ArrayList<String> aliases = new ArrayList<String>(
                Arrays.asList(ScriptLoader.replaceOptions(node.get("aliases", "")).split("\\s*,\\s*/?")));
        if (aliases.get(0).startsWith("/"))
            aliases.set(0, aliases.get(0).substring(1));
        else if (aliases.get(0).isEmpty())
            aliases = new ArrayList<String>(0);
        final String permission = ScriptLoader.replaceOptions(node.get("permission", ""));
        final String permissionMessage = ScriptLoader.replaceOptions(node.get("permission message", ""));
        final SectionNode trigger = (SectionNode) node.get("trigger");
        if (trigger == null)
            return null;
        final String[] by = ScriptLoader.replaceOptions(node.get("executable by", "console,players"))
                .split("\\s*,\\s*|\\s+(and|or)\\s+");
        int executableBy = 0;
        for (final String b : by) {
            if (b.equalsIgnoreCase("console") || b.equalsIgnoreCase("the console")) {
                executableBy |= ScriptCommand.CONSOLE;
            } else if (b.equalsIgnoreCase("players") || b.equalsIgnoreCase("player")) {
                executableBy |= ScriptCommand.PLAYERS;
            } else {
                Skript.warning(
                        "'executable by' should be either be 'players', 'console', or both, but found '" + b + "'");
            }
        }

        if (!permissionMessage.isEmpty() && permission.isEmpty()) {
            Skript.warning("command /" + command + " has a permission message set, but not a permission");
        }

        if (Skript.debug() || node.debug())
            Skript.debug("command " + desc + ":");

        final File config = node.getConfig().getFile();
        if (config == null) {
            assert false;
            return null;
        }

        Commands.currentArguments = currentArguments;
        final ScriptCommand c;
        try {
            c = new ScriptCommand(config, command, "" + pattern.toString(), currentArguments, description, usage,
                    aliases, permission, permissionMessage, executableBy, ScriptLoader.loadItems(trigger));
        } finally {
            Commands.currentArguments = null;
        }

        registerCommand(c);

        if (Skript.logVeryHigh() && !Skript.debug())
            Skript.info("registered command " + desc);
        currentArguments = null;
        return c;
    }

    //   public static boolean skriptCommandExists(final String command) {
    //      final ScriptCommand c = commands.get(command);
    //      return c != null && c.getName().equals(command);
    //   }

    public static void registerCommand(final ScriptCommand command) {
        if (commandMap != null) {
            assert cmKnownCommands != null;// && cmAliases != null;
            command.register(commandMap, cmKnownCommands, cmAliases);
        }
        commands.put(command.getLabel(), command);
        for (final String alias : command.getActiveAliases()) {
            commands.put(alias.toLowerCase(), command);
        }
        command.registerHelp();
    }

    public static int unregisterCommands(final File script) {
        int numCommands = 0;
        final Iterator<ScriptCommand> commandsIter = commands.values().iterator();
        while (commandsIter.hasNext()) {
            final ScriptCommand c = commandsIter.next();
            if (script.equals(c.getScript())) {
                numCommands++;
                c.unregisterHelp();
                if (commandMap != null) {
                    assert cmKnownCommands != null;// && cmAliases != null;
                    c.unregister(commandMap, cmKnownCommands, cmAliases);
                }
                commandsIter.remove();
            }
        }
        return numCommands;
    }

    private static boolean registeredListeners = false;

    public final static void registerListeners() {
        if (!registeredListeners) {
            Bukkit.getPluginManager().registerEvents(commandListener, Skript.getInstance());
            Bukkit.getPluginManager().registerEvents(
                    post1_3chatListener != null ? post1_3chatListener : pre1_3chatListener, Skript.getInstance());
            registeredListeners = true;
        }
    }

    public final static void clearCommands() {
        final SimpleCommandMap commandMap = Commands.commandMap;
        if (commandMap != null) {
            final Map<String, Command> cmKnownCommands = Commands.cmKnownCommands;
            final Set<String> cmAliases = Commands.cmAliases;
            assert cmKnownCommands != null;// && cmAliases != null;
            for (final ScriptCommand c : commands.values())
                c.unregister(commandMap, cmKnownCommands, cmAliases);
        }
        for (final ScriptCommand c : commands.values()) {
            c.unregisterHelp();
        }
        commands.clear();
    }

    /**
     * copied from CraftBukkit (org.bukkit.craftbukkit.help.CommandAliasHelpTopic)
     */
    public final static class CommandAliasHelpTopic extends HelpTopic {

        private final String aliasFor;
        private final HelpMap helpMap;

        public CommandAliasHelpTopic(final String alias, final String aliasFor, final HelpMap helpMap) {
            this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor;
            this.helpMap = helpMap;
            name = alias.startsWith("/") ? alias : "/" + alias;
            Validate.isTrue(!name.equals(this.aliasFor), "Command " + name + " cannot be alias for itself");
            shortText = ChatColor.YELLOW + "Alias for " + ChatColor.WHITE + this.aliasFor;
        }

        @Override
        public String getFullText(final @Nullable CommandSender forWho) {
            final StringBuilder sb = new StringBuilder(shortText);
            final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor);
            if (aliasForTopic != null) {
                sb.append("\n");
                sb.append(aliasForTopic.getFullText(forWho));
            }
            return "" + sb.toString();
        }

        @Override
        public boolean canSee(final @Nullable CommandSender commandSender) {
            if (amendedPermission == null) {
                final HelpTopic aliasForTopic = helpMap.getHelpTopic(aliasFor);
                if (aliasForTopic != null) {
                    return aliasForTopic.canSee(commandSender);
                } else {
                    return false;
                }
            } else {
                return commandSender != null && commandSender.hasPermission(amendedPermission);
            }
        }
    }

}