diff --git a/src/com/sk89q/util/StringUtil.java b/src/com/sk89q/util/StringUtil.java index 78a525ee7..4ac411dfe 100644 --- a/src/com/sk89q/util/StringUtil.java +++ b/src/com/sk89q/util/StringUtil.java @@ -18,6 +18,8 @@ package com.sk89q.util; +import java.util.List; + /** * String utilities. * @@ -133,4 +135,24 @@ public class StringUtil { } return buffer.toString(); } + + /** + * Join an list of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @return + */ + public static String joinString(List str, String delimiter, + int initialIndex) { + if (str.size() == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(str.get(initialIndex).toString()); + for (int i = initialIndex + 1; i < str.size(); i++) { + buffer.append(delimiter).append(str.get(i).toString()); + } + return buffer.toString(); + } } diff --git a/src/com/sk89q/util/commands/Command.java b/src/com/sk89q/util/commands/Command.java index e52bfbed2..e461ab13f 100644 --- a/src/com/sk89q/util/commands/Command.java +++ b/src/com/sk89q/util/commands/Command.java @@ -24,9 +24,9 @@ import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface Command { String[] aliases(); - String usage(); + String usage() default ""; String desc(); - int min(); - int max(); + int min() default 0; + int max() default -1; String flags() default ""; } diff --git a/src/com/sk89q/util/commands/NestedCommand.java b/src/com/sk89q/util/commands/NestedCommand.java new file mode 100644 index 000000000..e4f98c249 --- /dev/null +++ b/src/com/sk89q/util/commands/NestedCommand.java @@ -0,0 +1,28 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * This program 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. + * + * This program 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 this program. If not, see . +*/ + +package com.sk89q.util.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface NestedCommand { + Class[] value(); +} diff --git a/src/com/sk89q/worldedit/WorldEdit.java b/src/com/sk89q/worldedit/WorldEdit.java index 95bb12c6f..8f3d8df35 100644 --- a/src/com/sk89q/worldedit/WorldEdit.java +++ b/src/com/sk89q/worldedit/WorldEdit.java @@ -30,7 +30,6 @@ import java.util.logging.Logger; import java.io.*; import javax.script.ScriptException; import com.sk89q.util.StringUtil; -import com.sk89q.util.commands.CommandContext; import com.sk89q.worldedit.LocalSession.CompassMode; import com.sk89q.worldedit.bags.BlockBag; import com.sk89q.worldedit.blocks.*; @@ -841,7 +840,7 @@ public class WorldEdit { + StringUtil.joinString(split, " ")); } - return commands.execute(new CommandContext(split), this, + return commands.execute(split, this, session, player, editSession); } finally { session.remember(editSession); diff --git a/src/com/sk89q/worldedit/commands/CommandsManager.java b/src/com/sk89q/worldedit/commands/CommandsManager.java index f893ef663..b791bd131 100644 --- a/src/com/sk89q/worldedit/commands/CommandsManager.java +++ b/src/com/sk89q/worldedit/commands/CommandsManager.java @@ -21,10 +21,14 @@ package com.sk89q.worldedit.commands; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import com.sk89q.util.StringUtil; import com.sk89q.util.commands.Command; import com.sk89q.util.commands.CommandContext; +import com.sk89q.util.commands.NestedCommand; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalPlayer; import com.sk89q.worldedit.LocalSession; @@ -38,9 +42,10 @@ import com.sk89q.worldedit.WorldEditException; */ public class CommandsManager { /** - * Mapping of commands (including aliases) with their method. + * Mapping of nested commands (including aliases) with a description. */ - public Map commands = new HashMap(); + public Map> commands + = new HashMap>(); /** * Mapping of commands (not including aliases) with a description. */ @@ -55,6 +60,25 @@ public class CommandsManager { * @param cls */ public void register(Class cls) { + registerMethods(cls, null); + } + + /** + * Register the methods of a class. + * + * @param cls + * @param parent + */ + private void registerMethods(Class cls, Method parent) { + Map map; + + if (commands.containsKey(parent)) { + map = commands.get(parent); + } else { + map = new HashMap(); + commands.put(parent, map); + } + for (Method method : cls.getMethods()) { if (!method.isAnnotationPresent(Command.class)) { continue; @@ -64,7 +88,7 @@ public class CommandsManager { // Cache the commands for (String alias : cmd.aliases()) { - commands.put(alias, method); + map.put(alias, method); } // Build a list of commands and their usage details @@ -73,6 +97,14 @@ public class CommandsManager { } else { descs.put(cmd.aliases()[0], cmd.usage() + " - " + cmd.desc()); } + + if (method.isAnnotationPresent(NestedCommand.class)) { + NestedCommand nestedCmd = method.getAnnotation(NestedCommand.class); + + for (Class nestedCls : nestedCmd.value()) { + registerMethods(nestedCls, method); + } + } } } @@ -83,7 +115,7 @@ public class CommandsManager { * @return */ public boolean hasCommand(String command) { - return commands.containsKey(command.toLowerCase()); + return commands.get(null).containsKey(command.toLowerCase()); } /** @@ -98,14 +130,71 @@ public class CommandsManager { /** * Get the usage string for a command. * - * @param command + * @param args + * @param level * @param cmd * @return */ - private String getUsage(String command, Command cmd) { - return command - + (cmd.flags().length() > 0 ? " [-" + cmd.flags() + "] " : " ") - + cmd.usage(); + private String getUsage(String[] args, int level, Command cmd) { + StringBuilder command = new StringBuilder(); + + command.append("/"); + + for (int i = 0; i <= level; i++) { + command.append(args[i] + " "); + } + + command.append(cmd.flags().length() > 0 ? "[-" + cmd.flags() + "] " : ""); + command.append(cmd.usage()); + + return command.toString(); + } + + /** + * Get the usage string for a nested command. + * + * @param args + * @param level + * @param method + * @param palyer + * @return + */ + private String getNestedUsage(String[] args, int level, Method method, + LocalPlayer player) { + StringBuilder command = new StringBuilder(); + + command.append("/"); + + for (int i = 0; i <= level; i++) { + command.append(args[i] + " "); + } + + + Map map = commands.get(method); + + command.append("<"); + + List allowedCommands = new ArrayList(); + + for (Map.Entry entry : map.entrySet()) { + Method childMethod = entry.getValue(); + + if (hasPermission(childMethod, player)) { + Command childCmd = childMethod.getAnnotation(Command.class); + + allowedCommands.add(childCmd.aliases()[0]); + } + } + + if (allowedCommands.size() > 0) { + command.append(StringUtil.joinString(allowedCommands, "|", 0)); + } else { + command.append("action"); + } + + command.append(">"); + + return command.toString(); } /** @@ -118,61 +207,135 @@ public class CommandsManager { * @param editSession * @return */ - public boolean execute(CommandContext args, WorldEdit we, + public boolean execute(String[] args, WorldEdit we, LocalSession session, LocalPlayer player, EditSession editSession) throws WorldEditException, Throwable { - Method method = commands.get(args.getCommand().toLowerCase()); + return executeMethod(null, args, we, session, player, editSession, 0); + } + + /** + * Attempt to execute a command. + * + * @param parent + * @param args + * @param we + * @param session + * @param player + * @param editSession + * @param level + * @return + */ + public boolean executeMethod(Method parent, String[] args, WorldEdit we, + LocalSession session, LocalPlayer player, EditSession editSession, + int level) throws WorldEditException, Throwable { + String cmdName = args[level]; + + Map map = commands.get(parent); + Method method = map.get(cmdName.toLowerCase()); if (method == null) { - return false; // No command + if (parent == null) { // Root + return false; + } else { + player.printError(getNestedUsage(args, level - 1, method, player)); + return true; + } } if (!checkPermissions(method, player)) { return true; } + + int argsCount = args.length - 1 - level; - Command cmd = method.getAnnotation(Command.class); - - if (args.argsLength() < cmd.min()) { - player.printError("Too few arguments."); - player.printError(getUsage(args.getCommand(), cmd)); - return true; - } - - if (cmd.max() != -1 && args.argsLength() > cmd.max()) { - player.printError("Too many arguments."); - player.printError(getUsage(args.getCommand(), cmd)); - return true; - } - - for (char flag : args.getFlags()) { - if (cmd.flags().indexOf(String.valueOf(flag)) == -1) { - player.printError("Unknown flag: " + flag); - player.printError(getUsage(args.getCommand(), cmd)); + if (method.isAnnotationPresent(NestedCommand.class)) { + if (argsCount == 0) { + player.printError(getNestedUsage(args, level, method, player)); + return true; + } else { + return executeMethod(method, args, we, session, player, + editSession, level + 1); + } + } else { + Command cmd = method.getAnnotation(Command.class); + + String[] newArgs = new String[args.length - level]; + System.arraycopy(args, level, newArgs, 0, args.length - level); + + CommandContext context = new CommandContext(newArgs); + + if (context.argsLength() < cmd.min()) { + player.printError("Too few arguments."); + player.printError(getUsage(args, level, cmd)); return true; } - } - - try { - method.invoke(null, args, we, session, player, editSession); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - if (e.getCause() instanceof WorldEditException) { - throw (WorldEditException)e.getCause(); - } else if (e.getCause() instanceof NumberFormatException) { - throw (NumberFormatException)e.getCause(); - } else { - throw e.getCause(); + + if (cmd.max() != -1 && context.argsLength() > cmd.max()) { + player.printError("Too many arguments."); + player.printError(getUsage(args, level, cmd)); + return true; + } + + for (char flag : context.getFlags()) { + if (cmd.flags().indexOf(String.valueOf(flag)) == -1) { + player.printError("Unknown flag: " + flag); + player.printError(getUsage(args, level, cmd)); + return true; + } + } + + try { + method.invoke(null, context, we, session, player, editSession); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof WorldEditException) { + throw (WorldEditException)e.getCause(); + } else if (e.getCause() instanceof NumberFormatException) { + throw (NumberFormatException)e.getCause(); + } else { + throw e.getCause(); + } } } return true; } + /** + * Checks permissions, prints an error if needed. + * + * @param method + * @param player + * @return + */ private boolean checkPermissions(Method method, LocalPlayer player) { + if (!method.isAnnotationPresent(CommandPermissions.class)) { + return true; + } + + CommandPermissions perms = method.getAnnotation(CommandPermissions.class); + + for (String perm : perms.value()) { + if (player.hasPermission(perm)) { + return true; + } + } + + player.printError("You don't have permission for this command."); + return false; + } + + /** + * Returns whether a player has access to a command. + * + * @param method + * @param player + * @return + */ + private boolean hasPermission(Method method, LocalPlayer player) { CommandPermissions perms = method.getAnnotation(CommandPermissions.class); if (perms == null) { return true; @@ -184,7 +347,6 @@ public class CommandsManager { } } - player.printError("You don't have permission for this command."); return false; } }