From ff67f6343f5dfad1290b1eaea9555c90cffce208 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Mon, 1 Apr 2019 21:35:55 +1100 Subject: [PATCH] Binding improvements WIP towards deprecating parsers and unifying the command bindings Allow registering dynamic bindings - Supports nesting bindings --- .../com/boydti/fawe/command/FaweBinding.java | 5 +- .../fawe/command/FawePrimitiveBinding.java | 2 +- .../boydti/fawe/command/PatternBinding.java | 2 - .../boydti/fawe/object/brush/CircleBrush.java | 2 +- .../general/plot/FaweSchematicHandler.java | 1 - .../sk89q/jnbt/CompressedSchematicTag.java | 1 - .../internal/command/WorldEditBinding.java | 2 +- .../command/binding/PrimitiveBindings.java | 89 +---- .../command/binding/StandardBindings.java | 51 ++- .../command/parametric/BindingHelper.java | 121 +++++-- .../util/command/parametric/BindingMap.java | 304 ++++++++++++++++++ .../util/command/parametric/BindingMatch.java | 7 +- .../FunctionParametricCallable.java | 6 +- .../command/parametric/ParametricBuilder.java | 53 +-- .../parametric/ParametricCallable.java | 4 +- 15 files changed, 487 insertions(+), 163 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMap.java diff --git a/worldedit-core/src/main/java/com/boydti/fawe/command/FaweBinding.java b/worldedit-core/src/main/java/com/boydti/fawe/command/FaweBinding.java index 73b8c7a25..2973feb5e 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/command/FaweBinding.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/command/FaweBinding.java @@ -1,13 +1,12 @@ package com.boydti.fawe.command; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.internal.command.WorldEditBinding; +import com.sk89q.worldedit.util.command.parametric.BindingHelper; -public class FaweBinding extends WorldEditBinding { +public class FaweBinding extends BindingHelper { private final WorldEdit worldEdit; public FaweBinding(WorldEdit worldEdit) { - super(worldEdit); this.worldEdit = worldEdit; } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/command/FawePrimitiveBinding.java b/worldedit-core/src/main/java/com/boydti/fawe/command/FawePrimitiveBinding.java index eacaf53cf..0a2c76534 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/command/FawePrimitiveBinding.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/command/FawePrimitiveBinding.java @@ -42,7 +42,7 @@ import java.net.URI; import java.net.URL; import javax.annotation.Nullable; -public class FawePrimitiveBinding extends BindingHelper { +public class FawePrimitiveBinding { @BindingMatch(type = {Long.class, long.class}, behavior = BindingBehavior.CONSUMES, consumedCount = 1, diff --git a/worldedit-core/src/main/java/com/boydti/fawe/command/PatternBinding.java b/worldedit-core/src/main/java/com/boydti/fawe/command/PatternBinding.java index 078042d21..fb05ed40a 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/command/PatternBinding.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/command/PatternBinding.java @@ -4,8 +4,6 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.util.command.parametric.ParameterData; import com.sk89q.worldedit.world.block.BlockTypes; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/CircleBrush.java b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/CircleBrush.java index cea101db8..b7160c710 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/brush/CircleBrush.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/brush/CircleBrush.java @@ -35,4 +35,4 @@ public class CircleBrush implements Brush { Vector3 random = affine.apply(normal); return random.cross(normal).normalize(); } -} +} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/boydti/fawe/regions/general/plot/FaweSchematicHandler.java b/worldedit-core/src/main/java/com/boydti/fawe/regions/general/plot/FaweSchematicHandler.java index 12426beea..67c103c37 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/regions/general/plot/FaweSchematicHandler.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/regions/general/plot/FaweSchematicHandler.java @@ -98,7 +98,6 @@ public class FaweSchematicHandler extends SchematicHandler { if (tag instanceof CompressedCompoundTag) { CompressedCompoundTag cTag = (CompressedCompoundTag) tag; if (cTag instanceof CompressedSchematicTag) { - System.out.println("Write directly"); Clipboard clipboard = (Clipboard) cTag.getSource(); try (OutputStream stream = new FileOutputStream(tmp); NBTOutputStream output = new NBTOutputStream(new BufferedOutputStream(new PGZIPOutputStream(stream)))) { new SpongeSchematicWriter(output).write(clipboard); diff --git a/worldedit-core/src/main/java/com/sk89q/jnbt/CompressedSchematicTag.java b/worldedit-core/src/main/java/com/sk89q/jnbt/CompressedSchematicTag.java index bfdeb68ec..c5f43e4d6 100644 --- a/worldedit-core/src/main/java/com/sk89q/jnbt/CompressedSchematicTag.java +++ b/worldedit-core/src/main/java/com/sk89q/jnbt/CompressedSchematicTag.java @@ -19,7 +19,6 @@ public class CompressedSchematicTag extends CompressedCompoundTag { @Override public DataInputStream adapt(Clipboard src) throws IOException { - System.out.println("Decompress"); FastByteArrayOutputStream blocksOut = new FastByteArrayOutputStream(); try (LZ4BlockOutputStream lz4out = new LZ4BlockOutputStream(blocksOut)) { NBTOutputStream nbtOut = new NBTOutputStream(lz4out); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java index c3a8fe906..ead5cd02b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/command/WorldEditBinding.java @@ -64,7 +64,7 @@ import java.util.List; /** * Binds standard WorldEdit classes such as {@link Player} and {@link LocalSession}. */ -public class WorldEditBinding extends BindingHelper { +public class WorldEditBinding { private final WorldEdit worldEdit; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/binding/PrimitiveBindings.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/binding/PrimitiveBindings.java index 3a4d78edf..9085cf186 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/binding/PrimitiveBindings.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/binding/PrimitiveBindings.java @@ -32,12 +32,14 @@ import java.lang.annotation.Annotation; import javax.annotation.Nullable; +import static com.sk89q.worldedit.util.command.parametric.BindingHelper.validate; + /** * Handles basic Java types such as {@link String}s, {@link Byte}s, etc. * *

Handles both the object and primitive types.

*/ -public final class PrimitiveBindings extends BindingHelper { +public final class PrimitiveBindings { /** * Gets a type from a {@link ArgumentStack}. @@ -206,89 +208,4 @@ public final class PrimitiveBindings extends BindingHelper { } return null; } - - /** - * Validate a number value using relevant modifiers. - * - * @param number the number - * @param modifiers the list of modifiers to scan - * @throws ParameterException on a validation error - */ - private static void validate(double number, Annotation[] modifiers) - throws ParameterException { - for (Annotation modifier : modifiers) { - if (modifier instanceof Range) { - Range range = (Range) modifier; - if (number < range.min()) { - throw new ParameterException( - String.format( - "A valid value is greater than or equal to %s " + - "(you entered %s)", range.min(), number)); - } else if (number > range.max()) { - throw new ParameterException( - String.format( - "A valid value is less than or equal to %s " + - "(you entered %s)", range.max(), number)); - } - } - } - } - - /** - * Validate a number value using relevant modifiers. - * - * @param number the number - * @param modifiers the list of modifiers to scan - * @throws ParameterException on a validation error - */ - private static void validate(int number, Annotation[] modifiers) - throws ParameterException { - for (Annotation modifier : modifiers) { - if (modifier instanceof Range) { - Range range = (Range) modifier; - if (number < range.min()) { - throw new ParameterException( - String.format( - "A valid value is greater than or equal to %s " + - "(you entered %s)", range.min(), number)); - } else if (number > range.max()) { - throw new ParameterException( - String.format( - "A valid value is less than or equal to %s " + - "(you entered %s)", range.max(), number)); - } - } - } - } - - /** - * Validate a string value using relevant modifiers. - * - * @param string the string - * @param modifiers the list of modifiers to scan - * @throws ParameterException on a validation error - */ - private static void validate(String string, Annotation[] modifiers) - throws ParameterException { - if (string == null) { - return; - } - - for (Annotation modifier : modifiers) { - if (modifier instanceof Validate) { - Validate validate = (Validate) modifier; - - if (!validate.regex().isEmpty()) { - if (!string.matches(validate.regex())) { - throw new ParameterException( - String.format( - "The given text doesn't match the right " + - "format (technically speaking, the 'format' is %s)", - validate.regex())); - } - } - } - } - } - } \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/binding/StandardBindings.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/binding/StandardBindings.java index a5a06e5fe..1945c54d2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/binding/StandardBindings.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/binding/StandardBindings.java @@ -24,11 +24,17 @@ import com.sk89q.worldedit.util.command.parametric.ArgumentStack; 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.ParameterException; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static com.sk89q.worldedit.util.command.parametric.BindingHelper.validate; /** * Standard bindings that should be available to most configurations. */ -public final class StandardBindings extends BindingHelper { +public final class StandardBindings { /** * Gets a {@link CommandContext} from a {@link ArgumentStack}. @@ -42,5 +48,48 @@ public final class StandardBindings extends BindingHelper { context.markConsumed(); // Consume entire stack return context.getContext(); } + + @BindingMatch( + type = Annotation[].class, + behavior = BindingBehavior.PROVIDES, + consumedCount = 0, + provideModifiers = true, + provideType = true) + public Annotation[] getModifiers(ArgumentStack context, Annotation[] modifiers, Type type) throws ParameterException { + return modifiers; + } + + @BindingMatch( + type = Type.class, + behavior = BindingBehavior.PROVIDES, + consumedCount = 0, + provideModifiers = true, + provideType = true) + public Type getType(ArgumentStack context, Annotation[] modifiers, Type type) throws ParameterException { + return type; + } + + @BindingMatch( + type = Enum.class, + behavior = BindingBehavior.CONSUMES, + consumedCount = 1, + provideModifiers = true, + provideType = true) + public Enum getEnum(ArgumentStack context, Annotation[] modifiers, Type type) throws ParameterException { + String input = context.next(); + Enum value; + try { + value = Enum.valueOf((Class) type, input); + } catch (IllegalArgumentException ignore) { + try { + value = Enum.valueOf((Class) type, input.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new ParameterException("Invalid input " + input + " for type " + type); + } + } + validate(value.ordinal(), modifiers); + validate(input, modifiers); + return value; + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingHelper.java index e5024f77b..1d45d0b32 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingHelper.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingHelper.java @@ -22,6 +22,7 @@ package com.sk89q.worldedit.util.command.parametric; import com.boydti.fawe.util.StringMan; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.worldedit.util.command.binding.Range; +import com.sk89q.worldedit.util.command.binding.Validate; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; @@ -48,16 +49,17 @@ import java.util.List; *

Methods may throw any exception. Exceptions may be converted using a * {@link ExceptionConverter} registered with the {@link ParametricBuilder}.

*/ +@Deprecated public class BindingHelper implements Binding { - private final List bindings; + private final List bindings; private final Type[] types; /** * Create a new instance. */ public BindingHelper() { - List bindings = new ArrayList<>(); + List bindings = new ArrayList<>(); List types = new ArrayList<>(); for (Method method : this.getClass().getMethods()) { @@ -88,7 +90,7 @@ public class BindingHelper implements Binding { "A @BindingMatch needs either a type or classifier set"); } - BoundMethod handler = new BoundMethod(info, type, classifier, method); + BindingMap.BoundMethod handler = new BindingMap.BoundMethod(info, type, classifier, method, this); bindings.add(handler); } } @@ -110,8 +112,8 @@ public class BindingHelper implements Binding { * @param parameter the parameter * @return a binding */ - private BoundMethod match(ParameterData parameter) { - for (BoundMethod binding : bindings) { + private BindingMap.BoundMethod match(ParameterData parameter) { + for (BindingMap.BoundMethod binding : bindings) { Annotation classifer = parameter.getClassifier(); Type type = parameter.getType(); @@ -147,7 +149,7 @@ public class BindingHelper implements Binding { @Override public Object bind(ParameterData parameter, ArgumentStack scoped, boolean onlyConsume) throws ParameterException, CommandException, InvocationTargetException { - BoundMethod binding = match(parameter); + BindingMap.BoundMethod binding = match(parameter); List args = new ArrayList<>(); args.add(scoped); @@ -212,39 +214,88 @@ public class BindingHelper implements Binding { } return new ArrayList<>(); } - - private static class BoundMethod implements Comparable { - private final BindingMatch annotation; - private final Type type; - private final Class classifier; - private final Method method; - - BoundMethod(BindingMatch annotation, Type type, - Class 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; + /** + * 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 + */ + public 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)); } - } else { - return 0; } } } + /** + * 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 + */ + public 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 + */ + public 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())); + } + } + } + } + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMap.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMap.java new file mode 100644 index 000000000..6ff1589e4 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMap.java @@ -0,0 +1,304 @@ +package com.sk89q.worldedit.util.command.parametric; + +import com.boydti.fawe.util.ArrayUtil; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.StringMan; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandLocals; +import com.sk89q.worldedit.util.command.CommandMapping; +import com.sk89q.worldedit.util.command.MissingParameterException; +import com.sk89q.worldedit.util.command.SimpleDispatcher; +import com.sk89q.worldedit.util.command.binding.Range; +import com.sk89q.worldedit.util.command.parametric.ArgumentStack; +import com.sk89q.worldedit.util.command.parametric.Binding; +import com.sk89q.worldedit.util.command.parametric.BindingBehavior; +import com.sk89q.worldedit.util.command.parametric.BindingMatch; +import com.sk89q.worldedit.util.command.parametric.ParameterData; +import com.sk89q.worldedit.util.command.parametric.ParameterException; +import com.sk89q.worldedit.util.command.parametric.ParametricBuilder; + +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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A binding helper that uses the {@link BindingMatch} annotation to make + * writing bindings extremely easy. + * + *

Methods must have the following and only the following parameters:

+ * + *
    + *
  • A {@link ArgumentStack}
  • + *
  • A {@link Annotation} if there is a classifier set
  • + *
  • A {@link Annotation}[] + * if there {@link BindingMatch#provideModifiers()} is true
  • + *
+ * + *

Methods may throw any exception. Exceptions may be converted using a + * {@link ExceptionConverter} registered with the {@link ParametricBuilder}.

+ */ +public class BindingMap implements Binding { + + private final Set types; + private final Map legacy; + private final Map> bindings; + private final Map dynamicBindings; + private final ParametricBuilder builder; + + /** + * Create a new instance. + * @param builder + */ + public BindingMap(ParametricBuilder builder) { + this.dynamicBindings = new HashMap<>(); + this.legacy = new HashMap<>(); + this.bindings = new HashMap<>(); + this.builder = builder; + this.types = new HashSet<>(); + + } + + public void add(Object object, Type... requiredTypes) { + Method[] methods = object.getClass().getDeclaredMethods(); + for (Method method : methods) { + method.setAccessible(true); + BindingMatch info = method.getAnnotation(BindingMatch.class); + if (info != null) { + Class classifier = null; + + // Set classifier + if (!info.classifier().equals(Annotation.class)) { + classifier = info.classifier(); + } + + for (Type type : info.type()) { + if (type == Void.class) { + type = method.getReturnType(); + } + BoundMethod handler = new BoundMethod(info, type, classifier, method, object); + List list = bindings.get(type); + if (list == null) bindings.put(type, list = new ArrayList<>()); + list.add(handler); + types.add(type); + } + } + Command definition = method.getAnnotation(Command.class); + Class type = method.getReturnType(); + if (definition != null && type != null) { + SimpleDispatcher dispatcher = dynamicBindings.get(type); + if (dispatcher == null) dynamicBindings.put(type, dispatcher = new SimpleDispatcher()); + builder.registerMethodAsCommands(method, dispatcher, object, null); + types.add(type); + } + } + if (requiredTypes != null && requiredTypes.length > 0) { + for (Type type : requiredTypes) { + legacy.put(type, (Binding) object); + } + } + } + + /** + * Match a {@link BindingMatch} according to the given parameter. + * + * @param pd the parameter + * @return a binding + */ + private BoundMethod match(ParameterData pd) { + Type type = pd.getType(); + while (type != null) { + List methods = bindings.get(type); + if (methods != null) { + for (BoundMethod binding : methods) { + if (binding.classifier != null) { + if (pd.getClassifier() != null && pd.getClassifier().annotationType().equals(binding.classifier)) { + if (binding.type == null || binding.type.equals(type)) { + return binding; + } + } + } else if (binding.type.equals(type)) { + return binding; + } + } + } + type = (type instanceof Class) ? ((Class) type).getSuperclass() : null; + } + throw new RuntimeException("Unknown type " + pd.getType()); + } + + private SimpleDispatcher matchDynamic(ParameterData pd) { + return dynamicBindings.get(pd.getType()); + } + + @Override + public int getConsumedCount(ParameterData parameter) { + return match(parameter).annotation.consumedCount(); + } + + @Override + public BindingBehavior getBehavior(ParameterData parameter) { + BoundMethod matched = match(parameter); + if (matched != null) return matched.annotation.behavior(); + SimpleDispatcher dynamic = matchDynamic(parameter); + return dynamic != null ? BindingBehavior.CONSUMES : null; + } + + @Override + public Type[] getTypes() { + return types.toArray(new Type[0]); + } + + @Override + public Object bind(ParameterData parameter, ArgumentStack scoped, boolean onlyConsume) throws ParameterException, CommandException, InvocationTargetException { + BoundMethod binding = match(parameter); + List args = new ArrayList<>(); + args.add(scoped); + + if (binding.classifier != null) { + args.add(parameter.getClassifier()); + } + + if (binding.annotation.provideModifiers()) { + args.add(parameter.getModifiers()); + } + + if (binding.annotation.provideType()) { + args.add(parameter.getType()); + } + + if (onlyConsume && binding.annotation.behavior() == BindingBehavior.PROVIDES) { + return null; // Nothing to consume, nothing to do + } + + if (binding.annotation.behavior() != BindingBehavior.PROVIDES) { + SimpleDispatcher dynamic = matchDynamic(parameter); + if (dynamic != null) { + scoped.mark(); + String rest = scoped.remaining(); + scoped.reset(); + int start = rest.indexOf('{'); + if (start > 0) { + int end = StringMan.findMatchingBracket(rest, start); + if (end > start) { + String alias = rest.substring(0, start); + CommandMapping cmd = dynamic.get(alias); + if (cmd != null) { + String arguments = rest.substring(start + 1, end); + CommandLocals locals = scoped.getContext().getLocals(); + Object result = cmd.getCallable().call(arguments, locals, new String[0]); + int remaining = rest.length() - end; + while (rest.length() > remaining) { + scoped.next(); + try { + scoped.mark(); + rest = scoped.remaining(); + scoped.reset(); + } catch (MissingParameterException ignore) { rest = ""; } + } + return result; + } + } + } + } + } + Object[] argsArray = new Object[args.size()]; + args.toArray(argsArray); + + try { + return binding.method.invoke(binding.object, 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 e; + } + } + + @Override + public List getSuggestions(ParameterData parameter, String prefix) { + Binding legacySuggestions = legacy.get(parameter.getType()); + if (legacySuggestions != null) { + List result = legacySuggestions.getSuggestions(parameter, prefix); + if (result != null) return result; + } + if (prefix.isEmpty()) { + char bracket = parameter.isOptional() ? '[' : '<'; + char endBracket = StringMan.getMatchingBracket(bracket); + StringBuilder result = new StringBuilder(); + result.append("\u00A75"); + result.append(bracket); + result.append("\u00A7r"); + if (parameter.getFlag() != null) { + result.append('-').append(parameter.getFlag()).append("\u00A75 \u00A7r"); + } + result.append(parameter.getName()); + if (parameter.getDefaultValue() != null) { + result.append('=').append(StringMan.join(parameter.getDefaultValue(), " ")); + } + Range range = parameter.getModifier(Range.class); + if (range != null) { + result.append('|').append(StringMan.prettyFormat(range.min())).append(",").append(StringMan.prettyFormat(range.max())); + } + result.append("\u00A75"); + result.append(endBracket); + result.append("\u00A7r"); + return Collections.singletonList(result.toString()); + } + return new ArrayList<>(); + } + + protected static class BoundMethod implements Comparable { + protected final BindingMatch annotation; + protected final Type type; + protected final Class classifier; + protected final Method method; + protected final Object object; + + BoundMethod(BindingMatch annotation, Type type, + Class classifier, Method method, Object object) { + this.annotation = annotation; + this.type = type; + this.classifier = classifier; + this.method = method; + this.object = object; + } + + @Override + public int compareTo(BoundMethod o) { + if (classifier != null && o.classifier == null) { + return -1; + } else if (classifier == null && o.classifier != null) { + return 1; + } else if (classifier != null && o.classifier != null) { + if (type != null && o.type == null) { + return -1; + } else if (type == null && o.type != null) { + return 1; + } else { + return 0; + } + } else { + return 0; + } + } + } +} + diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMatch.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMatch.java index 049d3dc4d..a76be395b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMatch.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/BindingMatch.java @@ -45,7 +45,7 @@ public @interface BindingMatch { * @return the type, or {@link Class} if not set */ Class[] type() default Class.class; - + /** * The binding behavior. * @@ -68,4 +68,9 @@ public @interface BindingMatch { */ boolean provideModifiers() default false; + /** + * If the type should be passed to the method + */ + boolean provideType() default false; + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/FunctionParametricCallable.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/FunctionParametricCallable.java index 2e77631ff..edcd22b21 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/FunctionParametricCallable.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/FunctionParametricCallable.java @@ -33,10 +33,8 @@ public class FunctionParametricCallable extends AParametricCallable { List paramParsables = new ArrayList<>(); { - Map bindings = builder.getBindings(); Map unqualified = new HashMap<>(); - for (Map.Entry entry : bindings.entrySet()) { - Type type = entry.getKey(); + for (Type type : builder.getBindings().getTypes()) { String typeStr = type.getTypeName(); unqualified.put(typeStr, type); unqualified.put(typeStr.substring(typeStr.lastIndexOf('.') + 1), type); @@ -117,7 +115,7 @@ public class FunctionParametricCallable extends AParametricCallable { // No special @annotation binding... let's check for the type if (parameter.getBinding() == null) { - parameter.setBinding(builder.getBindings().get(type)); + parameter.setBinding(builder.getBindings()); // Don't know how to parse for this type of value if (parameter.getBinding() == null) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricBuilder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricBuilder.java index 1bc62aa31..4d3fbd6be 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricBuilder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricBuilder.java @@ -65,7 +65,7 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class ParametricBuilder { - private final Map bindings = new HashMap<>(); + private final BindingMap bindings; private final Paranamer paranamer = new FaweParanamer(); private final List invokeListeners = new ArrayList<>(); private final List exceptionConverters = new ArrayList<>(); @@ -79,8 +79,9 @@ public class ParametricBuilder { * {@link StandardBindings} and default bindings.

*/ public ParametricBuilder() { - addBinding(new FawePrimitiveBinding()); - addBinding(new StandardBindings()); + this.bindings = new BindingMap(this); + this.bindings.add(new FawePrimitiveBinding()); + this.bindings.add(new StandardBindings()); } /** @@ -103,14 +104,17 @@ public class ParametricBuilder { * @param binding the binding * @param type a list of types (if specified) to override the binding's types */ + @Deprecated public void addBinding(Binding binding, Type... type) { - if (type == null || type.length == 0) { - type = binding.getTypes(); - } + this.bindings.add(binding); + } - for (Type t : type) { - bindings.put(t, binding); - } + /** + * Add a binding (accepts @Command or @BindingMatch methods) + * @param binding + */ + public void addBinding(Object binding) { + this.bindings.add(binding); } /** @@ -175,21 +179,22 @@ public class ParametricBuilder { */ public void registerMethodsAsCommands(Dispatcher dispatcher, Object object, CallableProcessor processor) throws ParametricException { for (Method method : object.getClass().getDeclaredMethods()) { - Command definition = method.getAnnotation(Command.class); - if (definition != null) { - definition = Commands.translate(method.getDeclaringClass(), definition); - CommandCallable callable = build(object, method, definition); - if (processor != null) { - callable = new ProcessedCallable(callable, processor); - } - else if (object instanceof CallableProcessor) { - callable = new ProcessedCallable(callable, (CallableProcessor) object); - } - if (object instanceof MethodCommands) { - ((MethodCommands) object).register(method, callable, dispatcher); - } - dispatcher.registerCommand(callable, definition.aliases()); + registerMethodAsCommands(method, dispatcher, object, processor); + } + } + + public void registerMethodAsCommands(Method method, Dispatcher dispatcher, Object object, CallableProcessor processor) throws ParametricException { + Command definition = method.getAnnotation(Command.class); + if (definition != null) { + definition = Commands.translate(method.getDeclaringClass(), definition); + CommandCallable callable = build(object, method, definition); + if (processor != null) { + callable = new ProcessedCallable(callable, processor); } + else if (object instanceof CallableProcessor) { + callable = new ProcessedCallable(callable, (CallableProcessor) object); + } + dispatcher.registerCommand(callable, definition.aliases()); } } @@ -230,7 +235,7 @@ public class ParametricBuilder { * * @return the map of bindings */ - public Map getBindings() { + public BindingMap getBindings() { return bindings; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java index e2b90f6c0..9c66999db 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java @@ -113,7 +113,7 @@ public class ParametricCallable extends AParametricCallable { } // Special annotation bindings } else if (parameter.getBinding() == null) { - parameter.setBinding(builder.getBindings().get(annotation.annotationType())); + parameter.setBinding(builder.getBindings()); parameter.setClassifier(annotation); } } @@ -127,7 +127,7 @@ public class ParametricCallable extends AParametricCallable { // No special @annotation binding... let's check for the type if (parameter.getBinding() == null) { - parameter.setBinding(builder.getBindings().get(type)); + parameter.setBinding(builder.getBindings()); // Don't know how to parse for this type of value if (parameter.getBinding() == null) {