diff --git a/build.gradle b/build.gradle
index ea1ad00fa..118600546 100644
--- a/build.gradle
+++ b/build.gradle
@@ -44,6 +44,7 @@ dependencies {
compile group: 'com.google.guava', name: 'guava', version:'10.0.1'
compile group: 'com.sk89q', name: 'jchronic', version:'0.2.4a'
compile group: 'com.google.code.findbugs', name: 'jsr305', version: '1.3.9'
+ compile group: 'com.thoughtworks.paranamer', name: 'paranamer', version: '2.6'
testCompile group: 'org.mockito', name: 'mockito-core', version:'1.9.0-rc1'
}
@@ -87,6 +88,7 @@ shadow {
destinationDir "${buildDir}/libs/"
artifactSet {
include '*:jchronic:jar:'
+ include '*:paranamer:jar:'
}
}
diff --git a/pom.xml b/pom.xml
index b6b154888..b9d717b8b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,13 @@
+
+
+ sk89q-repo
+ http://maven.sk89q.com/repo/
+
+
+
@@ -151,6 +158,15 @@
jar
+
+
+ com.thoughtworks.paranamer
+ paranamer
+ 2.6
+ compile
+ jar
+
+
org.mockito
@@ -297,6 +313,26 @@
+
+
+ com.thoughtworks.paranamer
+ paranamer-maven-plugin-largestack
+ 2.5.5-SNAPSHOT
+
+
+ run
+ compile
+
+ ${project.build.sourceDirectory}
+ ${project.build.outputDirectory}
+
+
+ generate
+
+
+
+
+
org.apache.maven.plugins
@@ -352,6 +388,7 @@
com.sk89q:jchronic
+ com.thoughtworks.paranamer:paranamer
diff --git a/src/bukkit/java/com/sk89q/bukkit/util/CommandInspector.java b/src/bukkit/java/com/sk89q/bukkit/util/CommandInspector.java
new file mode 100644
index 000000000..a7d213944
--- /dev/null
+++ b/src/bukkit/java/com/sk89q/bukkit/util/CommandInspector.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.bukkit.util;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+public interface CommandInspector {
+
+ String getShortText(Command command);
+
+ String getFullText(Command command);
+
+ boolean testPermission(CommandSender sender, Command command);
+}
diff --git a/src/bukkit/java/com/sk89q/bukkit/util/DynamicPluginCommand.java b/src/bukkit/java/com/sk89q/bukkit/util/DynamicPluginCommand.java
index 1660d987f..c48df659c 100644
--- a/src/bukkit/java/com/sk89q/bukkit/util/DynamicPluginCommand.java
+++ b/src/bukkit/java/com/sk89q/bukkit/util/DynamicPluginCommand.java
@@ -26,9 +26,12 @@ import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginIdentifiableCommand;
+import org.bukkit.command.TabCompleter;
import org.bukkit.plugin.Plugin;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
/**
* An implementation of a dynamically registered {@link org.bukkit.command.Command} attached to a plugin
@@ -76,6 +79,15 @@ public class DynamicPluginCommand extends org.bukkit.command.Command implements
return owningPlugin;
}
+ @Override
+ public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
+ if (owner instanceof TabCompleter) {
+ return ((TabCompleter) owner).onTabComplete(sender, this, alias, args);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
@SuppressWarnings("unchecked")
@Override
public boolean testPermissionSilent(CommandSender sender) {
@@ -83,7 +95,10 @@ public class DynamicPluginCommand extends org.bukkit.command.Command implements
return true;
}
- if (registeredWith instanceof CommandsManager>) {
+ if (registeredWith instanceof CommandInspector) {
+ CommandInspector resolver = (CommandInspector) registeredWith;
+ return resolver.testPermission(sender, this);
+ } else if (registeredWith instanceof CommandsManager>) {
try {
for (String permission : permissions) {
if (((CommandsManager) registeredWith).hasPermission(sender, permission)) {
diff --git a/src/bukkit/java/com/sk89q/bukkit/util/DynamicPluginCommandHelpTopic.java b/src/bukkit/java/com/sk89q/bukkit/util/DynamicPluginCommandHelpTopic.java
index 6d0b4f690..fda92fc40 100644
--- a/src/bukkit/java/com/sk89q/bukkit/util/DynamicPluginCommandHelpTopic.java
+++ b/src/bukkit/java/com/sk89q/bukkit/util/DynamicPluginCommandHelpTopic.java
@@ -40,55 +40,64 @@ public class DynamicPluginCommandHelpTopic extends HelpTopic {
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;
- }
+ if (cmd.getRegisteredWith() instanceof CommandInspector) {
+ CommandInspector resolver = (CommandInspector) cmd.getRegisteredWith();
+ this.shortText = resolver.getShortText(cmd);
+ this.fullText = resolver.getFullText(cmd);
} else {
- this.shortText = cmd.getDescription();
- }
+ String fullTextTemp = null;
+ StringBuilder fullText = new StringBuilder();
- // 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(", ");
+ 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);
}
- fullText.append(alias);
- first = false;
+ // 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();
}
- fullText.append("\n");
+
+ // 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().isEmpty()) {
+ 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();
}
- 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 CommandInspector) {
+ CommandInspector resolver = (CommandInspector) cmd.getRegisteredWith();
+ return resolver.testPermission(player, cmd);
+ } else if (cmd.getPermissions() != null && cmd.getPermissions().length > 0) {
if (cmd.getRegisteredWith() instanceof CommandsManager) {
try {
for (String perm : cmd.getPermissions()) {
@@ -123,7 +132,7 @@ public class DynamicPluginCommandHelpTopic extends HelpTopic {
@Override
public String getFullText(CommandSender forWho) {
- if (this.fullText == null || this.fullText.length() == 0) {
+ if (this.fullText == null || this.fullText.isEmpty()) {
return getShortText();
} else {
return this.fullText;
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandInspector.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandInspector.java
new file mode 100644
index 000000000..9c11224c6
--- /dev/null
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandInspector.java
@@ -0,0 +1,83 @@
+/*
+ * 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.worldedit.bukkit;
+
+import com.sk89q.bukkit.util.CommandInspector;
+import com.sk89q.minecraft.util.commands.CommandLocals;
+import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.util.command.CommandMapping;
+import com.sk89q.worldedit.util.command.Description;
+import com.sk89q.worldedit.util.command.Dispatcher;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import java.util.logging.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+class BukkitCommandInspector implements CommandInspector {
+
+ private static final Logger logger = Logger.getLogger(BukkitCommandInspector.class.getCanonicalName());
+ private final WorldEditPlugin plugin;
+ private final Dispatcher dispatcher;
+
+ BukkitCommandInspector(WorldEditPlugin plugin, Dispatcher dispatcher) {
+ checkNotNull(plugin);
+ checkNotNull(dispatcher);
+ this.plugin = plugin;
+ this.dispatcher = dispatcher;
+ }
+
+ @Override
+ public String getShortText(Command command) {
+ CommandMapping mapping = dispatcher.get(command.getName());
+ if (mapping != null) {
+ return mapping.getDescription().getShortDescription();
+ } else {
+ logger.warning("BukkitCommandInspector doesn't know how about the command '" + command + "'");
+ return "Help text not available";
+ }
+ }
+
+ @Override
+ public String getFullText(Command command) {
+ CommandMapping mapping = dispatcher.get(command.getName());
+ if (mapping != null) {
+ Description description = mapping.getDescription();
+ return "Usage: " + description.getUsage() + (description.getHelp() != null ? "\n" + description.getHelp() : "");
+ } else {
+ logger.warning("BukkitCommandInspector doesn't know how about the command '" + command + "'");
+ return "Help text not available";
+ }
+ }
+
+ @Override
+ public boolean testPermission(CommandSender sender, Command command) {
+ CommandMapping mapping = dispatcher.get(command.getName());
+ if (mapping != null) {
+ CommandLocals locals = new CommandLocals();
+ locals.put(Actor.class, plugin.wrapCommandSender(sender));
+ return mapping.getCallable().testPermission(locals);
+ } else {
+ logger.warning("BukkitCommandInspector doesn't know how about the command '" + command + "'");
+ return false;
+ }
+ }
+}
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
index e2067fd90..a8dc5adc2 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
@@ -19,18 +19,29 @@
package com.sk89q.worldedit.bukkit;
-import com.sk89q.worldedit.*;
-import com.sk89q.worldedit.extent.inventory.BlockBag;
-import com.sk89q.worldedit.util.Location;
+import com.sk89q.worldedit.LocalWorld;
+import com.sk89q.worldedit.PlayerNeededException;
+import com.sk89q.worldedit.WorldEditPermissionException;
+import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.internal.cui.CUIEvent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
-public class BukkitCommandSender extends LocalPlayer {
+import java.io.File;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+class BukkitCommandSender implements Actor {
+
private CommandSender sender;
private WorldEditPlugin plugin;
- public BukkitCommandSender(WorldEditPlugin plugin, ServerInterface server, CommandSender sender) {
- super(server);
+ BukkitCommandSender(WorldEditPlugin plugin, CommandSender sender) {
+ checkNotNull(plugin);
+ checkNotNull(sender);
+ checkArgument(!(sender instanceof Player), "Cannot wrap a player");
+
this.plugin = plugin;
this.sender = sender;
}
@@ -68,6 +79,11 @@ public class BukkitCommandSender extends LocalPlayer {
}
}
+ @Override
+ public boolean canDestroyBedrock() {
+ return true;
+ }
+
@Override
public String[] getGroups() {
return new String[0];
@@ -75,60 +91,30 @@ public class BukkitCommandSender extends LocalPlayer {
@Override
public boolean hasPermission(String perm) {
- if (!plugin.getLocalConfiguration().noOpPermissions && sender.isOp()) {
- return true;
- }
+ return true;
+ }
- return plugin.getPermissionsResolver().hasPermission(null, sender.getName(), perm);
+ @Override
+ public void checkPermission(String permission) throws WorldEditPermissionException {
}
@Override
public boolean isPlayer() {
- return sender instanceof Player;
+ return false;
}
@Override
- public int getItemInHand() {
- throw new PlayerNeededException();
+ public File openFileOpenDialog(String[] extensions) {
+ return null;
}
@Override
- public Location getLocation() {
- throw new PlayerNeededException();
+ public File openFileSaveDialog(String[] extensions) {
+ return null;
}
@Override
- public WorldVector getPosition() {
- throw new PlayerNeededException();
+ public void dispatchCUIEvent(CUIEvent event) {
}
- @Override
- public LocalWorld getWorld() {
- throw new PlayerNeededException();
- }
-
- @Override
- public double getPitch() {
- throw new PlayerNeededException();
- }
-
- @Override
- public double getYaw() {
- throw new PlayerNeededException();
- }
-
- @Override
- public void giveItem(int type, int amt) {
- throw new PlayerNeededException();
- }
-
- @Override
- public void setPosition(Vector pos, float pitch, float yaw) {
- throw new PlayerNeededException();
- }
-
- @Override
- public BlockBag getInventoryBlockBag() {
- throw new PlayerNeededException();
- }
}
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
index 1499cd09f..637c7c7e8 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
@@ -41,7 +41,6 @@ public class BukkitPlayer extends LocalPlayer {
private WorldEditPlugin plugin;
public BukkitPlayer(WorldEditPlugin plugin, ServerInterface server, Player player) {
- super(server);
this.plugin = plugin;
this.player = player;
}
@@ -52,6 +51,7 @@ public class BukkitPlayer extends LocalPlayer {
return itemStack != null ? itemStack.getTypeId() : 0;
}
+ @Override
public BaseBlock getBlockInHand() throws WorldEditException {
ItemStack itemStack = player.getItemInHand();
return BukkitUtil.toBlock(getWorld(), itemStack);
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java
index 6a34748a9..bf08bf45a 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java
@@ -21,19 +21,25 @@ package com.sk89q.worldedit.bukkit;
import com.sk89q.bukkit.util.CommandInfo;
import com.sk89q.bukkit.util.CommandRegistration;
-import com.sk89q.minecraft.util.commands.Command;
-import com.sk89q.minecraft.util.commands.CommandPermissions;
-import com.sk89q.minecraft.util.commands.CommandsManager;
-import com.sk89q.worldedit.*;
+import com.sk89q.worldedit.BiomeTypes;
+import com.sk89q.worldedit.LocalConfiguration;
+import com.sk89q.worldedit.LocalWorld;
+import com.sk89q.worldedit.ServerInterface;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.platform.Capability;
+import com.sk89q.worldedit.extension.platform.Preference;
+import com.sk89q.worldedit.util.command.CommandMapping;
+import com.sk89q.worldedit.util.command.Description;
+import com.sk89q.worldedit.util.command.Dispatcher;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
-import java.lang.reflect.Method;
+import javax.annotation.Nullable;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.EnumMap;
import java.util.List;
import java.util.Map;
@@ -42,6 +48,7 @@ public class BukkitServerInterface extends ServerInterface {
public WorldEditPlugin plugin;
private CommandRegistration dynamicCommands;
private BukkitBiomeTypes biomes;
+ private boolean hookingEvents;
public BukkitServerInterface(WorldEditPlugin plugin, Server server) {
this.plugin = plugin;
@@ -50,6 +57,10 @@ public class BukkitServerInterface extends ServerInterface {
dynamicCommands = new CommandRegistration(plugin);
}
+ boolean isHookingEvents() {
+ return hookingEvents;
+ }
+
@Override
public int resolveItem(String name) {
Material mat = Material.matchMaterial(name);
@@ -89,31 +100,50 @@ public class BukkitServerInterface extends ServerInterface {
return ret;
}
+ @Nullable
@Override
- public void onCommandRegistration(List commands, CommandsManager manager) {
+ public Player matchPlayer(Player player) {
+ if (player instanceof BukkitPlayer) {
+ return player;
+ } else {
+ org.bukkit.entity.Player bukkitPlayer = server.getPlayerExact(player.getName());
+ return bukkitPlayer != null ? new BukkitPlayer(plugin, this, bukkitPlayer) : null;
+ }
+ }
+
+ @Nullable
+ @Override
+ public com.sk89q.worldedit.world.World matchWorld(com.sk89q.worldedit.world.World world) {
+ if (world instanceof BukkitWorld) {
+ return world;
+ } else {
+ World bukkitWorld = server.getWorld(world.getName());
+ return bukkitWorld != null ? new BukkitWorld(bukkitWorld) : null;
+ }
+ }
+
+ @Override
+ public void registerCommands(Dispatcher dispatcher) {
List toRegister = new ArrayList();
- for (Command command : commands) {
- List permissions = null;
- Method cmdMethod = manager.getMethods().get(null).get(command.aliases()[0]);
- Map childMethods = manager.getMethods().get(cmdMethod);
+ BukkitCommandInspector inspector = new BukkitCommandInspector(plugin, dispatcher);
+
+ for (CommandMapping command : dispatcher.getCommands()) {
+ Description description = command.getDescription();
+ List permissions = description.getPermissions();
+ String[] permissionsArray = new String[permissions.size()];
+ permissions.toArray(permissionsArray);
- if (cmdMethod != null && cmdMethod.isAnnotationPresent(CommandPermissions.class)) {
- permissions = Arrays.asList(cmdMethod.getAnnotation(CommandPermissions.class).value());
- } else if (cmdMethod != null && childMethods != null && childMethods.size() > 0) {
- permissions = new ArrayList();
- for (Method m : childMethods.values()) {
- if (m.isAnnotationPresent(CommandPermissions.class)) {
- permissions.addAll(Arrays.asList(m.getAnnotation(CommandPermissions.class).value()));
- }
- }
- }
-
- toRegister.add(new CommandInfo(command.usage(), command.desc(), command.aliases(), commands, permissions == null ? null : permissions.toArray(new String[permissions.size()])));
+ toRegister.add(new CommandInfo(description.getUsage(), description.getShortDescription(), command.getAllAliases(), inspector, permissionsArray));
}
dynamicCommands.register(toRegister);
}
+ @Override
+ public void registerGameHooks() {
+ hookingEvents = true;
+ }
+
@Override
public LocalConfiguration getConfiguration() {
return plugin.getLocalConfiguration();
@@ -134,6 +164,17 @@ public class BukkitServerInterface extends ServerInterface {
return plugin.getDescription().getVersion();
}
+ @Override
+ public Map getCapabilities() {
+ Map capabilities = new EnumMap(Capability.class);
+ capabilities.put(Capability.CONFIGURATION, Preference.NORMAL);
+ capabilities.put(Capability.GAME_HOOKS, Preference.PREFERRED);
+ capabilities.put(Capability.PERMISSIONS, Preference.PREFERRED);
+ capabilities.put(Capability.USER_COMMANDS, Preference.PREFERRED);
+ capabilities.put(Capability.WORLD_EDITING, Preference.PREFER_OTHERS);
+ return capabilities;
+ }
+
public void unregisterCommands() {
dynamicCommands.unregisterCommands();
}
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitWorld.java
index ea784545e..3f32fefda 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitWorld.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitWorld.java
@@ -1110,13 +1110,15 @@ public class BukkitWorld extends LocalWorld {
@Override
public boolean equals(Object other) {
- World world = getWorld();
-
- if (!(other instanceof BukkitWorld)) {
+ if (other == null) {
+ return false;
+ } else if ((other instanceof BukkitWorld)) {
+ return ((BukkitWorld) other).getWorld().equals(getWorld());
+ } else if (other instanceof com.sk89q.worldedit.world.World) {
+ return ((com.sk89q.worldedit.world.World) other).getName().equals(getName());
+ } else {
return false;
}
-
- return ((BukkitWorld) other).getWorld().equals(world);
}
@Override
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java
index 50e7f9e37..91a7adcda 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java
@@ -69,11 +69,19 @@ public class WorldEditListener implements Listener {
*/
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
+ if (!plugin.getInternalPlatform().isHookingEvents()) {
+ return;
+ }
+
plugin.getWorldEdit().markExpire(plugin.wrapPlayer(event.getPlayer()));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onGamemode(PlayerGameModeChangeEvent event) {
+ if (!plugin.getInternalPlatform().isHookingEvents()) {
+ return;
+ }
+
// this will automatically refresh their sesssion, we don't have to do anything
WorldEdit.getInstance().getSession(plugin.wrapPlayer(event.getPlayer()));
}
@@ -88,20 +96,21 @@ public class WorldEditListener implements Listener {
String[] split = event.getMessage().split(" ");
if (split.length > 0) {
- split = plugin.getWorldEdit().commandDetection(split);
- split[0] = "/" + split[0];
+ split[0] = split[0].substring(1);
+ split = plugin.getWorldEdit().getPlatformManager().getCommandManager().commandDetection(split);
}
- final String newMessage = StringUtil.joinString(split, " ");
+ final String newMessage = "/" + StringUtil.joinString(split, " ");
if (!newMessage.equals(event.getMessage())) {
event.setMessage(newMessage);
plugin.getServer().getPluginManager().callEvent(event);
+
if (!event.isCancelled()) {
- if (event.getMessage().length() > 0) {
- plugin.getServer().dispatchCommand(event.getPlayer(),
- event.getMessage().substring(1));
+ if (!event.getMessage().isEmpty()) {
+ plugin.getServer().dispatchCommand(event.getPlayer(), event.getMessage().substring(1));
}
+
event.setCancelled(true);
}
}
@@ -114,6 +123,10 @@ public class WorldEditListener implements Listener {
*/
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
+ if (!plugin.getInternalPlatform().isHookingEvents()) {
+ return;
+ }
+
if (event.useItemInHand() == Result.DENY) {
return;
}
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
index 7efb2b240..9f0beff64 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
@@ -19,6 +19,7 @@
package com.sk89q.worldedit.bukkit;
+import com.google.common.base.Joiner;
import com.sk89q.util.yaml.YAMLProcessor;
import com.sk89q.wepif.PermissionsResolverManager;
import com.sk89q.worldedit.*;
@@ -26,20 +27,22 @@ import com.sk89q.worldedit.bukkit.selections.CuboidSelection;
import com.sk89q.worldedit.bukkit.selections.CylinderSelection;
import com.sk89q.worldedit.bukkit.selections.Polygonal2DSelection;
import com.sk89q.worldedit.bukkit.selections.Selection;
-import com.sk89q.worldedit.extension.platform.PlatformRejectionException;
+import com.sk89q.worldedit.event.platform.CommandEvent;
+import com.sk89q.worldedit.event.platform.CommandSuggestionEvent;
+import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
+import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.inventory.BlockBag;
-import com.sk89q.worldedit.regions.CuboidRegion;
-import com.sk89q.worldedit.regions.CylinderRegion;
-import com.sk89q.worldedit.regions.Polygonal2DRegion;
-import com.sk89q.worldedit.regions.Region;
-import com.sk89q.worldedit.regions.RegionSelector;
+import com.sk89q.worldedit.regions.*;
import org.bukkit.World;
+import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
+import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.*;
import java.util.Enumeration;
+import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
@@ -49,7 +52,7 @@ import java.util.zip.ZipEntry;
*
* @author sk89q
*/
-public class WorldEditPlugin extends JavaPlugin {
+public class WorldEditPlugin extends JavaPlugin implements TabCompleter {
/**
* The name of the CUI's plugin channel registration
@@ -106,21 +109,19 @@ public class WorldEditPlugin extends JavaPlugin {
// Setup interfaces
server = new BukkitServerInterface(this, getServer());
controller = WorldEdit.getInstance();
- try {
- controller.getPlatformManager().register(server);
- api = new WorldEditAPI(this);
- getServer().getMessenger().registerIncomingPluginChannel(this, CUI_PLUGIN_CHANNEL, new CUIChannelListener(this));
- getServer().getMessenger().registerOutgoingPluginChannel(this, CUI_PLUGIN_CHANNEL);
- // Now we can register events!
- getServer().getPluginManager().registerEvents(new WorldEditListener(this), this);
+ controller.getPlatformManager().register(server);
+ api = new WorldEditAPI(this);
+ getServer().getMessenger().registerIncomingPluginChannel(this, CUI_PLUGIN_CHANNEL, new CUIChannelListener(this));
+ getServer().getMessenger().registerOutgoingPluginChannel(this, CUI_PLUGIN_CHANNEL);
+ // Now we can register events!
+ getServer().getPluginManager().registerEvents(new WorldEditListener(this), this);
- getServer().getScheduler().runTaskTimerAsynchronously(this,
- new SessionTimer(controller, getServer()), 120, 120);
- } catch (PlatformRejectionException e) {
- throw new RuntimeException(
- "WorldEdit rejected the Bukkit implementation of WorldEdit! This is strange and should " +
- "not have happened. Please report this error.", e);
- }
+ getServer().getScheduler().runTaskTimerAsynchronously(this, new SessionTimer(controller, getServer()), 120, 120);
+
+ // If we are on MCPC+/Cauldron, then Forge will have already loaded
+ // Forge WorldEdit and there's (probably) not going to be any other
+ // platforms to be worried about... at the current time of writing
+ WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent());
}
private void copyNmsBlockClasses(File target) {
@@ -220,24 +221,33 @@ public class WorldEditPlugin extends JavaPlugin {
}
}
- /**
- * Called on WorldEdit command.
- */
@Override
- public boolean onCommand(CommandSender sender, org.bukkit.command.Command cmd,
- String commandLabel, String[] args) {
-
+ public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
// Add the command to the array because the underlying command handling
// code of WorldEdit expects it
String[] split = new String[args.length + 1];
System.arraycopy(args, 0, split, 1, args.length);
- split[0] = "/" + cmd.getName();
+ split[0] = cmd.getName();
- controller.handleCommand(wrapCommandSender(sender), split);
+ CommandEvent event = new CommandEvent(wrapCommandSender(sender), Joiner.on(" ").join(split));
+ getWorldEdit().getEventBus().post(event);
return true;
}
+ @Override
+ public List onTabComplete(CommandSender sender, Command cmd, String commandLabel, String[] args) {
+ // Add the command to the array because the underlying command handling
+ // code of WorldEdit expects it
+ String[] split = new String[args.length + 1];
+ System.arraycopy(args, 0, split, 1, args.length);
+ split[0] = cmd.getName();
+
+ CommandSuggestionEvent event = new CommandSuggestionEvent(wrapCommandSender(sender), Joiner.on(" ").join(split));
+ getWorldEdit().getEventBus().post(event);
+ return event.getSuggestions();
+ }
+
/**
* Gets the session for the player.
*
@@ -340,12 +350,12 @@ public class WorldEditPlugin extends JavaPlugin {
return new BukkitPlayer(this, this.server, player);
}
- public LocalPlayer wrapCommandSender(CommandSender sender) {
+ public Actor wrapCommandSender(CommandSender sender) {
if (sender instanceof Player) {
return wrapPlayer((Player) sender);
}
- return new BukkitCommandSender(this, this.server, sender);
+ return new BukkitCommandSender(this, sender);
}
/**
@@ -357,6 +367,10 @@ public class WorldEditPlugin extends JavaPlugin {
return server;
}
+ BukkitServerInterface getInternalPlatform() {
+ return server;
+ }
+
/**
* Get WorldEdit.
*
diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgePlatform.java b/src/forge/java/com/sk89q/worldedit/forge/ForgePlatform.java
index f24396222..818a14573 100644
--- a/src/forge/java/com/sk89q/worldedit/forge/ForgePlatform.java
+++ b/src/forge/java/com/sk89q/worldedit/forge/ForgePlatform.java
@@ -19,36 +19,49 @@
package com.sk89q.worldedit.forge;
-import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.worldedit.BiomeTypes;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.ServerInterface;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.platform.Capability;
+import com.sk89q.worldedit.extension.platform.Preference;
+import com.sk89q.worldedit.util.command.CommandMapping;
+import com.sk89q.worldedit.util.command.Description;
+import com.sk89q.worldedit.util.command.Dispatcher;
+import com.sk89q.worldedit.world.World;
import cpw.mods.fml.common.FMLCommonHandler;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.command.ServerCommandManager;
import net.minecraft.entity.EntityList;
+import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.Item;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.DimensionManager;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import javax.annotation.Nullable;
+import java.util.*;
class ForgePlatform extends ServerInterface {
+
private final ForgeWorldEdit mod;
private final MinecraftServer server;
private final ForgeBiomeTypes biomes;
+ private boolean hookingEvents = false;
- public ForgePlatform(ForgeWorldEdit mod) {
+ ForgePlatform(ForgeWorldEdit mod) {
this.mod = mod;
this.server = FMLCommonHandler.instance().getMinecraftServerInstance();
this.biomes = new ForgeBiomeTypes();
}
+ boolean isHookingEvents() {
+ return hookingEvents;
+ }
+
+ @Override
public int resolveItem(String name) {
if (name == null) return 0;
for (Item item : Item.itemsList) {
@@ -65,21 +78,26 @@ class ForgePlatform extends ServerInterface {
return 0;
}
+ @Override
public boolean isValidMobType(String type) {
return EntityList.stringToClassMapping.containsKey(type);
}
+ @Override
public void reload() {
}
+ @Override
public BiomeTypes getBiomes() {
return this.biomes;
}
+ @Override
public int schedule(long delay, long period, Runnable task) {
return -1;
}
+ @Override
public List extends com.sk89q.worldedit.world.World> getWorlds() {
List worlds = Arrays.asList(DimensionManager.getWorlds());
List ret = new ArrayList(worlds.size());
@@ -89,20 +107,50 @@ class ForgePlatform extends ServerInterface {
return ret;
}
+ @Nullable
@Override
- public void onCommandRegistration(List commands) {
+ public Player matchPlayer(Player player) {
+ if (player instanceof ForgePlayer) {
+ return player;
+ } else {
+ EntityPlayerMP entity = server.getConfigurationManager().getPlayerForUsername(player.getName());
+ return entity != null ? new ForgePlayer(entity) : null;
+ }
+ }
+
+ @Nullable
+ @Override
+ public World matchWorld(World world) {
+ if (world instanceof ForgeWorld) {
+ return world;
+ } else {
+ for (WorldServer ws : DimensionManager.getWorlds()) {
+ if (ws.getWorldInfo().getWorldName().equals(world.getName())) {
+ return new ForgeWorld(ws);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ @Override
+ public void registerCommands(Dispatcher dispatcher) {
if (server == null) return;
ServerCommandManager mcMan = (ServerCommandManager) server.getCommandManager();
- for (final Command cmd : commands) {
+
+ for (final CommandMapping command : dispatcher.getCommands()) {
+ final Description description = command.getDescription();
+
mcMan.registerCommand(new CommandBase() {
@Override
public String getCommandName() {
- return cmd.aliases()[0];
+ return command.getPrimaryAlias();
}
@Override
public List getCommandAliases() {
- return Arrays.asList(cmd.aliases());
+ return Arrays.asList(command.getAllAliases());
}
@Override
@@ -110,12 +158,14 @@ class ForgePlatform extends ServerInterface {
@Override
public String getCommandUsage(ICommandSender icommandsender) {
- return "/" + cmd.aliases()[0] + " " + cmd.usage();
+ return "/" + command.getPrimaryAlias() + " " + description.getUsage();
}
@Override
- public int compareTo(Object o) {
- if (o instanceof ICommand) {
+ public int compareTo(@Nullable Object o) {
+ if (o == null) {
+ return -1;
+ } else if (o instanceof ICommand) {
return super.compareTo((ICommand) o);
} else {
return -1;
@@ -125,6 +175,12 @@ class ForgePlatform extends ServerInterface {
}
}
+ @Override
+ public void registerGameHooks() {
+ // We registered the events already anyway, so we just 'turn them on'
+ hookingEvents = true;
+ }
+
@Override
public LocalConfiguration getConfiguration() {
return mod.getConfig();
@@ -144,4 +200,16 @@ class ForgePlatform extends ServerInterface {
public String getPlatformVersion() {
return mod.getInternalVersion();
}
+
+ @Override
+ public Map getCapabilities() {
+ Map capabilities = new EnumMap(Capability.class);
+ capabilities.put(Capability.CONFIGURATION, Preference.PREFER_OTHERS);
+ capabilities.put(Capability.GAME_HOOKS, Preference.NORMAL);
+ capabilities.put(Capability.PERMISSIONS, Preference.PREFER_OTHERS);
+ capabilities.put(Capability.USER_COMMANDS, Preference.NORMAL);
+ capabilities.put(Capability.WORLD_EDITING, Preference.PREFERRED);
+ return capabilities;
+ }
+
}
diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java b/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java
index a3d441385..21978c270 100644
--- a/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java
+++ b/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java
@@ -38,7 +38,6 @@ public class ForgePlayer extends LocalPlayer {
private EntityPlayerMP player;
protected ForgePlayer(EntityPlayerMP player) {
- super((ServerInterface) ForgeWorldEdit.inst.getPlatform());
this.player = player;
}
diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorld.java b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorld.java
index 2f64a1f82..9271a1ce2 100644
--- a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorld.java
+++ b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorld.java
@@ -107,7 +107,7 @@ public class ForgeWorld extends AbstractWorld {
@Override
public String getName() {
- return getWorld().provider.getDimensionName();
+ return getWorld().getWorldInfo().getWorldName();
}
@Override
@@ -459,11 +459,15 @@ public class ForgeWorld extends AbstractWorld {
@Override
public boolean equals(Object o) {
- if ((o instanceof ForgeWorld)) {
- ForgeWorld other = ((ForgeWorld) o);
- World otherWorld = other.worldRef.get();
- World thisWorld = other.worldRef.get();
- return otherWorld != null && thisWorld != null && otherWorld.equals(thisWorld);
+ if (o == null) {
+ return false;
+ } else if ((o instanceof ForgeWorld)) {
+ ForgeWorld other = ((ForgeWorld) o);
+ World otherWorld = other.worldRef.get();
+ World thisWorld = other.worldRef.get();
+ return otherWorld != null && thisWorld != null && otherWorld.equals(thisWorld);
+ } else if (o instanceof com.sk89q.worldedit.world.World) {
+ return ((com.sk89q.worldedit.world.World) o).getName().equals(getName());
} else {
return false;
}
diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java
index 3de2beceb..dbc53eb1d 100644
--- a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java
+++ b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java
@@ -19,13 +19,14 @@
package com.sk89q.worldedit.forge;
+import com.google.common.base.Joiner;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldVector;
+import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
import com.sk89q.worldedit.extension.platform.Platform;
-import com.sk89q.worldedit.extension.platform.PlatformRejectionException;
import com.sk89q.worldedit.internal.LocalWorldAdapter;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.Mod;
@@ -103,11 +104,8 @@ public class ForgeWorldEdit {
}
this.platform = new ForgePlatform(this);
- try {
- WorldEdit.getInstance().getPlatformManager().register(platform);
- } catch (PlatformRejectionException e) {
- throw new RuntimeException("Failed to register with WorldEdit", e);
- }
+
+ WorldEdit.getInstance().getPlatformManager().register(platform);
}
@EventHandler
@@ -115,19 +113,28 @@ public class ForgeWorldEdit {
WorldEdit.getInstance().getPlatformManager().unregister(platform);
}
+ @EventHandler
+ public void serverStarted(FMLServerStartedEvent event) {
+ WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent());
+ }
+
@ForgeSubscribe
public void onCommandEvent(CommandEvent event) {
if ((event.sender instanceof EntityPlayerMP)) {
if (((EntityPlayerMP) event.sender).worldObj.isRemote) return;
String[] split = new String[event.parameters.length + 1];
System.arraycopy(event.parameters, 0, split, 1, event.parameters.length);
- split[0] = ("/" + event.command.getCommandName());
- WorldEdit.getInstance().handleCommand(wrap((EntityPlayerMP) event.sender), split);
+ split[0] = event.command.getCommandName();
+ com.sk89q.worldedit.event.platform.CommandEvent weEvent =
+ new com.sk89q.worldedit.event.platform.CommandEvent(wrap((EntityPlayerMP) event.sender), Joiner.on(" ").join(split));
+ WorldEdit.getInstance().getEventBus().post(weEvent);
}
}
@ForgeSubscribe
public void onPlayerInteract(PlayerInteractEvent event) {
+ if (!platform.isHookingEvents()) return; // We have to be told to catch these events
+
if (event.useItem == Result.DENY || event.entity.worldObj.isRemote) return;
WorldEdit we = WorldEdit.getInstance();
diff --git a/src/main/build/import-control.xml b/src/main/build/import-control.xml
index 56ba42a5c..9ef48920d 100644
--- a/src/main/build/import-control.xml
+++ b/src/main/build/import-control.xml
@@ -9,6 +9,7 @@
+
diff --git a/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java b/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java
index 3e26fdd0b..31900a9d2 100644
--- a/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java
+++ b/src/main/java/com/sk89q/minecraft/util/commands/CommandContext.java
@@ -28,15 +28,22 @@ 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(" "), null);
+ this(args.split(" ", -1), null);
}
public CommandContext(String[] args) throws CommandException {
@@ -44,28 +51,50 @@ public class CommandContext {
}
public CommandContext(String args, Set valueFlags) throws CommandException {
- this(args.split(" "), valueFlags);
+ 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);
}
/**
- * @param args An array with arguments. Empty strings outside quotes will be removed.
- * @param valueFlags A set containing all value flags. Pass null to disable value flag parsing.
- * @throws CommandException This is thrown if flag fails for some reason.
+ * 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) throws CommandException {
+ 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;
}
@@ -113,9 +142,14 @@ public class CommandContext {
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;
@@ -140,16 +174,30 @@ public class CommandContext {
}
if (nextArg >= argList.size()) {
- throw new CommandException("No value specified for the '-" + flagName + "' flag.");
+ 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() {
@@ -176,6 +224,18 @@ public class CommandContext {
}
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));
@@ -271,4 +331,8 @@ public class CommandContext {
public int argsLength() {
return parsedArgs.size();
}
+
+ public CommandLocals getLocals() {
+ return locals;
+ }
}
diff --git a/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java b/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java
index c87f0391c..eae94c75f 100644
--- a/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java
+++ b/src/main/java/com/sk89q/minecraft/util/commands/CommandException.java
@@ -19,8 +19,14 @@
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();
@@ -30,8 +36,37 @@ public class CommandException extends Exception {
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/src/main/java/com/sk89q/minecraft/util/commands/CommandLocals.java b/src/main/java/com/sk89q/minecraft/util/commands/CommandLocals.java
new file mode 100644
index 000000000..e0053f0b3
--- /dev/null
+++ b/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
+ * The given player may have been provided by a different platform.
+ *
+ * @param player the player to match
+ * @return a matched player, otherwise null
+ */
+ @Nullable Player matchPlayer(Player player);
- void onCommandRegistration(List commands, CommandsManager manager);
+ /**
+ * Create a duplicate of the given world.
+ *
+ * The given world may have been provided by a different platform.
+ *
+ * @param world the world to match
+ * @return a matched world, otherwise null
+ */
+ @Nullable World matchWorld(World world);
+
+ /**
+ * Register the commands contained within the given command dispatcher.
+ *
+ * @param dispatcher the dispatcher
+ */
+ void registerCommands(Dispatcher dispatcher);
+
+ /**
+ * Register game hooks.
+ */
+ void registerGameHooks();
/**
* Get the configuration from this platform.
@@ -116,4 +144,13 @@ public interface Platform {
*/
String getPlatformVersion();
+ /**
+ * Get a map of advertised capabilities of this platform, where each key
+ * in the given map is a supported capability and the respective value
+ * indicates the preference for this platform for that given capability.
+ *
+ * @return a map of capabilities
+ */
+ Map getCapabilities();
+
}
diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java b/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java
index 4a93df9e4..3e70e2483 100644
--- a/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java
+++ b/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java
@@ -20,19 +20,22 @@
package com.sk89q.worldedit.extension.platform;
import com.sk89q.worldedit.*;
+import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.command.tool.*;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.BlockInteractEvent;
import com.sk89q.worldedit.event.platform.Interaction;
+import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
import com.sk89q.worldedit.event.platform.PlayerInputEvent;
import com.sk89q.worldedit.internal.ServerInterfaceAdapter;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.eventbus.Subscribe;
+import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
+import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -48,11 +51,11 @@ public class PlatformManager {
private static final Logger logger = Logger.getLogger(PlatformManager.class.getCanonicalName());
- private final LocalConfiguration defaultConfig = new DefaultConfiguration();
- private final List platforms = new ArrayList();
private final WorldEdit worldEdit;
private final CommandManager commandManager;
- private @Nullable Platform primary = null;
+ private final List platforms = new ArrayList();
+ private final Map preferences = new EnumMap(Capability.class);
+ private @Nullable String firstSeenVersion;
/**
* Create a new platform manager.
@@ -62,7 +65,7 @@ public class PlatformManager {
public PlatformManager(WorldEdit worldEdit) {
checkNotNull(worldEdit);
this.worldEdit = worldEdit;
- this.commandManager = new CommandManager(worldEdit);
+ this.commandManager = new CommandManager(worldEdit, this);
// Register this instance for events
worldEdit.getEventBus().register(this);
@@ -72,56 +75,128 @@ public class PlatformManager {
* Register a platform with WorldEdit.
*
* @param platform the platform
- * @throws PlatformRejectionException thrown if the registration is rejected
*/
- public synchronized void register(Platform platform) throws PlatformRejectionException {
+ public synchronized void register(Platform platform) {
checkNotNull(platform);
+
logger.log(Level.FINE, "Got request to register " + platform.getClass() + " with WorldEdit [" + super.toString() + "]");
+
+ // Just add the platform to the list of platforms: we'll pick favorites
+ // once all the platforms have been loaded
platforms.add(platform);
- // Register primary platform
- if (this.primary == null) {
- commandManager.register(platform);
- this.primary = platform;
- } else {
- // Make sure that versions are in sync
- if (!primary.getVersion().equals(platform.getVersion())) {
+ // Make sure that versions are in sync
+ if (firstSeenVersion != null) {
+ if (!firstSeenVersion.equals(platform.getVersion())) {
logger.log(Level.WARNING,
"\n**********************************************\n" +
- "** There is a mismatch in available WorldEdit platforms!\n" +
+ "** You have WorldEdit installed for multiple platforms in the same \n" +
+ "** game/program. This is OK except that you have different WorldEdit versions\n" +
+ "** installed (i.e. {0} and {1}).\n" +
"**\n" +
- "** {0} v{1} is trying to register WE version v{2}\n" +
- "** but the primary platform, {3} v{4}, uses WE version v{5}\n" +
+ "** WorldEdit has seen both versions {0} and {1}.\n" +
"**\n" +
"** Things may break! Please make sure that your WE versions are in sync.\n" +
"**********************************************\n",
new Object[]{
- platform.getClass(), platform.getPlatformVersion(), platform.getVersion(),
- primary.getClass(), primary.getPlatformVersion(), primary.getVersion()
+ firstSeenVersion, platform.getVersion()
});
}
+ } else {
+ firstSeenVersion = platform.getVersion();
}
}
/**
* Unregister a platform from WorldEdit.
+ *
+ * If the platform has been chosen for any capabilities, then a new
+ * platform will be found.
*
* @param platform the platform
*/
public synchronized boolean unregister(Platform platform) {
checkNotNull(platform);
+
boolean removed = platforms.remove(platform);
+
if (removed) {
logger.log(Level.FINE, "Unregistering " + platform.getClass().getCanonicalName() + " from WorldEdit");
- if (platform == primary) {
- primary = null;
- commandManager.unregister();
+ boolean choosePreferred = false;
+
+ // Check whether this platform was chosen to be the preferred one
+ // for any capability and be sure to remove it
+ Iterator> it = preferences.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry entry = it.next();
+ if (entry.getValue().equals(platform)) {
+ entry.getKey().unload(this, entry.getValue());
+ it.remove();
+ choosePreferred = true; // Have to choose new favorites
+ }
+ }
+
+ if (choosePreferred) {
+ choosePreferred();
}
}
+
return removed;
}
+ /**
+ * Get the preferred platform for handling a certain capability. Returns
+ * null if none is available.
+ *
+ * @param capability the capability
+ * @return the platform
+ * @throws NoCapablePlatformException thrown if no platform is capable
+ */
+ public synchronized Platform queryCapability(Capability capability) throws NoCapablePlatformException {
+ Platform platform = preferences.get(checkNotNull(capability));
+ if (platform != null) {
+ return platform;
+ } else {
+ throw new NoCapablePlatformException("No platform was found supporting " + capability.name());
+ }
+ }
+
+ /**
+ * Choose preferred platforms and perform necessary initialization.
+ */
+ private synchronized void choosePreferred() {
+ for (Capability capability : Capability.values()) {
+ Platform preferred = findMostPreferred(capability);
+ if (preferred != null) {
+ preferences.put(capability, preferred);
+ capability.initialize(this, preferred);
+ }
+ }
+ }
+
+ /**
+ * Find the most preferred platform for a given capability from the list of
+ * platforms. This does not use the map of preferred platforms.
+ *
+ * @param capability the capability
+ * @return the most preferred platform, or null if no platform was found
+ */
+ private synchronized @Nullable Platform findMostPreferred(Capability capability) {
+ Platform preferred = null;
+ Preference highest = null;
+
+ for (Platform platform : platforms) {
+ Preference preference = platform.getCapabilities().get(capability);
+ if (preference != null && (highest == null || preference.isPreferredOver(highest))) {
+ preferred = platform;
+ highest = preference;
+ }
+ }
+
+ return preferred;
+ }
+
/**
* Get a list of loaded platforms.
*
@@ -134,12 +209,41 @@ public class PlatformManager {
}
/**
- * Get the primary platform.
+ * Given a world, possibly return the same world but using a different
+ * platform preferred for world editing operations.
*
- * @return the primary platform (may be null)
+ * @param base the world to match
+ * @return the preferred world, if one was found, otherwise the given world
*/
- public @Nullable Platform getPrimaryPlatform() {
- return primary;
+ public World getWorldForEditing(World base) {
+ checkNotNull(base);
+ World match = queryCapability(Capability.WORLD_EDITING).matchWorld(base);
+ return match != null ? match : base;
+ }
+
+ /**
+ * Given an actor, return a new one that may use a different platform
+ * for permissions and world editing.
+ *
+ * @param base the base actor to match
+ * @return a new delegate actor
+ */
+ @SuppressWarnings("unchecked")
+ public T createProxyActor(T base) {
+ checkNotNull(base);
+
+ if (base instanceof Player) {
+ Player player = (Player) base;
+
+ Player permActor = queryCapability(Capability.PERMISSIONS).matchPlayer(player);
+ if (permActor == null) {
+ permActor = player;
+ }
+
+ return (T) new PlayerProxy(player, permActor, getWorldForEditing(player.getWorld()));
+ } else {
+ return base;
+ }
}
/**
@@ -160,26 +264,7 @@ public class PlatformManager {
* @return the configuration
*/
public LocalConfiguration getConfiguration() {
- Platform platform = primary;
- if (platform != null) {
- return platform.getConfiguration();
- } else {
- return defaultConfig;
- }
- }
- /**
- * Return a {@link Platform}.
- *
- * @return a {@link Platform}
- * @throws IllegalStateException if no platform has been registered
- */
- public Platform getPlatform() throws IllegalStateException {
- Platform platform = primary;
- if (platform != null) {
- return platform;
- } else {
- throw new IllegalStateException("No platform has been registered");
- }
+ return queryCapability(Capability.CONFIGURATION).getConfiguration();
}
/**
@@ -188,22 +273,23 @@ public class PlatformManager {
* @return a {@link ServerInterface}
* @throws IllegalStateException if no platform has been registered
*/
+ @SuppressWarnings("deprecation")
public ServerInterface getServerInterface() throws IllegalStateException {
- Platform platform = primary;
- if (platform != null) {
- if (platform instanceof ServerInterface) {
- return (ServerInterface) platform;
- } else {
- return ServerInterfaceAdapter.adapt(platform);
- }
- } else {
- throw new IllegalStateException("No platform has been registered");
- }
+ return ServerInterfaceAdapter.adapt(queryCapability(Capability.USER_COMMANDS));
}
+ @Subscribe
+ public void handlePlatformReady(PlatformReadyEvent event) {
+ choosePreferred();
+ }
+
+ @SuppressWarnings("deprecation")
@Subscribe
public void handleBlockInteract(BlockInteractEvent event) {
- Actor actor = event.getCause();
+ // Create a proxy actor with a potentially different world for
+ // making changes to the world
+ Actor actor = createProxyActor(event.getCause());
+
Location location = event.getLocation();
Vector vector = location.toVector();
@@ -232,24 +318,19 @@ public class PlatformManager {
return;
}
- if (player instanceof LocalPlayer) { // Temporary workaround
- LocalPlayer localPlayer = (LocalPlayer) player;
- WorldVector worldVector = new WorldVector(location);
-
- if (player.isHoldingPickAxe() && session.hasSuperPickAxe()) {
- final BlockTool superPickaxe = session.getSuperPickaxe();
- if (superPickaxe != null && superPickaxe.canUse(localPlayer)) {
- event.setCancelled(superPickaxe.actPrimary(getServerInterface(), getConfiguration(), localPlayer, session, worldVector));
- return;
- }
+ if (player.isHoldingPickAxe() && session.hasSuperPickAxe()) {
+ final BlockTool superPickaxe = session.getSuperPickaxe();
+ if (superPickaxe != null && superPickaxe.canUse(player)) {
+ event.setCancelled(superPickaxe.actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location));
+ return;
}
+ }
- Tool tool = session.getTool(player.getItemInHand());
- if (tool != null && tool instanceof DoubleActionBlockTool) {
- if (tool.canUse(localPlayer)) {
- ((DoubleActionBlockTool) tool).actSecondary(getServerInterface(), getConfiguration(), localPlayer, session, worldVector);
- event.setCancelled(true);
- }
+ Tool tool = session.getTool(player.getItemInHand());
+ if (tool != null && tool instanceof DoubleActionBlockTool) {
+ if (tool.canUse(player)) {
+ ((DoubleActionBlockTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
+ event.setCancelled(true);
}
}
@@ -272,25 +353,24 @@ public class PlatformManager {
return;
}
- if (player instanceof LocalPlayer) { // Temporary workaround
- LocalPlayer localPlayer = (LocalPlayer) player;
- WorldVector worldVector = new WorldVector(location);
-
- Tool tool = session.getTool(player.getItemInHand());
- if (tool != null && tool instanceof BlockTool) {
- if (tool.canUse(localPlayer)) {
- ((BlockTool) tool).actPrimary(getServerInterface(), getConfiguration(), localPlayer, session, worldVector);
- event.setCancelled(true);
- }
+ Tool tool = session.getTool(player.getItemInHand());
+ if (tool != null && tool instanceof BlockTool) {
+ if (tool.canUse(player)) {
+ ((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
+ event.setCancelled(true);
}
}
}
}
}
+ @SuppressWarnings("deprecation")
@Subscribe
public void handlePlayerInput(PlayerInputEvent event) {
- Player player = event.getPlayer();
+ // Create a proxy actor with a potentially different world for
+ // making changes to the world
+ Player player = createProxyActor(event.getPlayer());
+ World world = player.getWorld();
switch (event.getInputType()) {
case PRIMARY: {
@@ -316,16 +396,12 @@ public class PlatformManager {
LocalSession session = worldEdit.getSessionManager().get(player);
- if (player instanceof LocalPlayer) { // Temporary workaround
- LocalPlayer localPlayer = (LocalPlayer) player;
-
- Tool tool = session.getTool(player.getItemInHand());
- if (tool != null && tool instanceof DoubleActionTraceTool) {
- if (tool.canUse(localPlayer)) {
- ((DoubleActionTraceTool) tool).actSecondary(getServerInterface(), getConfiguration(), localPlayer, session);
- event.setCancelled(true);
- return;
- }
+ Tool tool = session.getTool(player.getItemInHand());
+ if (tool != null && tool instanceof DoubleActionTraceTool) {
+ if (tool.canUse(player)) {
+ ((DoubleActionTraceTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
+ event.setCancelled(true);
+ return;
}
}
@@ -352,16 +428,12 @@ public class PlatformManager {
LocalSession session = worldEdit.getSessionManager().get(player);
- if (player instanceof LocalPlayer) { // Temporary workaround
- LocalPlayer localPlayer = (LocalPlayer) player;
-
- Tool tool = session.getTool(player.getItemInHand());
- if (tool != null && tool instanceof TraceTool) {
- if (tool.canUse(localPlayer)) {
- ((TraceTool) tool).actPrimary(getServerInterface(), getConfiguration(), localPlayer, session);
- event.setCancelled(true);
- return;
- }
+ Tool tool = session.getTool(player.getItemInHand());
+ if (tool != null && tool instanceof TraceTool) {
+ if (tool.canUse(player)) {
+ ((TraceTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
+ event.setCancelled(true);
+ return;
}
}
@@ -370,13 +442,5 @@ public class PlatformManager {
}
}
- /**
- * A default configuration for when none is set.
- */
- private static class DefaultConfiguration extends LocalConfiguration {
- @Override
- public void load() {
- }
- }
}
diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java b/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java
new file mode 100644
index 000000000..ce14651be
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java
@@ -0,0 +1,125 @@
+/*
+ * 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.worldedit.extension.platform;
+
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldVector;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extent.inventory.BlockBag;
+import com.sk89q.worldedit.util.Location;
+import com.sk89q.worldedit.world.World;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+class PlayerProxy extends AbstractPlayerActor {
+
+ private final Player basePlayer;
+ private final Actor permActor;
+ private final World world;
+
+ PlayerProxy(Player basePlayer, Actor permActor, World world) {
+ checkNotNull(basePlayer);
+ checkNotNull(permActor);
+ checkNotNull(world);
+ this.basePlayer = basePlayer;
+ this.permActor = permActor;
+ this.world = world;
+ }
+
+ @Override
+ public int getItemInHand() {
+ return basePlayer.getItemInHand();
+ }
+
+ @Override
+ public void giveItem(int type, int amount) {
+ basePlayer.giveItem(type, amount);
+ }
+
+ @Override
+ public BlockBag getInventoryBlockBag() {
+ return basePlayer.getInventoryBlockBag();
+ }
+
+ @Override
+ public String getName() {
+ return basePlayer.getName();
+ }
+
+ @Override
+ public Location getLocation() {
+ return basePlayer.getLocation();
+ }
+
+ @Override
+ public WorldVector getPosition() {
+ return basePlayer.getPosition();
+ }
+
+ @Override
+ public double getPitch() {
+ return basePlayer.getPitch();
+ }
+
+ @Override
+ public double getYaw() {
+ return basePlayer.getYaw();
+ }
+
+ @Override
+ public void setPosition(Vector pos, float pitch, float yaw) {
+ basePlayer.setPosition(pos, pitch, yaw);
+ }
+
+ @Override
+ public World getWorld() {
+ return world;
+ }
+
+ @Override
+ public void printRaw(String msg) {
+ basePlayer.printRaw(msg);
+ }
+
+ @Override
+ public void printDebug(String msg) {
+ basePlayer.printDebug(msg);
+ }
+
+ @Override
+ public void print(String msg) {
+ basePlayer.print(msg);
+ }
+
+ @Override
+ public void printError(String msg) {
+ basePlayer.printError(msg);
+ }
+
+ @Override
+ public String[] getGroups() {
+ return permActor.getGroups();
+ }
+
+ @Override
+ public boolean hasPermission(String perm) {
+ return permActor.hasPermission(perm);
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/Preference.java b/src/main/java/com/sk89q/worldedit/extension/platform/Preference.java
new file mode 100644
index 000000000..f047fa26d
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/extension/platform/Preference.java
@@ -0,0 +1,59 @@
+/*
+ * 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.worldedit.extension.platform;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Indicates the preference of a platform for a particular
+ * {@link Capability}.
+ */
+public enum Preference {
+
+ /**
+ * Indicates that the platform should be preferred for a given capability.
+ */
+ PREFERRED,
+
+ /**
+ * Indicates that preference for a platform is neutral for a given
+ * capability.
+ */
+ NORMAL,
+
+ /**
+ * Indicates that there should not be a preference for the platform for
+ * a given capability.
+ */
+ PREFER_OTHERS;
+
+ /**
+ * Returns whether this given preference is preferred over the given
+ * other preference.
+ *
+ * @param other the other preference
+ * @return true if this preference is greater
+ */
+ public boolean isPreferredOver(Preference other) {
+ checkNotNull(other);
+ return ordinal() < other.ordinal();
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/internal/LocalWorldAdapter.java b/src/main/java/com/sk89q/worldedit/internal/LocalWorldAdapter.java
index ea509acc3..a14d168f1 100644
--- a/src/main/java/com/sk89q/worldedit/internal/LocalWorldAdapter.java
+++ b/src/main/java/com/sk89q/worldedit/internal/LocalWorldAdapter.java
@@ -22,6 +22,7 @@ package com.sk89q.worldedit.internal;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BaseItemStack;
+import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.regions.Region;
@@ -250,7 +251,7 @@ public class LocalWorldAdapter extends LocalWorld {
}
@Override
- public boolean queueBlockBreakEffect(ServerInterface server, Vector position, int blockId, double priority) {
+ public boolean queueBlockBreakEffect(Platform server, Vector position, int blockId, double priority) {
return world.queueBlockBreakEffect(server, position, blockId, priority);
}
diff --git a/src/main/java/com/sk89q/worldedit/internal/ServerInterfaceAdapter.java b/src/main/java/com/sk89q/worldedit/internal/ServerInterfaceAdapter.java
index fa71a403a..16d86ee5e 100644
--- a/src/main/java/com/sk89q/worldedit/internal/ServerInterfaceAdapter.java
+++ b/src/main/java/com/sk89q/worldedit/internal/ServerInterfaceAdapter.java
@@ -19,13 +19,19 @@
package com.sk89q.worldedit.internal;
-import com.sk89q.minecraft.util.commands.Command;
-import com.sk89q.minecraft.util.commands.CommandsManager;
-import com.sk89q.worldedit.*;
+import com.sk89q.worldedit.BiomeTypes;
+import com.sk89q.worldedit.LocalConfiguration;
+import com.sk89q.worldedit.ServerInterface;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.Platform;
+import com.sk89q.worldedit.extension.platform.Preference;
+import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.world.World;
+import javax.annotation.Nullable;
import java.util.List;
+import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -76,15 +82,25 @@ public class ServerInterfaceAdapter extends ServerInterface {
return platform.getWorlds();
}
+ @Nullable
@Override
- @Deprecated
- public void onCommandRegistration(List commands) {
- platform.onCommandRegistration(commands);
+ public Player matchPlayer(Player player) {
+ return platform.matchPlayer(player);
+ }
+
+ @Nullable
+ @Override
+ public World matchWorld(World world) {
+ return platform.matchWorld(world);
}
@Override
- public void onCommandRegistration(List commands, CommandsManager manager) {
- platform.onCommandRegistration(commands, manager);
+ public void registerCommands(Dispatcher dispatcher) {
+ platform.registerCommands(dispatcher);
+ }
+
+ @Override
+ public void registerGameHooks() {
}
@Override
@@ -107,6 +123,11 @@ public class ServerInterfaceAdapter extends ServerInterface {
return platform.getPlatformVersion();
}
+ @Override
+ public Map getCapabilities() {
+ return platform.getCapabilities();
+ }
+
/**
* Adapt an {@link Platform} instance into a {@link ServerInterface}.
*
diff --git a/src/main/java/com/sk89q/worldedit/internal/annotation/Direction.java b/src/main/java/com/sk89q/worldedit/internal/annotation/Direction.java
new file mode 100644
index 000000000..45d495bbd
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/internal/annotation/Direction.java
@@ -0,0 +1,39 @@
+/*
+ * 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.worldedit.internal.annotation;
+
+import com.sk89q.worldedit.Vector;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a {@link Vector} parameter to inject a direction.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Direction {
+
+ public static final String AIM = "me";
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/internal/annotation/Selection.java b/src/main/java/com/sk89q/worldedit/internal/annotation/Selection.java
new file mode 100644
index 000000000..9647c360f
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/internal/annotation/Selection.java
@@ -0,0 +1,34 @@
+/*
+ * 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.worldedit.internal.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that this value should come from the current selection.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Selection {
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/internal/command/ActorAuthorizer.java b/src/main/java/com/sk89q/worldedit/internal/command/ActorAuthorizer.java
new file mode 100644
index 000000000..f2f301ca2
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/internal/command/ActorAuthorizer.java
@@ -0,0 +1,40 @@
+/*
+ * 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.worldedit.internal.command;
+
+import com.sk89q.minecraft.util.commands.CommandLocals;
+import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.util.auth.Authorizer;
+
+/**
+ * Implementation of an authorizer that uses {@link Actor#hasPermission(String)}.
+ */
+public class ActorAuthorizer implements Authorizer {
+
+ @Override
+ public boolean testPermission(CommandLocals locals, String permission) {
+ Actor sender = locals.get(Actor.class);
+ if (sender == null) {
+ throw new RuntimeException("Uh oh! No 'Actor' specified so that we can check permissions");
+ } else {
+ return sender.hasPermission(permission);
+ }
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/internal/command/CommandLoggingHandler.java b/src/main/java/com/sk89q/worldedit/internal/command/CommandLoggingHandler.java
new file mode 100644
index 000000000..b09a26bee
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/internal/command/CommandLoggingHandler.java
@@ -0,0 +1,156 @@
+/*
+ * 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.worldedit.internal.command;
+
+import com.sk89q.minecraft.util.commands.CommandContext;
+import com.sk89q.minecraft.util.commands.CommandException;
+import com.sk89q.minecraft.util.commands.Logging;
+import com.sk89q.worldedit.*;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.util.command.parametric.AbstractInvokeListener;
+import com.sk89q.worldedit.util.command.parametric.InvokeHandler;
+import com.sk89q.worldedit.util.command.parametric.ParameterData;
+import com.sk89q.worldedit.util.command.parametric.ParameterException;
+
+import java.io.Closeable;
+import java.lang.reflect.Method;
+import java.util.logging.Handler;
+import java.util.logging.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Logs called commands to a logger.
+ */
+public class CommandLoggingHandler extends AbstractInvokeListener implements InvokeHandler, Closeable {
+
+ private final WorldEdit worldEdit;
+ private final Logger logger;
+
+ /**
+ * Create a new instance.
+ *
+ * @param worldEdit an instance of WorldEdit
+ * @param logger the logger to send messages to
+ */
+ public CommandLoggingHandler(WorldEdit worldEdit, Logger logger) {
+ checkNotNull(worldEdit);
+ checkNotNull(logger);
+ this.worldEdit = worldEdit;
+ this.logger = logger;
+ }
+
+ @Override
+ public void preProcess(Object object, Method method, ParameterData[] parameters, CommandContext context) throws CommandException, ParameterException {
+ }
+
+ @Override
+ public void preInvoke(Object object, Method method, ParameterData[] parameters, Object[] args, CommandContext context) throws CommandException {
+ Logging loggingAnnotation = method.getAnnotation(Logging.class);
+ Logging.LogMode logMode;
+ StringBuilder builder = new StringBuilder();
+
+ if (loggingAnnotation == null) {
+ logMode = null;
+ } else {
+ logMode = loggingAnnotation.value();
+ }
+
+ Actor sender = context.getLocals().get(Actor.class);
+ Player player;
+
+ if (sender == null) {
+ return;
+ }
+
+ if (sender instanceof Player) {
+ player = (Player) sender;
+ } else {
+ return;
+ }
+
+ builder.append("WorldEdit: ").append(sender.getName());
+ if (sender.isPlayer()) {
+ builder.append(" (in \"" + player.getWorld().getName() + "\")");
+ }
+
+ builder.append(": ").append(context.getCommand());
+
+ if (context.argsLength() > 0) {
+ builder.append(" ").append(context.getJoinedStrings(0));
+ }
+
+ if (logMode != null && sender.isPlayer()) {
+ Vector position = player.getPosition();
+ LocalSession session = worldEdit.getSessionManager().get(player);
+
+ switch (logMode) {
+ case PLACEMENT:
+ try {
+ position = session.getPlacementPosition(player);
+ } catch (IncompleteRegionException e) {
+ break;
+ }
+ /* FALL-THROUGH */
+
+ case POSITION:
+ builder.append(" - Position: " + position);
+ break;
+
+ case ALL:
+ builder.append(" - Position: " + position);
+ /* FALL-THROUGH */
+
+ case ORIENTATION_REGION:
+ builder.append(" - Orientation: " + player.getCardinalDirection().name());
+ /* FALL-THROUGH */
+
+ case REGION:
+ try {
+ builder.append(" - Region: ")
+ .append(session.getSelection(player.getWorld()));
+ } catch (IncompleteRegionException e) {
+ break;
+ }
+ break;
+ }
+ }
+
+ logger.info(builder.toString());
+ }
+
+ @Override
+ public void postInvoke(Object object, Method method, ParameterData[] parameters, Object[] args, CommandContext context) throws CommandException {
+ }
+
+ @Override
+ public InvokeHandler createInvokeHandler() {
+ return this;
+ }
+
+ @Override
+ public void close() {
+ for (Handler h : logger.getHandlers()) {
+ h.close();
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java b/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java
new file mode 100644
index 000000000..ed29e8fb5
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java
@@ -0,0 +1,296 @@
+/*
+ * 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.worldedit.internal.command;
+
+import com.sk89q.worldedit.*;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.entity.Entity;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.input.NoMatchException;
+import com.sk89q.worldedit.extension.input.ParserContext;
+import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.function.mask.Mask;
+import com.sk89q.worldedit.function.pattern.Pattern;
+import com.sk89q.worldedit.internal.annotation.Direction;
+import com.sk89q.worldedit.internal.annotation.Selection;
+import com.sk89q.worldedit.regions.Region;
+import com.sk89q.worldedit.util.TreeGenerator;
+import com.sk89q.worldedit.util.TreeGenerator.TreeType;
+import com.sk89q.worldedit.util.command.parametric.*;
+
+import java.util.Arrays;
+
+/**
+ * Binds standard WorldEdit classes such as {@link Player} and {@link LocalSession}.
+ */
+public class WorldEditBinding extends BindingHelper {
+
+ private final WorldEdit worldEdit;
+
+ /**
+ * Create a new instance.
+ *
+ * @param worldEdit the WorldEdit instance to bind to
+ */
+ public WorldEditBinding(WorldEdit worldEdit) {
+ this.worldEdit = worldEdit;
+ }
+
+ /**
+ * Gets a selection from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @param selection the annotation
+ * @return a selection
+ * @throws IncompleteRegionException if no selection is available
+ * @throws ParameterException on other error
+ */
+ @BindingMatch(classifier = Selection.class,
+ type = Region.class,
+ behavior = BindingBehavior.PROVIDES)
+ public Object getSelection(ArgumentStack context, Selection selection) throws IncompleteRegionException, ParameterException {
+ Player sender = getPlayer(context);
+ LocalSession session = worldEdit.getSessionManager().get(sender);
+ return session.getSelection(sender.getWorld());
+ }
+
+ /**
+ * Gets an {@link EditSession} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return an edit session
+ * @throws ParameterException on other error
+ */
+ @BindingMatch(type = EditSession.class,
+ behavior = BindingBehavior.PROVIDES)
+ public EditSession getEditSession(ArgumentStack context) throws ParameterException {
+ Player sender = getPlayer(context);
+ LocalSession session = worldEdit.getSessionManager().get(sender);
+ EditSession editSession = session.createEditSession(sender);
+ editSession.enableQueue();
+ context.getContext().getLocals().put(EditSession.class, editSession);
+ session.tellVersion(sender);
+ return editSession;
+ }
+
+ /**
+ * Gets an {@link LocalSession} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a local session
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = LocalSession.class,
+ behavior = BindingBehavior.PROVIDES)
+ public LocalSession getLocalSession(ArgumentStack context) throws ParameterException {
+ Player sender = getPlayer(context);
+ return worldEdit.getSessionManager().get(sender);
+ }
+
+ /**
+ * Gets an {@link Actor} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a local player
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = Actor.class,
+ behavior = BindingBehavior.PROVIDES)
+ public Actor getActor(ArgumentStack context) throws ParameterException {
+ Actor sender = context.getContext().getLocals().get(Actor.class);
+ if (sender == null) {
+ throw new ParameterException("Missing 'Actor'");
+ } else {
+ return sender;
+ }
+ }
+
+ /**
+ * Gets an {@link Player} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a local player
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = Player.class,
+ behavior = BindingBehavior.PROVIDES)
+ public Player getPlayer(ArgumentStack context) throws ParameterException {
+ Actor sender = context.getContext().getLocals().get(Actor.class);
+ if (sender == null) {
+ throw new ParameterException("No player to get a session for");
+ } else if (sender instanceof Player) {
+ return (Player) sender;
+ } else {
+ throw new ParameterException("Caller is not a player");
+ }
+ }
+
+ /**
+ * Gets an {@link BaseBlock} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a pattern
+ * @throws ParameterException on error
+ * @throws WorldEditException on error
+ */
+ @BindingMatch(type = BaseBlock.class,
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1)
+ public BaseBlock getBaseBlock(ArgumentStack context) throws ParameterException, WorldEditException {
+ Actor actor = context.getContext().getLocals().get(Actor.class);
+ ParserContext parserContext = new ParserContext();
+ parserContext.setActor(context.getContext().getLocals().get(Actor.class));
+ if (actor instanceof Entity) {
+ parserContext.setWorld(((Entity) actor).getWorld());
+ }
+ parserContext.setSession(worldEdit.getSessionManager().get(actor));
+ try {
+ return worldEdit.getBlockRegistry().parseFromInput(context.next(), parserContext);
+ } catch (NoMatchException e) {
+ throw new ParameterException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Gets an {@link Pattern} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a pattern
+ * @throws ParameterException on error
+ * @throws WorldEditException on error
+ */
+ @BindingMatch(type = Pattern.class,
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1)
+ public Pattern getPattern(ArgumentStack context) throws ParameterException, WorldEditException {
+ Actor actor = context.getContext().getLocals().get(Actor.class);
+ ParserContext parserContext = new ParserContext();
+ parserContext.setActor(context.getContext().getLocals().get(Actor.class));
+ if (actor instanceof Entity) {
+ parserContext.setWorld(((Entity) actor).getWorld());
+ }
+ parserContext.setSession(worldEdit.getSessionManager().get(actor));
+ try {
+ return worldEdit.getPatternRegistry().parseFromInput(context.next(), parserContext);
+ } catch (NoMatchException e) {
+ throw new ParameterException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Gets an {@link Mask} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a pattern
+ * @throws ParameterException on error
+ * @throws WorldEditException on error
+ */
+ @BindingMatch(type = Mask.class,
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1)
+ public Mask getMask(ArgumentStack context) throws ParameterException, WorldEditException {
+ Actor actor = context.getContext().getLocals().get(Actor.class);
+ ParserContext parserContext = new ParserContext();
+ parserContext.setActor(context.getContext().getLocals().get(Actor.class));
+ if (actor instanceof Entity) {
+ parserContext.setWorld(((Entity) actor).getWorld());
+ }
+ parserContext.setSession(worldEdit.getSessionManager().get(actor));
+ try {
+ return worldEdit.getMaskRegistry().parseFromInput(context.next(), parserContext);
+ } catch (NoMatchException e) {
+ throw new ParameterException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Get a direction from the player.
+ *
+ * @param context the context
+ * @param direction the direction annotation
+ * @return a pattern
+ * @throws ParameterException on error
+ * @throws UnknownDirectionException on an unknown direction
+ */
+ @BindingMatch(classifier = Direction.class,
+ type = Vector.class,
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1)
+ public Vector getDirection(ArgumentStack context, Direction direction)
+ throws ParameterException, UnknownDirectionException {
+ Player sender = getPlayer(context);
+ return worldEdit.getDirection(sender, context.next());
+ }
+
+ /**
+ * Gets an {@link TreeType} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a pattern
+ * @throws ParameterException on error
+ * @throws WorldEditException on error
+ */
+ @BindingMatch(type = TreeType.class,
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1)
+ public TreeType getTreeType(ArgumentStack context) throws ParameterException, WorldEditException {
+ String input = context.next();
+ if (input != null) {
+ TreeType type = TreeGenerator.lookup(input);
+ if (type != null) {
+ return type;
+ } else {
+ throw new ParameterException(
+ String.format("Can't recognize tree type '%s' -- choose from: %s", input, Arrays.toString(TreeType.values())));
+ }
+ } else {
+ return TreeType.TREE;
+ }
+ }
+
+ /**
+ * Gets an {@link BiomeType} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a pattern
+ * @throws ParameterException on error
+ * @throws WorldEditException on error
+ */
+ @BindingMatch(type = BiomeType.class,
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1)
+ public BiomeType getBiomeType(ArgumentStack context) throws ParameterException, WorldEditException {
+ String input = context.next();
+ if (input != null) {
+ BiomeType type = worldEdit.getServer().getBiomes().get(input);
+ if (type != null) {
+ return type;
+ } else {
+ throw new ParameterException(
+ String.format("Can't recognize biome type '%s' -- use //biomelist to list available types", input));
+ }
+ } else {
+ throw new ParameterException(
+ "This command takes a 'default' biome if one is not set, except there is no particular " +
+ "biome that should be 'default', so the command should not be taking a default biome");
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/internal/command/WorldEditExceptionConverter.java b/src/main/java/com/sk89q/worldedit/internal/command/WorldEditExceptionConverter.java
new file mode 100644
index 000000000..6b8831d13
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/internal/command/WorldEditExceptionConverter.java
@@ -0,0 +1,156 @@
+/*
+ * 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.worldedit.internal.command;
+
+import com.sk89q.minecraft.util.commands.CommandException;
+import com.sk89q.worldedit.*;
+import com.sk89q.worldedit.blocks.ItemType;
+import com.sk89q.worldedit.command.InsufficientArgumentsException;
+import com.sk89q.worldedit.internal.expression.ExpressionException;
+import com.sk89q.worldedit.regions.RegionOperationException;
+import com.sk89q.worldedit.util.command.parametric.ExceptionConverterHelper;
+import com.sk89q.worldedit.util.command.parametric.ExceptionMatch;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * converts WorldEdit exceptions and converts them into {@link CommandException}s.
+ */
+public class WorldEditExceptionConverter extends ExceptionConverterHelper {
+
+ private static final Pattern numberFormat = Pattern.compile("^For input string: \"(.*)\"$");
+ private final WorldEdit worldEdit;
+
+ public WorldEditExceptionConverter(WorldEdit worldEdit) {
+ checkNotNull(worldEdit);
+ this.worldEdit = worldEdit;
+ }
+
+ @ExceptionMatch
+ public void convert(PlayerNeededException e) throws CommandException {
+ throw new CommandException(e.getMessage());
+ }
+
+ @ExceptionMatch
+ public void convert(NumberFormatException e) throws CommandException {
+ final Matcher matcher = numberFormat.matcher(e.getMessage());
+
+ if (matcher.matches()) {
+ throw new CommandException("Number expected; string \"" + matcher.group(1)
+ + "\" given.");
+ } else {
+ throw new CommandException("Number expected; string given.");
+ }
+ }
+
+ @ExceptionMatch
+ public void convert(IncompleteRegionException e) throws CommandException {
+ throw new CommandException("Make a region selection first.");
+ }
+
+ @ExceptionMatch
+ public void convert(UnknownItemException e) throws CommandException {
+ throw new CommandException("Block name '" + e.getID() + "' was not recognized.");
+ }
+
+ @ExceptionMatch
+ public void convert(InvalidItemException e) throws CommandException {
+ throw new CommandException(e.getMessage());
+ }
+
+ @ExceptionMatch
+ public void convert(DisallowedItemException e) throws CommandException {
+ throw new CommandException("Block '" + e.getID()
+ + "' not allowed (see WorldEdit configuration).");
+ }
+
+ @ExceptionMatch
+ public void convert(MaxChangedBlocksException e) throws CommandException {
+ throw new CommandException("Max blocks changed in an operation reached ("
+ + e.getBlockLimit() + ").");
+ }
+
+ @ExceptionMatch
+ public void convert(MaxBrushRadiusException e) throws CommandException {
+ throw new CommandException("Maximum brush radius (in configuration): " + worldEdit.getConfiguration().maxBrushRadius);
+ }
+
+ @ExceptionMatch
+ public void convert(MaxRadiusException e) throws CommandException {
+ throw new CommandException("Maximum radius (in configuration): " + worldEdit.getConfiguration().maxRadius);
+ }
+
+ @ExceptionMatch
+ public void convert(UnknownDirectionException e) throws CommandException {
+ throw new CommandException("Unknown direction: " + e.getDirection());
+ }
+
+ @ExceptionMatch
+ public void convert(InsufficientArgumentsException e) throws CommandException {
+ throw new CommandException(e.getMessage());
+ }
+
+ @ExceptionMatch
+ public void convert(RegionOperationException e) throws CommandException {
+ throw new CommandException(e.getMessage());
+ }
+
+ @ExceptionMatch
+ public void convert(ExpressionException e) throws CommandException {
+ throw new CommandException(e.getMessage());
+ }
+
+ @ExceptionMatch
+ public void convert(EmptyClipboardException e) throws CommandException {
+ throw new CommandException("Your clipboard is empty. Use //copy first.");
+ }
+
+ @ExceptionMatch
+ public void convert(InvalidFilenameException e) throws CommandException {
+ throw new CommandException("Filename '" + e.getFilename() + "' invalid: "
+ + e.getMessage());
+ }
+
+ @ExceptionMatch
+ public void convert(FilenameResolutionException e) throws CommandException {
+ throw new CommandException(
+ "File '" + e.getFilename() + "' resolution error: " + e.getMessage());
+ }
+
+ @ExceptionMatch
+ public void convert(InvalidToolBindException e) throws CommandException {
+ throw new CommandException("Can't bind tool to "
+ + ItemType.toHeldName(e.getItemId()) + ": " + e.getMessage());
+ }
+
+ @ExceptionMatch
+ public void convert(FileSelectionAbortedException e) throws CommandException {
+ throw new CommandException("File selection aborted.");
+ }
+
+ @ExceptionMatch
+ public void convert(WorldEditException e) throws CommandException {
+ throw new CommandException(e.getMessage());
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/scripting/CraftScriptContext.java b/src/main/java/com/sk89q/worldedit/scripting/CraftScriptContext.java
index 827dda8aa..f1cfaf157 100644
--- a/src/main/java/com/sk89q/worldedit/scripting/CraftScriptContext.java
+++ b/src/main/java/com/sk89q/worldedit/scripting/CraftScriptContext.java
@@ -19,24 +19,18 @@
package com.sk89q.worldedit.scripting;
+import com.sk89q.worldedit.*;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.command.InsufficientArgumentsException;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.platform.Platform;
+import com.sk89q.worldedit.patterns.Pattern;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
-import com.sk89q.worldedit.DisallowedItemException;
-import com.sk89q.worldedit.EditSession;
-import com.sk89q.worldedit.FilenameException;
-import com.sk89q.worldedit.LocalConfiguration;
-import com.sk89q.worldedit.LocalPlayer;
-import com.sk89q.worldedit.LocalSession;
-import com.sk89q.worldedit.ServerInterface;
-import com.sk89q.worldedit.UnknownItemException;
-import com.sk89q.worldedit.WorldEdit;
-import com.sk89q.worldedit.WorldEditException;
-import com.sk89q.worldedit.blocks.BaseBlock;
-import com.sk89q.worldedit.command.InsufficientArgumentsException;
-import com.sk89q.worldedit.patterns.Pattern;
/**
* The context given to scripts.
@@ -48,8 +42,8 @@ public class CraftScriptContext extends CraftScriptEnvironment {
private String[] args;
public CraftScriptContext(WorldEdit controller,
- ServerInterface server, LocalConfiguration config,
- LocalSession session, LocalPlayer player, String[] args) {
+ Platform server, LocalConfiguration config,
+ LocalSession session, Player player, String[] args) {
super(controller, server, config, session, player);
this.args = args;
}
@@ -74,7 +68,7 @@ public class CraftScriptContext extends CraftScriptEnvironment {
*
* @return
*/
- public LocalPlayer getPlayer() {
+ public Player getPlayer() {
return player;
}
diff --git a/src/main/java/com/sk89q/worldedit/scripting/CraftScriptEnvironment.java b/src/main/java/com/sk89q/worldedit/scripting/CraftScriptEnvironment.java
index b437bc005..68c145b4c 100644
--- a/src/main/java/com/sk89q/worldedit/scripting/CraftScriptEnvironment.java
+++ b/src/main/java/com/sk89q/worldedit/scripting/CraftScriptEnvironment.java
@@ -20,24 +20,25 @@
package com.sk89q.worldedit.scripting;
import com.sk89q.worldedit.LocalConfiguration;
-import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.LocalSession;
-import com.sk89q.worldedit.ServerInterface;
import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.platform.Platform;
public abstract class CraftScriptEnvironment {
+
protected WorldEdit controller;
- protected LocalPlayer player;
+ protected Player player;
protected LocalConfiguration config;
protected LocalSession session;
- protected ServerInterface server;
+ protected Platform server;
- public CraftScriptEnvironment(WorldEdit controller, ServerInterface server,
- LocalConfiguration config, LocalSession session, LocalPlayer player) {
+ public CraftScriptEnvironment(WorldEdit controller, Platform server, LocalConfiguration config, LocalSession session, Player player) {
this.controller = controller;
this.player = player;
this.config = config;
this.server = server;
this.session = session;
}
+
}
diff --git a/src/main/java/com/sk89q/worldedit/util/auth/Authorizer.java b/src/main/java/com/sk89q/worldedit/util/auth/Authorizer.java
new file mode 100644
index 000000000..adbeafc7c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/auth/Authorizer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.worldedit.util.auth;
+
+import com.sk89q.minecraft.util.commands.CommandLocals;
+
+/**
+ * Tests whether permission is granted.
+ */
+public interface Authorizer {
+
+ /**
+ * Tests whether permission is granted for the given context.
+ *
+ * @param locals locals
+ * @param permission the permission string
+ * @return true if permitted
+ */
+ boolean testPermission(CommandLocals locals, String permission);
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/auth/NullAuthorizer.java b/src/main/java/com/sk89q/worldedit/util/auth/NullAuthorizer.java
new file mode 100644
index 000000000..346e6b794
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/auth/NullAuthorizer.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.worldedit.util.auth;
+
+import com.sk89q.minecraft.util.commands.CommandLocals;
+
+/**
+ * An implementation of {@link Authorizer} that always returns false for
+ * tests of permissions.
+ */
+public class NullAuthorizer implements Authorizer {
+
+ @Override
+ public boolean testPermission(CommandLocals locals, String permission) {
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/CommandCallable.java b/src/main/java/com/sk89q/worldedit/util/command/CommandCallable.java
new file mode 100644
index 000000000..9a16a467c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/CommandCallable.java
@@ -0,0 +1,78 @@
+/*
+ * 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.worldedit.util.command;
+
+import com.sk89q.minecraft.util.commands.CommandException;
+import com.sk89q.minecraft.util.commands.CommandLocals;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A command that can be executed.
+ */
+public interface CommandCallable {
+
+ /**
+ * Get a list of value flags used by this command.
+ *
+ * @return a list of value flags
+ */
+ Set getValueFlags();
+
+ /**
+ * Execute the correct command based on the input.
+ *
+ * The implementing class must perform the necessary permission checks.
+ *
+ * @param arguments the arguments
+ * @param locals the locals
+ * @param parentCommands a list of parent commands, with the first most entry being the top-level command
+ * @return the called command, or null if there was no command found
+ * @throws CommandException thrown on a command error
+ */
+ boolean call(String arguments, CommandLocals locals, String[] parentCommands) throws CommandException;
+
+ /**
+ * Get a list of suggestions based on input.
+ *
+ * @param arguments the arguments entered up to this point
+ * @param locals the locals
+ * @return a list of suggestions
+ * @throws CommandException thrown if there was a parsing error
+ */
+ List getSuggestions(String arguments, CommandLocals locals) throws CommandException;
+
+ /**
+ * Get an object describing this command.
+ *
+ * @return the command description
+ */
+ Description getDescription();
+
+ /**
+ * Test whether this command can be executed with the given context.
+ *
+ * @param locals the locals
+ * @return true if execution is permitted
+ */
+ boolean testPermission(CommandLocals locals);
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/CommandMapping.java b/src/main/java/com/sk89q/worldedit/util/command/CommandMapping.java
new file mode 100644
index 000000000..6e44bff17
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/CommandMapping.java
@@ -0,0 +1,88 @@
+/*
+ * 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.worldedit.util.command;
+
+import java.util.Arrays;
+
+/**
+ * Tracks a command registration.
+ */
+public class CommandMapping {
+
+ private final String[] aliases;
+ private final CommandCallable callable;
+
+ /**
+ * Create a new instance.
+ *
+ * @param callable the command callable
+ * @param alias a list of all aliases, where the first one is the primary one
+ */
+ public CommandMapping(CommandCallable callable, String... alias) {
+ super();
+ this.aliases = alias;
+ this.callable = callable;
+ }
+
+ /**
+ * Get the primary alias.
+ *
+ * @return the primary alias
+ */
+ public String getPrimaryAlias() {
+ return aliases[0];
+ }
+
+ /**
+ * Get a list of all aliases.
+ *
+ * @return aliases
+ */
+ public String[] getAllAliases() {
+ return aliases;
+ }
+
+ /**
+ * Get the callable
+ *
+ * @return the callable
+ */
+ public CommandCallable getCallable() {
+ return callable;
+ }
+
+ /**
+ * Get the {@link Description} form the callable.
+ *
+ * @return the description
+ */
+ public Description getDescription() {
+ return getCallable().getDescription();
+ }
+
+ @Override
+ public String toString() {
+ return "CommandMapping{" +
+ "aliases=" + Arrays.toString(aliases) +
+ ", callable=" + callable +
+ '}';
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/Description.java b/src/main/java/com/sk89q/worldedit/util/command/Description.java
new file mode 100644
index 000000000..72e9fd5f0
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/Description.java
@@ -0,0 +1,70 @@
+/*
+ * 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.worldedit.util.command;
+
+import java.util.List;
+
+/**
+ * A description of a command.
+ */
+public interface Description {
+
+ /**
+ * Get the list of parameters for this command.
+ *
+ * @return a list of parameters
+ */
+ List getParameters();
+
+ /**
+ * Get a short one-line description of this command.
+ *
+ * @return a description, or null if no description is available
+ */
+ String getShortDescription();
+
+ /**
+ * Get a longer help text about this command.
+ *
+ * @return a help text, or null if no help is available
+ */
+ String getHelp();
+
+ /**
+ * Get the usage string of this command.
+ *
+ *
A usage string may look like
+ * [-w <world>] <var1> <var2>.
+ *
+ * @return a usage string
+ */
+ String getUsage();
+
+ /**
+ * Get a list of permissions that the player may have to have permission.
+ *
+ *
Permission data may or may not be available. This is only useful as a
+ * potential hint.
+ *
+ * @return the list of permissions
+ */
+ List getPermissions();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/sk89q/worldedit/util/command/Dispatcher.java b/src/main/java/com/sk89q/worldedit/util/command/Dispatcher.java
new file mode 100644
index 000000000..568db508e
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/Dispatcher.java
@@ -0,0 +1,85 @@
+/*
+ * 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.worldedit.util.command;
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Executes a command based on user input.
+ */
+public interface Dispatcher extends CommandCallable {
+
+ /**
+ * Register a command with this dispatcher.
+ *
+ * @param callable the command executor
+ * @param alias a list of aliases, where the first alias is the primary name
+ */
+ void registerCommand(CommandCallable callable, String... alias);
+
+ /**
+ * Get a list of commands. Each command, regardless of how many aliases
+ * it may have, will only appear once in the returned set.
+ *
+ *
The returned collection cannot be modified.
+ *
+ * @return a list of registrations
+ */
+ Set getCommands();
+
+ /**
+ * Get a list of primary aliases.
+ *
+ *
The returned collection cannot be modified.
+ *
+ * @return a list of aliases
+ */
+ Collection getPrimaryAliases();
+
+ /**
+ * Get a list of all the command aliases, which includes the primary alias.
+ *
+ *
A command may have more than one alias assigned to it. The returned
+ * collection cannot be modified.
+ *
+ * @return a list of aliases
+ */
+ Collection getAliases();
+
+ /**
+ * Get the {@link CommandCallable} associated with an alias. Returns
+ * null if no command is named by the given alias.
+ *
+ * @param alias the alias
+ * @return the command mapping (null if not found)
+ */
+ @Nullable CommandMapping get(String alias);
+
+ /**
+ * Returns whether the dispatcher contains a registered command for the given alias.
+ *
+ * @param alias the alias
+ * @return true if a registered command exists
+ */
+ boolean contains(String alias);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/sk89q/worldedit/util/command/InvalidUsageException.java b/src/main/java/com/sk89q/worldedit/util/command/InvalidUsageException.java
new file mode 100644
index 000000000..a97ce4d2f
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/InvalidUsageException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.worldedit.util.command;
+
+import com.sk89q.minecraft.util.commands.CommandException;
+
+/**
+ * Thrown when a command is not used properly.
+ */
+public class InvalidUsageException extends CommandException {
+
+ private static final long serialVersionUID = -3222004168669490390L;
+ private final Description description;
+
+ public InvalidUsageException(Description description) {
+ this.description = description;
+ }
+
+ public InvalidUsageException(String message, Description description) {
+ super(message);
+ this.description = description;
+ }
+
+ public Description getDescription() {
+ return description;
+ }
+
+ public String getUsage(String prefix) {
+ return toStackString(prefix, getDescription().getUsage());
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/MissingParameterException.java b/src/main/java/com/sk89q/worldedit/util/command/MissingParameterException.java
new file mode 100644
index 000000000..8080929e3
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/MissingParameterException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.worldedit.util.command;
+
+import com.sk89q.worldedit.util.command.parametric.ParameterException;
+
+/**
+ * Thrown when there is a missing parameter.
+ */
+public class MissingParameterException extends ParameterException {
+
+ private static final long serialVersionUID = 2169299987926950535L;
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/Parameter.java b/src/main/java/com/sk89q/worldedit/util/command/Parameter.java
new file mode 100644
index 000000000..868c3840a
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/Parameter.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 .
+ */
+
+package com.sk89q.worldedit.util.command;
+
+/**
+ * Describes a parameter.
+ *
+ * @see Description
+ */
+public interface Parameter {
+
+ /**
+ * The name of the parameter.
+ *
+ * @return the name
+ */
+ String getName();
+
+ /**
+ * Get the flag associated with this parameter.
+ *
+ * @return the flag, or null if there is no flag associated
+ * @see #isValueFlag()
+ */
+ Character getFlag();
+
+ /**
+ * Return whether the flag is a value flag.
+ *
+ * @return true if the flag is a value flag
+ * @see #getFlag()
+ */
+ boolean isValueFlag();
+
+ /**
+ * Get whether this parameter is optional.
+ *
+ * @return true if the parameter does not have to be specified
+ */
+ boolean isOptional();
+
+ /**
+ * Get the default value as a string to be parsed by the binding.
+ *
+ * @return a default value, or null if none is set
+ */
+ public String[] getDefaultValue();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/sk89q/worldedit/util/command/SimpleDescription.java b/src/main/java/com/sk89q/worldedit/util/command/SimpleDescription.java
new file mode 100644
index 000000000..efd5aed95
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/SimpleDescription.java
@@ -0,0 +1,130 @@
+/*
+ * 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.worldedit.util.command;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A simple implementation of {@link Description} which has setters.
+ */
+public class SimpleDescription implements Description {
+
+ private List parameters = new ArrayList();
+ private List permissions = new ArrayList();
+ private String description;
+ private String help;
+ private String overrideUsage;
+
+ @Override
+ public List getParameters() {
+ return parameters;
+ }
+
+ /**
+ * Set the list of parameters.
+ *
+ * @param parameters the list of parameters
+ * @see #getParameters()
+ */
+ public void setParameters(List parameters) {
+ this.parameters = Collections.unmodifiableList(parameters);
+ }
+
+ @Override
+ public String getShortDescription() {
+ return description;
+ }
+
+ /**
+ * Set the description of the command.
+ *
+ * @param description the description
+ * @see #getShortDescription()
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String getHelp() {
+ return help;
+ }
+
+ /**
+ * Set the help text of the command.
+ *
+ * @param help the help text
+ * @see #getHelp()
+ */
+ public void setHelp(String help) {
+ this.help = help;
+ }
+
+ @Override
+ public List getPermissions() {
+ return permissions;
+ }
+
+ /**
+ * Set the permissions of this command.
+ *
+ * @param permissions the permissions
+ */
+ public void setPermissions(List permissions) {
+ this.permissions = Collections.unmodifiableList(permissions);
+ }
+
+ /**
+ * Override the usage string returned with a given one.
+ *
+ * @param usage usage string, or null
+ */
+ public void overrideUsage(String usage) {
+ this.overrideUsage = usage;
+ }
+
+ @Override
+ public String getUsage() {
+ if (overrideUsage != null) {
+ return overrideUsage;
+ }
+
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+
+ for (Parameter parameter : parameters) {
+ if (!first) {
+ builder.append(" ");
+ }
+ builder.append(parameter.toString());
+ first = false;
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ public String toString() {
+ return getUsage();
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java b/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java
new file mode 100644
index 000000000..a84c6b497
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java
@@ -0,0 +1,200 @@
+/*
+ * 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.worldedit.util.command;
+
+import com.google.common.base.Joiner;
+import com.sk89q.minecraft.util.commands.*;
+import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
+import com.sk89q.worldedit.util.formatting.CommandListBox;
+import com.sk89q.worldedit.util.formatting.StyledFragment;
+
+import java.util.*;
+
+/**
+ * A simple implementation of {@link Dispatcher}.
+ */
+public class SimpleDispatcher implements Dispatcher {
+
+ private final Map commands = new HashMap();
+ private final SimpleDescription description = new SimpleDescription();
+
+ /**
+ * Create a new instance.
+ */
+ public SimpleDispatcher() {
+ description.getParameters().add(new SimpleParameter("subcommand"));
+ SimpleParameter extraArgs = new SimpleParameter("...");
+ extraArgs.setOptional(true);
+ description.getParameters().add(extraArgs);
+ }
+
+ @Override
+ public void registerCommand(CommandCallable callable, String... alias) {
+ CommandMapping mapping = new CommandMapping(callable, alias);
+
+ // Check for replacements
+ for (String a : alias) {
+ String lower = a.toLowerCase();
+ if (commands.containsKey(lower)) {
+ throw new IllegalArgumentException(
+ "Replacing commands is currently undefined behavior");
+ }
+ }
+
+ for (String a : alias) {
+ String lower = a.toLowerCase();
+ commands.put(lower, mapping);
+ }
+ }
+
+ @Override
+ public Set getCommands() {
+ return Collections.unmodifiableSet(new HashSet(commands.values()));
+ }
+
+ @Override
+ public Set getAliases() {
+ return Collections.unmodifiableSet(commands.keySet());
+ }
+
+ @Override
+ public Set getPrimaryAliases() {
+ Set aliases = new HashSet();
+ for (CommandMapping mapping : getCommands()) {
+ aliases.add(mapping.getPrimaryAlias());
+ }
+ return Collections.unmodifiableSet(aliases);
+ }
+
+ @Override
+ public boolean contains(String alias) {
+ return commands.containsKey(alias.toLowerCase());
+ }
+
+ @Override
+ public CommandMapping get(String alias) {
+ return commands.get(alias.toLowerCase());
+ }
+
+ @Override
+ public Set getValueFlags() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean call(String arguments, CommandLocals locals, String[] parentCommands) throws CommandException {
+ // We have permission for this command if we have permissions for subcommands
+ if (!testPermission(locals)) {
+ throw new CommandPermissionsException();
+ }
+
+ String[] split = CommandContext.split(arguments);
+ Set aliases = getPrimaryAliases();
+
+ if (aliases.isEmpty()) {
+ throw new InvalidUsageException("This command has no sub-commands.", getDescription());
+ } else if (split.length > 0) {
+ String subCommand = split[0];
+ String subArguments = Joiner.on(" ").join(Arrays.copyOfRange(split, 1, split.length));
+ String[] subParents = Arrays.copyOf(parentCommands, parentCommands.length + 1);
+ subParents[parentCommands.length] = subCommand;
+ CommandMapping mapping = get(subCommand);
+
+ if (mapping != null) {
+ try {
+ mapping.getCallable().call(subArguments, locals, subParents);
+ } catch (CommandException e) {
+ e.prependStack(subCommand);
+ throw e;
+ } catch (Throwable t) {
+ throw new WrappedCommandException(t);
+ }
+
+ return true;
+ }
+
+ }
+
+ throw new InvalidUsageException(ColorCodeBuilder.asColorCodes(getSubcommandList(locals, parentCommands)), getDescription());
+ }
+
+ @Override
+ public List getSuggestions(String arguments, CommandLocals locals) throws CommandException {
+ String[] split = CommandContext.split(arguments);
+
+ if (split.length <= 1) {
+ String prefix = split.length > 0 ? split[0] : "";
+
+ List suggestions = new ArrayList();
+
+ for (CommandMapping mapping : getCommands()) {
+ if (mapping.getCallable().testPermission(locals)) {
+ for (String alias : mapping.getAllAliases()) {
+ if (prefix.isEmpty() || alias.startsWith(arguments)) {
+ suggestions.add(mapping.getPrimaryAlias());
+ break;
+ }
+ }
+ }
+ }
+
+ return suggestions;
+ } else {
+ String subCommand = split[0];
+ CommandMapping mapping = get(subCommand);
+ String passedArguments = Joiner.on(" ").join(Arrays.copyOfRange(split, 1, split.length));
+
+ if (mapping != null) {
+ return mapping.getCallable().getSuggestions(passedArguments, locals);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+ }
+
+ @Override
+ public SimpleDescription getDescription() {
+ return description;
+ }
+
+ @Override
+ public boolean testPermission(CommandLocals locals) {
+ for (CommandMapping mapping : getCommands()) {
+ if (mapping.getCallable().testPermission(locals)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private StyledFragment getSubcommandList(CommandLocals locals, String[] parentCommands) {
+ CommandListBox box = new CommandListBox("Subcommands");
+ String prefix = parentCommands.length > 0 ? "/" + Joiner.on(" ").join(parentCommands) + " " : "";
+ for (CommandMapping mapping : getCommands()) {
+ if (mapping.getCallable().testPermission(locals)) {
+ box.appendCommand(prefix + mapping.getPrimaryAlias(), mapping.getDescription().getShortDescription());
+ }
+ }
+
+ return box;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/SimpleParameter.java b/src/main/java/com/sk89q/worldedit/util/command/SimpleParameter.java
new file mode 100644
index 000000000..59fc8dddd
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/SimpleParameter.java
@@ -0,0 +1,131 @@
+/*
+ * 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.worldedit.util.command;
+
+/**
+ * A simple implementation of {@link Parameter} that has setters.
+ */
+public class SimpleParameter implements Parameter {
+
+ private String name;
+ private Character flag;
+ private boolean isValue;
+ private boolean isOptional;
+ private String[] defaultValue;
+
+ /**
+ * Create a new parameter with no name defined yet.
+ */
+ public SimpleParameter() {
+ }
+
+ /**
+ * Create a new parameter of the given name.
+ *
+ * @param name the name
+ */
+ public SimpleParameter(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the name of the parameter.
+ *
+ * @param name the parameter name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Character getFlag() {
+ return flag;
+ }
+
+ @Override
+ public boolean isValueFlag() {
+ return flag != null && isValue;
+ }
+
+ /**
+ * Set the flag used by this parameter.
+ *
+ * @param flag the flag, or null if there is no flag
+ * @param isValue true if the flag is a value flag
+ */
+ public void setFlag(Character flag, boolean isValue) {
+ this.flag = flag;
+ this.isValue = isValue;
+ }
+
+ @Override
+ public boolean isOptional() {
+ return isOptional || getFlag() != null;
+ }
+
+ /**
+ * Set whether this parameter is optional.
+ *
+ * @param isOptional true if this parameter is optional
+ */
+ public void setOptional(boolean isOptional) {
+ this.isOptional = isOptional;
+ }
+
+ @Override
+ public String[] getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Set the default value.
+ *
+ * @param defaultValue a default value, or null if none
+ */
+ public void setDefaultValue(String[] defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ if (getFlag() != null) {
+ if (isValueFlag()) {
+ builder.append("[-")
+ .append(getFlag()).append(" <").append(getName()).append(">]");
+ } else {
+ builder.append("[-").append(getFlag()).append("]");
+ }
+ } else {
+ if (isOptional()) {
+ builder.append("[<").append(getName()).append(">]");
+ } else {
+ builder.append("<").append(getName()).append(">");
+ }
+ }
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/UnconsumedParameterException.java b/src/main/java/com/sk89q/worldedit/util/command/UnconsumedParameterException.java
new file mode 100644
index 000000000..360a5408c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/UnconsumedParameterException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.worldedit.util.command;
+
+import com.sk89q.worldedit.util.command.parametric.ParameterException;
+
+/**
+ * Thrown when there are leftover parameters that were not consumed, particular in the
+ * case of the user providing too many parameters.
+ */
+public class UnconsumedParameterException extends ParameterException {
+
+ private static final long serialVersionUID = 4449104854894946023L;
+
+ private String unconsumed;
+
+ public UnconsumedParameterException(String unconsumed) {
+ this.unconsumed = unconsumed;
+ }
+
+ public String getUnconsumed() {
+ return unconsumed;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/binding/PrimitiveBindings.java b/src/main/java/com/sk89q/worldedit/util/command/binding/PrimitiveBindings.java
new file mode 100644
index 000000000..a4ed7ab0c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/binding/PrimitiveBindings.java
@@ -0,0 +1,255 @@
+/*
+ * 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.worldedit.util.command.binding;
+
+import com.sk89q.worldedit.util.command.parametric.*;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Handles basic Java types such as {@link String}s, {@link Byte}s, etc.
+ *
+ *
Handles both the object and primitive types.
+ */
+public final class PrimitiveBindings extends BindingHelper {
+
+ /**
+ * Gets a type from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @param text the text annotation
+ * @param modifiers a list of modifiers
+ * @return the requested type
+ * @throws ParameterException on error
+ */
+ @BindingMatch(classifier = Text.class,
+ type = String.class,
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = -1,
+ provideModifiers = true)
+ public String getText(ArgumentStack context, Text text, Annotation[] modifiers)
+ throws ParameterException {
+ String v = context.remaining();
+ validate(v, modifiers);
+ return v;
+ }
+
+ /**
+ * Gets a type from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @param modifiers a list of modifiers
+ * @return the requested type
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = String.class,
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1,
+ provideModifiers = true)
+ public String getString(ArgumentStack context, Annotation[] modifiers)
+ throws ParameterException {
+ String v = context.next();
+ validate(v, modifiers);
+ return v;
+ }
+
+ /**
+ * Gets a type from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return the requested type
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = { Boolean.class, boolean.class },
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1)
+ public Boolean getBoolean(ArgumentStack context) throws ParameterException {
+ return context.nextBoolean();
+ }
+
+ /**
+ * Gets a type from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @param modifiers a list of modifiers
+ * @return the requested type
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = { Integer.class, int.class },
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1,
+ provideModifiers = true)
+ public Integer getInteger(ArgumentStack context, Annotation[] modifiers)
+ throws ParameterException {
+ Integer v = context.nextInt();
+ if (v != null) {
+ validate(v, modifiers);
+ }
+ return v;
+ }
+
+ /**
+ * Gets a type from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @param modifiers a list of modifiers
+ * @return the requested type
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = { Short.class, short.class },
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1,
+ provideModifiers = true)
+ public Short getShort(ArgumentStack context, Annotation[] modifiers)
+ throws ParameterException {
+ Integer v = getInteger(context, modifiers);
+ if (v != null) {
+ return v.shortValue();
+ }
+ return null;
+ }
+
+ /**
+ * Gets a type from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @param modifiers a list of modifiers
+ * @return the requested type
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = { Double.class, double.class },
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1,
+ provideModifiers = true)
+ public Double getDouble(ArgumentStack context, Annotation[] modifiers)
+ throws ParameterException {
+ Double v = context.nextDouble();
+ if (v != null) {
+ validate(v, modifiers);
+ }
+ return v;
+ }
+
+ /**
+ * Gets a type from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @param modifiers a list of modifiers
+ * @return the requested type
+ * @throws ParameterException on error
+ */
+ @BindingMatch(type = { Float.class, float.class },
+ behavior = BindingBehavior.CONSUMES,
+ consumedCount = 1,
+ provideModifiers = true)
+ public Float getFloat(ArgumentStack context, Annotation[] modifiers)
+ throws ParameterException {
+ Double v = getDouble(context, modifiers);
+ if (v != null) {
+ return v.floatValue();
+ }
+ return null;
+ }
+
+ /**
+ * Validate a number value using relevant modifiers.
+ *
+ * @param number the number
+ * @param modifiers the list of modifiers to scan
+ * @throws ParameterException on a validation error
+ */
+ private static void validate(double number, Annotation[] modifiers)
+ throws ParameterException {
+ for (Annotation modifier : modifiers) {
+ if (modifier instanceof Range) {
+ Range range = (Range) modifier;
+ if (number < range.min()) {
+ throw new ParameterException(
+ String.format(
+ "A valid value is greater than or equal to %s " +
+ "(you entered %s)", range.min(), number));
+ } else if (number > range.max()) {
+ throw new ParameterException(
+ String.format(
+ "A valid value is less than or equal to %s " +
+ "(you entered %s)", range.max(), number));
+ }
+ }
+ }
+ }
+
+ /**
+ * Validate a number value using relevant modifiers.
+ *
+ * @param number the number
+ * @param modifiers the list of modifiers to scan
+ * @throws ParameterException on a validation error
+ */
+ private static void validate(int number, Annotation[] modifiers)
+ throws ParameterException {
+ for (Annotation modifier : modifiers) {
+ if (modifier instanceof Range) {
+ Range range = (Range) modifier;
+ if (number < range.min()) {
+ throw new ParameterException(
+ String.format(
+ "A valid value is greater than or equal to %s " +
+ "(you entered %s)", range.min(), number));
+ } else if (number > range.max()) {
+ throw new ParameterException(
+ String.format(
+ "A valid value is less than or equal to %s " +
+ "(you entered %s)", range.max(), number));
+ }
+ }
+ }
+ }
+
+ /**
+ * Validate a string value using relevant modifiers.
+ *
+ * @param string the string
+ * @param modifiers the list of modifiers to scan
+ * @throws ParameterException on a validation error
+ */
+ private static void validate(String string, Annotation[] modifiers)
+ throws ParameterException {
+ if (string == null) {
+ return;
+ }
+
+ for (Annotation modifier : modifiers) {
+ if (modifier instanceof Validate) {
+ Validate validate = (Validate) modifier;
+
+ if (!validate.regex().isEmpty()) {
+ if (!string.matches(validate.regex())) {
+ throw new ParameterException(
+ String.format(
+ "The given text doesn't match the right " +
+ "format (technically speaking, the 'format' is %s)",
+ validate.regex()));
+ }
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/sk89q/worldedit/util/command/binding/Range.java b/src/main/java/com/sk89q/worldedit/util/command/binding/Range.java
new file mode 100644
index 000000000..0ff21ca12
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/binding/Range.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.worldedit.util.command.binding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies a range of values for numbers.
+ *
+ * @see PrimitiveBindings a user of this annotation as a modifier
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Range {
+
+ /**
+ * The minimum value that the number can be at, inclusive.
+ *
+ * @return the minimum value
+ */
+ double min() default Double.MIN_VALUE;
+
+ /**
+ * The maximum value that the number can be at, inclusive.
+ *
+ * @return the maximum value
+ */
+ double max() default Double.MAX_VALUE;
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/binding/StandardBindings.java b/src/main/java/com/sk89q/worldedit/util/command/binding/StandardBindings.java
new file mode 100644
index 000000000..a45b88a41
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/binding/StandardBindings.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.worldedit.util.command.binding;
+
+import com.sk89q.minecraft.util.commands.CommandContext;
+import com.sk89q.worldedit.util.command.parametric.BindingBehavior;
+import com.sk89q.worldedit.util.command.parametric.BindingHelper;
+import com.sk89q.worldedit.util.command.parametric.BindingMatch;
+import com.sk89q.worldedit.util.command.parametric.ArgumentStack;
+
+/**
+ * Standard bindings that should be available to most configurations.
+ */
+public final class StandardBindings extends BindingHelper {
+
+ /**
+ * Gets a {@link CommandContext} from a {@link ArgumentStack}.
+ *
+ * @param context the context
+ * @return a selection
+ */
+ @BindingMatch(type = CommandContext.class,
+ behavior = BindingBehavior.PROVIDES)
+ public CommandContext getCommandContext(ArgumentStack context) {
+ context.markConsumed(); // Consume entire stack
+ return context.getContext();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/sk89q/worldedit/util/command/binding/Switch.java b/src/main/java/com/sk89q/worldedit/util/command/binding/Switch.java
new file mode 100644
index 000000000..34c25cad5
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/binding/Switch.java
@@ -0,0 +1,44 @@
+/*
+ * 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.worldedit.util.command.binding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates a command flag, such as /command -f.
+ *
+ *
If used on a boolean type, then the flag will be a non-value flag. If
+ * used on any other type, then the flag will be a value flag.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Switch {
+
+ /**
+ * The flag character.
+ *
+ * @return the flag character (A-Z a-z 0-9 is acceptable)
+ */
+ char value();
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/binding/Text.java b/src/main/java/com/sk89q/worldedit/util/command/binding/Text.java
new file mode 100644
index 000000000..030bc9ad4
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/binding/Text.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.worldedit.util.command.binding;
+
+import com.sk89q.worldedit.util.command.parametric.ArgumentStack;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates a {@link String} parameter will call {@link ArgumentStack#remaining()} and
+ * therefore consume all remaining arguments.
+ *
+ *
This should only be used at the end of a list of parameters (of parameters that
+ * need to consume from the stack of arguments), otherwise following parameters will
+ * have no values left to consume.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Text {
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/binding/Validate.java b/src/main/java/com/sk89q/worldedit/util/command/binding/Validate.java
new file mode 100644
index 000000000..3686aa359
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/binding/Validate.java
@@ -0,0 +1,45 @@
+/*
+ * 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.worldedit.util.command.binding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.regex.Pattern;
+
+/**
+ * Used to validate a string.
+ *
+ * @see PrimitiveBindings where this validation is used
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Validate {
+
+ /**
+ * An optional regular expression that must match the string.
+ *
+ * @see Pattern regular expression class
+ * @return the pattern
+ */
+ String regex() default "";
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/fluent/CommandGraph.java b/src/main/java/com/sk89q/worldedit/util/command/fluent/CommandGraph.java
new file mode 100644
index 000000000..1b9dacb32
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/fluent/CommandGraph.java
@@ -0,0 +1,84 @@
+/*
+ * 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.worldedit.util.command.fluent;
+
+import com.sk89q.worldedit.util.command.Dispatcher;
+import com.sk89q.worldedit.util.command.SimpleDispatcher;
+import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
+
+/**
+ * A fluent interface to creating a command graph.
+ *
+ *
A command graph may have multiple commands, and multiple sub-commands below that,
+ * and possibly below that.
+ */
+public class CommandGraph {
+
+ private final DispatcherNode rootDispatcher;
+ private ParametricBuilder builder;
+
+ /**
+ * Create a new command graph.
+ */
+ public CommandGraph() {
+ SimpleDispatcher dispatcher = new SimpleDispatcher();
+ rootDispatcher = new DispatcherNode(this, null, dispatcher);
+ }
+
+ /**
+ * Get the root dispatcher node.
+ *
+ * @return the root dispatcher node
+ */
+ public DispatcherNode commands() {
+ return rootDispatcher;
+ }
+
+ /**
+ * Get the {@link ParametricBuilder}.
+ *
+ * @return the builder, or null.
+ */
+ public ParametricBuilder getBuilder() {
+ return builder;
+ }
+
+ /**
+ * Set the {@link ParametricBuilder} used for calls to
+ * {@link DispatcherNode#registerMethods(Object)}.
+ *
+ * @param builder the builder, or null
+ * @return this object
+ */
+ public CommandGraph builder(ParametricBuilder builder) {
+ this.builder = builder;
+ return this;
+ }
+
+ /**
+ * Get the root dispatcher.
+ *
+ * @return the root dispatcher
+ */
+ public Dispatcher getDispatcher() {
+ return rootDispatcher.getDispatcher();
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/fluent/DispatcherNode.java b/src/main/java/com/sk89q/worldedit/util/command/fluent/DispatcherNode.java
new file mode 100644
index 000000000..ed0d6dae5
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/fluent/DispatcherNode.java
@@ -0,0 +1,138 @@
+/*
+ * 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.worldedit.util.command.fluent;
+
+import com.sk89q.worldedit.util.command.CommandCallable;
+import com.sk89q.worldedit.util.command.Dispatcher;
+import com.sk89q.worldedit.util.command.SimpleDispatcher;
+import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
+
+/**
+ * A collection of commands.
+ */
+public class DispatcherNode {
+
+ private final CommandGraph graph;
+ private final DispatcherNode parent;
+ private final SimpleDispatcher dispatcher;
+
+ /**
+ * Create a new instance.
+ *
+ * @param graph the root fluent graph object
+ * @param parent the parent node, or null
+ * @param dispatcher the dispatcher for this node
+ */
+ DispatcherNode(CommandGraph graph, DispatcherNode parent,
+ SimpleDispatcher dispatcher) {
+ this.graph = graph;
+ this.parent = parent;
+ this.dispatcher = dispatcher;
+ }
+
+ /**
+ * Set the description.
+ *
+ *
This can only be used on {@link DispatcherNode}s returned by
+ * {@link #group(String...)}.
+ *
+ * @param description the description
+ * @return this object
+ */
+ public DispatcherNode describeAs(String description) {
+ dispatcher.getDescription().setDescription(description);
+ return this;
+ }
+
+ /**
+ * Register a command with this dispatcher.
+ *
+ * @param callable the executor
+ * @param alias the list of aliases, where the first alias is the primary one
+ */
+ public void register(CommandCallable callable, String... alias) {
+ dispatcher.registerCommand(callable, alias);
+ }
+
+ /**
+ * Build and register a command with this dispatcher using the
+ * {@link ParametricBuilder} assigned on the root {@link CommandGraph}.
+ *
+ * @param object the object provided to the {@link ParametricBuilder}
+ * @return this object
+ * @see ParametricBuilder#registerMethodsAsCommands(com.sk89q.worldedit.util.command.Dispatcher, Object)
+ */
+ public DispatcherNode registerMethods(Object object) {
+ ParametricBuilder builder = graph.getBuilder();
+ if (builder == null) {
+ throw new RuntimeException("No ParametricBuilder set");
+ }
+ builder.registerMethodsAsCommands(getDispatcher(), object);
+ return this;
+ }
+
+ /**
+ * Create a new command that will contain sub-commands.
+ *
+ *
The object returned by this method can be used to add sub-commands. To
+ * return to this "parent" context, use {@link DispatcherNode#graph()}.
+ *
+ * @param alias the list of aliases, where the first alias is the primary one
+ * @return an object to place sub-commands
+ */
+ public DispatcherNode group(String... alias) {
+ SimpleDispatcher command = new SimpleDispatcher();
+ getDispatcher().registerCommand(command, alias);
+ return new DispatcherNode(graph, this, command);
+ }
+
+ /**
+ * Return the parent node.
+ *
+ * @return the parent node
+ * @throws RuntimeException if there is no parent node.
+ */
+ public DispatcherNode parent() {
+ if (parent != null) {
+ return parent;
+ }
+
+ throw new RuntimeException("This node does not have a parent");
+ }
+
+ /**
+ * Get the root command graph.
+ *
+ * @return the root command graph
+ */
+ public CommandGraph graph() {
+ return graph;
+ }
+
+ /**
+ * Get the underlying dispatcher of this object.
+ *
+ * @return the dispatcher
+ */
+ public Dispatcher getDispatcher() {
+ return dispatcher;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/AbstractInvokeListener.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/AbstractInvokeListener.java
new file mode 100644
index 000000000..65fadc101
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/AbstractInvokeListener.java
@@ -0,0 +1,36 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.worldedit.util.command.SimpleDescription;
+
+import java.lang.reflect.Method;
+
+/**
+ * An abstract listener.
+ */
+public abstract class AbstractInvokeListener implements InvokeListener {
+
+ @Override
+ public void updateDescription(Object object, Method method,
+ ParameterData[] parameters, SimpleDescription description) {
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ArgumentStack.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ArgumentStack.java
new file mode 100644
index 000000000..0f0b9a52b
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ArgumentStack.java
@@ -0,0 +1,78 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandContext;
+
+public interface ArgumentStack {
+
+ /**
+ * Get the next string, which may come from the stack or a value flag.
+ *
+ * @return the value
+ * @throws ParameterException on a parameter error
+ */
+ String next() throws ParameterException;
+
+ /**
+ * Get the next integer, which may come from the stack or a value flag.
+ *
+ * @return the value
+ * @throws ParameterException on a parameter error
+ */
+ Integer nextInt() throws ParameterException;
+
+ /**
+ * Get the next double, which may come from the stack or a value flag.
+ *
+ * @return the value
+ * @throws ParameterException on a parameter error
+ */
+ Double nextDouble() throws ParameterException;
+
+ /**
+ * Get the next boolean, which may come from the stack or a value flag.
+ *
+ * @return the value
+ * @throws ParameterException on a parameter error
+ */
+ Boolean nextBoolean() throws ParameterException;
+
+ /**
+ * Get all remaining string values, which will consume the rest of the stack.
+ *
+ * @return the value
+ * @throws ParameterException on a parameter error
+ */
+ String remaining() throws ParameterException;
+
+ /**
+ * Set as completely consumed.
+ */
+ void markConsumed();
+
+ /**
+ * Get the underlying context.
+ *
+ * @return the context
+ */
+ CommandContext getContext();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/Binding.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/Binding.java
new file mode 100644
index 000000000..dcf931c81
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/Binding.java
@@ -0,0 +1,92 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandException;
+import com.sk89q.worldedit.util.command.binding.PrimitiveBindings;
+import com.sk89q.worldedit.util.command.binding.StandardBindings;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Used to parse user input for a command, based on available method types
+ * and annotations.
+ *
+ *
A binding can be used to handle several types at once. For a binding to be
+ * called, it must be registered with a {@link ParametricBuilder} with
+ * {@link ParametricBuilder#addBinding(Binding, java.lang.reflect.Type...)}.
+ *
+ * @see PrimitiveBindings an example of primitive bindings
+ * @see StandardBindings standard bindings
+ */
+public interface Binding {
+
+ /**
+ * Get the types that this binding handles.
+ *
+ * @return the types
+ */
+ Type[] getTypes();
+
+ /**
+ * Get how this binding consumes from a {@link ArgumentStack}.
+ *
+ * @param parameter information about the parameter
+ * @return the behavior
+ */
+ BindingBehavior getBehavior(ParameterData parameter);
+
+ /**
+ * Get the number of arguments that this binding will consume, if this
+ * information is available.
+ *
+ *
This method must return -1 for binding behavior types that are not
+ * {@link BindingBehavior#CONSUMES}.
+ *
+ * @param parameter information about the parameter
+ * @return the number of consumed arguments, or -1 if unknown or irrelevant
+ */
+ int getConsumedCount(ParameterData parameter);
+
+ /**
+ * Attempt to consume values (if required) from the given {@link ArgumentStack}
+ * in order to instantiate an object for the given parameter.
+ *
+ * @param parameter information about the parameter
+ * @param scoped the arguments the user has input
+ * @param onlyConsume true to only consume arguments
+ * @return an object parsed for the given parameter
+ * @throws ParameterException thrown if the parameter could not be formulated
+ * @throws CommandException on a command exception
+ */
+ Object bind(ParameterData parameter, ArgumentStack scoped, boolean onlyConsume)
+ throws ParameterException, CommandException;
+
+ /**
+ * Get a list of suggestions for the given parameter and user arguments.
+ *
+ * @param parameter information about the parameter
+ * @param prefix what the user has typed so far (may be an empty string)
+ * @return a list of suggestions
+ */
+ List getSuggestions(ParameterData parameter, String prefix);
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingBehavior.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingBehavior.java
new file mode 100644
index 000000000..e194bce56
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingBehavior.java
@@ -0,0 +1,52 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandLocals;
+import com.sk89q.worldedit.util.command.binding.Switch;
+
+/**
+ * Determines the type of binding.
+ */
+public enum BindingBehavior {
+
+ /**
+ * Always consumes from a {@link ArgumentStack}.
+ */
+ CONSUMES,
+
+ /**
+ * Sometimes consumes from a {@link ArgumentStack}.
+ *
+ *
Bindings that exhibit this behavior must be defined as a {@link Switch}
+ * by commands utilizing the given binding.
+ */
+ INDETERMINATE,
+
+ /**
+ * Never consumes from a {@link ArgumentStack}.
+ *
+ *
Bindings that exhibit this behavior generate objects from other sources,
+ * such as from a {@link CommandLocals}. These are "magic" bindings that inject
+ * variables.
+ */
+ PROVIDES;
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingHelper.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingHelper.java
new file mode 100644
index 000000000..b442da6a2
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingHelper.java
@@ -0,0 +1,224 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandException;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A binding helper that uses the {@link BindingMatch} annotation to make
+ * writing bindings extremely easy.
+ *
+ *
Methods must have the following and only the following parameters:
+ *
+ *
+ *
A {@link ArgumentStack}
+ *
A {@link Annotation} if there is a classifier set
+ *
A {@link Annotation}[]
+ * if there {@link BindingMatch#provideModifiers()} is true
+ *
+ *
+ *
Methods may throw any exception. Exceptions may be converted using a
+ * {@link ExceptionConverter} registered with the {@link ParametricBuilder}.
+ */
+public class BindingHelper implements Binding {
+
+ private final List bindings;
+ private final Type[] types;
+
+ /**
+ * Create a new instance.
+ */
+ public BindingHelper() {
+ List bindings = new ArrayList();
+ List types = new ArrayList();
+
+ for (Method method : this.getClass().getMethods()) {
+ BindingMatch info = method.getAnnotation(BindingMatch.class);
+ if (info != null) {
+ Class extends Annotation> classifier = null;
+
+ // Set classifier
+ if (!info.classifier().equals(Annotation.class)) {
+ classifier = (Class extends Annotation>) info.classifier();
+ types.add(classifier);
+ }
+
+ for (Type t : info.type()) {
+ Type type = null;
+
+ // Set type
+ if (!t.equals(Class.class)) {
+ type = t;
+ if (classifier == null) {
+ types.add(type); // Only if there is no classifier set!
+ }
+ }
+
+ // Check to see if at least one is set
+ if (type == null && classifier == null) {
+ throw new RuntimeException(
+ "A @BindingMatch needs either a type or classifier set");
+ }
+
+ BoundMethod handler = new BoundMethod(info, type, classifier, method);
+ bindings.add(handler);
+ }
+ }
+ }
+
+ Collections.sort(bindings);
+
+ this.bindings = bindings;
+
+ Type[] typesArray = new Type[types.size()];
+ types.toArray(typesArray);
+ this.types = typesArray;
+
+ }
+
+ /**
+ * Match a {@link BindingMatch} according to the given parameter.
+ *
+ * @param parameter the parameter
+ * @return a binding
+ */
+ private BoundMethod match(ParameterData parameter) {
+ for (BoundMethod binding : bindings) {
+ Annotation classifer = parameter.getClassifier();
+ Type type = parameter.getType();
+
+ if (binding.classifier != null) {
+ if (classifer != null && classifer.annotationType().equals(binding.classifier)) {
+ if (binding.type == null || binding.type.equals(type)) {
+ return binding;
+ }
+ }
+ } else if (binding.type.equals(type)) {
+ return binding;
+ }
+ }
+
+ throw new RuntimeException("Unknown type");
+ }
+
+ @Override
+ public Type[] getTypes() {
+ return types;
+ }
+
+ @Override
+ public int getConsumedCount(ParameterData parameter) {
+ return match(parameter).annotation.consumedCount();
+ }
+
+ @Override
+ public BindingBehavior getBehavior(ParameterData parameter) {
+ return match(parameter).annotation.behavior();
+ }
+
+ @Override
+ public Object bind(ParameterData parameter, ArgumentStack scoped,
+ boolean onlyConsume) throws ParameterException, CommandException {
+ BoundMethod binding = match(parameter);
+ List args = new ArrayList();
+ args.add(scoped);
+
+ if (binding.classifier != null) {
+ args.add(parameter.getClassifier());
+ }
+
+ if (binding.annotation.provideModifiers()) {
+ args.add(parameter.getModifiers());
+ }
+
+ if (onlyConsume && binding.annotation.behavior() == BindingBehavior.PROVIDES) {
+ return null; // Nothing to consume, nothing to do
+ }
+
+ Object[] argsArray = new Object[args.size()];
+ args.toArray(argsArray);
+
+ try {
+ return binding.method.invoke(this, argsArray);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(
+ "Processing of classifier " + parameter.getClassifier() +
+ " and type " + parameter.getType() + " failed for method\n" +
+ binding.method + "\nbecause the parameters for that method are wrong", e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof ParameterException) {
+ throw (ParameterException) e.getCause();
+ } else if (e.getCause() instanceof CommandException) {
+ throw (CommandException) e.getCause();
+ }
+ throw new RuntimeException(e.getCause());
+ }
+ }
+
+ @Override
+ public List getSuggestions(ParameterData parameter, String prefix) {
+ return new ArrayList();
+ }
+
+ private static class BoundMethod implements Comparable {
+ private final BindingMatch annotation;
+ private final Type type;
+ private final Class extends Annotation> classifier;
+ private final Method method;
+
+ BoundMethod(BindingMatch annotation, Type type,
+ Class extends Annotation> classifier, Method method) {
+ this.annotation = annotation;
+ this.type = type;
+ this.classifier = classifier;
+ this.method = method;
+ }
+
+ @Override
+ public int compareTo(BoundMethod o) {
+ if (classifier != null && o.classifier == null) {
+ return -1;
+ } else if (classifier == null && o.classifier != null) {
+ return 1;
+ } else if (classifier != null && o.classifier != null) {
+ if (type != null && o.type == null) {
+ return -1;
+ } else if (type == null && o.type != null) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMatch.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMatch.java
new file mode 100644
index 000000000..049d3dc4d
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMatch.java
@@ -0,0 +1,71 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes a match of a binding.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BindingMatch {
+
+ /**
+ * The classifier.
+ *
+ * @return the classifier, or {@link Annotation} if not set
+ */
+ Class extends Annotation> classifier() default Annotation.class;
+
+ /**
+ * The type.
+ *
+ * @return the type, or {@link Class} if not set
+ */
+ Class>[] type() default Class.class;
+
+ /**
+ * The binding behavior.
+ *
+ * @return the behavior
+ */
+ BindingBehavior behavior();
+
+ /**
+ * Get the number of arguments that this binding consumes.
+ *
+ * @return -1 if unknown or irrelevant
+ */
+ int consumedCount() default -1;
+
+ /**
+ * Set whether an array of modifier annotations is provided in the list of
+ * arguments.
+ *
+ * @return true to provide modifiers
+ */
+ boolean provideModifiers() default false;
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ContextArgumentStack.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ContextArgumentStack.java
new file mode 100644
index 000000000..c7222b9e1
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ContextArgumentStack.java
@@ -0,0 +1,178 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandContext;
+import com.sk89q.worldedit.util.command.MissingParameterException;
+
+/**
+ * Makes an instance of a {@link CommandContext} into a stack of arguments
+ * that can be consumed.
+ *
+ * @see ParametricBuilder a user of this class
+ */
+public class ContextArgumentStack implements ArgumentStack {
+
+ private final CommandContext context;
+ private int index = 0;
+ private int markedIndex = 0;
+
+ /**
+ * Create a new instance using the given context.
+ *
+ * @param context the context
+ */
+ public ContextArgumentStack(CommandContext context) {
+ this.context = context;
+ }
+
+ @Override
+ public String next() throws ParameterException {
+ try {
+ return context.getString(index++);
+ } catch (IndexOutOfBoundsException e) {
+ throw new MissingParameterException();
+ }
+ }
+
+ @Override
+ public Integer nextInt() throws ParameterException {
+ try {
+ return Integer.parseInt(next());
+ } catch (NumberFormatException e) {
+ throw new ParameterException(
+ "Expected a number, got '" + context.getString(index - 1) + "'");
+ }
+ }
+
+ @Override
+ public Double nextDouble() throws ParameterException {
+ try {
+ return Double.parseDouble(next());
+ } catch (NumberFormatException e) {
+ throw new ParameterException(
+ "Expected a number, got '" + context.getString(index - 1) + "'");
+ }
+ }
+
+ @Override
+ public Boolean nextBoolean() throws ParameterException {
+ try {
+ return next().equalsIgnoreCase("true");
+ } catch (IndexOutOfBoundsException e) {
+ throw new MissingParameterException();
+ }
+ }
+
+ @Override
+ public String remaining() throws ParameterException {
+ try {
+ String value = context.getJoinedStrings(index);
+ index = context.argsLength();
+ return value;
+ } catch (IndexOutOfBoundsException e) {
+ throw new MissingParameterException();
+ }
+ }
+
+ /**
+ * Get the unconsumed arguments left over, without touching the stack.
+ *
+ * @return the unconsumed arguments
+ */
+ public String getUnconsumed() {
+ if (index >= context.argsLength()) {
+ return null;
+ }
+
+ return context.getJoinedStrings(index);
+ }
+
+ @Override
+ public void markConsumed() {
+ index = context.argsLength();
+ }
+
+ /**
+ * Return the current position.
+ *
+ * @return the position
+ */
+ public int position() {
+ return index;
+ }
+
+ /**
+ * Mark the current position of the stack.
+ *
+ *
The marked position initially starts at 0.
+ */
+ public void mark() {
+ markedIndex = index;
+ }
+
+ /**
+ * Reset to the previously {@link #mark()}ed position of the stack, and return
+ * the arguments that were consumed between this point and that previous point.
+ *
+ *
The marked position initially starts at 0.
+ *
+ * @return the consumed arguments
+ */
+ public String reset() {
+ String value = context.getString(markedIndex, index);
+ index = markedIndex;
+ return value;
+ }
+
+ /**
+ * Return whether any arguments were consumed between the marked position
+ * and the current position.
+ *
+ *
The marked position initially starts at 0.
+ *
+ * @return true if values were consumed.
+ */
+ public boolean wasConsumed() {
+ return markedIndex != index;
+ }
+
+ /**
+ * Return the arguments that were consumed between this point and that marked point.
+ *
+ *
The marked position initially starts at 0.
+ *
+ * @return the consumed arguments
+ */
+ public String getConsumed() {
+ return context.getString(markedIndex, index);
+ }
+
+ /**
+ * Get the underlying context.
+ *
+ * @return the context
+ */
+ @Override
+ public CommandContext getContext() {
+ return context;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionConverter.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionConverter.java
new file mode 100644
index 000000000..1eb9491ca
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionConverter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandException;
+import com.sk89q.minecraft.util.commands.WrappedCommandException;
+
+/**
+ * Used to convert a recognized {@link Throwable} into an appropriate
+ * {@link CommandException}.
+ *
+ *
Methods (when invoked by a {@link ParametricBuilder}-created command) may throw
+ * relevant exceptions that are not caught by the command manager, but translate
+ * into reasonable exceptions for an application. However, unknown exceptions are
+ * normally simply wrapped in a {@link WrappedCommandException} and bubbled up. Only
+ * normal {@link CommandException}s will be printed correctly, so a converter translates
+ * one of these unknown exceptions into an appropriate {@link CommandException}.
+ *
+ *
This also allows the code calling the command to not need be aware of these
+ * application-specific exceptions, as they will all be converted to
+ * {@link CommandException}s that are handled normally.
+ */
+public interface ExceptionConverter {
+
+ /**
+ * Attempt to convert the given throwable into a {@link CommandException}.
+ *
+ *
If the exception is not recognized, then nothing should be thrown.
+ *
+ * @param t the throwable
+ * @throws CommandException a command exception
+ */
+ void convert(Throwable t) throws CommandException;
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionConverterHelper.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionConverterHelper.java
new file mode 100644
index 000000000..8a72f1a33
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionConverterHelper.java
@@ -0,0 +1,109 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandException;
+import com.sk89q.minecraft.util.commands.WrappedCommandException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An implementation of an {@link ExceptionConverter} that automatically calls
+ * the correct method defined on this object.
+ *
+ *
Only public methods will be used. Methods will be called in order of decreasing
+ * levels of inheritance (between classes where one inherits the other). For two
+ * different inheritance branches, the order between them is undefined.
+ */
+public abstract class ExceptionConverterHelper implements ExceptionConverter {
+
+ private final List handlers;
+
+ @SuppressWarnings("unchecked")
+ public ExceptionConverterHelper() {
+ List handlers = new ArrayList();
+
+ for (Method method : this.getClass().getMethods()) {
+ if (method.getAnnotation(ExceptionMatch.class) == null) {
+ continue;
+ }
+
+ Class>[] parameters = method.getParameterTypes();
+ if (parameters.length == 1) {
+ Class> cls = parameters[0];
+ if (Throwable.class.isAssignableFrom(cls)) {
+ handlers.add(new ExceptionHandler(
+ (Class extends Throwable>) cls, method));
+ }
+ }
+ }
+
+ Collections.sort(handlers);
+
+ this.handlers = handlers;
+ }
+
+ @Override
+ public void convert(Throwable t) throws CommandException {
+ Class> throwableClass = t.getClass();
+ for (ExceptionHandler handler : handlers) {
+ if (handler.cls.isAssignableFrom(throwableClass)) {
+ try {
+ handler.method.invoke(this, t);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof CommandException) {
+ throw (CommandException) e.getCause();
+ }
+ throw new WrappedCommandException(e);
+ } catch (IllegalArgumentException e) {
+ throw new WrappedCommandException(e);
+ } catch (IllegalAccessException e) {
+ throw new WrappedCommandException(e);
+ }
+ }
+ }
+ }
+
+ private static class ExceptionHandler implements Comparable {
+ final Class extends Throwable> cls;
+ final Method method;
+
+ public ExceptionHandler(Class extends Throwable> cls, Method method) {
+ this.cls = cls;
+ this.method = method;
+ }
+
+ @Override
+ public int compareTo(ExceptionHandler o) {
+ if (cls.equals(o.cls)) {
+ return 0;
+ } else if (cls.isAssignableFrom(o.cls)) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionMatch.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionMatch.java
new file mode 100644
index 000000000..619bc9d46
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ExceptionMatch.java
@@ -0,0 +1,34 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes a match of an exception.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExceptionMatch {
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/InvokeHandler.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/InvokeHandler.java
new file mode 100644
index 000000000..465f89689
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/InvokeHandler.java
@@ -0,0 +1,82 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandContext;
+import com.sk89q.minecraft.util.commands.CommandException;
+
+import java.lang.reflect.Method;
+
+/**
+ * Called before and after a command is invoked for commands executed by a command
+ * created using {@link ParametricBuilder}.
+ *
+ *
Invocation handlers are created by {@link InvokeListener}s. Multiple
+ * listeners and handlers can be registered, and all be run. However, if one handler
+ * throws an exception, future handlers will not execute and the command will
+ * not execute (if thrown in
+ * {@link #preInvoke(Object, Method, ParameterData[], Object[], CommandContext)}).
+ *
+ * @see InvokeListener the factory
+ */
+public interface InvokeHandler {
+
+ /**
+ * Called before parameters are processed.
+ *
+ * @param object the object
+ * @param method the method
+ * @param parameters the list of parameters
+ * @param context the context
+ * @throws CommandException can be thrown for an error, which will stop invocation
+ * @throws ParameterException on parameter error
+ */
+ void preProcess(Object object, Method method, ParameterData[] parameters,
+ CommandContext context) throws CommandException, ParameterException;
+
+ /**
+ * Called before the parameter is invoked.
+ *
+ * @param object the object
+ * @param method the method
+ * @param parameters the list of parameters
+ * @param args the arguments to be given to the method
+ * @param context the context
+ * @throws CommandException can be thrown for an error, which will stop invocation
+ * @throws ParameterException on parameter error
+ */
+ void preInvoke(Object object, Method method, ParameterData[] parameters,
+ Object[] args, CommandContext context) throws CommandException, ParameterException;
+
+ /**
+ * Called after the parameter is invoked.
+ *
+ * @param object the object
+ * @param method the method
+ * @param parameters the list of parameters
+ * @param args the arguments to be given to the method
+ * @param context the context
+ * @throws CommandException can be thrown for an error
+ * @throws ParameterException on parameter error
+ */
+ void postInvoke(Object object, Method method, ParameterData[] parameters,
+ Object[] args, CommandContext context) throws CommandException, ParameterException;
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/InvokeListener.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/InvokeListener.java
new file mode 100644
index 000000000..f30f86c9f
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/InvokeListener.java
@@ -0,0 +1,58 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandPermissions;
+import com.sk89q.worldedit.util.command.CommandCallable;
+import com.sk89q.worldedit.util.command.SimpleDescription;
+
+import java.lang.reflect.Method;
+
+/**
+ * Listens to events related to {@link ParametricBuilder}.
+ */
+public interface InvokeListener {
+
+ /**
+ * Create a new invocation handler.
+ *
+ *
An example use of an {@link InvokeHandler} would be to verify permissions
+ * added by the {@link CommandPermissions} annotation.
+ *
+ *
For simple {@link InvokeHandler}, an object can implement both this
+ * interface and {@link InvokeHandler}.
+ *
+ * @return a new invocation handler
+ */
+ InvokeHandler createInvokeHandler();
+
+ /**
+ * During creation of a {@link CommandCallable} by a {@link ParametricBuilder},
+ * this will be called in case the description needs to be updated.
+ *
+ * @param object the object
+ * @param method the method
+ * @param parameters a list of parameters
+ * @param description the description to be updated
+ */
+ void updateDescription(Object object, Method method, ParameterData[] parameters,
+ SimpleDescription description);
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/LegacyCommandsHandler.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/LegacyCommandsHandler.java
new file mode 100644
index 000000000..fbc1794c2
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/LegacyCommandsHandler.java
@@ -0,0 +1,97 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.Command;
+import com.sk89q.minecraft.util.commands.CommandContext;
+import com.sk89q.minecraft.util.commands.CommandException;
+import com.sk89q.worldedit.util.command.MissingParameterException;
+import com.sk89q.worldedit.util.command.SimpleDescription;
+import com.sk89q.worldedit.util.command.UnconsumedParameterException;
+
+import java.lang.reflect.Method;
+
+/**
+ * Handles legacy properties on {@link Command} such as {@link Command#min()} and
+ * {@link Command#max()}.
+ */
+public class LegacyCommandsHandler extends AbstractInvokeListener implements InvokeHandler {
+
+ @Override
+ public InvokeHandler createInvokeHandler() {
+ return this;
+ }
+
+ @Override
+ public void preProcess(Object object, Method method,
+ ParameterData[] parameters, CommandContext context)
+ throws CommandException, ParameterException {
+ }
+
+ @Override
+ public void preInvoke(Object object, Method method,
+ ParameterData[] parameters, Object[] args, CommandContext context)
+ throws ParameterException {
+ Command annotation = method.getAnnotation(Command.class);
+
+ if (annotation != null) {
+ if (context.argsLength() < annotation.min()) {
+ throw new MissingParameterException();
+ }
+
+ if (annotation.max() != -1 && context.argsLength() > annotation.max()) {
+ throw new UnconsumedParameterException(
+ context.getRemainingString(annotation.max()));
+ }
+ }
+ }
+
+ @Override
+ public void postInvoke(Object object, Method method,
+ ParameterData[] parameters, Object[] args, CommandContext context) {
+
+ }
+
+ @Override
+ public void updateDescription(Object object, Method method,
+ ParameterData[] parameters, SimpleDescription description) {
+ Command annotation = method.getAnnotation(Command.class);
+
+ // Handle the case for old commands where no usage is set and all of its
+ // parameters are provider bindings, so its usage information would
+ // be blank and would imply that there were no accepted parameters
+ if (annotation != null && annotation.usage().isEmpty()
+ && (annotation.min() > 0 || annotation.max() > 0)) {
+ boolean hasUserParameters = false;
+
+ for (ParameterData parameter : parameters) {
+ if (parameter.getBinding().getBehavior(parameter) != BindingBehavior.PROVIDES) {
+ hasUserParameters = true;
+ break;
+ }
+ }
+
+ if (!hasUserParameters) {
+ description.overrideUsage("(unknown usage information)");
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/Optional.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/Optional.java
new file mode 100644
index 000000000..bd6d5448f
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/Optional.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.worldedit.util.command.parametric;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an optional parameter.
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Optional {
+
+ /**
+ * The default value to use if no value is set.
+ *
+ * @return a string value, or an empty list
+ */
+ String[] value() default {};
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterData.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterData.java
new file mode 100644
index 000000000..649c576b4
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterData.java
@@ -0,0 +1,194 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.worldedit.util.command.SimpleParameter;
+import com.sk89q.worldedit.util.command.binding.PrimitiveBindings;
+import com.sk89q.worldedit.util.command.binding.Range;
+import com.sk89q.worldedit.util.command.binding.Text;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+/**
+ * Describes a parameter in detail.
+ */
+public class ParameterData extends SimpleParameter {
+
+ private Binding binding;
+ private Annotation classifier;
+ private Annotation[] modifiers;
+ private Type type;
+
+ /**
+ * Get the binding associated with this parameter.
+ *
+ * @return the binding
+ */
+ public Binding getBinding() {
+ return binding;
+ }
+
+ /**
+ * Set the binding associated with this parameter.
+ *
+ * @param binding the binding
+ */
+ void setBinding(Binding binding) {
+ this.binding = binding;
+ }
+
+ /**
+ * Set the main type of this parameter.
+ *
+ *
The type is normally that is used to determine which binding is used
+ * for a particular method's parameter.
+ *
+ * @return the main type
+ * @see #getClassifier() which can override the type
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Set the main type of this parameter.
+ *
+ * @param type the main type
+ */
+ void setType(Type type) {
+ this.type = type;
+ }
+
+ /**
+ * Get the classifier annotation.
+ *
+ *
Normally, the type determines what binding is called, but classifiers
+ * take precedence if one is found (and registered with
+ * {@link ParametricBuilder#addBinding(Binding, Type...)}).
+ * An example of a classifier annotation is {@link Text}.
+ *
+ * @return the classifier annotation, null is possible
+ */
+ public Annotation getClassifier() {
+ return classifier;
+ }
+
+ /**
+ * Set the classifier annotation.
+ *
+ * @param classifier the classifier annotation, null is possible
+ */
+ void setClassifier(Annotation classifier) {
+ this.classifier = classifier;
+ }
+
+ /**
+ * Get a list of modifier annotations.
+ *
+ *
Modifier annotations are not considered in the process of choosing a binding
+ * for a method parameter, but they can be used to modify the behavior of a binding.
+ * An example of a modifier annotation is {@link Range}, which can restrict
+ * numeric values handled by {@link PrimitiveBindings} to be within a range. The list
+ * of annotations may contain a classifier and other unrelated annotations.
+ *
+ * @return a list of annotations
+ */
+ public Annotation[] getModifiers() {
+ return modifiers;
+ }
+
+ /**
+ * Set the list of modifiers.
+ *
+ * @param modifiers a list of annotations
+ */
+ void setModifiers(Annotation[] modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ /**
+ * Return the number of arguments this binding consumes.
+ *
+ * @return -1 if unknown or unavailable
+ */
+ int getConsumedCount() {
+ return getBinding().getConsumedCount(this);
+ }
+
+ /**
+ * Get whether this parameter is entered by the user.
+ *
+ * @return true if this parameter is entered by the user.
+ */
+ boolean isUserInput() {
+ return getBinding().getBehavior(this) != BindingBehavior.PROVIDES;
+ }
+
+ /**
+ * Get whether this parameter consumes non-flag arguments.
+ *
+ * @return true if this parameter consumes non-flag arguments
+ */
+ boolean isNonFlagConsumer() {
+ return getBinding().getBehavior(this) != BindingBehavior.PROVIDES && !isValueFlag();
+ }
+
+ /**
+ * Validate this parameter and its binding.
+ */
+ void validate(Method method, int parameterIndex) throws ParametricException {
+ // We can't have indeterminate consumers without @Switches otherwise
+ // it may screw up parameter processing for later bindings
+ BindingBehavior behavior = getBinding().getBehavior(this);
+ boolean indeterminate = (behavior == BindingBehavior.INDETERMINATE);
+ if (!isValueFlag() && indeterminate) {
+ throw new ParametricException(
+ "@Switch missing for indeterminate consumer\n\n" +
+ "Notably:\nFor the type " + type + ", the binding " +
+ getBinding().getClass().getCanonicalName() +
+ "\nmay or may not consume parameters (isIndeterminateConsumer(" + type + ") = true)" +
+ "\nand therefore @Switch(flag) is required for parameter #" + parameterIndex + " of \n" +
+ method.toGenericString());
+ }
+
+ // getConsumedCount() better return -1 if the BindingBehavior is not CONSUMES
+ if (behavior != BindingBehavior.CONSUMES && binding.getConsumedCount(this) != -1) {
+ throw new ParametricException(
+ "getConsumedCount() does not return -1 for binding " +
+ getBinding().getClass().getCanonicalName() +
+ "\neven though its behavior type is " + behavior.name() +
+ "\nfor parameter #" + parameterIndex + " of \n" +
+ method.toGenericString());
+ }
+
+ // getConsumedCount() should not return 0 if the BindingBehavior is not PROVIDES
+ if (behavior != BindingBehavior.PROVIDES && binding.getConsumedCount(this) == 0) {
+ throw new ParametricException(
+ "getConsumedCount() must not return 0 for binding " +
+ getBinding().getClass().getCanonicalName() +
+ "\nwhen its behavior type is " + behavior.name() + " and not PROVIDES " +
+ "\nfor parameter #" + parameterIndex + " of \n" +
+ method.toGenericString());
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterException.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterException.java
new file mode 100644
index 000000000..8dc924f15
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+/**
+ * Thrown if there is an error with a parameter.
+ */
+public class ParameterException extends Exception {
+
+ private static final long serialVersionUID = -8255175019708245673L;
+
+ public ParameterException() {
+ super();
+ }
+
+ public ParameterException(String message) {
+ super(message);
+ }
+
+ public ParameterException(Throwable cause) {
+ super(cause);
+ }
+
+ public ParameterException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricBuilder.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricBuilder.java
new file mode 100644
index 000000000..dd96c4a2a
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricBuilder.java
@@ -0,0 +1,228 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.google.common.collect.ImmutableBiMap.Builder;
+import com.sk89q.minecraft.util.commands.Command;
+import com.sk89q.minecraft.util.commands.CommandContext;
+import com.sk89q.minecraft.util.commands.CommandException;
+import com.sk89q.minecraft.util.commands.CommandPermissions;
+import com.sk89q.worldedit.util.auth.Authorizer;
+import com.sk89q.worldedit.util.auth.NullAuthorizer;
+import com.sk89q.worldedit.util.command.CommandCallable;
+import com.sk89q.worldedit.util.command.Dispatcher;
+import com.sk89q.worldedit.util.command.binding.PrimitiveBindings;
+import com.sk89q.worldedit.util.command.binding.StandardBindings;
+import com.sk89q.worldedit.util.command.binding.Switch;
+import com.thoughtworks.paranamer.CachingParanamer;
+import com.thoughtworks.paranamer.Paranamer;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Creates commands using annotations placed on methods and individual parameters of
+ * such methods.
+ *
+ * @see Command defines a command
+ * @see Switch defines a flag
+ */
+public class ParametricBuilder {
+
+ private final Map bindings = new HashMap();
+ private final Paranamer paranamer = new CachingParanamer();
+ private final List invokeListeners = new ArrayList();
+ private final List exceptionConverters = new ArrayList();
+ private Authorizer authorizer = new NullAuthorizer();
+
+ /**
+ * Create a new builder.
+ *
+ *
This method will install {@link PrimitiveBindings} and
+ * {@link StandardBindings} and default bindings.
+ */
+ public ParametricBuilder() {
+ addBinding(new PrimitiveBindings());
+ addBinding(new StandardBindings());
+ }
+
+ /**
+ * Add a binding for a given type or classifier (annotation).
+ *
+ *
Whenever a method parameter is encountered, a binding must be found for it
+ * so that it can be called later to consume the stack of arguments provided by
+ * the user and return an object that is later passed to
+ * {@link Method#invoke(Object, Object...)}.
+ *
+ *
Normally, a {@link Type} is used to discern between different bindings, but
+ * if this is not specific enough, an annotation can be defined and used. This
+ * makes it a "classifier" and it will take precedence over the base type. For
+ * example, even if there is a binding that handles {@link String} parameters,
+ * a special @MyArg annotation can be assigned to a {@link String}
+ * parameter, which will cause the {@link Builder} to consult the {@link Binding}
+ * associated with @MyArg rather than with the binding for
+ * the {@link String} type.
+ *
+ * @param binding the binding
+ * @param type a list of types (if specified) to override the binding's types
+ */
+ public void addBinding(Binding binding, Type... type) {
+ if (type == null || type.length == 0) {
+ type = binding.getTypes();
+ }
+
+ for (Type t : type) {
+ bindings.put(t, binding);
+ }
+ }
+
+ /**
+ * Attach an invocation listener.
+ *
+ *
Invocation handlers are called in order that their listeners are
+ * registered with a {@link ParametricBuilder}. It is not guaranteed that
+ * a listener may be called, in the case of a {@link CommandException} being
+ * thrown at any time before the appropriate listener or handler is called.
+ * It is possible for a
+ * {@link InvokeHandler#preInvoke(Object, Method, ParameterData[], Object[], CommandContext)} to
+ * be called for a invocation handler, but not the associated
+ * {@link InvokeHandler#postInvoke(Object, Method, ParameterData[], Object[], CommandContext)}.
+ *
+ *
An example of an invocation listener is one to handle
+ * {@link CommandPermissions}, by first checking to see if permission is available
+ * in a {@link InvokeHandler#preInvoke(Object, Method, ParameterData[], Object[], CommandContext)}
+ * call. If permission is not found, then an appropriate {@link CommandException}
+ * can be thrown to cease invocation.
+ *
+ * @param listener the listener
+ * @see InvokeHandler the handler
+ */
+ public void addInvokeListener(InvokeListener listener) {
+ invokeListeners.add(listener);
+ }
+
+ /**
+ * Attach an exception converter to this builder in order to wrap unknown
+ * {@link Throwable}s into known {@link CommandException}s.
+ *
+ *
Exception converters are called in order that they are registered.
+ *
+ * @param converter the converter
+ * @see ExceptionConverter for an explanation
+ */
+ public void addExceptionConverter(ExceptionConverter converter) {
+ exceptionConverters.add(converter);
+ }
+
+ /**
+ * Build a list of commands from methods specially annotated with {@link Command}
+ * (and other relevant annotations) and register them all with the given
+ * {@link Dispatcher}.
+ *
+ * @param dispatcher the dispatcher to register commands with
+ * @param object the object contain the methods
+ * @throws ParametricException thrown if the commands cannot be registered
+ */
+ public void registerMethodsAsCommands(Dispatcher dispatcher, Object object) throws ParametricException {
+ for (Method method : object.getClass().getDeclaredMethods()) {
+ Command definition = method.getAnnotation(Command.class);
+ if (definition != null) {
+ CommandCallable callable = build(object, method, definition);
+ dispatcher.registerCommand(callable, definition.aliases());
+ }
+ }
+ }
+
+ /**
+ * Build a {@link CommandCallable} for the given method.
+ *
+ * @param object the object to be invoked on
+ * @param method the method to invoke
+ * @param definition the command definition annotation
+ * @return the command executor
+ * @throws ParametricException thrown on an error
+ */
+ private CommandCallable build(Object object, Method method, Command definition)
+ throws ParametricException {
+ return new ParametricCallable(this, object, method, definition);
+ }
+
+ /**
+ * Get the object used to get method names on Java versions before 8 (assuming
+ * that Java 8 is given the ability to reliably reflect method names at runtime).
+ *
+ * @return the paranamer
+ */
+ Paranamer getParanamer() {
+ return paranamer;
+ }
+
+ /**
+ * Get the map of bindings.
+ *
+ * @return the map of bindings
+ */
+ Map getBindings() {
+ return bindings;
+ }
+
+ /**
+ * Get a list of invocation listeners.
+ *
+ * @return a list of invocation listeners
+ */
+ List getInvokeListeners() {
+ return invokeListeners;
+ }
+
+ /**
+ * Get the list of exception converters.
+ *
+ * @return a list of exception converters
+ */
+ List getExceptionConverters() {
+ return exceptionConverters;
+ }
+
+ /**
+ * Get the authorizer.
+ *
+ * @return the authorizer
+ */
+ public Authorizer getAuthorizer() {
+ return authorizer;
+ }
+
+ /**
+ * Set the authorizer.
+ *
+ * @param authorizer the authorizer
+ */
+ public void setAuthorizer(Authorizer authorizer) {
+ checkNotNull(authorizer);
+ this.authorizer = authorizer;
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java
new file mode 100644
index 000000000..9cbecc03e
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java
@@ -0,0 +1,448 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.*;
+import com.sk89q.worldedit.util.command.*;
+import com.sk89q.worldedit.util.command.binding.Switch;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.*;
+
+/**
+ * The implementation of a {@link CommandCallable} for the {@link ParametricBuilder}.
+ */
+class ParametricCallable implements CommandCallable {
+
+ private final ParametricBuilder builder;
+ private final Object object;
+ private final Method method;
+ private final ParameterData[] parameters;
+ private final Set valueFlags = new HashSet();
+ private final SimpleDescription description = new SimpleDescription();
+ private final CommandPermissions commandPermissions;
+
+ /**
+ * Create a new instance.
+ *
+ * @param builder the parametric builder
+ * @param object the object to invoke on
+ * @param method the method to invoke
+ * @param definition the command definition annotation
+ * @throws ParametricException thrown on an error
+ */
+ ParametricCallable(ParametricBuilder builder, Object object, Method method, Command definition) throws ParametricException {
+ this.builder = builder;
+ this.object = object;
+ this.method = method;
+
+ Annotation[][] annotations = method.getParameterAnnotations();
+ String[] names = builder.getParanamer().lookupParameterNames(method, false);
+ Type[] types = method.getGenericParameterTypes();
+ parameters = new ParameterData[types.length];
+ List userParameters = new ArrayList();
+
+ // This helps keep tracks of @Nullables that appear in the middle of a list
+ // of parameters
+ int numOptional = 0;
+
+ // Set permission hint
+ CommandPermissions permHint = method.getAnnotation(CommandPermissions.class);
+ if (permHint != null) {
+ description.setPermissions(Arrays.asList(permHint.value()));
+ }
+
+ // Go through each parameter
+ for (int i = 0; i < types.length; i++) {
+ Type type = types[i];
+
+ ParameterData parameter = new ParameterData();
+ parameter.setType(type);
+ parameter.setModifiers(annotations[i]);
+
+ // Search for annotations
+ for (Annotation annotation : annotations[i]) {
+ if (annotation instanceof Switch) {
+ parameter.setFlag(((Switch) annotation).value(), type != boolean.class);
+ } else if (annotation instanceof Optional) {
+ parameter.setOptional(true);
+ String[] value = ((Optional) annotation).value();
+ if (value.length > 0) {
+ parameter.setDefaultValue(value);
+ }
+ // Special annotation bindings
+ } else if (parameter.getBinding() == null) {
+ parameter.setBinding(builder.getBindings().get(
+ annotation.annotationType()));
+ parameter.setClassifier(annotation);
+ }
+ }
+
+ parameter.setName(names.length > 0 ? names[i] : generateName(type, parameter.getClassifier(), i));
+
+ // Track all value flags
+ if (parameter.isValueFlag()) {
+ valueFlags.add(parameter.getFlag());
+ }
+
+ // No special @annotation binding... let's check for the type
+ if (parameter.getBinding() == null) {
+ parameter.setBinding(builder.getBindings().get(type));
+
+ // Don't know how to parse for this type of value
+ if (parameter.getBinding() == null) {
+ throw new ParametricException("Don't know how to handle the parameter type '" + type + "' in\n" + method.toGenericString());
+ }
+ }
+
+ // Do some validation of this parameter
+ parameter.validate(method, i + 1);
+
+ // Keep track of optional parameters
+ if (parameter.isOptional() && parameter.getFlag() == null) {
+ numOptional++;
+ } else {
+ if (numOptional > 0 && parameter.isNonFlagConsumer()) {
+ if (parameter.getConsumedCount() < 0) {
+ throw new ParametricException(
+ "Found an parameter using the binding " +
+ parameter.getBinding().getClass().getCanonicalName() +
+ "\nthat does not know how many arguments it consumes, but " +
+ "it follows an optional parameter\nMethod: " +
+ method.toGenericString());
+ }
+ }
+ }
+
+ parameters[i] = parameter;
+
+ // Make a list of "real" parameters
+ if (parameter.isUserInput()) {
+ userParameters.add(parameter);
+ }
+ }
+
+ // Finish description
+ description.setDescription(!definition.desc().isEmpty() ? definition.desc() : null);
+ description.setHelp(!definition.help().isEmpty() ? definition.help() : null);
+ description.overrideUsage(!definition.usage().isEmpty() ? definition.usage() : null);
+
+ for (InvokeListener listener : builder.getInvokeListeners()) {
+ listener.updateDescription(object, method, parameters, description);
+ }
+
+ // Set parameters
+ description.setParameters(userParameters);
+
+ // Get permissions annotation
+ commandPermissions = method.getAnnotation(CommandPermissions.class);
+ }
+
+ @Override
+ public boolean call(String stringArguments, CommandLocals locals, String[] parentCommands) throws CommandException {
+ // Test permission
+ if (!testPermission(locals)) {
+ throw new CommandPermissionsException();
+ }
+
+ String calledCommand = parentCommands.length > 0 ? parentCommands[parentCommands.length - 1] : "_";
+ String[] split = CommandContext.split(calledCommand + " " + stringArguments);
+ CommandContext context = new CommandContext(split, getValueFlags(), false, locals);
+
+ Object[] args = new Object[parameters.length];
+ ContextArgumentStack arguments = new ContextArgumentStack(context);
+ ParameterData parameter = null;
+
+ try {
+ // preProcess handlers
+ List handlers = new ArrayList();
+ for (InvokeListener listener : builder.getInvokeListeners()) {
+ InvokeHandler handler = listener.createInvokeHandler();
+ handlers.add(handler);
+ handler.preProcess(object, method, parameters, context);
+ }
+
+ // Collect parameters
+ for (int i = 0; i < parameters.length; i++) {
+ parameter = parameters[i];
+
+ if (mayConsumeArguments(i, arguments)) {
+ // Parse the user input into a method argument
+ ArgumentStack usedArguments = getScopedContext(parameter, arguments);
+
+ try {
+ args[i] = parameter.getBinding().bind(parameter, usedArguments, false);
+ } catch (MissingParameterException e) {
+ // Not optional? Then we can't execute this command
+ if (!parameter.isOptional()) {
+ throw e;
+ }
+
+ args[i] = getDefaultValue(i, arguments);
+ }
+ } else {
+ args[i] = getDefaultValue(i, arguments);
+ }
+ }
+
+ // Check for unused arguments
+ checkUnconsumed(arguments);
+
+ // preInvoke handlers
+ for (InvokeHandler handler : handlers) {
+ handler.preInvoke(object, method, parameters, args, context);
+ }
+
+ // Execute!
+ method.invoke(object, args);
+
+ // postInvoke handlers
+ for (InvokeHandler handler : handlers) {
+ handler.postInvoke(handler, method, parameters, args, context);
+ }
+ } catch (MissingParameterException e) {
+ throw new InvalidUsageException("Too few parameters!", getDescription());
+ } catch (UnconsumedParameterException e) {
+ throw new InvalidUsageException("Too many parameters! Unused parameters: " + e.getUnconsumed(), getDescription());
+ } catch (ParameterException e) {
+ assert parameter != null;
+ String name = parameter.getName();
+
+ throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), getDescription());
+ } catch (InvocationTargetException e) {
+ for (ExceptionConverter converter : builder.getExceptionConverters()) {
+ converter.convert(e.getCause());
+ }
+ throw new WrappedCommandException(e);
+ } catch (IllegalArgumentException e) {
+ throw new WrappedCommandException(e);
+ } catch (CommandException e) {
+ throw e;
+ } catch (Throwable e) {
+ throw new WrappedCommandException(e);
+ }
+
+ return true;
+ }
+
+ @Override
+ public List getSuggestions(String stringArguments, CommandLocals locals) throws CommandException {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Set getValueFlags() {
+ return valueFlags;
+ }
+
+ @Override
+ public SimpleDescription getDescription() {
+ return description;
+ }
+
+ @Override
+ public boolean testPermission(CommandLocals locals) {
+ if (commandPermissions != null) {
+ for (String perm : commandPermissions.value()) {
+ if (builder.getAuthorizer().testPermission(locals, perm)) {
+ return true;
+ }
+ }
+
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Get the right {@link ArgumentStack}.
+ *
+ * @param parameter the parameter
+ * @param existing the existing scoped context
+ * @return the context to use
+ */
+ private static ArgumentStack getScopedContext(Parameter parameter, ArgumentStack existing) {
+ if (parameter.getFlag() != null) {
+ CommandContext context = existing.getContext();
+
+ if (parameter.isValueFlag()) {
+ return new StringArgumentStack(
+ context, context.getFlag(parameter.getFlag()), false);
+ } else {
+ String v = context.hasFlag(parameter.getFlag()) ? "true" : "false";
+ return new StringArgumentStack(context, v, true);
+ }
+ }
+
+ return existing;
+ }
+
+ /**
+ * Get whether a parameter is allowed to consume arguments.
+ *
+ * @param i the index of the parameter
+ * @param scoped the scoped context
+ * @return true if arguments may be consumed
+ */
+ private boolean mayConsumeArguments(int i, ContextArgumentStack scoped) {
+ CommandContext context = scoped.getContext();
+ ParameterData parameter = parameters[i];
+
+ // Flag parameters: Always consume
+ // Required non-flag parameters: Always consume
+ // Optional non-flag parameters:
+ // - Before required parameters: Consume if there are 'left over' args
+ // - At the end: Always consumes
+
+ if (parameter.isOptional() && parameter.getFlag() == null) {
+ int numberFree = context.argsLength() - scoped.position();
+ for (int j = i; j < parameters.length; j++) {
+ if (parameters[j].isNonFlagConsumer() && !parameters[j].isOptional()) {
+ // We already checked if the consumed count was > -1
+ // when we created this object
+ numberFree -= parameters[j].getConsumedCount();
+ }
+ }
+
+ // Skip this optional parameter
+ if (numberFree < 1) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the default value for a parameter.
+ *
+ * @param i the index of the parameter
+ * @param scoped the scoped context
+ * @return a value
+ * @throws ParameterException on an error
+ * @throws CommandException on an error
+ */
+ private Object getDefaultValue(int i, ContextArgumentStack scoped)
+ throws ParameterException, CommandException {
+ CommandContext context = scoped.getContext();
+ ParameterData parameter = parameters[i];
+
+ String[] defaultValue = parameter.getDefaultValue();
+ if (defaultValue != null) {
+ try {
+ return parameter.getBinding().bind(
+ parameter, new StringArgumentStack(
+ context, defaultValue, false), false);
+ } catch (MissingParameterException e) {
+ throw new ParametricException(
+ "The default value of the parameter using the binding " +
+ parameter.getBinding().getClass() + " in the method\n" +
+ method.toGenericString() + "\nis invalid");
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Check to see if all arguments, including flag arguments, were consumed.
+ *
+ * @param scoped the argument scope
+ * @throws UnconsumedParameterException thrown if parameters were not consumed
+ */
+ private void checkUnconsumed(ContextArgumentStack scoped)
+ throws UnconsumedParameterException {
+ CommandContext context = scoped.getContext();
+ String unconsumed;
+ String unconsumedFlags = getUnusedFlags(context);
+
+ if ((unconsumed = scoped.getUnconsumed()) != null) {
+ throw new UnconsumedParameterException(unconsumed + " " + unconsumedFlags);
+ }
+
+ if (unconsumedFlags != null) {
+ throw new UnconsumedParameterException(unconsumedFlags);
+ }
+ }
+
+ /**
+ * Get any unused flag arguments.
+ *
+ * @param context the command context
+ */
+ private String getUnusedFlags(CommandContext context) {
+ Set unusedFlags = null;
+ for (char flag : context.getFlags()) {
+ boolean found = false;
+
+ for (ParameterData parameter : parameters) {
+ Character paramFlag = parameter.getFlag();
+ if (paramFlag != null && flag == paramFlag) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (unusedFlags == null) {
+ unusedFlags = new HashSet();
+ }
+ unusedFlags.add(flag);
+ }
+ }
+
+ if (unusedFlags != null) {
+ StringBuilder builder = new StringBuilder();
+ for (Character flag : unusedFlags) {
+ builder.append("-").append(flag).append(" ");
+ }
+
+ return builder.toString().trim();
+ }
+
+ return null;
+ }
+
+ /**
+ * Generate a name for a parameter.
+ *
+ * @param type the type
+ * @param classifier the classifier
+ * @param index the index
+ * @return a generated name
+ */
+ private static String generateName(Type type, Annotation classifier, int index) {
+ if (classifier != null) {
+ return classifier.annotationType().getSimpleName().toLowerCase();
+ } else {
+ if (type instanceof Class>) {
+ return ((Class>) type).getSimpleName().toLowerCase();
+ } else {
+ return "unknown" + index;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricException.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricException.java
new file mode 100644
index 000000000..f2c826d73
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricException.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.worldedit.util.command.parametric;
+
+/**
+ * Thrown if the {@link ParametricBuilder} can't build commands from
+ * an object for whatever reason.
+ */
+public class ParametricException extends RuntimeException {
+
+ private static final long serialVersionUID = -5426219576099680971L;
+
+ public ParametricException() {
+ super();
+ }
+
+ public ParametricException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ParametricException(String message) {
+ super(message);
+ }
+
+ public ParametricException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/command/parametric/StringArgumentStack.java b/src/main/java/com/sk89q/worldedit/util/command/parametric/StringArgumentStack.java
new file mode 100644
index 000000000..7d93070fa
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/command/parametric/StringArgumentStack.java
@@ -0,0 +1,128 @@
+/*
+ * 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.worldedit.util.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandContext;
+import com.sk89q.worldedit.util.command.MissingParameterException;
+import com.sk89q.util.StringUtil;
+
+/**
+ * A virtual scope that does not actually read from the underlying
+ * {@link CommandContext}.
+ */
+public class StringArgumentStack implements ArgumentStack {
+
+ private final boolean nonNullBoolean;
+ private final CommandContext context;
+ private final String[] arguments;
+ private int index = 0;
+
+ /**
+ * Create a new instance using the given context.
+ *
+ * @param context the context
+ * @param arguments a list of arguments
+ * @param nonNullBoolean true to have {@link #nextBoolean()} return false instead of null
+ */
+ public StringArgumentStack(
+ CommandContext context, String[] arguments, boolean nonNullBoolean) {
+ this.context = context;
+ this.arguments = arguments;
+ this.nonNullBoolean = nonNullBoolean;
+ }
+
+ /**
+ * Create a new instance using the given context.
+ *
+ * @param context the context
+ * @param arguments an argument string to be parsed
+ * @param nonNullBoolean true to have {@link #nextBoolean()} return false instead of null
+ */
+ public StringArgumentStack(
+ CommandContext context, String arguments, boolean nonNullBoolean) {
+ this.context = context;
+ this.arguments = CommandContext.split(arguments);
+ this.nonNullBoolean = nonNullBoolean;
+ }
+
+ @Override
+ public String next() throws ParameterException {
+ try {
+ return arguments[index++];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new MissingParameterException();
+ }
+ }
+
+ @Override
+ public Integer nextInt() throws ParameterException {
+ try {
+ return Integer.parseInt(next());
+ } catch (NumberFormatException e) {
+ throw new ParameterException(
+ "Expected a number, got '" + context.getString(index - 1) + "'");
+ }
+ }
+
+ @Override
+ public Double nextDouble() throws ParameterException {
+ try {
+ return Double.parseDouble(next());
+ } catch (NumberFormatException e) {
+ throw new ParameterException(
+ "Expected a number, got '" + context.getString(index - 1) + "'");
+ }
+ }
+
+ @Override
+ public Boolean nextBoolean() throws ParameterException {
+ try {
+ return next().equalsIgnoreCase("true");
+ } catch (IndexOutOfBoundsException e) {
+ if (nonNullBoolean) { // Special case
+ return false;
+ }
+
+ throw new MissingParameterException();
+ }
+ }
+
+ @Override
+ public String remaining() throws ParameterException {
+ try {
+ String value = StringUtil.joinString(arguments, " ", index);
+ markConsumed();
+ return value;
+ } catch (IndexOutOfBoundsException e) {
+ throw new MissingParameterException();
+ }
+ }
+
+ @Override
+ public void markConsumed() {
+ index = arguments.length;
+ }
+
+ @Override
+ public CommandContext getContext() {
+ return context;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java b/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java
new file mode 100644
index 000000000..c0ea43961
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java
@@ -0,0 +1,275 @@
+/*
+ * 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.worldedit.util.formatting;
+
+import com.google.common.base.Joiner;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class ColorCodeBuilder {
+
+ private static final ColorCodeBuilder instance = new ColorCodeBuilder();
+ private static final Joiner newLineJoiner = Joiner.on("\n");
+ public static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 47;
+
+ /**
+ * Convert a message into color-coded text.
+ *
+ * @param message the message
+ * @return a list of lines
+ */
+ public String[] build(StyledFragment message) {
+ StringBuilder builder = new StringBuilder();
+ buildFragment(builder, message, message.getStyle(), new StyleSet());
+ return wordWrap(builder.toString(), GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH);
+ }
+
+ /**
+ * Build a fragment.
+ *
+ * @param builder the string builder
+ * @param message the message
+ * @param parentStyle the parent style
+ * @param lastStyle the last style
+ * @return the last style used
+ */
+ private StyleSet buildFragment(StringBuilder builder, StyledFragment message, StyleSet parentStyle, StyleSet lastStyle) {
+ for (Fragment node : message.getChildren()) {
+ if (node instanceof StyledFragment) {
+ StyledFragment fragment = (StyledFragment) node;
+ lastStyle = buildFragment(
+ builder, fragment,
+ parentStyle.extend(message.getStyle()), lastStyle);
+ } else {
+ StyleSet style = parentStyle.extend(message.getStyle());
+ builder.append(getAdditive(style, lastStyle));
+ builder.append(node.toString());
+ lastStyle = style;
+ }
+ }
+
+ return lastStyle;
+ }
+
+ /**
+ * Get the formatting codes.
+ *
+ * @param style the style
+ * @return the color codes
+ */
+ public static String getFormattingCode(StyleSet style) {
+ StringBuilder builder = new StringBuilder();
+ if (style.isBold()) {
+ builder.append(Style.BOLD);
+ }
+ if (style.isItalic()) {
+ builder.append(Style.ITALIC);
+ }
+ if (style.isUnderline()) {
+ builder.append(Style.UNDERLINE);
+ }
+ if (style.isStrikethrough()) {
+ builder.append(Style.STRIKETHROUGH);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Get the formatting and color codes.
+ *
+ * @param style the style
+ * @return the color codes
+ */
+ public static String getCode(StyleSet style) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getFormattingCode(style));
+ if (style.getColor() != null) {
+ builder.append(style.getColor().toString());
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Get the additional color codes needed to set the given style when the current
+ * style is the other given one.
+ *
+ * @param resetTo the style to reset to
+ * @param resetFrom the style to reset from
+ * @return the color codes
+ */
+ public static String getAdditive(StyleSet resetTo, StyleSet resetFrom) {
+ if (!resetFrom.hasFormatting() && resetTo.hasFormatting()) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getFormattingCode(resetTo));
+ if (resetFrom.getColor() != resetTo.getColor()) {
+ builder.append(resetTo.getColor());
+ }
+ return builder.toString();
+ } else if (!resetFrom.hasEqualFormatting(resetTo) ||
+ (resetFrom.getColor() != null && resetTo.getColor() == null)) {
+ StringBuilder builder = new StringBuilder();
+ // Have to set reset code and add back all the formatting codes
+ builder.append(Style.RESET);
+ builder.append(getCode(resetTo));
+ return builder.toString();
+ } else {
+ if (resetFrom.getColor() != resetTo.getColor()) {
+ return String.valueOf(resetTo.getColor());
+ }
+ }
+
+ return "";
+ }
+
+ /**
+ * Word wrap the given text and maintain color codes throughout lines.
+ *
+ *
This is borrowed from Bukkit.
+ *
+ * @param rawString the raw string
+ * @param lineLength the maximum line length
+ * @return a list of lines
+ */
+ private String[] wordWrap(String rawString, int lineLength) {
+ // A null string is a single line
+ if (rawString == null) {
+ return new String[] {""};
+ }
+
+ // A string shorter than the lineWidth is a single line
+ if (rawString.length() <= lineLength && !rawString.contains("\n")) {
+ return new String[] {rawString};
+ }
+
+ char[] rawChars = (rawString + ' ').toCharArray(); // add a trailing space to trigger pagination
+ StringBuilder word = new StringBuilder();
+ StringBuilder line = new StringBuilder();
+ List lines = new LinkedList();
+ int lineColorChars = 0;
+
+ for (int i = 0; i < rawChars.length; i++) {
+ char c = rawChars[i];
+
+ // skip chat color modifiers
+ if (c == Style.COLOR_CHAR) {
+ word.append(Style.getByChar(rawChars[i + 1]));
+ lineColorChars += 2;
+ i++; // Eat the next character as we have already processed it
+ continue;
+ }
+
+ if (c == ' ' || c == '\n') {
+ if (line.length() == 0 && word.length() > lineLength) { // special case: extremely long word begins a line
+ String wordStr = word.toString();
+ String transformed;
+ if ((transformed = transform(wordStr)) != null) {
+ line.append(transformed);
+ } else {
+ for (String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})")) {
+ lines.add(partialWord);
+ }
+ }
+ } else if (line.length() + word.length() - lineColorChars == lineLength) { // Line exactly the correct length...newline
+ line.append(' ');
+ line.append(word);
+ lines.add(line.toString());
+ line = new StringBuilder();
+ lineColorChars = 0;
+ } else if (line.length() + 1 + word.length() - lineColorChars > lineLength) { // Line too long...break the line
+ String wordStr = word.toString();
+ String transformed;
+ if (word.length() > lineLength && (transformed = transform(wordStr)) != null) {
+ if (line.length() + 1 + transformed.length() - lineColorChars > lineLength) {
+ lines.add(line.toString());
+ line = new StringBuilder(transformed);
+ lineColorChars = 0;
+ } else {
+ if (line.length() > 0) {
+ line.append(' ');
+ }
+ line.append(transformed);
+ }
+ } else {
+ for (String partialWord : wordStr.split("(?<=\\G.{" + lineLength + "})")) {
+ lines.add(line.toString());
+ line = new StringBuilder(partialWord);
+ }
+ lineColorChars = 0;
+ }
+ } else {
+ if (line.length() > 0) {
+ line.append(' ');
+ }
+ line.append(word);
+ }
+ word = new StringBuilder();
+
+ if (c == '\n') { // Newline forces the line to flush
+ lines.add(line.toString());
+ line = new StringBuilder();
+ }
+ } else {
+ word.append(c);
+ }
+ }
+
+ if(line.length() > 0) { // Only add the last line if there is anything to add
+ lines.add(line.toString());
+ }
+
+ // Iterate over the wrapped lines, applying the last color from one line to the beginning of the next
+ if (lines.get(0).length() == 0 || lines.get(0).charAt(0) != Style.COLOR_CHAR) {
+ lines.set(0, Style.WHITE + lines.get(0));
+ }
+ for (int i = 1; i < lines.size(); i++) {
+ final String pLine = lines.get(i-1);
+ final String subLine = lines.get(i);
+
+ char color = pLine.charAt(pLine.lastIndexOf(Style.COLOR_CHAR) + 1);
+ if (subLine.length() == 0 || subLine.charAt(0) != Style.COLOR_CHAR) {
+ lines.set(i, Style.getByChar(color) + subLine);
+ }
+ }
+
+ return lines.toArray(new String[lines.size()]);
+ }
+
+ /**
+ * Callback for transforming a word, such as a URL.
+ *
+ * @param word the word
+ * @return the transformed value, or null to do nothing
+ */
+ protected String transform(String word) {
+ return null;
+ }
+
+ /**
+ * Convert the given styled fragment into color codes.
+ *
+ * @param fragment the fragment
+ * @return color codes
+ */
+ public static String asColorCodes(StyledFragment fragment) {
+ return newLineJoiner.join(instance.build(fragment));
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/CommandListBox.java b/src/main/java/com/sk89q/worldedit/util/formatting/CommandListBox.java
new file mode 100644
index 000000000..03fa0b754
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/formatting/CommandListBox.java
@@ -0,0 +1,45 @@
+/*
+ * 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.worldedit.util.formatting;
+
+public class CommandListBox extends MessageBox {
+
+ private boolean first = true;
+
+ /**
+ * Create a new box.
+ *
+ * @param title the title
+ */
+ public CommandListBox(String title) {
+ super(title);
+ }
+
+ public CommandListBox appendCommand(String alias, String description) {
+ if (!first) {
+ getContents().newLine();
+ }
+ getContents().createFragment(Style.YELLOW_DARK).append(alias).append(": ");
+ getContents().append(description);
+ first = false;
+ return this;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java b/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java
new file mode 100644
index 000000000..3d1add7f4
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java
@@ -0,0 +1,92 @@
+/*
+ * 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.worldedit.util.formatting;
+
+/**
+ * A fragment of text.
+ */
+public class Fragment {
+
+ private final StringBuilder builder = new StringBuilder();
+
+ Fragment() {
+ }
+
+ public Fragment append(String str) {
+ builder.append(Style.stripColor(str));
+ return this;
+ }
+
+ public Fragment append(Object obj) {
+ append(String.valueOf(obj));
+ return this;
+ }
+
+ public Fragment append(StringBuffer sb) {
+ append(String.valueOf(sb));
+ return this;
+ }
+
+ public Fragment append(CharSequence s) {
+ append(String.valueOf(s));
+ return this;
+ }
+
+ public Fragment append(boolean b) {
+ append(String.valueOf(b));
+ return this;
+ }
+
+ public Fragment append(char c) {
+ append(String.valueOf(c));
+ return this;
+ }
+
+ public Fragment append(int i) {
+ append(String.valueOf(i));
+ return this;
+ }
+
+ public Fragment append(long lng) {
+ append(String.valueOf(lng));
+ return this;
+ }
+
+ public Fragment append(float f) {
+ append(String.valueOf(f));
+ return this;
+ }
+
+ public Fragment append(double d) {
+ append(String.valueOf(d));
+ return this;
+ }
+
+ public Fragment newLine() {
+ append("\n");
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/MessageBox.java b/src/main/java/com/sk89q/worldedit/util/formatting/MessageBox.java
new file mode 100644
index 000000000..66cf81ab3
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/formatting/MessageBox.java
@@ -0,0 +1,70 @@
+/*
+ * 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.worldedit.util.formatting;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Makes for a box with a border above and below.
+ */
+public class MessageBox extends StyledFragment {
+
+ private final StyledFragment contents = new StyledFragment();
+
+ /**
+ * Create a new box.
+ */
+ public MessageBox(String title) {
+ checkNotNull(title);
+
+ int leftOver = ColorCodeBuilder.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - title.length() - 2;
+ int leftSide = (int) Math.floor(leftOver * 1.0/3);
+ int rightSide = (int) Math.floor(leftOver * 2.0/3);
+ if (leftSide > 0) {
+ createFragment(Style.YELLOW).append(createBorder(leftSide));
+ }
+ append(" ");
+ append(title);
+ append(" ");
+ if (rightSide > 0) {
+ createFragment(Style.YELLOW).append(createBorder(rightSide));
+ }
+ newLine();
+ append(contents);
+ }
+
+ private String createBorder(int count) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.append("-");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Get the internal contents.
+ *
+ * @return the contents
+ */
+ public StyledFragment getContents() {
+ return contents;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/Style.java b/src/main/java/com/sk89q/worldedit/util/formatting/Style.java
new file mode 100644
index 000000000..3eafd3c8d
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/formatting/Style.java
@@ -0,0 +1,276 @@
+/*
+ * 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.worldedit.util.formatting;
+
+import com.google.common.collect.Maps;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * All supported color values for chat.
+ *
+ *
From Bukkit.
+ */
+public enum Style {
+ /**
+ * Represents black
+ */
+ BLACK('0', 0x00),
+ /**
+ * Represents dark blue
+ */
+ BLUE_DARK('1', 0x1),
+ /**
+ * Represents dark green
+ */
+ GREEN_DARK('2', 0x2),
+ /**
+ * Represents dark blue (aqua)
+ */
+ CYAN_DARK('3', 0x3),
+ /**
+ * Represents dark red
+ */
+ RED_DARK('4', 0x4),
+ /**
+ * Represents dark purple
+ */
+ PURPLE_DARK('5', 0x5),
+ /**
+ * Represents gold
+ */
+ YELLOW_DARK('6', 0x6),
+ /**
+ * Represents gray
+ */
+ GRAY('7', 0x7),
+ /**
+ * Represents dark gray
+ */
+ GRAY_DARK('8', 0x8),
+ /**
+ * Represents blue
+ */
+ BLUE('9', 0x9),
+ /**
+ * Represents green
+ */
+ GREEN('a', 0xA),
+ /**
+ * Represents aqua
+ */
+ CYAN('b', 0xB),
+ /**
+ * Represents red
+ */
+ RED('c', 0xC),
+ /**
+ * Represents light purple
+ */
+ PURPLE('d', 0xD),
+ /**
+ * Represents yellow
+ */
+ YELLOW('e', 0xE),
+ /**
+ * Represents white
+ */
+ WHITE('f', 0xF),
+ /**
+ * Represents magical characters that change around randomly
+ */
+ RANDOMIZE('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 color codes. Use this if you need to dynamically
+ * convert color 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 = Maps.newHashMap();
+ private final static Map BY_CHAR = Maps.newHashMap();
+
+ private Style(char code, int intCode) {
+ this(code, intCode, false);
+ }
+
+ private Style(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 code;
+ }
+
+ @Override
+ public String toString() {
+ return toString;
+ }
+
+ /**
+ * Checks if this code is a format code as opposed to a color code.
+ *
+ * @return the if the code is a formatting code
+ */
+ public boolean isFormat() {
+ return isFormat;
+ }
+
+ /**
+ * Checks if this code is a color code as opposed to a format code.
+ *
+ * @return the if the code is a color
+ */
+ public boolean isColor() {
+ return !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 Style 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 Style getByChar(String code) {
+ checkNotNull(code);
+ checkArgument(!code.isEmpty(), "Code must have at least one character");
+
+ 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] = Style.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);
+ Style 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 (Style color : values()) {
+ BY_ID.put(color.intCode, color);
+ BY_CHAR.put(color.code, color);
+ }
+ }
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java b/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java
new file mode 100644
index 000000000..35663fa9e
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java
@@ -0,0 +1,249 @@
+/*
+ * 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.worldedit.util.formatting;
+
+/**
+ * Represents set of styles, such as color, bold, etc.
+ */
+public class StyleSet {
+
+ private Boolean bold;
+ private Boolean italic;
+ private Boolean underline;
+ private Boolean strikethrough;
+ private Style color;
+
+ /**
+ * Create a new style set with no properties set.
+ */
+ public StyleSet() {
+ }
+
+ /**
+ * Create a new style set with the given styles.
+ *
+ *
{@link Style#RESET} will be ignored if provided.
+ *
+ * @param styles a list of styles
+ */
+ public StyleSet(Style... styles) {
+ for (Style style : styles) {
+ if (style.isColor()) {
+ color = style;
+ } else if (style == Style.BOLD) {
+ bold = true;
+ } else if (style == Style.ITALIC) {
+ italic = true;
+ } else if (style == Style.UNDERLINE) {
+ underline = true;
+ } else if (style == Style.STRIKETHROUGH) {
+ strikethrough = true;
+ }
+ }
+ }
+
+ /**
+ * Get whether this style set is bold.
+ *
+ * @return true, false, or null if unset
+ */
+ public Boolean getBold() {
+ return bold;
+ }
+
+ /**
+ * Get whether the text is bold.
+ *
+ * @return true if bold
+ */
+ public boolean isBold() {
+ return getBold() != null && getBold() == true;
+ }
+
+ /**
+ * Set whether the text is bold.
+ *
+ * @param bold true, false, or null to unset
+ */
+ public void setBold(Boolean bold) {
+ this.bold = bold;
+ }
+
+ /**
+ * Get whether this style set is italicized.
+ *
+ * @return true, false, or null if unset
+ */
+ public Boolean getItalic() {
+ return italic;
+ }
+
+ /**
+ * Get whether the text is italicized.
+ *
+ * @return true if italicized
+ */
+ public boolean isItalic() {
+ return getItalic() != null && getItalic() == true;
+ }
+
+ /**
+ * Set whether the text is italicized.
+ *
+ * @param italic false, or null to unset
+ */
+ public void setItalic(Boolean italic) {
+ this.italic = italic;
+ }
+
+ /**
+ * Get whether this style set is underlined.
+ *
+ * @return true, false, or null if unset
+ */
+ public Boolean getUnderline() {
+ return underline;
+ }
+
+ /**
+ * Get whether the text is underlined.
+ *
+ * @return true if underlined
+ */
+ public boolean isUnderline() {
+ return getUnderline() != null && getUnderline() == true;
+ }
+
+ /**
+ * Set whether the text is underline.
+ *
+ * @param underline false, or null to unset
+ */
+ public void setUnderline(Boolean underline) {
+ this.underline = underline;
+ }
+
+ /**
+ * Get whether this style set is stricken through.
+ *
+ * @return true, false, or null if unset
+ */
+ public Boolean getStrikethrough() {
+ return strikethrough;
+ }
+
+ /**
+ * Get whether the text is stricken through.
+ *
+ * @return true if there is strikethrough applied
+ */
+ public boolean isStrikethrough() {
+ return getStrikethrough() != null && getStrikethrough() == true;
+ }
+
+ /**
+ * Set whether the text is stricken through.
+ *
+ * @param strikethrough false, or null to unset
+ */
+ public void setStrikethrough(Boolean strikethrough) {
+ this.strikethrough = strikethrough;
+ }
+
+ /**
+ * Get the color of the text.
+ *
+ * @return true, false, or null if unset
+ */
+ public Style getColor() {
+ return color;
+ }
+
+ /**
+ * Set the color of the text.
+ *
+ * @param color the color
+ */
+ public void setColor(Style color) {
+ this.color = color;
+ }
+
+ /**
+ * Return whether text formatting (bold, italics, underline, strikethrough) is set.
+ *
+ * @return true if formatting is set
+ */
+ public boolean hasFormatting() {
+ return getBold() != null || getItalic() != null
+ || getUnderline() != null || getStrikethrough() != null;
+ }
+
+ /**
+ * Return where the text formatting of the given style set is different from
+ * that assigned to this one.
+ *
+ * @param other the other style set
+ * @return true if there is a difference
+ */
+ public boolean hasEqualFormatting(StyleSet other) {
+ return getBold() == other.getBold() && getItalic() == other.getItalic()
+ && getUnderline() == other.getUnderline() &&
+ getStrikethrough() == other.getStrikethrough();
+ }
+
+ /**
+ * Create a new instance with styles inherited from this one but with new styles
+ * from the given style set.
+ *
+ * @param style the style set
+ * @return a new style set instance
+ */
+ public StyleSet extend(StyleSet style) {
+ StyleSet newStyle = clone();
+ if (style.getBold() != null) {
+ newStyle.setBold(style.getBold());
+ }
+ if (style.getItalic() != null) {
+ newStyle.setItalic(style.getItalic());
+ }
+ if (style.getUnderline() != null) {
+ newStyle.setUnderline(style.getUnderline());
+ }
+ if (style.getStrikethrough() != null) {
+ newStyle.setStrikethrough(style.getStrikethrough());
+ }
+ if (style.getColor() != null) {
+ newStyle.setColor(style.getColor());
+ }
+ return newStyle;
+ }
+
+ @Override
+ public StyleSet clone() {
+ StyleSet style = new StyleSet();
+ style.setBold(getBold());
+ style.setItalic(getItalic());
+ style.setUnderline(getUnderline());
+ style.setStrikethrough(getStrikethrough());
+ style.setColor(getColor());
+ return style;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java b/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java
new file mode 100644
index 000000000..7674d9bf8
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java
@@ -0,0 +1,150 @@
+/*
+ * 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.worldedit.util.formatting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A fragment of text that can be styled.
+ */
+public class StyledFragment extends Fragment {
+
+ private final List children = new ArrayList();
+ private StyleSet style;
+ private Fragment lastText;
+
+ public StyledFragment() {
+ style = new StyleSet();
+ }
+
+ public StyledFragment(StyleSet style) {
+ this.style = style;
+ }
+
+ public StyledFragment(Style... styles) {
+ this.style = new StyleSet(styles);
+ }
+
+ public StyleSet getStyle() {
+ return style;
+ }
+
+ public void setStyles(StyleSet style) {
+ this.style = style;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ protected Fragment lastText() {
+ Fragment text;
+ if (children.size() > 0) {
+ text = children.get(children.size() - 1);
+ if (text == lastText) {
+ return text;
+ }
+ }
+
+ text = new Fragment();
+ this.lastText = text;
+ children.add(text);
+ return text;
+ }
+
+ public StyledFragment createFragment(Style... styles) {
+ StyledFragment fragment = new StyledFragment(styles);
+ append(fragment);
+ return fragment;
+ }
+
+ public StyledFragment append(StyledFragment fragment) {
+ children.add(fragment);
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(String str) {
+ lastText().append(str);
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(Object obj) {
+ append(String.valueOf(obj));
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(StringBuffer sb) {
+ append(String.valueOf(sb));
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(CharSequence s) {
+ append(String.valueOf(s));
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(boolean b) {
+ append(String.valueOf(b));
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(char c) {
+ append(String.valueOf(c));
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(int i) {
+ append(String.valueOf(i));
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(long lng) {
+ append(String.valueOf(lng));
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(float f) {
+ append(String.valueOf(f));
+ return this;
+ }
+
+ @Override
+ public StyledFragment append(double d) {
+ append(String.valueOf(d));
+ return this;
+ }
+
+ @Override
+ public StyledFragment newLine() {
+ append("\n");
+ return this;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java b/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java
index 29aa34f78..e5876107c 100644
--- a/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java
+++ b/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java
@@ -25,6 +25,7 @@ import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.BlockID;
import com.sk89q.worldedit.blocks.BlockType;
+import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.function.mask.BlockMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.Operation;
@@ -219,7 +220,7 @@ public abstract class AbstractWorld implements World {
@SuppressWarnings("deprecation")
@Override
- public boolean queueBlockBreakEffect(ServerInterface server, Vector position, int blockId, double priority) {
+ public boolean queueBlockBreakEffect(Platform server, Vector position, int blockId, double priority) {
if (taskId == -1) {
taskId = server.schedule(0, 1, new Runnable() {
@Override
diff --git a/src/main/java/com/sk89q/worldedit/world/World.java b/src/main/java/com/sk89q/worldedit/world/World.java
index 9f69b122e..8accbe122 100644
--- a/src/main/java/com/sk89q/worldedit/world/World.java
+++ b/src/main/java/com/sk89q/worldedit/world/World.java
@@ -22,6 +22,7 @@ package com.sk89q.worldedit.world;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BaseItemStack;
+import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.regions.Region;
@@ -342,7 +343,7 @@ public interface World extends Extent {
* @param priority the priority
* @return true if the effect was played
*/
- boolean queueBlockBreakEffect(ServerInterface server, Vector position, int blockId, double priority);
+ boolean queueBlockBreakEffect(Platform server, Vector position, int blockId, double priority);
@Override
boolean equals(Object other);
diff --git a/src/main/java/com/sk89q/worldedit/world/snapshot/SnapshotRestore.java b/src/main/java/com/sk89q/worldedit/world/snapshot/SnapshotRestore.java
index 9cd1ef7d9..51db687ce 100644
--- a/src/main/java/com/sk89q/worldedit/world/snapshot/SnapshotRestore.java
+++ b/src/main/java/com/sk89q/worldedit/world/snapshot/SnapshotRestore.java
@@ -123,7 +123,7 @@ public class SnapshotRestore {
}
private void checkAndAddBlock(Vector pos) {
- if (editSession.getMask() != null && !editSession.getMask().matches(editSession, pos))
+ if (editSession.getMask() != null && !editSession.getMask().test(pos))
return;
BlockVector2D chunkPos = ChunkStore.toChunk(pos);