From 2af8c8dbf2d5eb16d7492fd12b4b786d6205d0f9 Mon Sep 17 00:00:00 2001 From: Charlie Date: Thu, 31 Jul 2014 11:37:54 -0400 Subject: [PATCH] Squashed 'lib/' content from commit 25c3423 git-subtree-dir: lib git-subtree-split: 25c34231861721918079749db731f8af500eb371 --- .gitignore | 8 + LICENSE.txt | 227 +++++++ NOTICE.txt | 10 + README.md | 18 + bukkit/pom.xml | 58 ++ .../bukkit/util/BukkitCommandsManager.java | 12 + .../util/BukkitWrappedCommandSender.java | 54 ++ .../com/sk89q/bukkit/util/CommandInfo.java | 65 ++ .../bukkit/util/CommandRegistration.java | 112 ++++ .../util/CommandsManagerRegistration.java | 66 ++ .../bukkit/util/DynamicPluginCommand.java | 98 +++ .../util/DynamicPluginCommandHelpTopic.java | 126 ++++ .../util/FallbackRegistrationListener.java | 44 ++ bungee/pom.xml | 58 ++ .../bungee/util/BungeeCommandsManager.java | 12 + .../util/BungeeWrappedCommandSender.java | 47 ++ .../sk89q/bungee/util/CommandExecutor.java | 6 + .../bungee/util/CommandRegistration.java | 36 ++ .../com/sk89q/bungee/util/CommandWrapper.java | 18 + core/pom.xml | 13 + .../minecraft/util/commands/ChatColor.java | 252 ++++++++ .../minecraft/util/commands/Command.java | 93 +++ .../minecraft/util/commands/CommandAlias.java | 41 ++ .../util/commands/CommandContext.java | 338 ++++++++++ .../util/commands/CommandException.java | 72 +++ .../util/commands/CommandLocals.java | 50 ++ .../util/commands/CommandPermissions.java | 37 ++ .../commands/CommandPermissionsException.java | 29 + .../util/commands/CommandUsageException.java | 35 ++ .../util/commands/CommandsManager.java | 594 ++++++++++++++++++ .../minecraft/util/commands/Console.java | 32 + .../minecraft/util/commands/Injector.java | 41 ++ .../minecraft/util/commands/Logging.java | 66 ++ .../MissingNestedCommandException.java | 29 + .../util/commands/NestedCommand.java | 46 ++ .../util/commands/SimpleInjector.java | 62 ++ .../util/commands/SuggestionContext.java | 68 ++ .../commands/UnhandledCommandException.java | 25 + .../commands/WrappedCommandException.java | 28 + .../util/commands/WrappedCommandSender.java | 22 + .../util/commands/WrappedCommandsManager.java | 8 + .../util/pagination/PaginatedResult.java | 71 +++ .../pagination/SimplePaginatedResult.java | 23 + .../java/com/sk89q/util/ReflectionUtil.java | 48 ++ .../main/java/com/sk89q/util/StringUtil.java | 311 +++++++++ pom.xml | 61 ++ 46 files changed, 3570 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 NOTICE.txt create mode 100644 README.md create mode 100644 bukkit/pom.xml create mode 100644 bukkit/src/main/java/com/sk89q/bukkit/util/BukkitCommandsManager.java create mode 100644 bukkit/src/main/java/com/sk89q/bukkit/util/BukkitWrappedCommandSender.java create mode 100644 bukkit/src/main/java/com/sk89q/bukkit/util/CommandInfo.java create mode 100644 bukkit/src/main/java/com/sk89q/bukkit/util/CommandRegistration.java create mode 100644 bukkit/src/main/java/com/sk89q/bukkit/util/CommandsManagerRegistration.java create mode 100644 bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommand.java create mode 100644 bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommandHelpTopic.java create mode 100644 bukkit/src/main/java/com/sk89q/bukkit/util/FallbackRegistrationListener.java create mode 100644 bungee/pom.xml create mode 100644 bungee/src/main/java/com/sk89q/bungee/util/BungeeCommandsManager.java create mode 100644 bungee/src/main/java/com/sk89q/bungee/util/BungeeWrappedCommandSender.java create mode 100644 bungee/src/main/java/com/sk89q/bungee/util/CommandExecutor.java create mode 100644 bungee/src/main/java/com/sk89q/bungee/util/CommandRegistration.java create mode 100644 bungee/src/main/java/com/sk89q/bungee/util/CommandWrapper.java create mode 100644 core/pom.xml create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/ChatColor.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/Command.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/CommandAlias.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/CommandLocals.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/CommandPermissions.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/CommandPermissionsException.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/CommandUsageException.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/Console.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/Injector.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/Logging.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/MissingNestedCommandException.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/NestedCommand.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/SimpleInjector.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/SuggestionContext.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/UnhandledCommandException.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandException.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandSender.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandsManager.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/pagination/PaginatedResult.java create mode 100644 core/src/main/java/com/sk89q/minecraft/util/pagination/SimplePaginatedResult.java create mode 100644 core/src/main/java/com/sk89q/util/ReflectionUtil.java create mode 100644 core/src/main/java/com/sk89q/util/StringUtil.java create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0794c18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Maven +target/ + +# Java +*.class +*.jar +*.war +*.ear diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..be7c930 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,227 @@ +WorldEdit License +----------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + + +JNBT License +------------ + +Copyright (c) 2010 Graham Edgecombe +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the JNBT team nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +JChronic License +---------------- + +The MIT License + +Copyright (c) 2009 Mike Schrag, Sam Tingleff + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..4c14f12 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,10 @@ +This product includes software from JNBT (http://jnbt.sourceforge.net/). + +This product includes software from WorldEdit +(http://www.sk89q.com), under GNU Lesser General Public License, version 3. + +This product includes software from JOpt Simple, under the MIT license. + +This product includes software by toi. + +This product includes software from JChronic, under the MIT license. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..99274a2 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +sk89q-command-framework +======================= + +sk89q-command-framework is the command framework from sk89q's WorldEdit. It has been factored out so it may be used in other projects without having to include WorldEdit as a dependency. + +Compiling +--------- + +You need to have Maven installed (http://maven.apache.org). Once installed, simply run: + + mvn clean install + +Maven will automatically download dependencies for you. Note: For that to work, be sure to add Maven to your "PATH". + +Contributing +------------ + +Your submissions have to be licensed under the GNU General Public License v3. diff --git a/bukkit/pom.xml b/bukkit/pom.xml new file mode 100644 index 0000000..97201bf --- /dev/null +++ b/bukkit/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + + com.sk89q + command-framework-parent + 0.5-SNAPSHOT + + command-framework-bukkit + jar + 0.5-SNAPSHOT + Sk89q Command Framework for Bukkit + Supporting classes for running the command framework on Bukkit servers. + + + + bukkit-repo + http://repo.bukkit.org/content/groups/public + + + + + + com.sk89q + command-framework-core + 0.5-SNAPSHOT + + + org.bukkit + bukkit + 1.4.7-R1.0 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.1 + + + + com.sk89q:command-framework-core + + + + + + package + + shade + + + + + + + diff --git a/bukkit/src/main/java/com/sk89q/bukkit/util/BukkitCommandsManager.java b/bukkit/src/main/java/com/sk89q/bukkit/util/BukkitCommandsManager.java new file mode 100644 index 0000000..41ee940 --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/bukkit/util/BukkitCommandsManager.java @@ -0,0 +1,12 @@ +package com.sk89q.bukkit.util; + +import org.bukkit.command.CommandSender; + +import com.sk89q.minecraft.util.commands.CommandsManager; + +public class BukkitCommandsManager extends CommandsManager { + @Override + public boolean hasPermission(CommandSender player, String perm) { + return player.hasPermission(perm); + } +} diff --git a/bukkit/src/main/java/com/sk89q/bukkit/util/BukkitWrappedCommandSender.java b/bukkit/src/main/java/com/sk89q/bukkit/util/BukkitWrappedCommandSender.java new file mode 100644 index 0000000..e68d0d6 --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/bukkit/util/BukkitWrappedCommandSender.java @@ -0,0 +1,54 @@ +package com.sk89q.bukkit.util; + +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import com.sk89q.minecraft.util.commands.WrappedCommandSender; + +public class BukkitWrappedCommandSender implements WrappedCommandSender { + public BukkitWrappedCommandSender(CommandSender wrapped) { + this.wrapped = wrapped; + } + + @Override + public String getName() { + return this.getName(); + } + + @Override + public void sendMessage(String message) { + this.wrapped.sendMessage(message); + } + + @Override + public void sendMessage(String[] messages) { + this.wrapped.sendMessage(messages); + } + + @Override + public boolean hasPermission(String permission) { + return this.wrapped.hasPermission(permission); + } + + @Override + public Type getType() { + if (this.wrapped instanceof ConsoleCommandSender) { + return Type.CONSOLE; + } else if (this.wrapped instanceof Player) { + return Type.PLAYER; + } else if (this.wrapped instanceof BlockCommandSender) { + return Type.BLOCK; + } else { + return Type.UNKNOWN; + } + } + + @Override + public Object getCommandSender() { + return this.wrapped; + } + + private final CommandSender wrapped; +} diff --git a/bukkit/src/main/java/com/sk89q/bukkit/util/CommandInfo.java b/bukkit/src/main/java/com/sk89q/bukkit/util/CommandInfo.java new file mode 100644 index 0000000..6bcdd75 --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/bukkit/util/CommandInfo.java @@ -0,0 +1,65 @@ +/* + * WorldEdit + * Copyright (C) 2012 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.bukkit.util; + +/** + * @author zml2008 + */ +public class CommandInfo { + private final String[] aliases; + private final Object registeredWith; + private final String usage, desc; + private final String[] permissions; + + public CommandInfo(String usage, String desc, String[] aliases, Object registeredWith) { + this(usage, desc, aliases, registeredWith, null); + } + + public CommandInfo(String usage, String desc, String[] aliases, Object registeredWith, String[] permissions) { + this.usage = usage; + this.desc = desc; + this.aliases = aliases; + this.permissions = permissions; + this.registeredWith = registeredWith; + } + + public String[] getAliases() { + return aliases; + } + + public String getName() { + return aliases[0]; + } + + public String getUsage() { + return usage; + } + + public String getDesc() { + return desc; + } + + public String[] getPermissions() { + return permissions; + } + + public Object getRegisteredWith() { + return registeredWith; + } +} diff --git a/bukkit/src/main/java/com/sk89q/bukkit/util/CommandRegistration.java b/bukkit/src/main/java/com/sk89q/bukkit/util/CommandRegistration.java new file mode 100644 index 0000000..8c5b5f8 --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/bukkit/util/CommandRegistration.java @@ -0,0 +1,112 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2011 sk89q and contributors + * + * 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.bukkit.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.sk89q.util.ReflectionUtil; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.plugin.Plugin; + +/** + * @author zml2008 + */ +public class CommandRegistration { + + static { + Bukkit.getServer().getHelpMap().registerHelpTopicFactory(DynamicPluginCommand.class, new DynamicPluginCommandHelpTopic.Factory()); + } + + protected final Plugin plugin; + protected final CommandExecutor executor; + private CommandMap fallbackCommands; + + public CommandRegistration(Plugin plugin) { + this(plugin, plugin); + } + + public CommandRegistration(Plugin plugin, CommandExecutor executor) { + this.plugin = plugin; + this.executor = executor; + } + + public boolean register(List registered) { + CommandMap commandMap = getCommandMap(); + if (registered == null || commandMap == null) { + return false; + } + for (CommandInfo command : registered) { + DynamicPluginCommand cmd = new DynamicPluginCommand(command.getAliases(), + command.getDesc(), "/" + command.getAliases()[0] + " " + command.getUsage(), executor, command.getRegisteredWith(), plugin); + cmd.setPermissions(command.getPermissions()); + commandMap.register(plugin.getDescription().getName(), cmd); + } + return true; + } + + public CommandMap getCommandMap() { + CommandMap commandMap = ReflectionUtil.getField(plugin.getServer().getPluginManager(), "commandMap"); + if (commandMap == null) { + if (fallbackCommands != null) { + commandMap = fallbackCommands; + } else { + Bukkit.getServer().getLogger().severe(plugin.getDescription().getName() + + ": Could not retrieve server CommandMap, using fallback instead! Please report to http://redmine.sk89q.com"); + fallbackCommands = commandMap = new SimpleCommandMap(Bukkit.getServer()); + Bukkit.getServer().getPluginManager().registerEvents(new FallbackRegistrationListener(fallbackCommands), plugin); + } + } + return commandMap; + } + + public boolean unregisterCommands() { + CommandMap commandMap = getCommandMap(); + List toRemove = new ArrayList(); + Map knownCommands = ReflectionUtil.getField(commandMap, "knownCommands"); + Set aliases = ReflectionUtil.getField(commandMap, "aliases"); + if (knownCommands == null || aliases == null) { + return false; + } + for (Iterator i = knownCommands.values().iterator(); i.hasNext();) { + org.bukkit.command.Command cmd = i.next(); + if (cmd instanceof DynamicPluginCommand && ((DynamicPluginCommand) cmd).getOwner().equals(executor)) { + i.remove(); + for (String alias : cmd.getAliases()) { + org.bukkit.command.Command aliasCmd = knownCommands.get(alias); + if (cmd.equals(aliasCmd)) { + aliases.remove(alias); + toRemove.add(alias); + } + } + } + } + for (String string : toRemove) { + knownCommands.remove(string); + } + return true; + } +} diff --git a/bukkit/src/main/java/com/sk89q/bukkit/util/CommandsManagerRegistration.java b/bukkit/src/main/java/com/sk89q/bukkit/util/CommandsManagerRegistration.java new file mode 100644 index 0000000..57862ed --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/bukkit/util/CommandsManagerRegistration.java @@ -0,0 +1,66 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2011 sk89q and contributors + * + * 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.bukkit.util; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.minecraft.util.commands.CommandsManager; +import org.bukkit.command.CommandExecutor; +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * @author zml2008 + */ +public class CommandsManagerRegistration extends CommandRegistration { + protected CommandsManager commands; + + public CommandsManagerRegistration(Plugin plugin, CommandsManager commands) { + super(plugin); + this.commands = commands; + } + + public CommandsManagerRegistration(Plugin plugin, CommandExecutor executor, CommandsManager commands) { + super(plugin, executor); + this.commands = commands; + } + + public boolean register(Class clazz) { + return registerAll(commands.registerAndReturn(clazz)); + } + + public boolean registerAll(List registered) { + List toRegister = new ArrayList(); + for (Command command : registered) { + String[] permissions = null; + Method cmdMethod = commands.getMethods().get(null).get(command.aliases()[0]); + if (cmdMethod != null && cmdMethod.isAnnotationPresent(CommandPermissions.class)) { + permissions = cmdMethod.getAnnotation(CommandPermissions.class).value(); + } + + toRegister.add(new CommandInfo(command.usage(), command.desc(), command.aliases(), commands, permissions)); + } + + return register(toRegister); + } +} diff --git a/bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommand.java b/bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommand.java new file mode 100644 index 0000000..e4ef24e --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommand.java @@ -0,0 +1,98 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2011 sk89q and contributors + * + * 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.bukkit.util; + +import com.sk89q.minecraft.util.commands.CommandsManager; +import com.sk89q.util.StringUtil; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginIdentifiableCommand; +import org.bukkit.plugin.Plugin; + +import java.util.Arrays; + +/** +* @author zml2008 +*/ +public class DynamicPluginCommand extends org.bukkit.command.Command implements PluginIdentifiableCommand { + + protected final CommandExecutor owner; + protected final Object registeredWith; + protected final Plugin owningPlugin; + protected String[] permissions = new String[0]; + + public DynamicPluginCommand(String[] aliases, String desc, String usage, CommandExecutor owner, Object registeredWith, Plugin plugin) { + super(aliases[0], desc, usage, Arrays.asList(aliases)); + this.owner = owner; + this.owningPlugin = plugin; + this.registeredWith = registeredWith; + } + + @Override + public boolean execute(CommandSender sender, String label, String[] args) { + return owner.onCommand(sender, this, label, args); + } + + public Object getOwner() { + return owner; + } + + public Object getRegisteredWith() { + return registeredWith; + } + + public void setPermissions(String[] permissions) { + this.permissions = permissions; + if (permissions != null) { + super.setPermission(StringUtil.joinString(permissions, ";")); + } + } + + public String[] getPermissions() { + return permissions; + } + + @Override + public Plugin getPlugin() { + return owningPlugin; + } + + @SuppressWarnings("unchecked") + @Override + public boolean testPermissionSilent(CommandSender sender) { + if (permissions == null || permissions.length == 0) { + return true; + } + + if (registeredWith instanceof CommandsManager) { + try { + for (String permission : permissions) { + if (((CommandsManager) registeredWith).hasPermission(sender, permission)) { + return true; + } + } + return false; + } catch (Throwable ignore) { + } + } + return super.testPermissionSilent(sender); + } +} diff --git a/bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommandHelpTopic.java b/bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommandHelpTopic.java new file mode 100644 index 0000000..4b4d409 --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/bukkit/util/DynamicPluginCommandHelpTopic.java @@ -0,0 +1,126 @@ +/* + * WorldEdit + * Copyright (C) 2012 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.bukkit.util; + +import com.sk89q.minecraft.util.commands.CommandsManager; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.help.HelpTopic; +import org.bukkit.help.HelpTopicFactory; + +import java.util.Map; + +/** + * @author zml2008 + */ +public class DynamicPluginCommandHelpTopic extends HelpTopic { + private final DynamicPluginCommand cmd; + + public DynamicPluginCommandHelpTopic(DynamicPluginCommand cmd) { + this.cmd = cmd; + this.name = "/" + cmd.getName(); + + String fullTextTemp = null; + StringBuilder fullText = new StringBuilder(); + + if (cmd.getRegisteredWith() instanceof CommandsManager) { + Map helpText = ((CommandsManager) cmd.getRegisteredWith()).getHelpMessages(); + final String lookupName = cmd.getName().replaceAll("/", ""); + if (helpText.containsKey(lookupName)) { // We have full help text for this command + fullTextTemp = helpText.get(lookupName); + } + // No full help text, assemble help text from info + helpText = ((CommandsManager) cmd.getRegisteredWith()).getCommands(); + if (helpText.containsKey(cmd.getName())) { + final String shortText = helpText.get(cmd.getName()); + if (fullTextTemp == null) { + fullTextTemp = this.name + " " + shortText; + } + this.shortText = shortText; + } + } else { + this.shortText = cmd.getDescription(); + } + + // Put the usage in the format: Usage string (newline) Aliases (newline) Help text + String[] split = fullTextTemp == null ? new String[2] : fullTextTemp.split("\n", 2); + fullText.append(ChatColor.BOLD).append(ChatColor.GOLD).append("Usage: ").append(ChatColor.WHITE); + fullText.append(split[0]).append("\n"); + + if (cmd.getAliases().size() > 0) { + fullText.append(ChatColor.BOLD).append(ChatColor.GOLD).append("Aliases: ").append(ChatColor.WHITE); + boolean first = true; + for (String alias : cmd.getAliases()) { + if (!first) { + fullText.append(", "); + } + fullText.append(alias); + first = false; + } + fullText.append("\n"); + } + if (split.length > 1) { + fullText.append(split[1]); + } + this.fullText = fullText.toString(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean canSee(CommandSender player) { + if (cmd.getPermissions() != null && cmd.getPermissions().length > 0) { + if (cmd.getRegisteredWith() instanceof CommandsManager) { + try { + for (String perm : cmd.getPermissions()) { + if (((CommandsManager) cmd.getRegisteredWith()).hasPermission(player, perm)) { + return true; + } + } + } catch (Throwable t) { + // Doesn't take the CommandSender (Hooray for compile-time generics!), we have other methods at our disposal + } + } + for (String perm : cmd.getPermissions()) { + if (player.hasPermission(perm)) { + return true; + } + } + return false; + } + return true; + } + + @Override + public String getFullText(CommandSender forWho) { + if (this.fullText == null || this.fullText.length() == 0) { + return getShortText(); + } else { + return this.fullText; + } + } + + public static class Factory implements HelpTopicFactory { + + @Override + public HelpTopic createTopic(DynamicPluginCommand command) { + return new DynamicPluginCommandHelpTopic(command); + } + } +} diff --git a/bukkit/src/main/java/com/sk89q/bukkit/util/FallbackRegistrationListener.java b/bukkit/src/main/java/com/sk89q/bukkit/util/FallbackRegistrationListener.java new file mode 100644 index 0000000..6123e02 --- /dev/null +++ b/bukkit/src/main/java/com/sk89q/bukkit/util/FallbackRegistrationListener.java @@ -0,0 +1,44 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2011 sk89q and contributors + * + * 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.bukkit.util; + +import org.bukkit.command.CommandMap; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; + +/** +* @author zml2008 +*/ +public class FallbackRegistrationListener implements Listener { + + private final CommandMap commandRegistration; + + public FallbackRegistrationListener(CommandMap commandRegistration) { + this.commandRegistration = commandRegistration; + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { + if (commandRegistration.dispatch(event.getPlayer(), event.getMessage())) { + event.setCancelled(true); + } + } +} diff --git a/bungee/pom.xml b/bungee/pom.xml new file mode 100644 index 0000000..d5bf4f4 --- /dev/null +++ b/bungee/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + + com.sk89q + command-framework-parent + 0.5-SNAPSHOT + + command-framework-bungee + jar + 0.5-SNAPSHOT + Sk89q Command Framework for BungeeCord + Supporting classes for using the command framework on BungeeCord. + + + + sonatype-public + https://oss.sonatype.org/content/groups/public + + + + + + com.sk89q + command-framework-core + 0.5-SNAPSHOT + + + net.md-5 + bungeecord-api + 1.5-SNAPSHOT + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.1 + + + + com.sk89q:command-framework-core + + + + + + package + + shade + + + + + + + diff --git a/bungee/src/main/java/com/sk89q/bungee/util/BungeeCommandsManager.java b/bungee/src/main/java/com/sk89q/bungee/util/BungeeCommandsManager.java new file mode 100644 index 0000000..dc48966 --- /dev/null +++ b/bungee/src/main/java/com/sk89q/bungee/util/BungeeCommandsManager.java @@ -0,0 +1,12 @@ +package com.sk89q.bungee.util; + +import net.md_5.bungee.api.CommandSender; + +import com.sk89q.minecraft.util.commands.CommandsManager; + +public class BungeeCommandsManager extends CommandsManager { + @Override + public boolean hasPermission(CommandSender player, String perm) { + return player.hasPermission(perm); + } +} diff --git a/bungee/src/main/java/com/sk89q/bungee/util/BungeeWrappedCommandSender.java b/bungee/src/main/java/com/sk89q/bungee/util/BungeeWrappedCommandSender.java new file mode 100644 index 0000000..1096fe8 --- /dev/null +++ b/bungee/src/main/java/com/sk89q/bungee/util/BungeeWrappedCommandSender.java @@ -0,0 +1,47 @@ +package com.sk89q.bungee.util; + +import net.md_5.bungee.api.CommandSender; + +import com.sk89q.minecraft.util.commands.WrappedCommandSender; + +public class BungeeWrappedCommandSender implements WrappedCommandSender { + public BungeeWrappedCommandSender(CommandSender wrapped) { + this.wrapped = wrapped; + } + + @Override + public String getName() { + return this.wrapped.getName(); + } + + @Override + public void sendMessage(String message) { + this.wrapped.sendMessage(message); + } + + @Override + public void sendMessage(String[] messages) { + this.wrapped.sendMessages(messages); + } + + @Override + public boolean hasPermission(String permission) { + return this.wrapped.hasPermission(permission); + } + + @Override + public Type getType() { + if (this.wrapped.getName().equals("CONSOLE")) { // hack because Bungee does not export ConsoleCommandSender + return Type.CONSOLE; + } else { + return Type.PLAYER; + } + } + + @Override + public Object getCommandSender() { + return this.wrapped; + } + + private final CommandSender wrapped; +} diff --git a/bungee/src/main/java/com/sk89q/bungee/util/CommandExecutor.java b/bungee/src/main/java/com/sk89q/bungee/util/CommandExecutor.java new file mode 100644 index 0000000..1a93c25 --- /dev/null +++ b/bungee/src/main/java/com/sk89q/bungee/util/CommandExecutor.java @@ -0,0 +1,6 @@ +package com.sk89q.bungee.util; + + +public interface CommandExecutor { + void onCommand(T sender, String commandName, String[] args); +} diff --git a/bungee/src/main/java/com/sk89q/bungee/util/CommandRegistration.java b/bungee/src/main/java/com/sk89q/bungee/util/CommandRegistration.java new file mode 100644 index 0000000..b244a52 --- /dev/null +++ b/bungee/src/main/java/com/sk89q/bungee/util/CommandRegistration.java @@ -0,0 +1,36 @@ +package com.sk89q.bungee.util; + +import java.util.List; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.plugin.PluginManager; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandsManager; + +public class CommandRegistration { + public CommandRegistration(Plugin plugin, PluginManager pluginManager, CommandsManager commands, CommandExecutor executor) { + this.plugin = plugin; + this.pluginManager = pluginManager; + this.commands = commands; + this.executor = executor; + } + + public boolean register(Class clazz) { + return this.registerAll(this.commands.registerAndReturn(clazz)); + } + + public boolean registerAll(List registered) { + for(Command command : registered) { + CommandWrapper wrapper = new CommandWrapper(this.executor, command.aliases()[0], command.aliases()); + this.pluginManager.registerCommand(this.plugin, wrapper); + } + return true; + } + + private final Plugin plugin; + private final PluginManager pluginManager; + private final CommandsManager commands; + private final CommandExecutor executor; +} diff --git a/bungee/src/main/java/com/sk89q/bungee/util/CommandWrapper.java b/bungee/src/main/java/com/sk89q/bungee/util/CommandWrapper.java new file mode 100644 index 0000000..7255de0 --- /dev/null +++ b/bungee/src/main/java/com/sk89q/bungee/util/CommandWrapper.java @@ -0,0 +1,18 @@ +package com.sk89q.bungee.util; + +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.plugin.Command; + +public class CommandWrapper extends Command { + public CommandWrapper(CommandExecutor executor, String commandName, String... aliases) { + super(commandName, null, aliases); + this.executor = executor; + } + + @Override + public void execute(CommandSender sender, String[] args) { + this.executor.onCommand(sender, this.getName(), args); + } + + private final CommandExecutor executor; +} diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..216d6db --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,13 @@ + + 4.0.0 + + com.sk89q + command-framework-parent + 0.5-SNAPSHOT + + command-framework-core + jar + 0.5-SNAPSHOT + Sk89q Command Framework Core + Core classes for the sk89q command framework. + diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/ChatColor.java b/core/src/main/java/com/sk89q/minecraft/util/commands/ChatColor.java new file mode 100644 index 0000000..9de0e73 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/ChatColor.java @@ -0,0 +1,252 @@ +package com.sk89q.minecraft.util.commands; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * All supported color values for chat + * + * Class provided as part of the Bukkit project with slight modifications to + * reduce dependencies. + * + * @see https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/ChatColor.java + */ +public enum ChatColor { + /** + * Represents black + */ + BLACK('0', 0x00), + /** + * Represents dark blue + */ + DARK_BLUE('1', 0x1), + /** + * Represents dark green + */ + DARK_GREEN('2', 0x2), + /** + * Represents dark blue (aqua) + */ + DARK_AQUA('3', 0x3), + /** + * Represents dark red + */ + DARK_RED('4', 0x4), + /** + * Represents dark purple + */ + DARK_PURPLE('5', 0x5), + /** + * Represents gold + */ + GOLD('6', 0x6), + /** + * Represents gray + */ + GRAY('7', 0x7), + /** + * Represents dark gray + */ + DARK_GRAY('8', 0x8), + /** + * Represents blue + */ + BLUE('9', 0x9), + /** + * Represents green + */ + GREEN('a', 0xA), + /** + * Represents aqua + */ + AQUA('b', 0xB), + /** + * Represents red + */ + RED('c', 0xC), + /** + * Represents light purple + */ + LIGHT_PURPLE('d', 0xD), + /** + * Represents yellow + */ + YELLOW('e', 0xE), + /** + * Represents white + */ + WHITE('f', 0xF), + /** + * Represents magical characters that change around randomly + */ + MAGIC('k', 0x10, true), + /** + * Makes the text bold. + */ + BOLD('l', 0x11, true), + /** + * Makes a line appear through the text. + */ + STRIKETHROUGH('m', 0x12, true), + /** + * Makes the text appear underlined. + */ + UNDERLINE('n', 0x13, true), + /** + * Makes the text italic. + */ + ITALIC('o', 0x14, true), + /** + * Resets all previous chat colors or formats. + */ + RESET('r', 0x15); + + /** + * The special character which prefixes all chat colour codes. Use this if you need to dynamically + * convert colour codes from your custom format. + */ + public static final char COLOR_CHAR = '\u00A7'; + private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf(COLOR_CHAR) + "[0-9A-FK-OR]"); + + private final int intCode; + private final char code; + private final boolean isFormat; + private final String toString; + private final static Map BY_ID = new HashMap(); + private final static Map BY_CHAR = new HashMap(); + + private ChatColor(char code, int intCode) { + this(code, intCode, false); + } + + private ChatColor(char code, int intCode, boolean isFormat) { + this.code = code; + this.intCode = intCode; + this.isFormat = isFormat; + this.toString = new String(new char[] {COLOR_CHAR, code}); + } + + /** + * Gets the char value associated with this color + * + * @return A char value of this color code + */ + public char getChar() { + return this.code; + } + + @Override + public String toString() { + return this.toString; + } + + /** + * Checks if this code is a format code as opposed to a color code. + */ + public boolean isFormat() { + return this.isFormat; + } + + /** + * Checks if this code is a color code as opposed to a format code. + */ + public boolean isColor() { + return !this.isFormat && this != RESET; + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link org.bukkit.ChatColor} with the given code, or null if it doesn't exist + */ + public static ChatColor getByChar(char code) { + return BY_CHAR.get(code); + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link org.bukkit.ChatColor} with the given code, or null if it doesn't exist + */ + public static ChatColor getByChar(String code) { + if (code == null) throw new NullPointerException("Code cannot be null"); + if (code.isEmpty()) throw new IllegalArgumentException("Code must have at least one char"); + + return BY_CHAR.get(code.charAt(0)); + } + + /** + * Strips the given message of all color codes + * + * @param input String to strip of color + * @return A copy of the input string, without any coloring + */ + public static String stripColor(final String input) { + if (input == null) { + return null; + } + + return STRIP_COLOR_PATTERN.matcher(input).replaceAll(""); + } + + /** + * Translates a string using an alternate color code character into a string that uses the internal + * ChatColor.COLOR_CODE color code character. The alternate color code character will only be replaced + * if it is immediately followed by 0-9, A-F, a-f, K-O, k-o, R or r. + * + * @param altColorChar The alternate color code character to replace. Ex: & + * @param textToTranslate Text containing the alternate color code character. + * @return Text containing the ChatColor.COLOR_CODE color code character. + */ + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { + char[] b = textToTranslate.toCharArray(); + for (int i = 0; i < b.length - 1; i++) { + if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i+1]) > -1) { + b[i] = ChatColor.COLOR_CHAR; + b[i+1] = Character.toLowerCase(b[i+1]); + } + } + return new String(b); + } + + /** + * Gets the ChatColors used at the end of the given input string. + * + * @param input Input string to retrieve the colors from. + * @return Any remaining ChatColors to pass onto the next line. + */ + public static String getLastColors(String input) { + String result = ""; + int length = input.length(); + + // Search backwards from the end as it is faster + for (int index = length - 1; index > -1; index--) { + char section = input.charAt(index); + if (section == COLOR_CHAR && index < length - 1) { + char c = input.charAt(index + 1); + ChatColor color = getByChar(c); + + if (color != null) { + result = color.toString() + result; + + // Once we find a color or reset we can stop searching + if (color.isColor() || color.equals(RESET)) { + break; + } + } + } + } + + return result; + } + + static { + for (ChatColor color : values()) { + BY_ID.put(color.intCode, color); + BY_CHAR.put(color.code, color); + } + } +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/Command.java b/core/src/main/java/com/sk89q/minecraft/util/commands/Command.java new file mode 100644 index 0000000..44277cd --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/Command.java @@ -0,0 +1,93 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This annotation indicates a command. Methods should be marked with this + * annotation to tell {@link CommandsManager} that the method is a command. + * Note that the method name can actually be anything. + * + * @author sk89q + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Command { + /** + * A list of aliases for the command. The first alias is the most + * important -- it is the main name of the command. (The method name + * is never used for anything). + * + * @return Aliases for a command + */ + String[] aliases(); + + /** + * Usage instruction. Example text for usage could be + * [-h harps] [name] [message]. + * + * @return Usage instructions for a command + */ + String usage() default ""; + + /** + * @return A short description for the command. + */ + String desc(); + + /** + * The minimum number of arguments. This should be 0 or above. + * + * @return the minimum number of arguments + */ + int min() default 0; + + /** + * The maximum number of arguments. Use -1 for an unlimited number + * of arguments. + * + * @return the maximum number of arguments + */ + int max() default -1; + + /** + * Flags allow special processing for flags such as -h in the command, + * allowing users to easily turn on a flag. This is a string with + * each character being a flag. Use A-Z and a-z as possible flags. + * Appending a flag with a : makes the flag character before a value flag, + * meaning that if it is given it must have a value + * + * @return Flags matching a-zA-Z + */ + String flags() default ""; + + /** + * @return A long description for the command. + */ + String help() default ""; + + /** + * + * + * @return Whether any flag can be provided to the command, even if it is not in {@link #flags()} + */ + boolean anyFlags() default false; +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/CommandAlias.java b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandAlias.java new file mode 100644 index 0000000..61042b9 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandAlias.java @@ -0,0 +1,41 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Any command with this annotation will run the raw command as shown in the + * thing, as long as it is registered in the current {@link CommandsManager}. + * Mostly to move commands around without breaking things. + * + * @author zml2008 + */ + +@Retention(RetentionPolicy.RUNTIME) +public @interface CommandAlias { + + /** + * + * @return Raw {@link CommandsManager}-formatted command arg array to run + */ + String[] value(); +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java new file mode 100644 index 0000000..24cfcf7 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java @@ -0,0 +1,338 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class CommandContext { + + protected final String command; + protected final List parsedArgs; + protected final List originalArgIndices; + protected final String[] originalArgs; + protected final Set booleanFlags = new HashSet(); + protected final Map valueFlags = new HashMap(); + protected final SuggestionContext suggestionContext; + protected final CommandLocals locals; + + public static String[] split(String args) { + return args.split(" ", -1); + } + + public CommandContext(String args) throws CommandException { + this(args.split(" ", -1), null); + } + + public CommandContext(String[] args) throws CommandException { + this(args, null); + } + + public CommandContext(String args, Set valueFlags) throws CommandException { + this(args.split(" ", -1), valueFlags); + } + + public CommandContext(String args, Set valueFlags, boolean allowHangingFlag) + throws CommandException { + this(args.split(" ", -1), valueFlags, allowHangingFlag, new CommandLocals()); + } + + public CommandContext(String[] args, Set valueFlags) throws CommandException { + this(args, valueFlags, false, null); + } + + /** + * Parse the given array of arguments. + * + *

Empty arguments are removed from the list of arguments.

+ * + * @param args an array with arguments + * @param valueFlags a set containing all value flags (pass null to disable value flag parsing) + * @param allowHangingFlag true if hanging flags are allowed + * @param locals the locals, null to create empty one + * @throws CommandException thrown on a parsing error + */ + public CommandContext(String[] args, Set valueFlags, + boolean allowHangingFlag, CommandLocals locals) throws CommandException { + if (valueFlags == null) { + valueFlags = Collections.emptySet(); + } + + originalArgs = args; + command = args[0]; + this.locals = locals != null ? locals : new CommandLocals(); + boolean isHanging = false; + SuggestionContext suggestionContext = SuggestionContext.hangingValue(); + + // Eliminate empty args and combine multiword args first + List argIndexList = new ArrayList(args.length); + List argList = new ArrayList(args.length); + for (int i = 1; i < args.length; ++i) { + isHanging = false; + + String arg = args[i]; + if (arg.length() == 0) { + isHanging = true; + continue; + } + + argIndexList.add(i); + + switch (arg.charAt(0)) { + case '\'': + case '"': + final StringBuilder build = new StringBuilder(); + final char quotedChar = arg.charAt(0); + + int endIndex; + for (endIndex = i; endIndex < args.length; ++endIndex) { + final String arg2 = args[endIndex]; + if (arg2.charAt(arg2.length() - 1) == quotedChar && arg2.length() > 1) { + if (endIndex != i) build.append(' '); + build.append(arg2.substring(endIndex == i ? 1 : 0, arg2.length() - 1)); + break; + } else if (endIndex == i) { + build.append(arg2.substring(1)); + } else { + build.append(' ').append(arg2); + } + } + + if (endIndex < args.length) { + arg = build.toString(); + i = endIndex; + } + + // In case there is an empty quoted string + if (arg.length() == 0) { + continue; + } + // else raise exception about hanging quotes? + } + argList.add(arg); + } + + // Then flags + + this.originalArgIndices = new ArrayList(argIndexList.size()); + this.parsedArgs = new ArrayList(argList.size()); + + for (int nextArg = 0; nextArg < argList.size(); ) { + // Fetch argument + String arg = argList.get(nextArg++); + suggestionContext = SuggestionContext.hangingValue(); + + // Not a flag? + if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z]+$")) { + if (!isHanging) { + suggestionContext = SuggestionContext.lastValue(); + } + + originalArgIndices.add(argIndexList.get(nextArg - 1)); + parsedArgs.add(arg); + continue; + } + + // Handle flag parsing terminator -- + if (arg.equals("--")) { + while (nextArg < argList.size()) { + originalArgIndices.add(argIndexList.get(nextArg)); + parsedArgs.add(argList.get(nextArg++)); + } + break; + } + + // Go through the flag characters + for (int i = 1; i < arg.length(); ++i) { + char flagName = arg.charAt(i); + + if (valueFlags.contains(flagName)) { + if (this.valueFlags.containsKey(flagName)) { + throw new CommandException("Value flag '" + flagName + "' already given"); + } + + if (nextArg >= argList.size()) { + if (allowHangingFlag) { + suggestionContext = SuggestionContext.flag(flagName); + break; + } else { + throw new CommandException("No value specified for the '-" + flagName + "' flag."); + } + } + + // If it is a value flag, read another argument and add it + this.valueFlags.put(flagName, argList.get(nextArg++)); + if (!isHanging) { + suggestionContext = SuggestionContext.flag(flagName); + } + } else { + booleanFlags.add(flagName); + } + } + } + + this.suggestionContext = suggestionContext; + } + + public SuggestionContext getSuggestionContext() { + return suggestionContext; + } + + public String getCommand() { + return command; + } + + public boolean matches(String command) { + return this.command.equalsIgnoreCase(command); + } + + public String getString(int index) { + return parsedArgs.get(index); + } + + public String getString(int index, String def) { + return index < parsedArgs.size() ? parsedArgs.get(index) : def; + } + + public String getJoinedStrings(int initialIndex) { + initialIndex = originalArgIndices.get(initialIndex); + StringBuilder buffer = new StringBuilder(originalArgs[initialIndex]); + for (int i = initialIndex + 1; i < originalArgs.length; ++i) { + buffer.append(" ").append(originalArgs[i]); + } + return buffer.toString(); + } + + public String getRemainingString(int start) { + return getString(start, parsedArgs.size() - 1); + } + + public String getString(int start, int end) { + StringBuilder buffer = new StringBuilder(parsedArgs.get(start)); + for (int i = start + 1; i < end + 1; ++i) { + buffer.append(" ").append(parsedArgs.get(i)); + } + return buffer.toString(); + } + + public int getInteger(int index) throws NumberFormatException { + return Integer.parseInt(parsedArgs.get(index)); + } + + public int getInteger(int index, int def) throws NumberFormatException { + return index < parsedArgs.size() ? Integer.parseInt(parsedArgs.get(index)) : def; + } + + public double getDouble(int index) throws NumberFormatException { + return Double.parseDouble(parsedArgs.get(index)); + } + + public double getDouble(int index, double def) throws NumberFormatException { + return index < parsedArgs.size() ? Double.parseDouble(parsedArgs.get(index)) : def; + } + + public String[] getSlice(int index) { + String[] slice = new String[originalArgs.length - index]; + System.arraycopy(originalArgs, index, slice, 0, originalArgs.length - index); + return slice; + } + + public String[] getPaddedSlice(int index, int padding) { + String[] slice = new String[originalArgs.length - index + padding]; + System.arraycopy(originalArgs, index, slice, padding, originalArgs.length - index); + return slice; + } + + public String[] getParsedSlice(int index) { + String[] slice = new String[parsedArgs.size() - index]; + System.arraycopy(parsedArgs.toArray(new String[parsedArgs.size()]), index, slice, 0, parsedArgs.size() - index); + return slice; + } + + public String[] getParsedPaddedSlice(int index, int padding) { + String[] slice = new String[parsedArgs.size() - index + padding]; + System.arraycopy(parsedArgs.toArray(new String[parsedArgs.size()]), index, slice, padding, parsedArgs.size() - index); + return slice; + } + + public boolean hasFlag(char ch) { + return booleanFlags.contains(ch) || valueFlags.containsKey(ch); + } + + public Set getFlags() { + return booleanFlags; + } + + public Map getValueFlags() { + return valueFlags; + } + + public String getFlag(char ch) { + return valueFlags.get(ch); + } + + public String getFlag(char ch, String def) { + final String value = valueFlags.get(ch); + if (value == null) { + return def; + } + + return value; + } + + public int getFlagInteger(char ch) throws NumberFormatException { + return Integer.parseInt(valueFlags.get(ch)); + } + + public int getFlagInteger(char ch, int def) throws NumberFormatException { + final String value = valueFlags.get(ch); + if (value == null) { + return def; + } + + return Integer.parseInt(value); + } + + public double getFlagDouble(char ch) throws NumberFormatException { + return Double.parseDouble(valueFlags.get(ch)); + } + + public double getFlagDouble(char ch, double def) throws NumberFormatException { + final String value = valueFlags.get(ch); + if (value == null) { + return def; + } + + return Double.parseDouble(value); + } + + public int argsLength() { + return parsedArgs.size(); + } + + public CommandLocals getLocals() { + return locals; + } +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java new file mode 100644 index 0000000..513235e --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java @@ -0,0 +1,72 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; + +public class CommandException extends Exception { + + private static final long serialVersionUID = 870638193072101739L; + private List commandStack = new ArrayList(); + + public CommandException() { + super(); + } + + public CommandException(String message) { + super(message); + } + + public CommandException(String message, Throwable t) { + super(message, t); + } + + public CommandException(Throwable t) { + super(t); + } + + public void prependStack(String name) { + commandStack.add(name); + } + + public String toStackString(String prefix, String spacedSuffix) { + StringBuilder builder = new StringBuilder(); + if (prefix != null) { + builder.append(prefix); + } + ListIterator li = commandStack.listIterator(commandStack.size()); + while (li.hasPrevious()) { + if (li.previousIndex() != commandStack.size() - 1) { + builder.append(" "); + } + builder.append(li.previous()); + } + if (spacedSuffix != null) { + if (builder.length() > 0) { + builder.append(" "); + } + builder.append(spacedSuffix); + } + return builder.toString(); + } + +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/CommandLocals.java b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandLocals.java new file mode 100644 index 0000000..e0053f0 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandLocals.java @@ -0,0 +1,50 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.util.HashMap; +import java.util.Map; + +public class CommandLocals { + + private final Map locals = new HashMap(); + + public boolean containsKey(Object key) { + return locals.containsKey(key); + } + + public boolean containsValue(Object value) { + return locals.containsValue(value); + } + + public Object get(Object key) { + return locals.get(key); + } + + @SuppressWarnings("unchecked") + public T get(Class key) { + return (T) locals.get(key); + } + + public Object put(Object key, Object value) { + return locals.put(key, value); + } + +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/CommandPermissions.java b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandPermissions.java new file mode 100644 index 0000000..c6429ff --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandPermissions.java @@ -0,0 +1,37 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates a list of permissions that should be checked. + * + * @author sk89q + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface CommandPermissions { + /** + * A list of permissions. Only one permission has to be met + * for the command to be permitted. + */ + String[] value(); +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/CommandPermissionsException.java b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandPermissionsException.java new file mode 100644 index 0000000..d057045 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandPermissionsException.java @@ -0,0 +1,29 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +/** + * Thrown when not enough permissions are satisfied. + * + * @author sk89q + */ +public class CommandPermissionsException extends CommandException { + private static final long serialVersionUID = -602374621030168291L; +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/CommandUsageException.java b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandUsageException.java new file mode 100644 index 0000000..b7a847c --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandUsageException.java @@ -0,0 +1,35 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +public class CommandUsageException extends CommandException { + private static final long serialVersionUID = -6761418114414516542L; + + protected String usage; + + public CommandUsageException(String message, String usage) { + super(message); + this.usage = usage; + } + + public String getUsage() { + return usage; + } +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java new file mode 100644 index 0000000..be0f161 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java @@ -0,0 +1,594 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sk89q.util.StringUtil; + +/** + *

Manager for handling commands. This allows you to easily process commands, + * including nested commands, by correctly annotating methods of a class.

+ * + *

To use this, it is merely a matter of registering classes containing + * the commands (as methods with the proper annotations) with the + * manager. When you want to process a command, use one of the + * execute methods. If something is wrong, such as incorrect + * usage, insufficient permissions, or a missing command altogether, an + * exception will be raised for upstream handling.

+ * + *

Methods of a class to be registered can be static, but if an injector + * is registered with the class, the instances of the command classes + * will be created automatically and methods will be called non-statically.

+ * + *

To mark a method as a command, use {@link Command}. For nested commands, + * see {@link NestedCommand}. To handle permissions, use + * {@link CommandPermissions}.

+ * + *

This uses Java reflection extensively, but to reduce the overhead of + * reflection, command lookups are completely cached on registration. This + * allows for fast command handling. Method invocation still has to be done + * with reflection, but this is quite fast in that of itself.

+ * + * @author sk89q + * @param command sender class + */ +public abstract class CommandsManager { + /** + * Logger for general errors. + */ + protected static final Logger logger = + Logger.getLogger(CommandsManager.class.getCanonicalName()); + + /** + * 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 {@link Method}. The child map has + * the key of the command name (one for each alias) with the + * method. + */ + protected Map> commands = new HashMap>(); + + /** + * Used to store the instances associated with a method. + */ + protected Map instances = new HashMap(); + + /** + * Mapping of commands (not including aliases) with a description. This + * is only for top level commands. + */ + protected Map descs = new HashMap(); + + /** + * Stores the injector used to getInstance. + */ + protected Injector injector; + + /** + * Mapping of commands (not including aliases) with a description. This + * is only for top level commands. + */ + protected Map helpMessages = new HashMap(); + + /** + * Register an class that contains commands (denoted by {@link Command}. + * If no dependency injector is specified, then the methods of the + * class will be registered to be called statically. Otherwise, new + * instances will be created of the command classes and methods will + * not be called statically. + * + * @param cls + */ + public void register(Class cls) { + registerMethods(cls, null); + } + + /** + * Register an class that contains commands (denoted by {@link Command}. + * If no dependency injector is specified, then the methods of the + * class will be registered to be called statically. Otherwise, new + * instances will be created of the command classes and methods will + * not be called statically. A List of {@link Command} annotations from + * registered commands is returned. + * + * @param cls + * @return A List of {@link Command} annotations from registered commands, + * for use in eg. a dynamic command registration system. + */ + public List registerAndReturn(Class cls) { + return registerMethods(cls, null); + } + + /** + * Register the methods of a class. This will automatically construct + * instances as necessary. + * + * @param cls + * @param parent + * @return Commands Registered + */ + public List registerMethods(Class cls, Method parent) { + try { + if (getInjector() == null) { + return registerMethods(cls, parent, null); + } else { + Object obj = getInjector().getInstance(cls); + return registerMethods(cls, parent, obj); + } + } catch (InvocationTargetException e) { + logger.log(Level.SEVERE, "Failed to register commands", e); + } catch (IllegalAccessException e) { + logger.log(Level.SEVERE, "Failed to register commands", e); + } catch (InstantiationException e) { + logger.log(Level.SEVERE, "Failed to register commands", e); + } + return null; + } + + /** + * Register the methods of a class. + * + * @param cls + * @param parent + * @param obj + * @return + */ + private List registerMethods(Class cls, Method parent, Object obj) { + Map map; + List registered = new ArrayList(); + + // Make a new hash map to cache the commands for this class + // as looking up methods via reflection is fairly slow + 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; + } + + boolean isStatic = Modifier.isStatic(method.getModifiers()); + + Command cmd = method.getAnnotation(Command.class); + + // Cache the aliases too + for (String alias : cmd.aliases()) { + map.put(alias, method); + } + + // We want to be able invoke with an instance + if (!isStatic) { + // Can't register this command if we don't have an instance + if (obj == null) { + continue; + } + + instances.put(method, obj); + } + + // Build a list of commands and their usage details, at least for + // root level commands + if (parent == null) { + final String commandName = cmd.aliases()[0]; + final String desc = cmd.desc(); + + final String usage = cmd.usage(); + if (usage.length() == 0) { + descs.put(commandName, desc); + } else { + descs.put(commandName, usage + " - " + desc); + } + + String help = cmd.help(); + if (help.length() == 0) { + help = desc; + } + + final CharSequence arguments = getArguments(cmd); + for (String alias : cmd.aliases()) { + final String helpMessage = "/" + alias + " " + arguments + "\n\n" + help; + final String key = alias.replaceAll("/", ""); + String previous = helpMessages.put(key, helpMessage); + + if (previous != null && !previous.replaceAll("^/[^ ]+ ", "").equals(helpMessage.replaceAll("^/[^ ]+ ", ""))) { + helpMessages.put(key, previous + "\n\n" + helpMessage); + } + } + + } + + // Add the command to the registered command list for return + registered.add(cmd); + + // Look for nested commands -- if there are any, those have + // to be cached too so that they can be quickly looked + // up when processing commands + if (method.isAnnotationPresent(NestedCommand.class)) { + NestedCommand nestedCmd = method.getAnnotation(NestedCommand.class); + + for (Class nestedCls : nestedCmd.value()) { + registerMethods(nestedCls, method); + } + } + } + + if (cls.getSuperclass() != null) { + registerMethods(cls.getSuperclass(), parent, obj); + } + + return registered; + } + + /** + * Checks to see whether there is a command named such at the root level. + * This will check aliases as well. + * + * @param command + * @return + */ + public boolean hasCommand(String command) { + return commands.get(null).containsKey(command.toLowerCase()); + } + + /** + * Get a list of command descriptions. This is only for root commands. + * + * @return + */ + public Map getCommands() { + return descs; + } + + public Map> getMethods() { + return commands; + } + + /** + * Get a map from command name to help message. This is only for root commands. + * + * @return + */ + public Map getHelpMessages() { + return helpMessages; + } + + /** + * Get the usage string for a command. + * + * @param args + * @param level + * @param cmd + * @return + */ + protected String getUsage(String[] args, int level, Command cmd) { + final StringBuilder command = new StringBuilder(); + + command.append('/'); + + for (int i = 0; i <= level; ++i) { + command.append(args[i]); + command.append(' '); + } + command.append(getArguments(cmd)); + + final String help = cmd.help(); + if (help.length() > 0) { + command.append("\n\n"); + command.append(help); + } + + return command.toString(); + } + + protected CharSequence getArguments(Command cmd) { + final String flags = cmd.flags(); + + final StringBuilder command2 = new StringBuilder(); + if (flags.length() > 0) { + String flagString = flags.replaceAll(".:", ""); + if (flagString.length() > 0) { + command2.append("[-"); + for (int i = 0; i < flagString.length(); ++i) { + command2.append(flagString.charAt(i)); + } + command2.append("] "); + } + } + + command2.append(cmd.usage()); + + return command2; + } + + /** + * Get the usage string for a nested command. + * + * @param args + * @param level + * @param method + * @param player + * @return + * @throws CommandException + */ + protected String getNestedUsage(String[] args, int level, + Method method, T player) throws CommandException { + + StringBuilder command = new StringBuilder(); + + command.append("/"); + + for (int i = 0; i <= level; ++i) { + command.append(args[i] + " "); + } + + Map map = commands.get(method); + boolean found = false; + + command.append("<"); + + Set allowedCommands = new HashSet(); + + for (Map.Entry entry : map.entrySet()) { + Method childMethod = entry.getValue(); + found = true; + + 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 { + if (!found) { + command.append("?"); + } else { + //command.append("action"); + throw new CommandPermissionsException(); + } + } + + command.append(">"); + + return command.toString(); + } + + /** + * Attempt to execute a command. This version takes a separate command + * name (for the root command) and then a list of following arguments. + * + * @param cmd command to run + * @param args arguments + * @param player command source + * @param methodArgs method arguments + * @throws CommandException + */ + public void execute(String cmd, String[] args, T player, + Object... methodArgs) throws CommandException { + + String[] newArgs = new String[args.length + 1]; + System.arraycopy(args, 0, newArgs, 1, args.length); + newArgs[0] = cmd; + Object[] newMethodArgs = new Object[methodArgs.length + 1]; + System.arraycopy(methodArgs, 0, newMethodArgs, 1, methodArgs.length); + + executeMethod(null, newArgs, player, newMethodArgs, 0); + } + + /** + * Attempt to execute a command. + * + * @param args + * @param player + * @param methodArgs + * @throws CommandException + */ + public void execute(String[] args, T player, + Object... methodArgs) throws CommandException { + + Object[] newMethodArgs = new Object[methodArgs.length + 1]; + System.arraycopy(methodArgs, 0, newMethodArgs, 1, methodArgs.length); + executeMethod(null, args, player, newMethodArgs, 0); + } + + /** + * Attempt to execute a command. + * + * @param parent + * @param args + * @param player + * @param methodArgs + * @param level + * @throws CommandException + */ + public void executeMethod(Method parent, String[] args, + T player, Object[] methodArgs, int level) throws CommandException { + + String cmdName = args[level]; + + Map map = commands.get(parent); + Method method = map.get(cmdName.toLowerCase()); + + if (method == null) { + if (parent == null) { // Root + throw new UnhandledCommandException(); + } else { + throw new MissingNestedCommandException("Unknown command: " + cmdName, + getNestedUsage(args, level - 1, parent, player)); + } + } + + checkPermission(player, method); + + int argsCount = args.length - 1 - level; + + // checks if we need to execute the body of the nested command method (false) + // or display the help what commands are available (true) + // this is all for an args count of 0 if it is > 0 and a NestedCommand Annotation is present + // it will always handle the methods that NestedCommand points to + // e.g.: + // - /cmd - @NestedCommand(executeBody = true) will go into the else loop and execute code in that method + // - /cmd - @NestedCommand(executeBody = true) will always go to the nested command class + // - /cmd - @NestedCommand(executeBody = false) will always go to the nested command class not matter the args + boolean executeNested = method.isAnnotationPresent(NestedCommand.class) + && (argsCount > 0 || !method.getAnnotation(NestedCommand.class).executeBody()); + + if (executeNested) { + if (argsCount == 0) { + throw new MissingNestedCommandException("Sub-command required.", + getNestedUsage(args, level, method, player)); + } else { + executeMethod(method, args, player, methodArgs, level + 1); + } + } else if (method.isAnnotationPresent(CommandAlias.class)) { + CommandAlias aCmd = method.getAnnotation(CommandAlias.class); + executeMethod(parent, aCmd.value(), player, methodArgs, level); + } else { + Command cmd = method.getAnnotation(Command.class); + + String[] newArgs = new String[args.length - level]; + System.arraycopy(args, level, newArgs, 0, args.length - level); + + final Set valueFlags = new HashSet(); + + char[] flags = cmd.flags().toCharArray(); + Set newFlags = new HashSet(); + for (int i = 0; i < flags.length; ++i) { + if (flags.length > i + 1 && flags[i + 1] == ':') { + valueFlags.add(flags[i]); + ++i; + } + newFlags.add(flags[i]); + } + + CommandContext context = new CommandContext(newArgs, valueFlags); + + if (context.argsLength() < cmd.min()) { + throw new CommandUsageException("Too few arguments.", getUsage(args, level, cmd)); + } + + if (cmd.max() != -1 && context.argsLength() > cmd.max()) { + throw new CommandUsageException("Too many arguments.", getUsage(args, level, cmd)); + } + + if (!cmd.anyFlags()) { + for (char flag : context.getFlags()) { + if (!newFlags.contains(flag)) { + throw new CommandUsageException("Unknown flag: " + flag, getUsage(args, level, cmd)); + } + } + } + + methodArgs[0] = context; + + Object instance = instances.get(method); + + invokeMethod(parent, args, player, method, instance, methodArgs, argsCount); + } + } + + protected void checkPermission(T player, Method method) throws CommandException { + if (!hasPermission(method, player)) { + throw new CommandPermissionsException(); + } + } + + public void invokeMethod(Method parent, String[] args, T player, Method method, + Object instance, Object[] methodArgs, int level) throws CommandException { + try { + method.invoke(instance, methodArgs); + } catch (IllegalArgumentException e) { + logger.log(Level.SEVERE, "Failed to execute command", e); + } catch (IllegalAccessException e) { + logger.log(Level.SEVERE, "Failed to execute command", e); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof CommandException) { + throw (CommandException) e.getCause(); + } + + throw new WrappedCommandException(e.getCause()); + } + } + + /** + * Returns whether a player has access to a command. + * + * @param method + * @param player + * @return + */ + protected boolean hasPermission(Method method, T player) { + CommandPermissions perms = method.getAnnotation(CommandPermissions.class); + if (perms == null) { + return true; + } + + for (String perm : perms.value()) { + if (hasPermission(player, perm)) { + return true; + } + } + + return false; + } + + /** + * Returns whether a player permission.. + * + * @param player + * @param perm + * @return + */ + public abstract boolean hasPermission(T player, String perm); + + /** + * Get the injector used to create new instances. This can be + * null, in which case only classes will be registered statically. + */ + public Injector getInjector() { + return injector; + } + + /** + * Set the injector for creating new instances. + * + * @param injector injector or null + */ + public void setInjector(Injector injector) { + this.injector = injector; + } +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/Console.java b/core/src/main/java/com/sk89q/minecraft/util/commands/Console.java new file mode 100644 index 0000000..5ba60ba --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/Console.java @@ -0,0 +1,32 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This annotation indicates that a command can be used from the console. + * + * @author sk89q + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Console { +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/Injector.java b/core/src/main/java/com/sk89q/minecraft/util/commands/Injector.java new file mode 100644 index 0000000..facb425 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/Injector.java @@ -0,0 +1,41 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.lang.reflect.InvocationTargetException; + +/** + * Constructs new instances. + */ +public interface Injector { + + /** + * Constructs a new instance of the given class. + * + * @param cls class + * @return object + * @throws IllegalAccessException + * @throws InstantiationException + * @throws InvocationTargetException + */ + public Object getInstance(Class cls) throws InvocationTargetException, + IllegalAccessException, InstantiationException; + +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/Logging.java b/core/src/main/java/com/sk89q/minecraft/util/commands/Logging.java new file mode 100644 index 0000000..28001e5 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/Logging.java @@ -0,0 +1,66 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +//$Id$ + + +package com.sk89q.minecraft.util.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates how the affected blocks should be hinted at in the log. + * + * @author sk89q + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Logging { + public enum LogMode { + /** + * Player position + */ + POSITION, + + /** + * Region selection + */ + REGION, + + /** + * Player orientation and region selection + */ + ORIENTATION_REGION, + + /** + * Either the player position or pos1, depending on the placeAtPos1 flag + */ + PLACEMENT, + + /** + * Log all information available + */ + ALL + } + + /** + * Log mode. Can be either POSITION, REGION, ORIENTATION_REGION, PLACEMENT or ALL. + */ + LogMode value(); +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/MissingNestedCommandException.java b/core/src/main/java/com/sk89q/minecraft/util/commands/MissingNestedCommandException.java new file mode 100644 index 0000000..d54add7 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/MissingNestedCommandException.java @@ -0,0 +1,29 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +public class MissingNestedCommandException extends CommandUsageException { + private static final long serialVersionUID = -4382896182979285355L; + + public MissingNestedCommandException(String message, String usage) { + super(message, usage); + } + +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/NestedCommand.java b/core/src/main/java/com/sk89q/minecraft/util/commands/NestedCommand.java new file mode 100644 index 0000000..1cb7d0f --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/NestedCommand.java @@ -0,0 +1,46 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates a nested command. Mark methods with this annotation to tell + * {@link CommandsManager} that a method is merely a shell for child + * commands. Note that the body of a method marked with this annotation + * will never called. Additionally, not all fields of {@link Command} apply + * when it is used in conjunction with this annotation, although both + * are still required. + * + * @author sk89q + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface NestedCommand { + /** + * A list of classes with the child commands. + */ + Class[] value(); + + /** + * If set to true it will execute the body of the tagged method. + */ + boolean executeBody() default false; +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/SimpleInjector.java b/core/src/main/java/com/sk89q/minecraft/util/commands/SimpleInjector.java new file mode 100644 index 0000000..4c23c02 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/SimpleInjector.java @@ -0,0 +1,62 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.logging.Logger; + +public class SimpleInjector implements Injector { + private static final Logger logger = Logger.getLogger(SimpleInjector.class.getCanonicalName()); + private Object[] args; + private Class[] argClasses; + + public SimpleInjector(Object... args) { + this.args = args; + argClasses = new Class[args.length]; + for (int i = 0; i < args.length; ++i) { + argClasses[i] = args[i].getClass(); + } + } + + public Object getInstance(Class clazz) { + try { + Constructor ctr = clazz.getConstructor(argClasses); + ctr.setAccessible(true); + return ctr.newInstance(args); + } catch (NoSuchMethodException e) { + logger.severe("Error initializing commands class " + clazz + ": "); + e.printStackTrace(); + return null; + } catch (InvocationTargetException e) { + logger.severe("Error initializing commands class " + clazz + ": "); + e.printStackTrace(); + return null; + } catch (InstantiationException e) { + logger.severe("Error initializing commands class " + clazz + ": "); + e.printStackTrace(); + return null; + } catch (IllegalAccessException e) { + logger.severe("Error initializing commands class " + clazz + ": "); + e.printStackTrace(); + return null; + } + } +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/SuggestionContext.java b/core/src/main/java/com/sk89q/minecraft/util/commands/SuggestionContext.java new file mode 100644 index 0000000..7f435cf --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/SuggestionContext.java @@ -0,0 +1,68 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +public class SuggestionContext { + + private static final SuggestionContext FOR_LAST = new SuggestionContext(null, true); + private static final SuggestionContext FOR_HANGING = new SuggestionContext(null, false); + + private final Character flag; + private final boolean forLast; + + private SuggestionContext(Character flag, boolean forLast) { + this.flag = flag; + this.forLast = forLast; + } + + public boolean forHangingValue() { + return flag == null && !forLast; + } + + public boolean forLastValue() { + return flag == null && forLast; + } + + public boolean forFlag() { + return flag != null; + } + + public Character getFlag() { + return flag; + } + + @Override + public String toString() { + return forFlag() ? ("-" + getFlag()) : (forHangingValue() ? "hanging" : "last"); + } + + public static SuggestionContext flag(Character flag) { + return new SuggestionContext(flag, false); + } + + public static SuggestionContext lastValue() { + return FOR_LAST; + } + + public static SuggestionContext hangingValue() { + return FOR_HANGING; + } + +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/UnhandledCommandException.java b/core/src/main/java/com/sk89q/minecraft/util/commands/UnhandledCommandException.java new file mode 100644 index 0000000..0c42b94 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/UnhandledCommandException.java @@ -0,0 +1,25 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +public class UnhandledCommandException extends CommandException { + private static final long serialVersionUID = 3370887306593968091L; + +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandException.java b/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandException.java new file mode 100644 index 0000000..1eac2a1 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandException.java @@ -0,0 +1,28 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.minecraft.util.commands; + +public class WrappedCommandException extends CommandException { + private static final long serialVersionUID = -4075721444847778918L; + + public WrappedCommandException(Throwable t) { + super(t); + } +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandSender.java b/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandSender.java new file mode 100644 index 0000000..e4d41cb --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandSender.java @@ -0,0 +1,22 @@ +package com.sk89q.minecraft.util.commands; + +public interface WrappedCommandSender { + String getName(); + + void sendMessage(String message); + + void sendMessage(String[] messages); + + boolean hasPermission(String permission); + + Type getType(); + + Object getCommandSender(); + + public static enum Type { + CONSOLE, + PLAYER, + BLOCK, + UNKNOWN + } +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandsManager.java b/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandsManager.java new file mode 100644 index 0000000..f905a90 --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/commands/WrappedCommandsManager.java @@ -0,0 +1,8 @@ +package com.sk89q.minecraft.util.commands; + +public class WrappedCommandsManager extends CommandsManager { + @Override + public boolean hasPermission(WrappedCommandSender player, String perm) { + return player.hasPermission(perm); + } +} diff --git a/core/src/main/java/com/sk89q/minecraft/util/pagination/PaginatedResult.java b/core/src/main/java/com/sk89q/minecraft/util/pagination/PaginatedResult.java new file mode 100644 index 0000000..18307be --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/pagination/PaginatedResult.java @@ -0,0 +1,71 @@ +/* + * CommandBook + * Copyright (C) 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.minecraft.util.pagination; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.WrappedCommandSender; + +/** + * Commands that wish to display a paginated list of results can use this class to do + * the actual pagination, giving a list of items, a page number, and basic formatting information. + */ +public abstract class PaginatedResult { + protected final int resultsPerPage; + + public PaginatedResult() { + this(6); + } + + public PaginatedResult(int resultsPerPage) { + assert resultsPerPage > 0; + this.resultsPerPage = resultsPerPage; + } + + public void display(WrappedCommandSender sender, Collection results, int page) throws CommandException { + this.display(sender, new ArrayList(results), page); + } + + public void display(WrappedCommandSender sender, List results, int page) throws CommandException { + if (results.size() == 0) throw new CommandException("No results match!"); + + int maxPages = results.size() / this.resultsPerPage + 1; + + // If the content divides perfectly, eg (18 entries, and 9 per page) + // we end up with a blank page this handles this case + if (results.size() % this.resultsPerPage == 0) { + maxPages--; + } + + if (page <= 0 || page > maxPages) throw new CommandException("Unknown page selected! " + maxPages + " total pages."); + + sender.sendMessage(this.formatHeader(page, maxPages)); + for (int i = this.resultsPerPage * (page - 1); i < this.resultsPerPage * page && i < results.size(); i++) { + sender.sendMessage(this.format(results.get(i), i)); + } + } + + public abstract String formatHeader(int page, int maxPages); + + public abstract String format(T entry, int index); +} + diff --git a/core/src/main/java/com/sk89q/minecraft/util/pagination/SimplePaginatedResult.java b/core/src/main/java/com/sk89q/minecraft/util/pagination/SimplePaginatedResult.java new file mode 100644 index 0000000..69c1eae --- /dev/null +++ b/core/src/main/java/com/sk89q/minecraft/util/pagination/SimplePaginatedResult.java @@ -0,0 +1,23 @@ +package com.sk89q.minecraft.util.pagination; + +import com.sk89q.minecraft.util.commands.ChatColor; + + +public abstract class SimplePaginatedResult extends PaginatedResult { + protected final String header; + + public SimplePaginatedResult(String header) { + super(); + this.header = header; + } + + public SimplePaginatedResult(String header, int resultsPerPage) { + super(resultsPerPage); + this.header = header; + } + + @Override + public String formatHeader(int page, int maxPages) { + return ChatColor.YELLOW + this.header + " (page " + page + "/" + maxPages + ")"; + } +} diff --git a/core/src/main/java/com/sk89q/util/ReflectionUtil.java b/core/src/main/java/com/sk89q/util/ReflectionUtil.java new file mode 100644 index 0000000..db877cf --- /dev/null +++ b/core/src/main/java/com/sk89q/util/ReflectionUtil.java @@ -0,0 +1,48 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.util; + +import java.lang.reflect.Field; + +/** + * @author zml2008 + */ +public final class ReflectionUtil { + + private ReflectionUtil() { + } + + @SuppressWarnings("unchecked") + public static T getField(Object from, String name) { + Class checkClass = from.getClass(); + do { + try { + Field field = checkClass.getDeclaredField(name); + field.setAccessible(true); + return (T) field.get(from); + } catch (NoSuchFieldException e) { + } catch (IllegalAccessException e) { + } + } while (checkClass.getSuperclass() != Object.class && ((checkClass = checkClass.getSuperclass()) != null)); + return null; + } + +} + diff --git a/core/src/main/java/com/sk89q/util/StringUtil.java b/core/src/main/java/com/sk89q/util/StringUtil.java new file mode 100644 index 0000000..34127fd --- /dev/null +++ b/core/src/main/java/com/sk89q/util/StringUtil.java @@ -0,0 +1,311 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.util; + +import java.util.Collection; +import java.util.Map; + +/** + * String utilities. + * + * @author sk89q + */ +public final class StringUtil { + + private StringUtil() { + } + + /** + * Trim a string if it is longer than a certain length. + * + * @param str + * @param len + * @return + */ + public static String trimLength(String str, int len) { + if (str.length() > len) { + return str.substring(0, len); + } + + return str; + } + + /** + * Join an array of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @return + */ + public static String joinString(String[] str, String delimiter, + int initialIndex) { + if (str.length == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(str[initialIndex]); + for (int i = initialIndex + 1; i < str.length; ++i) { + buffer.append(delimiter).append(str[i]); + } + return buffer.toString(); + } + + /** + * Join an array of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @param quote + * @return + */ + public static String joinQuotedString(String[] str, String delimiter, + int initialIndex, String quote) { + if (str.length == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(); + buffer.append(quote); + buffer.append(str[initialIndex]); + buffer.append(quote); + for (int i = initialIndex + 1; i < str.length; ++i) { + buffer.append(delimiter).append(quote).append(str[i]).append(quote); + } + return buffer.toString(); + } + + /** + * Join an array of strings into a string. + * + * @param str + * @param delimiter + * @return + */ + public static String joinString(String[] str, String delimiter) { + return joinString(str, delimiter, 0); + } + + /** + * Join an array of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @return + */ + public static String joinString(Object[] str, String delimiter, + int initialIndex) { + if (str.length == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(str[initialIndex].toString()); + for (int i = initialIndex + 1; i < str.length; ++i) { + buffer.append(delimiter).append(str[i].toString()); + } + return buffer.toString(); + } + + /** + * Join an array of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @return + */ + public static String joinString(int[] str, String delimiter, + int initialIndex) { + if (str.length == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(Integer.toString(str[initialIndex])); + for (int i = initialIndex + 1; i < str.length; ++i) { + buffer.append(delimiter).append(Integer.toString(str[i])); + } + return buffer.toString(); + } + + /** + * Join an list of strings into a string. + * + * @param str + * @param delimiter + * @param initialIndex + * @return + */ + public static String joinString(Collection str, String delimiter, + int initialIndex) { + if (str.size() == 0) { + return ""; + } + StringBuilder buffer = new StringBuilder(); + int i = 0; + for (Object o : str) { + if (i >= initialIndex) { + if (i > 0) { + buffer.append(delimiter); + } + + buffer.append(o.toString()); + } + ++i; + } + return buffer.toString(); + } + + /** + *

Find the Levenshtein distance between two Strings.

+ * + *

This is the number of changes needed to change one String into + * another, where each change is a single character modification (deletion, + * insertion or substitution).

+ * + *

The previous implementation of the Levenshtein distance algorithm + * was from http://www.merriampark.com/ld.htm

+ * + *

Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError + * which can occur when my Java implementation is used with very large strings.
+ * This implementation of the Levenshtein distance algorithm + * is from http://www.merriampark.com/ldjava.htm

+ * + *
+     * StringUtil.getLevenshteinDistance(null, *)             = IllegalArgumentException
+     * StringUtil.getLevenshteinDistance(*, null)             = IllegalArgumentException
+     * StringUtil.getLevenshteinDistance("","")               = 0
+     * StringUtil.getLevenshteinDistance("","a")              = 1
+     * StringUtil.getLevenshteinDistance("aaapppp", "")       = 7
+     * StringUtil.getLevenshteinDistance("frog", "fog")       = 1
+     * StringUtil.getLevenshteinDistance("fly", "ant")        = 3
+     * StringUtil.getLevenshteinDistance("elephant", "hippo") = 7
+     * StringUtil.getLevenshteinDistance("hippo", "elephant") = 7
+     * StringUtil.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
+     * StringUtil.getLevenshteinDistance("hello", "hallo")    = 1
+     * 
+ * + * @param s the first String, must not be null + * @param t the second String, must not be null + * @return result distance + * @throws IllegalArgumentException if either String input null + */ + public static int getLevenshteinDistance(String s, String t) { + if (s == null || t == null) { + throw new IllegalArgumentException("Strings must not be null"); + } + + /* + * The difference between this impl. and the previous is that, rather + * than creating and retaining a matrix of size s.length()+1 by + * t.length()+1, we maintain two single-dimensional arrays of length + * s.length()+1. The first, d, is the 'current working' distance array + * that maintains the newest distance cost counts as we iterate through + * the characters of String s. Each time we increment the index of + * String t we are comparing, d is copied to p, the second int[]. Doing + * so allows us to retain the previous cost counts as required by the + * algorithm (taking the minimum of the cost count to the left, up one, + * and diagonally up and to the left of the current cost count being + * calculated). (Note that the arrays aren't really copied anymore, just + * switched...this is clearly much better than cloning an array or doing + * a System.arraycopy() each time through the outer loop.) + * + * Effectively, the difference between the two implementations is this + * one does not cause an out of memory condition when calculating the LD + * over two very large strings. + */ + + 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 tj; // jth character of t + + int cost; // cost + + for (i = 0; i <= n; ++i) { + p[i] = i; + } + + for (j = 1; j <= m; ++j) { + tj = t.charAt(j - 1); + d[0] = j; + + for (i = 1; i <= n; ++i) { + cost = s.charAt(i - 1) == tj ? 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]; + } + + public static > T lookup(Map lookup, String name, boolean fuzzy) { + String testName = name.replaceAll("[ _]", "").toLowerCase(); + + T type = lookup.get(testName); + if (type != null) { + return type; + } + + if (!fuzzy) { + return null; + } + + int minDist = -1; + + for (Map.Entry entry : lookup.entrySet()) { + final String key = entry.getKey(); + if (key.charAt(0) != testName.charAt(0)) { + continue; + } + + int dist = getLevenshteinDistance(key, testName); + + if ((dist < minDist || minDist == -1) && dist < 2) { + minDist = dist; + type = entry.getValue(); + } + } + + return type; + } +} + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f12d07c --- /dev/null +++ b/pom.xml @@ -0,0 +1,61 @@ + + 4.0.0 + com.sk89q + command-framework-parent + pom + 0.5-SNAPSHOT + Sk89q Command Framework + sk89q's command system from WorldEdit factored out + + + core + bukkit + bungee + + + + scm:git:git://github.com/OvercastNetwork/sk89q-command-framework.git + scm:git:git@github.com:OvercastNetwork/sk89q-command-framework.git + https://github.com/OvercastNetwork/sk89q-command-framework + + + + + overcast-deployment + https://repo.oc.tc/content/repositories/releases + + + overcast-deployment + https://repo.oc.tc/content/repositories/snapshots + + + + + ${project.basedir}/src/main/java/ + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.3.1 + + + + + + UTF-8 + +