();
+
+ /**
+ * Create a new builder.
+ *
+ * This method will install {@link PrimitiveBindings} and
+ * {@link StandardBindings} and default bindings.
+ */
+ public ParametricBuilder() {
+ addBinding(new PrimitiveBindings());
+ addBinding(new StandardBindings());
+ }
+
+ /**
+ * Add a binding for a given type or classifier (annotation).
+ *
+ * Whenever a method parameter is encountered, a binding must be found for it
+ * so that it can be called later to consume the stack of arguments provided by
+ * the user and return an object that is later passed to
+ * {@link Method#invoke(Object, Object...)}.
+ *
+ * Normally, a {@link Type} is used to discern between different bindings, but
+ * if this is not specific enough, an annotation can be defined and used. This
+ * makes it a "classifier" and it will take precedence over the base type. For
+ * example, even if there is a binding that handles {@link String} parameters,
+ * a special @MyArg
annotation can be assigned to a {@link String}
+ * parameter, which will cause the {@link Builder} to consult the {@link Binding}
+ * associated with @MyArg
rather than with the binding for
+ * the {@link String} type.
+ *
+ * @param binding the binding
+ * @param type a list of types (if specified) to override the binding's types
+ */
+ public void addBinding(Binding binding, Type... type) {
+ if (type == null || type.length == 0) {
+ type = binding.getTypes();
+ }
+
+ for (Type t : type) {
+ bindings.put(t, binding);
+ }
+ }
+
+ /**
+ * Attach an invocation listener.
+ *
+ * Invocation handlers are called in order that their listeners are
+ * registered with a {@link ParametricBuilder}. It is not guaranteed that
+ * a listener may be called, in the case of a {@link CommandException} being
+ * thrown at any time before the appropriate listener or handler is called.
+ * It is possible for a
+ * {@link InvokeHandler#preInvoke(Object, Method, ParameterData[], Object[], CommandContext)} to
+ * be called for a invocation handler, but not the associated
+ * {@link InvokeHandler#postInvoke(Object, Method, ParameterData[], Object[], CommandContext)}.
+ *
+ * An example of an invocation listener is one to handle
+ * {@link CommandPermissions}, by first checking to see if permission is available
+ * in a {@link InvokeHandler#preInvoke(Object, Method, ParameterData[], Object[], CommandContext)}
+ * call. If permission is not found, then an appropriate {@link CommandException}
+ * can be thrown to cease invocation.
+ *
+ * @param listener the listener
+ * @see InvokeHandler the handler
+ */
+ public void 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.
+ *
+ * Exception converters are called in order that they are registered.
+ *
+ * @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 getBindings() {
+ return bindings;
+ }
+
+ /**
+ * Get a list of invocation listeners.
+ *
+ * @return a list of invocation listeners
+ */
+ List getInvokeListeners() {
+ return invokeListeners;
+ }
+
+ /**
+ * Get the list of exception converters.
+ *
+ * @return a list of exception converters
+ */
+ List getExceptionConverters() {
+ return exceptionConverters;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/rebar/command/parametric/ParametricCallable.java b/src/main/java/com/sk89q/rebar/command/parametric/ParametricCallable.java
new file mode 100644
index 000000000..47d76ed4c
--- /dev/null
+++ b/src/main/java/com/sk89q/rebar/command/parametric/ParametricCallable.java
@@ -0,0 +1,537 @@
+// $Id$
+/*
+ * This file is a part of WorldEdit.
+ * Copyright (c) sk89q
+ * Copyright (c) the 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
+ * (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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see .
+*/
+
+package com.sk89q.rebar.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.rebar.command.CommandCallable;
+import com.sk89q.rebar.command.InvalidUsageException;
+import com.sk89q.rebar.command.MissingParameterException;
+import com.sk89q.rebar.command.Parameter;
+import com.sk89q.rebar.command.SimpleDescription;
+import com.sk89q.rebar.command.UnconsumedParameterException;
+import com.sk89q.rebar.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 valueFlags = new HashSet();
+ 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 userParameters = new ArrayList();
+
+ // This helps keep tracks of @Nullables that appear in the middle of a list
+ // of parameters
+ int numOptional = 0;
+
+ // Set permission hint
+ CommandPermissions permHint = method.getAnnotation(CommandPermissions.class);
+ if (permHint != null) {
+ description.setPermissions(Arrays.asList(permHint.value()));
+ }
+
+ // Go through each parameter
+ for (int i = 0; i < types.length; i++) {
+ Type type = types[i];
+
+ ParameterData parameter = new ParameterData();
+ parameter.setType(type);
+ parameter.setModifiers(annotations[i]);
+
+ // Search for annotations
+ for (Annotation annotation : annotations[i]) {
+ if (annotation instanceof Switch) {
+ parameter.setFlag(((Switch) annotation).value(), type != boolean.class);
+ } else if (annotation instanceof 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 handlers = new ArrayList();
+ for (InvokeListener listener : builder.getInvokeListeners()) {
+ InvokeHandler handler = listener.createInvokeHandler();
+ handlers.add(handler);
+ handler.preProcess(object, method, parameters, context);
+ }
+
+ // Collect parameters
+ for (int i = 0; i < parameters.length; i++) {
+ parameter = parameters[i];
+
+ if (mayConsumeArguments(i, arguments)) {
+ // Parse the user input into a method argument
+ ArgumentStack usedArguments = getScopedContext(parameter, arguments);
+
+ try {
+ args[i] = parameter.getBinding().bind(parameter, usedArguments, false);
+ } catch (MissingParameterException e) {
+ // Not optional? Then we can't execute this command
+ if (!parameter.isOptional()) {
+ throw e;
+ }
+
+ args[i] = getDefaultValue(i, arguments);
+ }
+ } else {
+ args[i] = getDefaultValue(i, arguments);
+ }
+ }
+
+ // Check for unused arguments
+ checkUnconsumed(arguments);
+
+ // preInvoke handlers
+ for (InvokeHandler handler : handlers) {
+ handler.preInvoke(object, method, parameters, args, context);
+ }
+
+ // Execute!
+ method.invoke(object, args);
+
+ // postInvoke handlers
+ for (InvokeHandler handler : handlers) {
+ handler.postInvoke(handler, method, parameters, args, context);
+ }
+ } catch (MissingParameterException e) {
+ throw new InvalidUsageException(
+ "Too few parameters!", getDescription());
+ } catch (UnconsumedParameterException e) {
+ throw new InvalidUsageException(
+ "Too many parameters! Unused parameters: "
+ + e.getUnconsumed(), getDescription());
+ } catch (ParameterException e) {
+ 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 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();
+ }
+
+ 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();
+ } 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 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 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();
+ }
+ unusedFlags.add(flag);
+ }
+ }
+
+ if (unusedFlags != null) {
+ StringBuilder builder = new StringBuilder();
+ for (Character flag : unusedFlags) {
+ builder.append("-").append(flag).append(" ");
+ }
+
+ return builder.toString().trim();
+ }
+
+ return null;
+ }
+
+ /**
+ * Generate a name for a parameter.
+ *
+ * @param type the type
+ * @param classifier the classifier
+ * @param index the index
+ * @return a generated name
+ */
+ private static String generateName(Type type, Annotation classifier, int index) {
+ if (classifier != null) {
+ return classifier.annotationType().getSimpleName().toLowerCase();
+ } else {
+ if (type instanceof Class>) {
+ return ((Class>) type).getSimpleName().toLowerCase();
+ } else {
+ return "unknown" + index;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/sk89q/rebar/command/parametric/ParametricException.java b/src/main/java/com/sk89q/rebar/command/parametric/ParametricException.java
new file mode 100644
index 000000000..24176200c
--- /dev/null
+++ b/src/main/java/com/sk89q/rebar/command/parametric/ParametricException.java
@@ -0,0 +1,27 @@
+package com.sk89q.rebar.command.parametric;
+
+/**
+ * Thrown if the {@link ParametricBuilder} can't build commands from
+ * an object for whatever reason.
+ */
+public class ParametricException extends RuntimeException {
+
+ private static final long serialVersionUID = -5426219576099680971L;
+
+ public ParametricException() {
+ super();
+ }
+
+ public ParametricException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ParametricException(String message) {
+ super(message);
+ }
+
+ public ParametricException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/com/sk89q/rebar/command/parametric/PermissionsHandler.java b/src/main/java/com/sk89q/rebar/command/parametric/PermissionsHandler.java
new file mode 100644
index 000000000..585799bf3
--- /dev/null
+++ b/src/main/java/com/sk89q/rebar/command/parametric/PermissionsHandler.java
@@ -0,0 +1,66 @@
+// $Id$
+/*
+ * This file is a part of WorldEdit.
+ * Copyright (c) sk89q
+ * Copyright (c) the 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
+ * (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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see .
+*/
+
+package com.sk89q.rebar.command.parametric;
+
+import java.lang.reflect.Method;
+
+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;
+
+/**
+ * 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);
+
+}
diff --git a/src/main/java/com/sk89q/rebar/command/parametric/StringArgumentStack.java b/src/main/java/com/sk89q/rebar/command/parametric/StringArgumentStack.java
new file mode 100644
index 000000000..b81526790
--- /dev/null
+++ b/src/main/java/com/sk89q/rebar/command/parametric/StringArgumentStack.java
@@ -0,0 +1,127 @@
+// $Id$
+/*
+ * This file is a part of WorldEdit.
+ * Copyright (c) sk89q
+ * Copyright (c) the 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
+ * (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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see .
+*/
+
+package com.sk89q.rebar.command.parametric;
+
+import com.sk89q.minecraft.util.commands.CommandContext;
+import com.sk89q.rebar.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;
+ }
+
+}