Merge commit '142f5c8e5c889ee5098c05ba2fde20b52467c1df' into feature/platform-caps

This commit is contained in:
sk89q 2014-06-27 16:03:29 -07:00
commit 8f1943fd84
69 changed files with 5209 additions and 359 deletions

View File

@ -44,6 +44,7 @@ dependencies {
compile group: 'com.google.guava', name: 'guava', version:'10.0.1' compile group: 'com.google.guava', name: 'guava', version:'10.0.1'
compile group: 'com.sk89q', name: 'jchronic', version:'0.2.4a' 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.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' testCompile group: 'org.mockito', name: 'mockito-core', version:'1.9.0-rc1'
} }
@ -87,6 +88,7 @@ shadow {
destinationDir "${buildDir}/libs/" destinationDir "${buildDir}/libs/"
artifactSet { artifactSet {
include '*:jchronic:jar:' include '*:jchronic:jar:'
include '*:paranamer:jar:'
} }
} }

37
pom.xml
View File

@ -97,6 +97,13 @@
</repository> </repository>
</repositories> </repositories>
<pluginRepositories>
<pluginRepository>
<id>sk89q-repo</id>
<url>http://maven.sk89q.com/repo/</url>
</pluginRepository>
</pluginRepositories>
<dependencies> <dependencies>
<!-- Used for snapshots --> <!-- Used for snapshots -->
<dependency> <dependency>
@ -151,6 +158,15 @@
<type>jar</type> <type>jar</type>
</dependency> </dependency>
<!-- Method names for the command framework -->
<dependency>
<groupId>com.thoughtworks.paranamer</groupId>
<artifactId>paranamer</artifactId>
<version>2.6</version>
<scope>compile</scope>
<type>jar</type>
</dependency>
<!-- Unit tests --> <!-- Unit tests -->
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
@ -297,6 +313,26 @@
</configuration> </configuration>
</plugin> </plugin>
<!-- Stores parameter information -->
<plugin>
<groupId>com.thoughtworks.paranamer</groupId>
<artifactId>paranamer-maven-plugin-largestack</artifactId>
<version>2.5.5-SNAPSHOT</version>
<executions>
<execution>
<id>run</id>
<phase>compile</phase>
<configuration>
<sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
<outputDirectory>${project.build.outputDirectory}</outputDirectory>
</configuration>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- JAR plugin --> <!-- JAR plugin -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -352,6 +388,7 @@
<artifactSet> <artifactSet>
<includes> <includes>
<include>com.sk89q:jchronic</include> <include>com.sk89q:jchronic</include>
<include>com.thoughtworks.paranamer:paranamer</include>
</includes> </includes>
</artifactSet> </artifactSet>
</configuration> </configuration>

View File

@ -21,13 +21,16 @@ package com.sk89q.worldedit.bukkit;
import com.sk89q.bukkit.util.CommandInfo; import com.sk89q.bukkit.util.CommandInfo;
import com.sk89q.bukkit.util.CommandRegistration; import com.sk89q.bukkit.util.CommandRegistration;
import com.sk89q.minecraft.util.commands.Command; import com.sk89q.worldedit.BiomeTypes;
import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.minecraft.util.commands.CommandsManager; import com.sk89q.worldedit.LocalWorld;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.ServerInterface;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.Preference; 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.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Server; import org.bukkit.Server;
@ -35,8 +38,10 @@ import org.bukkit.World;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.reflect.Method; import java.util.ArrayList;
import java.util.*; import java.util.EnumMap;
import java.util.List;
import java.util.Map;
public class BukkitServerInterface extends ServerInterface { public class BukkitServerInterface extends ServerInterface {
public Server server; public Server server;
@ -118,25 +123,17 @@ public class BukkitServerInterface extends ServerInterface {
} }
@Override @Override
public void onCommandRegistration(List<Command> commands, CommandsManager<LocalPlayer> manager) { public void registerCommands(Dispatcher dispatcher) {
List<CommandInfo> toRegister = new ArrayList<CommandInfo>(); List<CommandInfo> toRegister = new ArrayList<CommandInfo>();
for (Command command : commands) { for (CommandMapping command : dispatcher.getCommands()) {
List<String> permissions = null; Description description = command.getDescription();
Method cmdMethod = manager.getMethods().get(null).get(command.aliases()[0]); List<String> permissions = description.getPermissions();
Map<String, Method> childMethods = manager.getMethods().get(cmdMethod); String[] permissionsArray = new String[permissions.size()];
permissions.toArray(permissionsArray);
if (cmdMethod != null && cmdMethod.isAnnotationPresent(CommandPermissions.class)) { toRegister.add(new CommandInfo(
permissions = Arrays.asList(cmdMethod.getAnnotation(CommandPermissions.class).value()); description.getUsage(), description.getDescription(),
} else if (cmdMethod != null && childMethods != null && childMethods.size() > 0) { command.getAllAliases(), dispatcher, permissionsArray));
permissions = new ArrayList<String>();
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()])));
} }
dynamicCommands.register(toRegister); dynamicCommands.register(toRegister);

View File

@ -19,13 +19,15 @@
package com.sk89q.worldedit.forge; package com.sk89q.worldedit.forge;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.worldedit.BiomeTypes; import com.sk89q.worldedit.BiomeTypes;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.ServerInterface; import com.sk89q.worldedit.ServerInterface;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.Preference; 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 com.sk89q.worldedit.world.World;
import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.FMLCommonHandler;
import net.minecraft.command.CommandBase; import net.minecraft.command.CommandBase;
@ -133,19 +135,22 @@ class ForgePlatform extends ServerInterface {
} }
@Override @Override
public void onCommandRegistration(List<Command> commands) { public void registerCommands(Dispatcher dispatcher) {
if (server == null) return; if (server == null) return;
ServerCommandManager mcMan = (ServerCommandManager) server.getCommandManager(); 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() { mcMan.registerCommand(new CommandBase() {
@Override @Override
public String getCommandName() { public String getCommandName() {
return cmd.aliases()[0]; return command.getPrimaryAlias();
} }
@Override @Override
public List<String> getCommandAliases() { public List<String> getCommandAliases() {
return Arrays.asList(cmd.aliases()); return Arrays.asList(command.getAllAliases());
} }
@Override @Override
@ -153,12 +158,14 @@ class ForgePlatform extends ServerInterface {
@Override @Override
public String getCommandUsage(ICommandSender icommandsender) { public String getCommandUsage(ICommandSender icommandsender) {
return "/" + cmd.aliases()[0] + " " + cmd.usage(); return "/" + command.getPrimaryAlias() + " " + description.getUsage();
} }
@Override @Override
public int compareTo(Object o) { public int compareTo(@Nullable Object o) {
if (o instanceof ICommand) { if (o == null) {
return -1;
} else if (o instanceof ICommand) {
return super.compareTo((ICommand) o); return super.compareTo((ICommand) o);
} else { } else {
return -1; return -1;

View File

@ -9,6 +9,7 @@
<allow pkg="org.mockito"/> <allow pkg="org.mockito"/>
<allow pkg="com.sk89q"/> <allow pkg="com.sk89q"/>
<allow pkg="com.google.common"/> <allow pkg="com.google.common"/>
<allow pkg="com.thoughtworks.paranamer"/>
<subpackage name="util.yaml"> <subpackage name="util.yaml">
<allow pkg="org.yaml.snakeyaml"/> <allow pkg="org.yaml.snakeyaml"/>

View File

@ -28,15 +28,22 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
public class CommandContext { public class CommandContext {
protected final String command; protected final String command;
protected final List<String> parsedArgs; protected final List<String> parsedArgs;
protected final List<Integer> originalArgIndices; protected final List<Integer> originalArgIndices;
protected final String[] originalArgs; protected final String[] originalArgs;
protected final Set<Character> booleanFlags = new HashSet<Character>(); protected final Set<Character> booleanFlags = new HashSet<Character>();
protected final Map<Character, String> valueFlags = new HashMap<Character, String>(); protected final Map<Character, String> valueFlags = new HashMap<Character, String>();
protected final SuggestionContext suggestionContext;
protected final CommandLocals locals;
public static String[] split(String args) {
return args.split(" ", -1);
}
public CommandContext(String args) throws CommandException { public CommandContext(String args) throws CommandException {
this(args.split(" "), null); this(args.split(" ", -1), null);
} }
public CommandContext(String[] args) throws CommandException { public CommandContext(String[] args) throws CommandException {
@ -44,28 +51,50 @@ public class CommandContext {
} }
public CommandContext(String args, Set<Character> valueFlags) throws CommandException { public CommandContext(String args, Set<Character> valueFlags) throws CommandException {
this(args.split(" "), valueFlags); this(args.split(" ", -1), valueFlags);
}
public CommandContext(String args, Set<Character> valueFlags, boolean allowHangingFlag)
throws CommandException {
this(args.split(" ", -1), valueFlags, allowHangingFlag, new CommandLocals());
}
public CommandContext(String[] args, Set<Character> valueFlags) throws CommandException {
this(args, valueFlags, false, null);
} }
/** /**
* @param args An array with arguments. Empty strings outside quotes will be removed. * Parse the given array of arguments.
* @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. * <p>Empty arguments are removed from the list of arguments.</p>
*
* @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<Character> valueFlags) throws CommandException { public CommandContext(String[] args, Set<Character> valueFlags,
boolean allowHangingFlag, CommandLocals locals) throws CommandException {
if (valueFlags == null) { if (valueFlags == null) {
valueFlags = Collections.emptySet(); valueFlags = Collections.emptySet();
} }
originalArgs = args; originalArgs = args;
command = args[0]; 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 // Eliminate empty args and combine multiword args first
List<Integer> argIndexList = new ArrayList<Integer>(args.length); List<Integer> argIndexList = new ArrayList<Integer>(args.length);
List<String> argList = new ArrayList<String>(args.length); List<String> argList = new ArrayList<String>(args.length);
for (int i = 1; i < args.length; ++i) { for (int i = 1; i < args.length; ++i) {
isHanging = false;
String arg = args[i]; String arg = args[i];
if (arg.length() == 0) { if (arg.length() == 0) {
isHanging = true;
continue; continue;
} }
@ -113,9 +142,14 @@ public class CommandContext {
for (int nextArg = 0; nextArg < argList.size(); ) { for (int nextArg = 0; nextArg < argList.size(); ) {
// Fetch argument // Fetch argument
String arg = argList.get(nextArg++); String arg = argList.get(nextArg++);
suggestionContext = SuggestionContext.hangingValue();
// Not a flag? // Not a flag?
if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z]+$")) { if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z]+$")) {
if (!isHanging) {
suggestionContext = SuggestionContext.lastValue();
}
originalArgIndices.add(argIndexList.get(nextArg - 1)); originalArgIndices.add(argIndexList.get(nextArg - 1));
parsedArgs.add(arg); parsedArgs.add(arg);
continue; continue;
@ -140,16 +174,30 @@ public class CommandContext {
} }
if (nextArg >= argList.size()) { 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 // If it is a value flag, read another argument and add it
this.valueFlags.put(flagName, argList.get(nextArg++)); this.valueFlags.put(flagName, argList.get(nextArg++));
if (!isHanging) {
suggestionContext = SuggestionContext.flag(flagName);
}
} else { } else {
booleanFlags.add(flagName); booleanFlags.add(flagName);
} }
} }
} }
this.suggestionContext = suggestionContext;
}
public SuggestionContext getSuggestionContext() {
return suggestionContext;
} }
public String getCommand() { public String getCommand() {
@ -177,6 +225,18 @@ public class CommandContext {
return buffer.toString(); 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 { public int getInteger(int index) throws NumberFormatException {
return Integer.parseInt(parsedArgs.get(index)); return Integer.parseInt(parsedArgs.get(index));
} }
@ -271,4 +331,8 @@ public class CommandContext {
public int argsLength() { public int argsLength() {
return parsedArgs.size(); return parsedArgs.size();
} }
public CommandLocals getLocals() {
return locals;
}
} }

View File

@ -19,8 +19,14 @@
package com.sk89q.minecraft.util.commands; package com.sk89q.minecraft.util.commands;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class CommandException extends Exception { public class CommandException extends Exception {
private static final long serialVersionUID = 870638193072101739L; private static final long serialVersionUID = 870638193072101739L;
private List<String> commandStack = new ArrayList<String>();
public CommandException() { public CommandException() {
super(); super();
@ -30,8 +36,37 @@ public class CommandException extends Exception {
super(message); super(message);
} }
public CommandException(String message, Throwable t) {
super(message, t);
}
public CommandException(Throwable t) { public CommandException(Throwable t) {
super(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<String> 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();
}
} }

View File

@ -0,0 +1,50 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.util.HashMap;
import java.util.Map;
public class CommandLocals {
private final Map<Object, Object> locals = new HashMap<Object, Object>();
public boolean containsKey(Object key) {
return locals.containsKey(key);
}
public boolean containsValue(Object value) {
return locals.containsValue(value);
}
public Object get(Object key) {
return locals.get(key);
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> key) {
return (T) locals.get(key);
}
public Object put(Object key, Object value) {
return locals.put(key, value);
}
}

View File

@ -0,0 +1,68 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
public class SuggestionContext {
private static final SuggestionContext FOR_LAST = new SuggestionContext(null, true);
private static final SuggestionContext FOR_HANGING = new SuggestionContext(null, false);
private final Character flag;
private final boolean forLast;
private SuggestionContext(Character flag, boolean forLast) {
this.flag = flag;
this.forLast = forLast;
}
public boolean forHangingValue() {
return flag == null && !forLast;
}
public boolean forLastValue() {
return flag == null && forLast;
}
public boolean forFlag() {
return flag != null;
}
public Character getFlag() {
return flag;
}
@Override
public String toString() {
return forFlag() ? ("-" + getFlag()) : (forHangingValue() ? "hanging" : "last");
}
public static SuggestionContext flag(Character flag) {
return new SuggestionContext(flag, false);
}
public static SuggestionContext lastValue() {
return FOR_LAST;
}
public static SuggestionContext hangingValue() {
return FOR_HANGING;
}
}

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit; package com.sk89q.worldedit;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.extent.EditSessionEvent; import com.sk89q.worldedit.event.extent.EditSessionEvent;
import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.util.eventbus.EventBus; import com.sk89q.worldedit.util.eventbus.EventBus;
@ -63,7 +64,7 @@ public class EditSessionFactory {
* @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit * @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit
* @param player the player that the {@link EditSession} is for * @param player the player that the {@link EditSession} is for
*/ */
public EditSession getEditSession(World world, int maxBlocks, LocalPlayer player) { public EditSession getEditSession(World world, int maxBlocks, Player player) {
throw new IllegalArgumentException("This class is being removed"); throw new IllegalArgumentException("This class is being removed");
} }
@ -96,7 +97,7 @@ public class EditSessionFactory {
* @param blockBag an optional {@link BlockBag} to use, otherwise null * @param blockBag an optional {@link BlockBag} to use, otherwise null
* @param player the player that the {@link EditSession} is for * @param player the player that the {@link EditSession} is for
*/ */
public EditSession getEditSession(World world, int maxBlocks, BlockBag blockBag, LocalPlayer player) { public EditSession getEditSession(World world, int maxBlocks, BlockBag blockBag, Player player) {
throw new IllegalArgumentException("This class is being removed"); throw new IllegalArgumentException("This class is being removed");
} }
@ -123,7 +124,7 @@ public class EditSessionFactory {
} }
@Override @Override
public EditSession getEditSession(World world, int maxBlocks, LocalPlayer player) { public EditSession getEditSession(World world, int maxBlocks, Player player) {
return new EditSession(eventBus, world, maxBlocks, null, new EditSessionEvent(world, player, maxBlocks, null)); return new EditSession(eventBus, world, maxBlocks, null, new EditSessionEvent(world, player, maxBlocks, null));
} }
@ -133,7 +134,7 @@ public class EditSessionFactory {
} }
@Override @Override
public EditSession getEditSession(World world, int maxBlocks, BlockBag blockBag, LocalPlayer player) { public EditSession getEditSession(World world, int maxBlocks, BlockBag blockBag, Player player) {
return new EditSession(eventBus, world, maxBlocks, blockBag, new EditSessionEvent(world, player, maxBlocks, null)); return new EditSession(eventBus, world, maxBlocks, blockBag, new EditSessionEvent(world, player, maxBlocks, null));
} }

View File

@ -27,6 +27,7 @@ import com.sk89q.worldedit.command.tool.BlockTool;
import com.sk89q.worldedit.command.tool.BrushTool; import com.sk89q.worldedit.command.tool.BrushTool;
import com.sk89q.worldedit.command.tool.SinglePickaxe; import com.sk89q.worldedit.command.tool.SinglePickaxe;
import com.sk89q.worldedit.command.tool.Tool; import com.sk89q.worldedit.command.tool.Tool;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.internal.cui.CUIEvent;
@ -419,7 +420,7 @@ public class LocalSession {
* @param player * @param player
* @return * @return
*/ */
public BlockBag getBlockBag(LocalPlayer player) { public BlockBag getBlockBag(Player player) {
if (!useInventory) { if (!useInventory) {
return null; return null;
} }
@ -550,7 +551,7 @@ public class LocalSession {
* *
* @param player * @param player
*/ */
public void tellVersion(LocalPlayer player) { public void tellVersion(Actor player) {
if (config.showFirstUseVersion) { if (config.showFirstUseVersion) {
if (!beenToldVersion) { if (!beenToldVersion) {
player.printRaw("\u00A78WorldEdit ver. " + WorldEdit.getVersion() player.printRaw("\u00A78WorldEdit ver. " + WorldEdit.getVersion()
@ -713,6 +714,17 @@ public class LocalSession {
* @return * @return
*/ */
public EditSession createEditSession(LocalPlayer player) { public EditSession createEditSession(LocalPlayer player) {
return createEditSession((Player) player);
}
/**
* Construct a new edit session.
*
* @param player
* @return
*/
@SuppressWarnings("deprecation")
public EditSession createEditSession(Player player) {
BlockBag blockBag = getBlockBag(player); BlockBag blockBag = getBlockBag(player);
// Create an edit session // Create an edit session
@ -721,8 +733,8 @@ public class LocalSession {
getBlockChangeLimit(), blockBag, player); getBlockChangeLimit(), blockBag, player);
editSession.setFastMode(fastMode); editSession.setFastMode(fastMode);
Request.request().setEditSession(editSession); Request.request().setEditSession(editSession);
if (mask != null) { if (mask != null && player instanceof LocalPlayer) {
mask.prepare(this, player, null); mask.prepare(this, (LocalPlayer) player, null);
} }
editSession.setMask(mask); editSession.setMask(mask);

View File

@ -19,12 +19,12 @@
package com.sk89q.worldedit; package com.sk89q.worldedit;
import com.sk89q.minecraft.util.commands.CommandsManager;
import com.sk89q.worldedit.CuboidClipboard.FlipDirection; import com.sk89q.worldedit.CuboidClipboard.FlipDirection;
import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.blocks.BlockType; import com.sk89q.worldedit.blocks.BlockType;
import com.sk89q.worldedit.event.platform.BlockInteractEvent; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.extent.EditSessionEvent; import com.sk89q.worldedit.event.extent.EditSessionEvent;
import com.sk89q.worldedit.event.platform.BlockInteractEvent;
import com.sk89q.worldedit.event.platform.CommandEvent; import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.event.platform.InputType; import com.sk89q.worldedit.event.platform.InputType;
import com.sk89q.worldedit.event.platform.PlayerInputEvent; import com.sk89q.worldedit.event.platform.PlayerInputEvent;
@ -483,7 +483,7 @@ public class WorldEdit {
* @return a direction vector * @return a direction vector
* @throws UnknownDirectionException thrown if the direction is not known * @throws UnknownDirectionException thrown if the direction is not known
*/ */
public Vector getDirection(LocalPlayer player, String dirStr) throws UnknownDirectionException { public Vector getDirection(Player player, String dirStr) throws UnknownDirectionException {
dirStr = dirStr.toLowerCase(); dirStr = dirStr.toLowerCase();
final PlayerDirection dir = getPlayerDirection(player, dirStr); final PlayerDirection dir = getPlayerDirection(player, dirStr);
@ -511,7 +511,7 @@ public class WorldEdit {
* @return a direction enum value * @return a direction enum value
* @throws UnknownDirectionException thrown if the direction is not known * @throws UnknownDirectionException thrown if the direction is not known
*/ */
private PlayerDirection getPlayerDirection(LocalPlayer player, String dirStr) throws UnknownDirectionException { private PlayerDirection getPlayerDirection(Player player, String dirStr) throws UnknownDirectionException {
final PlayerDirection dir; final PlayerDirection dir;
switch (dirStr.charAt(0)) { switch (dirStr.charAt(0)) {
@ -661,24 +661,6 @@ public class WorldEdit {
} }
} }
/**
* Get the map of commands (internal usage only).
*
* @return the commands
*/
public Map<String, String> getCommands() {
return getCommandsManager().getCommands();
}
/**
* Get the commands manager (internal usage only).
*
* @return the commands
*/
public CommandsManager<LocalPlayer> getCommandsManager() {
return getPlatformManager().getCommandManager().getCommands();
}
/** /**
* Handle a disconnection. * Handle a disconnection.
* *

View File

@ -260,13 +260,6 @@ public class ClipboardCommands {
player.printError("This command is no longer used. See //schematic save."); player.printError("This command is no longer used. See //schematic save.");
} }
@Command(
aliases = { "/schematic", "/schem"},
desc = "Schematic-related commands"
)
@NestedCommand(SchematicCommands.class)
public void schematic() {}
@Command( @Command(
aliases = { "clearclipboard" }, aliases = { "clearclipboard" },
usage = "", usage = "",

View File

@ -23,13 +23,7 @@ import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.Console; import com.sk89q.minecraft.util.commands.Console;
import com.sk89q.minecraft.util.commands.NestedCommand; import com.sk89q.worldedit.*;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.ItemType; import com.sk89q.worldedit.blocks.ItemType;
import com.sk89q.worldedit.masks.Mask; import com.sk89q.worldedit.masks.Mask;
@ -226,13 +220,4 @@ public class GeneralCommands {
} }
} }
@Command(
aliases = { "we", "worldedit" },
desc = "WorldEdit commands"
)
@NestedCommand(WorldEditCommands.class)
@Console
public void we(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException {
}
} }

View File

@ -54,15 +54,6 @@ public class SnapshotUtilCommands {
this.we = we; this.we = we;
} }
@Command(
aliases = { "snapshot", "snap" },
desc = "Snapshot commands"
)
@NestedCommand(SnapshotCommands.class)
public void snapshot(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException {
}
@Command( @Command(
aliases = { "restore", "/restore" }, aliases = { "restore", "/restore" },
usage = "[snapshot]", usage = "[snapshot]",

View File

@ -151,15 +151,6 @@ public class ToolCommands {
+ ItemType.toHeldName(player.getItemInHand()) + "."); + ItemType.toHeldName(player.getItemInHand()) + ".");
} }
@Command(
aliases = { "brush", "br" },
desc = "Brush tool"
)
@NestedCommand(BrushCommands.class)
public void brush(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException {
}
@Command( @Command(
aliases = { "deltree" }, aliases = { "deltree" },
usage = "", usage = "",

View File

@ -22,7 +22,6 @@ package com.sk89q.worldedit.command;
import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.NestedCommand;
import com.sk89q.worldedit.*; import com.sk89q.worldedit.*;
import com.sk89q.worldedit.masks.Mask; import com.sk89q.worldedit.masks.Mask;
import com.sk89q.worldedit.patterns.Pattern; import com.sk89q.worldedit.patterns.Pattern;
@ -70,24 +69,6 @@ public class ToolUtilCommands {
} }
@Command(
aliases = { "superpickaxe", "pickaxe", "sp" },
desc = "Select super pickaxe mode"
)
@NestedCommand(SuperPickaxeCommands.class)
public void pickaxe(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException {
}
@Command(
aliases = {"tool"},
desc = "Select a tool to bind"
)
@NestedCommand(ToolCommands.class)
public void tool(CommandContext args, LocalSession session, LocalPlayer player,
EditSession editSession) throws WorldEditException {
}
@Command( @Command(
aliases = { "mask" }, aliases = { "mask" },
usage = "[mask]", usage = "[mask]",

View File

@ -27,6 +27,9 @@ import com.sk89q.worldedit.patterns.Pattern;
import com.sk89q.worldedit.patterns.SingleBlockPattern; import com.sk89q.worldedit.patterns.SingleBlockPattern;
import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
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 com.sk89q.worldedit.world.World;
import java.util.Comparator; import java.util.Comparator;
@ -512,7 +515,7 @@ public class UtilityCommands {
} }
public static void help(CommandContext args, WorldEdit we, LocalSession session, LocalPlayer player, EditSession editSession) { public static void help(CommandContext args, WorldEdit we, LocalSession session, LocalPlayer player, EditSession editSession) {
final CommandsManager<LocalPlayer> commandsManager = we.getCommandsManager(); final Dispatcher dispatcher = we.getPlatformManager().getCommandManager().getDispatcher();
if (args.argsLength() == 0) { if (args.argsLength() == 0) {
SortedSet<String> commands = new TreeSet<String>(new Comparator<String>() { SortedSet<String> commands = new TreeSet<String>(new Comparator<String>() {
@ -525,7 +528,7 @@ public class UtilityCommands {
return ret; return ret;
} }
}); });
commands.addAll(commandsManager.getCommands().keySet()); commands.addAll(dispatcher.getPrimaryAliases());
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
boolean first = true; boolean first = true;
@ -544,14 +547,26 @@ public class UtilityCommands {
return; return;
} }
String command = args.getJoinedStrings(0).toLowerCase().replaceAll("/", ""); String command = args.getJoinedStrings(0).toLowerCase().replaceAll("^/", "");
CommandMapping mapping = dispatcher.get(command);
String helpMessage = commandsManager.getHelpMessages().get(command); if (mapping == null) {
if (helpMessage == null) {
player.printError("Unknown command '" + command + "'."); player.printError("Unknown command '" + command + "'.");
return; return;
} }
player.print(helpMessage); Description description = mapping.getDescription();
if (description.getUsage() != null) {
player.printDebug("Usage: " + description.getUsage());
}
if (description.getHelp() != null) {
player.print(description.getHelp());
} else if (description.getDescription() != null) {
player.print(description.getDescription());
} else {
player.print("No further help is available.");
}
} }
} }

View File

@ -42,15 +42,4 @@ public abstract class AbstractPlatform implements Platform {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
@Deprecated
public void onCommandRegistration(List<Command> commands) {
// Do nothing :)
}
@Override
public void onCommandRegistration(List<Command> commands, CommandsManager<LocalPlayer> manager) {
onCommandRegistration(commands);
}
} }

View File

@ -19,22 +19,35 @@
package com.sk89q.worldedit.extension.platform; package com.sk89q.worldedit.extension.platform;
import com.sk89q.minecraft.util.commands.*; import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.util.StringUtil; import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.worldedit.*; import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import com.sk89q.worldedit.blocks.ItemType; import com.sk89q.minecraft.util.commands.WrappedCommandException;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.*; import com.sk89q.worldedit.command.*;
import com.sk89q.worldedit.event.platform.CommandEvent; import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.session.request.Request; import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.util.logging.LogFormat; import com.sk89q.worldedit.util.CommandLoggingHandler;
import com.sk89q.worldedit.util.CommandPermissionsHandler;
import com.sk89q.worldedit.util.WorldEditBinding;
import com.sk89q.worldedit.util.WorldEditExceptionConverter;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.util.command.InvalidUsageException;
import com.sk89q.worldedit.util.command.fluent.CommandGraph;
import com.sk89q.worldedit.util.command.parametric.LegacyCommandsHandler;
import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
import com.sk89q.worldedit.util.eventbus.Subscribe; import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.util.logging.DynamicStreamHandler; import com.sk89q.worldedit.util.logging.DynamicStreamHandler;
import com.sk89q.worldedit.util.logging.LogFormat;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.util.logging.FileHandler;
import java.util.logging.*; import java.util.logging.Level;
import java.util.regex.Matcher; import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -49,7 +62,7 @@ public final class CommandManager {
private static final java.util.regex.Pattern numberFormatExceptionPattern = java.util.regex.Pattern.compile("^For input string: \"(.*)\"$"); private static final java.util.regex.Pattern numberFormatExceptionPattern = java.util.regex.Pattern.compile("^For input string: \"(.*)\"$");
private final WorldEdit worldEdit; private final WorldEdit worldEdit;
private final CommandsManager<LocalPlayer> commands; private final Dispatcher dispatcher;
private final DynamicStreamHandler dynamicHandler = new DynamicStreamHandler(); private final DynamicStreamHandler dynamicHandler = new DynamicStreamHandler();
/** /**
@ -69,8 +82,56 @@ public final class CommandManager {
dynamicHandler.setFormatter(new LogFormat()); dynamicHandler.setFormatter(new LogFormat());
// Set up the commands manager // Set up the commands manager
commands = new CommandsManagerImpl(); ParametricBuilder builder = new ParametricBuilder();
commands.setInjector(new SimpleInjector(worldEdit)); builder.addBinding(new WorldEditBinding(worldEdit));
builder.attach(new CommandPermissionsHandler());
builder.attach(new WorldEditExceptionConverter(worldEdit));
builder.attach(new LegacyCommandsHandler());
builder.attach(new CommandLoggingHandler(worldEdit, logger));
dispatcher = new CommandGraph()
.builder(builder)
.commands()
.build(new BiomeCommands(worldEdit))
.build(new ChunkCommands(worldEdit))
.build(new ClipboardCommands(worldEdit))
.build(new GeneralCommands(worldEdit))
.build(new GenerationCommands(worldEdit))
.build(new HistoryCommands(worldEdit))
.build(new NavigationCommands(worldEdit))
.build(new RegionCommands(worldEdit))
.build(new ScriptingCommands(worldEdit))
.build(new SelectionCommands(worldEdit))
.build(new SnapshotUtilCommands(worldEdit))
.build(new ToolUtilCommands(worldEdit))
.build(new ToolCommands(worldEdit))
.build(new UtilityCommands(worldEdit))
.group("worldedit", "we")
.describe("WorldEdit commands")
.build(new WorldEditCommands(worldEdit))
.parent()
.group("schematic", "schem", "/schematic", "/schem")
.describe("Schematic commands for saving/loading areas")
.build(new SchematicCommands(worldEdit))
.parent()
.group("snapshot", "snap")
.describe("Schematic commands for saving/loading areas")
.build(new SnapshotCommands(worldEdit))
.parent()
.group("brush", "br")
.describe("Brushing commands")
.build(new BrushCommands(worldEdit))
.parent()
.group("superpickaxe", "pickaxe", "sp")
.describe("Super-pickaxe commands")
.build(new SuperPickaxeCommands(worldEdit))
.parent()
.group("tool")
.describe("Bind functions to held items")
.build(new ToolCommands(worldEdit))
.parent()
.graph()
.getDispatcher();
} }
void register(Platform platform) { void register(Platform platform) {
@ -95,37 +156,14 @@ public final class CommandManager {
} }
} }
register(platform, BiomeCommands.class); platform.registerCommands(dispatcher);
register(platform, ChunkCommands.class);
register(platform, ClipboardCommands.class);
register(platform, GeneralCommands.class);
register(platform, GenerationCommands.class);
register(platform, HistoryCommands.class);
register(platform, NavigationCommands.class);
register(platform, RegionCommands.class);
register(platform, ScriptingCommands.class);
register(platform, SelectionCommands.class);
register(platform, SnapshotUtilCommands.class);
register(platform, ToolUtilCommands.class);
register(platform, ToolCommands.class);
register(platform, UtilityCommands.class);
} }
void unregister() { void unregister() {
dynamicHandler.setHandler(null); dynamicHandler.setHandler(null);
} }
private void register(Platform platform, Class<?> clazz) {
platform.onCommandRegistration(commands.registerAndReturn(clazz), commands);
}
public CommandsManager<LocalPlayer> getCommands() {
return commands;
}
public String[] commandDetection(String[] split) { public String[] commandDetection(String[] split) {
Request.reset();
split[0] = split[0].substring(1); split[0] = split[0].substring(1);
// Quick script shortcut // Quick script shortcut
@ -140,14 +178,13 @@ public final class CommandManager {
String searchCmd = split[0].toLowerCase(); String searchCmd = split[0].toLowerCase();
// Try to detect the command // Try to detect the command
if (commands.hasCommand(searchCmd)) { if (dispatcher.contains(searchCmd)) {
} else if (worldEdit.getConfiguration().noDoubleSlash && commands.hasCommand("/" + searchCmd)) { } else if (worldEdit.getConfiguration().noDoubleSlash && dispatcher.contains("/" + searchCmd)) {
split[0] = "/" + split[0]; split[0] = "/" + split[0];
} else if (split[0].length() >= 2 && split[0].charAt(0) == '/' } else if (split[0].length() >= 2 && split[0].charAt(0) == '/'
&& commands.hasCommand(searchCmd.substring(1))) { && dispatcher.contains(searchCmd.substring(1))) {
split[0] = split[0].substring(1); split[0] = split[0].substring(1);
} }
return split; return split;
} }
@ -155,185 +192,69 @@ public final class CommandManager {
public void handleCommand(CommandEvent event) { public void handleCommand(CommandEvent event) {
Request.reset(); Request.reset();
LocalPlayer player = event.getPlayer(); Actor actor = event.getPlayer();
String[] split = event.getArguments(); String split[] = commandDetection(event.getArguments());
// No command found!
if (!dispatcher.contains(split[0])) {
return;
}
LocalSession session = worldEdit.getSessionManager().get(actor);
LocalConfiguration config = worldEdit.getConfiguration();
CommandLocals locals = new CommandLocals();
locals.put(Actor.class, actor);
long start = System.currentTimeMillis();
try { try {
split = commandDetection(split); dispatcher.call(split, locals);
} catch (CommandPermissionsException e) {
actor.printError("You don't have permission to do this.");
} catch (InvalidUsageException e) {
actor.printError(e.getMessage() + "\nUsage: " + e.getUsage("/"));
} catch (WrappedCommandException e) {
Throwable t = e.getCause();
actor.printError("Please report this error: [See console]");
actor.printRaw(t.getClass().getName() + ": " + t.getMessage());
t.printStackTrace();
} catch (CommandException e) {
actor.printError(e.getMessage());
} finally {
EditSession editSession = locals.get(EditSession.class);
// No command found! if (editSession != null) {
if (!commands.hasCommand(split[0])) {
return;
}
LocalSession session = worldEdit.getSession(player);
EditSession editSession = session.createEditSession(player);
editSession.enableQueue();
session.tellVersion(player);
long start = System.currentTimeMillis();
try {
commands.execute(split, player, session, player, editSession);
} catch (CommandPermissionsException e) {
player.printError("You don't have permission to do this.");
} catch (MissingNestedCommandException e) {
player.printError(e.getUsage());
} catch (CommandUsageException e) {
player.printError(e.getMessage());
player.printError(e.getUsage());
} catch (PlayerNeededException e) {
player.printError(e.getMessage());
} catch (WrappedCommandException e) {
throw e.getCause();
} catch (UnhandledCommandException e) {
player.printError("Command could not be handled; invalid sender!");
event.setCancelled(true);
return;
} finally {
session.remember(editSession); session.remember(editSession);
editSession.flushQueue(); editSession.flushQueue();
if (worldEdit.getConfiguration().profile) { if (config.profile) {
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
int changed = editSession.getBlockChangeCount(); int changed = editSession.getBlockChangeCount();
if (time > 0) { if (time > 0) {
double throughput = changed / (time / 1000.0); double throughput = changed / (time / 1000.0);
player.printDebug((time / 1000.0) + "s elapsed (history: " actor.printDebug((time / 1000.0) + "s elapsed (history: "
+ changed + " changed; " + changed + " changed; "
+ Math.round(throughput) + " blocks/sec)."); + Math.round(throughput) + " blocks/sec).");
} else { } else {
player.printDebug((time / 1000.0) + "s elapsed."); actor.printDebug((time / 1000.0) + "s elapsed.");
} }
} }
worldEdit.flushBlockBag(player, editSession); worldEdit.flushBlockBag(event.getPlayer(), editSession);
} }
} catch (NumberFormatException e) {
final Matcher matcher = numberFormatExceptionPattern.matcher(e.getMessage());
if (matcher.matches()) {
player.printError("Number expected; string \"" + matcher.group(1) + "\" given.");
} else {
player.printError("Number expected; string given.");
}
} catch (IncompleteRegionException e) {
player.printError("Make a region selection first.");
} catch (UnknownItemException e) {
player.printError("Block name '" + e.getID() + "' was not recognized.");
} catch (InvalidItemException e) {
player.printError(e.getMessage());
} catch (DisallowedItemException e) {
player.printError("Block '" + e.getID() + "' not allowed (see WorldEdit configuration).");
} catch (MaxChangedBlocksException e) {
player.printError("Max blocks changed in an operation reached ("
+ e.getBlockLimit() + ").");
} catch (MaxBrushRadiusException e) {
player.printError("Maximum allowed brush size: " + worldEdit.getConfiguration().maxBrushRadius);
} catch (MaxRadiusException e) {
player.printError("Maximum allowed size: " + worldEdit.getConfiguration().maxRadius);
} catch (UnknownDirectionException e) {
player.printError("Unknown direction: " + e.getDirection());
} catch (InsufficientArgumentsException e) {
player.printError(e.getMessage());
} catch (EmptyClipboardException e) {
player.printError("Your clipboard is empty. Use //copy first.");
} catch (InvalidFilenameException e) {
player.printError("Filename '" + e.getFilename() + "' invalid: "
+ e.getMessage());
} catch (FilenameResolutionException e) {
player.printError("File '" + e.getFilename() + "' resolution error: "
+ e.getMessage());
} catch (InvalidToolBindException e) {
player.printError("Can't bind tool to "
+ ItemType.toHeldName(e.getItemId()) + ": " + e.getMessage());
} catch (FileSelectionAbortedException e) {
player.printError("File selection aborted.");
} catch (WorldEditException e) {
player.printError(e.getMessage());
} catch (Throwable excp) {
player.printError("Please report this error: [See console]");
player.printRaw(excp.getClass().getName() + ": " + excp.getMessage());
excp.printStackTrace();
} }
event.setCancelled(true); event.setCancelled(true);
} }
private class CommandsManagerImpl extends CommandsManager<LocalPlayer> { /**
@Override * Get the command dispatcher instance.
protected void checkPermission(LocalPlayer player, Method method) throws CommandException { *
if (!player.isPlayer() && !method.isAnnotationPresent(Console.class)) { * @return the command dispatcher
throw new UnhandledCommandException(); */
} public Dispatcher getDispatcher() {
return dispatcher;
super.checkPermission(player, method);
}
@Override
public boolean hasPermission(LocalPlayer player, String perm) {
return player.hasPermission(perm);
}
@Override
public void invokeMethod(Method parent, String[] args,
LocalPlayer player, Method method, Object instance,
Object[] methodArgs, int level) throws CommandException {
if (worldEdit.getConfiguration().logCommands) {
final Logging loggingAnnotation = method.getAnnotation(Logging.class);
final Logging.LogMode logMode;
if (loggingAnnotation == null) {
logMode = null;
} else {
logMode = loggingAnnotation.value();
}
String msg = "WorldEdit: " + player.getName();
if (player.isPlayer()) {
msg += " (in \"" + player.getWorld().getName() + "\")";
}
msg += ": " + StringUtil.joinString(args, " ");
if (logMode != null && player.isPlayer()) {
Vector position = player.getPosition();
final LocalSession session = worldEdit.getSessionManager().get(player);
switch (logMode) {
case PLACEMENT:
try {
position = session.getPlacementPosition(player);
} catch (IncompleteRegionException e) {
break;
}
/* FALL-THROUGH */
case POSITION:
msg += " - Position: " + position;
break;
case ALL:
msg += " - Position: " + position;
/* FALL-THROUGH */
case ORIENTATION_REGION:
msg += " - Orientation: " + player.getCardinalDirection().name();
/* FALL-THROUGH */
case REGION:
try {
msg += " - Region: " + session.getSelection(player.getWorld());
} catch (IncompleteRegionException e) {
break;
}
break;
}
}
getLogger().info(msg);
}
super.invokeMethod(parent, args, player, method, instance, methodArgs, level);
}
} }
public static Logger getLogger() { public static Logger getLogger() {

View File

@ -19,12 +19,10 @@
package com.sk89q.worldedit.extension.platform; package com.sk89q.worldedit.extension.platform;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandsManager;
import com.sk89q.worldedit.BiomeTypes; import com.sk89q.worldedit.BiomeTypes;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -100,10 +98,12 @@ public interface Platform {
*/ */
@Nullable World matchWorld(World world); @Nullable World matchWorld(World world);
@Deprecated /**
void onCommandRegistration(List<Command> commands); * Register the commands contained within the given command dispatcher.
*
void onCommandRegistration(List<Command> commands, CommandsManager<LocalPlayer> manager); * @param dispatcher the dispatcher
*/
void registerCommands(Dispatcher dispatcher);
/** /**
* Register game hooks. * Register game hooks.

View File

@ -19,16 +19,14 @@
package com.sk89q.worldedit.internal; package com.sk89q.worldedit.internal;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandsManager;
import com.sk89q.worldedit.BiomeTypes; import com.sk89q.worldedit.BiomeTypes;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.ServerInterface; import com.sk89q.worldedit.ServerInterface;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.extension.platform.Platform;
import com.sk89q.worldedit.extension.platform.Preference; import com.sk89q.worldedit.extension.platform.Preference;
import com.sk89q.worldedit.util.command.Dispatcher;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -97,14 +95,8 @@ public class ServerInterfaceAdapter extends ServerInterface {
} }
@Override @Override
@Deprecated public void registerCommands(Dispatcher dispatcher) {
public void onCommandRegistration(List<Command> commands) { platform.registerCommands(dispatcher);
platform.onCommandRegistration(commands);
}
@Override
public void onCommandRegistration(List<Command> commands, CommandsManager<LocalPlayer> manager) {
platform.onCommandRegistration(commands, manager);
} }
@Override @Override

View File

@ -0,0 +1,39 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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";
}

View File

@ -0,0 +1,34 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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 {
}

View File

@ -0,0 +1,147 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
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.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();
}
LocalPlayer sender = context.getLocals().get(LocalPlayer.class);
if (sender == null) {
return;
}
builder.append("WorldEdit: ").append(sender.getName());
if (sender.isPlayer()) {
builder.append(" (in \"" + sender.getWorld().getName() + "\")");
}
builder.append(": ").append(context.getCommand());
if (context.argsLength() > 0) {
builder.append(" ").append(context.getJoinedStrings(0));
}
if (logMode != null && sender.isPlayer()) {
Vector position = sender.getPosition();
LocalSession session = worldEdit.getSession(sender);
switch (logMode) {
case PLACEMENT:
try {
position = session.getPlacementPosition(sender);
} 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: "
+ sender.getCardinalDirection().name());
/* FALL-THROUGH */
case REGION:
try {
builder.append(" - Region: ")
.append(session.getSelection(sender.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();
}
}
}

View File

@ -0,0 +1,41 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.worldedit.util.command.parametric.PermissionsHandler;
import com.sk89q.worldedit.LocalPlayer;
public class CommandPermissionsHandler extends PermissionsHandler {
public CommandPermissionsHandler() {
}
@Override
protected boolean hasPermission(CommandContext context, String permission) {
LocalPlayer sender = context.getLocals().get(LocalPlayer.class);
if (sender == null) {
return true;
} else {
return sender.hasPermission(permission);
}
}
}

View File

@ -0,0 +1,198 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.entity.Player;
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.command.parametric.*;
/**
* 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 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 Player} from a {@link ArgumentStack}.
*
* @param context the context
* @return a local player
* @throws ParameterException on error
*/
@SuppressWarnings("deprecation")
@BindingMatch(type = LocalPlayer.class,
behavior = BindingBehavior.PROVIDES)
public Player getLocalPlayer(ArgumentStack context) throws ParameterException {
Player player = getPlayer(context);
if (player instanceof LocalPlayer) {
return (LocalPlayer) player;
} else {
throw new ParameterException("This command/function needs to be updated to take 'Player' rather than 'LocalPlayer'");
}
}
/**
* 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));
parserContext.setWorld(actor.getWorld());
parserContext.setSession(worldEdit.getSessionManager().get(actor));
return worldEdit.getPatternRegistry().parseFromInput(context.next(), parserContext);
}
/**
* 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));
parserContext.setWorld(actor.getWorld());
parserContext.setSession(worldEdit.getSessionManager().get(actor));
return worldEdit.getMaskRegistry().parseFromInput(context.next(), parserContext);
}
/**
* 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());
}
}

View File

@ -0,0 +1,151 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
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(MaxRadiusException e) throws CommandException {
throw new CommandException("Maximum radius: " + 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());
}
}

View File

@ -0,0 +1,64 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import java.util.Collection;
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<Character> getValueFlags();
/**
* Execute the command.
*
* @param context the user input
* @throws CommandException thrown on any sort of command exception
*/
void call(CommandContext context) throws CommandException;
/**
* Get a list of suggestions.
*
* @param context the user input
* @return a list of suggestions
* @throws CommandException
*/
Collection<String> getSuggestions(CommandContext context) throws CommandException;
/**
* Get an object describing this command.
*
* @return the command description
*/
Description getDescription();
}

View File

@ -0,0 +1,79 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
/**
* 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();
}
}

View File

@ -0,0 +1,70 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<Parameter> getParameters();
/**
* Get a short one-line description of this command.
*
* @return a description, or null if no description is available
*/
String getDescription();
/**
* 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.
*
* <p>A usage string may look like
* <code>[-w &lt;world&gt;] &lt;var1&gt; &lt;var2&gt;</code>.</p>
*
* @return a usage string
*/
String getUsage();
/**
* Get a list of permissions that the player may have to have permission.
*
* <p>Permission data may or may not be available. This is only useful as a
* potential hint.</p>
*
* @return the list of permissions
*/
List<String> getPermissions();
}

View File

@ -0,0 +1,113 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
import java.util.Collection;
/**
* Executes a command based on user input.
*/
public interface Dispatcher {
/**
* 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 register(CommandCallable callable, String... alias);
/**
* Get a list of command registrations.
*
* <p>The returned collection cannot be modified.</p>
*
* @return a list of registrations
*/
Collection<CommandMapping> getCommands();
/**
* Get a list of primary aliases.
*
* <p>The returned collection cannot be modified.</p>
*
* @return a list of aliases
*/
Collection<String> getPrimaryAliases();
/**
* Get a list of all the command aliases.
*
* <p>A command may have more than one alias assigned to it. The returned
* collection cannot be modified.</p>
*
* @return a list of aliases
*/
Collection<String> getAllAliases();
/**
* Get the {@link CommandCallable} associated with an alias.
*
* @param alias the alias
* @return the command mapping
*/
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);
/**
* Execute the correct command based on the input.
*
* @param arguments the arguments
* @param locals the locals
* @return the called command, or null if there was no command found
* @throws CommandException thrown on a command error
*/
CommandMapping call(String arguments, CommandLocals locals) throws CommandException;
/**
* Execute the correct command based on the input.
*
* @param arguments the arguments
* @param locals the locals
* @return the called command, or null if there was no command found
* @throws CommandException thrown on a command error
*/
CommandMapping call(String[] arguments, CommandLocals locals) throws CommandException;
/**
* Get a list of suggestions based on input.
*
* @param arguments the arguments entered up to this point
* @return a list of suggestions
* @throws CommandException thrown if there was a parsing error
*/
Collection<String> getSuggestions(String arguments) throws CommandException;
}

View File

@ -0,0 +1,49 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@ -0,0 +1,31 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@ -0,0 +1,66 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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();
}

View File

@ -0,0 +1,129 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import java.util.Collections;
import java.util.List;
/**
* A simple implementation of {@link Description} which has setters.
*/
public class SimpleDescription implements Description {
private List<Parameter> parameters = Collections.emptyList();
private List<String> permissions = Collections.emptyList();
private String description;
private String help;
private String overrideUsage;
@Override
public List<Parameter> getParameters() {
return parameters;
}
/**
* Set the list of parameters.
*
* @param parameters the list of parameters
* @see #getParameters()
*/
public void setParameters(List<Parameter> parameters) {
this.parameters = Collections.unmodifiableList(parameters);
}
@Override
public String getDescription() {
return description;
}
/**
* Set the description of the command.
*
* @param description the description
* @see #getDescription()
*/
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<String> getPermissions() {
return permissions;
}
/**
* Set the permissions of this command.
*
* @param permissions the permissions
*/
public void setPermissions(List<String> 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();
}
}

View File

@ -0,0 +1,122 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandLocals;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
import java.util.*;
/**
* A simple implementation of {@link Dispatcher}.
*/
public class SimpleDispatcher implements Dispatcher {
private final Map<String, CommandMapping> commands = new HashMap<String, CommandMapping>();
@Override
public void register(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 Collection<CommandMapping> getCommands() {
return Collections.unmodifiableCollection(commands.values());
}
@Override
public Set<String> getAllAliases() {
return Collections.unmodifiableSet(commands.keySet());
}
@Override
public Set<String> getPrimaryAliases() {
Set<String> aliases = new HashSet<String>();
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 CommandMapping call(String arguments, CommandLocals locals) throws CommandException {
return call(CommandContext.split(arguments), locals);
}
@Override
public CommandMapping call(String[] arguments, CommandLocals locals) throws CommandException {
CommandContext dummyContext = new CommandContext(arguments);
CommandMapping mapping = get(dummyContext.getCommand());
if (mapping != null) {
CommandCallable c = mapping.getCallable();
CommandContext context =
new CommandContext(arguments, c.getValueFlags(), false, locals);
try {
c.call(context);
} catch (CommandException e) {
e.prependStack(context.getCommand());
throw e;
} catch (Throwable t) {
throw new WrappedCommandException(t);
}
}
return mapping;
}
@Override
public Collection<String> getSuggestions(String arguments) throws CommandException {
CommandContext dummyContext = new CommandContext(arguments);
CommandMapping mapping = get(dummyContext.getCommand());
if (mapping != null) {
CommandCallable c = mapping.getCallable();
CommandContext context =
new CommandContext(arguments, c.getValueFlags(), true);
return c.getSuggestions(context);
}
return new ArrayList<String>();
}
}

View File

@ -0,0 +1,96 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import java.util.*;
/**
* A combination {@link Dispatcher} and {@link CommandCallable} that is backed by
* a {@link SimpleDispatcher}.
*
* <p>The primary use of this is to make nested commands.</p>
*/
public class SimpleDispatcherCommand extends SimpleDispatcher implements CommandCallable {
private final SimpleDescription description = new SimpleDescription();
@Override
public Set<Character> getValueFlags() {
return Collections.emptySet();
}
@Override
public void call(CommandContext context) throws CommandException {
if (context.argsLength() >= 1) {
super.call(context.getRemainingString(0), context.getLocals());
} else {
Set<String> aliases = getPrimaryAliases();
if (aliases.size() == 0) {
throw new InvalidUsageException(
"This command is supposed to have sub-commands, " +
"but it has no sub-commands.",
getDescription());
}
StringBuilder builder = new StringBuilder();
for (String alias : getPrimaryAliases()) {
builder.append("\n- ").append(alias);
}
if (aliases.size() == 1) {
builder.append(" (there is only one)");
}
throw new InvalidUsageException(
"Select one of these subcommand(s):" + builder.toString(),
getDescription());
}
}
@Override
public SimpleDescription getDescription() {
return description;
}
@Override
public Collection<String> getSuggestions(CommandContext context) throws CommandException {
if (context.argsLength() == 0) {
return super.getAllAliases();
} else if (context.argsLength() == 1 &&
context.getSuggestionContext().forLastValue()) {
String prefix = context.getString(0).toLowerCase();
List<String> suggestions = new ArrayList<String>();
for (String alias : super.getAllAliases()) {
if (alias.startsWith(prefix)) {
suggestions.add(alias);
}
}
return suggestions;
}
return super.getSuggestions(
context.argsLength() > 1 ? context.getRemainingString(1) : "");
}
}

View File

@ -0,0 +1,131 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -0,0 +1,42 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,255 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>Handles both the object and primitive types.</p>
*/
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()));
}
}
}
}
}
}

View File

@ -0,0 +1,50 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@ -0,0 +1,46 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@ -0,0 +1,44 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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 <code>/command -f</code>.
*
* <p>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.</p>
*/
@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();
}

View File

@ -0,0 +1,41 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>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.</p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Text {
}

View File

@ -0,0 +1,45 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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 "";
}

View File

@ -0,0 +1,84 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>A command graph may have multiple commands, and multiple sub-commands below that,
* and possibly below that.</p>
*/
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#build(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();
}
}

View File

@ -0,0 +1,142 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.SimpleDispatcherCommand;
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.
*
* <p>This can only be used on {@link DispatcherNode}s returned by
* {@link #group(String...)}.</p>
*
* @param description the description
* @return this object
*/
public DispatcherNode describe(String description) {
if (dispatcher instanceof SimpleDispatcherCommand) {
((SimpleDispatcherCommand) 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.register(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#register(com.sk89q.worldedit.util.command.Dispatcher, Object)
*/
public DispatcherNode build(Object object) {
ParametricBuilder builder = graph.getBuilder();
if (builder == null) {
throw new RuntimeException("No ParametricBuilder set");
}
builder.register(getDispatcher(), object);
return this;
}
/**
* Create a new command that will contain sub-commands.
*
* <p>The object returned by this method can be used to add sub-commands. To
* return to this "parent" context, use {@link DispatcherNode#graph()}.</p>
*
* @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) {
SimpleDispatcherCommand command = new SimpleDispatcherCommand();
getDispatcher().register(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;
}
}

View File

@ -0,0 +1,36 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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) {
}
}

View File

@ -0,0 +1,78 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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();
}

View File

@ -0,0 +1,92 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>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...)}.</p>
*
* @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.
*
* <p>This method must return -1 for binding behavior types that are not
* {@link BindingBehavior#CONSUMES}.</p>
*
* @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<String> getSuggestions(ParameterData parameter, String prefix);
}

View File

@ -0,0 +1,52 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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}.
*
* <p>Bindings that exhibit this behavior must be defined as a {@link Switch}
* by commands utilizing the given binding.</p>
*/
INDETERMINATE,
/**
* Never consumes from a {@link ArgumentStack}.
*
* <p>Bindings that exhibit this behavior generate objects from other sources,
* such as from a {@link CommandLocals}. These are "magic" bindings that inject
* variables.</p>
*/
PROVIDES;
}

View File

@ -0,0 +1,224 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>Methods must have the following and only the following parameters:</p>
*
* <ul>
* <li>A {@link ArgumentStack}</li>
* <li>A {@link Annotation} <strong>if there is a classifier set</strong></li>
* <li>A {@link Annotation}[]
* <strong>if there {@link BindingMatch#provideModifiers()} is true</strong></li>
* </ul>
*
* <p>Methods may throw any exception. Exceptions may be converted using a
* {@link ExceptionConverter} registered with the {@link ParametricBuilder}.</p>
*/
public class BindingHelper implements Binding {
private final List<BoundMethod> bindings;
private final Type[] types;
/**
* Create a new instance.
*/
public BindingHelper() {
List<BoundMethod> bindings = new ArrayList<BoundMethod>();
List<Type> types = new ArrayList<Type>();
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<Object> args = new ArrayList<Object>();
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 ParameterException(e.getCause());
}
}
@Override
public List<String> getSuggestions(ParameterData parameter, String prefix) {
return new ArrayList<String>();
}
private static class BoundMethod implements Comparable<BoundMethod> {
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;
}
}
}
}

View File

@ -0,0 +1,71 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@ -0,0 +1,178 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>The marked position initially starts at 0.</p>
*/
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.
*
* <p>The marked position initially starts at 0.</p>
*
* @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.
*
* <p>The marked position initially starts at 0.</p>
*
* @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.
*
* <p>The marked position initially starts at 0.</p>
*
* @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;
}
}

View File

@ -0,0 +1,52 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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}.
*
* <p>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}.</p>
*
* <p>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.</p>
*/
public interface ExceptionConverter {
/**
* Attempt to convert the given throwable into a {@link CommandException}.
*
* <p>If the exception is not recognized, then nothing should be thrown.</p>
*
* @param t the throwable
* @throws CommandException a command exception
*/
void convert(Throwable t) throws CommandException;
}

View File

@ -0,0 +1,109 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>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.</p>
*/
public abstract class ExceptionConverterHelper implements ExceptionConverter {
private final List<ExceptionHandler> handlers;
@SuppressWarnings("unchecked")
public ExceptionConverterHelper() {
List<ExceptionHandler> handlers = new ArrayList<ExceptionHandler>();
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<ExceptionHandler> {
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;
}
}
}
}

View File

@ -0,0 +1,34 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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 {
}

View File

@ -0,0 +1,82 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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}.
*
* <p>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)}).</p>
*
* @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;
}

View File

@ -0,0 +1,58 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>An example use of an {@link InvokeHandler} would be to verify permissions
* added by the {@link CommandPermissions} annotation.</p>
*
* <p>For simple {@link InvokeHandler}, an object can implement both this
* interface and {@link InvokeHandler}.</p>
*
* @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);
}

View File

@ -0,0 +1,97 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
return;
}
}
if (!hasUserParameters) {
description.overrideUsage("(unknown usage information)");
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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 {};
}

View File

@ -0,0 +1,194 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>The type is normally that is used to determine which binding is used
* for a particular method's parameter.</p>
*
* @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.
*
* <p>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}.</p>
*
* @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.
*
* <p>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.</p>
*
* @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());
}
}
}

View File

@ -0,0 +1,45 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,205 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.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;
/**
* 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<Type, Binding> bindings = new HashMap<Type, Binding>();
private final Paranamer paranamer = new CachingParanamer();
private final List<InvokeListener> invokeListeners = new ArrayList<InvokeListener>();
private final List<ExceptionConverter> exceptionConverters = new ArrayList<ExceptionConverter>();
/**
* Create a new builder.
*
* <p>This method will install {@link PrimitiveBindings} and
* {@link StandardBindings} and default bindings.</p>
*/
public ParametricBuilder() {
addBinding(new PrimitiveBindings());
addBinding(new StandardBindings());
}
/**
* Add a binding for a given type or classifier (annotation).
*
* <p>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...)}.</p>
*
* <p>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 <code>@MyArg</code> annotation can be assigned to a {@link String}
* parameter, which will cause the {@link Builder} to consult the {@link Binding}
* associated with <code>@MyArg</code> rather than with the binding for
* the {@link String} type.</p>
*
* @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.
*
* <p>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)}.</p>
*
* <p>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.</p>
*
* @param listener the listener
* @see InvokeHandler the handler
*/
public void attach(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.
*
* <p>Exception converters are called in order that they are registered.</p>
*
* @param converter the converter
* @see ExceptionConverter for an explanation
*/
public void attach(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 register(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.register(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<Type, Binding> getBindings() {
return bindings;
}
/**
* Get a list of invocation listeners.
*
* @return a list of invocation listeners
*/
List<InvokeListener> getInvokeListeners() {
return invokeListeners;
}
/**
* Get the list of exception converters.
*
* @return a list of exception converters
*/
List<ExceptionConverter> getExceptionConverters() {
return exceptionConverters;
}
}

View File

@ -0,0 +1,538 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
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.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
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.minecraft.util.commands.SuggestionContext;
import com.sk89q.minecraft.util.commands.WrappedCommandException;
import com.sk89q.worldedit.util.command.CommandCallable;
import com.sk89q.worldedit.util.command.InvalidUsageException;
import com.sk89q.worldedit.util.command.MissingParameterException;
import com.sk89q.worldedit.util.command.Parameter;
import com.sk89q.worldedit.util.command.SimpleDescription;
import com.sk89q.worldedit.util.command.UnconsumedParameterException;
import com.sk89q.worldedit.util.command.binding.Switch;
/**
* 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<Character> valueFlags = new HashSet<Character>();
private final SimpleDescription description = new SimpleDescription();
/**
* 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<Parameter> userParameters = new ArrayList<Parameter>();
// 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 Nullable) {
parameter.setOptional(true);
} 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);
}
@Override
public void call(CommandContext context) throws CommandException {
Object[] args = new Object[parameters.length];
ContextArgumentStack arguments = new ContextArgumentStack(context);
ParameterData parameter = null;
try {
// preProcess handlers
List<InvokeHandler> handlers = new ArrayList<InvokeHandler>();
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) {
if (e.getCause() != null) {
for (ExceptionConverter converter : builder.getExceptionConverters()) {
converter.convert(e.getCause());
}
}
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);
}
}
@Override
public List<String> getSuggestions(CommandContext context) throws CommandException {
ContextArgumentStack scoped = new ContextArgumentStack(context);
SuggestionContext suggestable = context.getSuggestionContext();
// For /command -f |
// For /command -f flag|
if (suggestable.forFlag()) {
for (int i = 0; i < parameters.length; i++) {
ParameterData parameter = parameters[i];
if (parameter.getFlag() == suggestable.getFlag()) {
String prefix = context.getFlag(parameter.getFlag());
if (prefix == null) {
prefix = "";
}
return parameter.getBinding().getSuggestions(parameter, prefix);
}
}
// This should not happen
return new ArrayList<String>();
}
int consumerIndex = 0;
ParameterData lastConsumer = null;
String lastConsumed = null;
for (int i = 0; i < parameters.length; i++) {
ParameterData parameter = parameters[i];
if (parameter.getFlag() != null) {
continue; // We already handled flags
}
try {
scoped.mark();
parameter.getBinding().bind(parameter, scoped, true);
if (scoped.wasConsumed()) {
lastConsumer = parameter;
lastConsumed = scoped.getConsumed();
consumerIndex++;
}
} catch (MissingParameterException e) {
// For /command value1 |value2
// For /command |value1 value2
if (suggestable.forHangingValue()) {
return parameter.getBinding().getSuggestions(parameter, "");
} else {
// For /command value1| value2
if (lastConsumer != null) {
return lastConsumer.getBinding()
.getSuggestions(lastConsumer, lastConsumed);
// For /command| value1 value2
// This should never occur
} else {
throw new RuntimeException("Invalid suggestion context");
}
}
} catch (ParameterException e) {
if (suggestable.forHangingValue()) {
String name = getDescription().getParameters()
.get(consumerIndex).getName();
throw new InvalidUsageException("For parameter '" + name + "': "
+ e.getMessage(), getDescription());
} else {
return parameter.getBinding().getSuggestions(parameter, "");
}
}
}
// For /command value1 value2 |
if (suggestable.forHangingValue()) {
// There's nothing that we can suggest because there's no more parameters
// to add on, and we can't change the previous parameter
return new ArrayList<String>();
} else {
// For /command value1 value2|
if (lastConsumer != null) {
return lastConsumer.getBinding()
.getSuggestions(lastConsumer, lastConsumed);
// This should never occur
} else {
throw new RuntimeException("Invalid suggestion context");
}
}
}
@Override
public Set<Character> getValueFlags() {
return valueFlags;
}
@Override
public SimpleDescription getDescription() {
return description;
}
/**
* 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
* @param parameters the list of parameters
*/
private String getUnusedFlags(CommandContext context) {
Set<Character> unusedFlags = null;
for (char flag : context.getFlags()) {
boolean found = false;
for (int i = 0; i < parameters.length; i++) {
Character paramFlag = parameters[i].getFlag();
if (paramFlag != null && flag == paramFlag) {
found = true;
break;
}
}
if (!found) {
if (unusedFlags == null) {
unusedFlags = new HashSet<Character>();
}
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;
}
}
}
}

View File

@ -0,0 +1,46 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,67 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.command.parametric;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import java.lang.reflect.Method;
/**
* A handler for the {@link CommandPermissions} annotation.
*/
public abstract class PermissionsHandler 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 {
CommandPermissions annotation = method.getAnnotation(CommandPermissions.class);
if (annotation != null) {
for (String perm : annotation.value()) {
if (hasPermission(context, perm)) {
return;
}
}
throw new CommandPermissionsException();
}
}
@Override
public void preInvoke(Object object, Method method, ParameterData[] parameters,
Object[] args, CommandContext context) throws CommandException {
}
@Override
public void postInvoke(Object object, Method method, ParameterData[] parameters,
Object[] args, CommandContext context) throws CommandException {
}
protected abstract boolean hasPermission(CommandContext context, String permission);
}

View File

@ -0,0 +1,128 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}