From c980adac3b765d8d1312540b74388ec1a2c9a110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Fri, 25 Sep 2020 02:20:04 +0200 Subject: [PATCH] Add command proxies --- .../annotations/AnnotationParser.java | 11 ++++- .../commands/annotations/ProxiedBy.java | 47 +++++++++++++++++++ .../annotations/AnnotationParserTest.java | 8 ++-- .../intellectualsites/commands/Command.java | 27 +++++++++++ .../commands/arguments/CommandArgument.java | 21 +++++++++ .../commands/CommandTreeTest.java | 16 +++++++ 6 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ProxiedBy.java diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java index 3718fac8..dc2d9fb4 100644 --- a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java @@ -211,7 +211,16 @@ public final class AnnotationParser { } catch (final Exception e) { throw new RuntimeException("Failed to construct command execution handler", e); } - commands.add(builder.build()); + final Command builtCommand = builder.build(); + commands.add(builtCommand); + /* Check if we need to construct a proxy */ + if (method.isAnnotationPresent(ProxiedBy.class)) { + final String proxy = method.getAnnotation(ProxiedBy.class).value(); + if (proxy.contains(" ")) { + throw new IllegalArgumentException("@ProxiedBy proxies may only contain single literals"); + } + manager.command(manager.commandBuilder(proxy, builtCommand.getCommandMeta()).proxies(builtCommand).build()); + } } return commands; } diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ProxiedBy.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ProxiedBy.java new file mode 100644 index 00000000..fd52e572 --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ProxiedBy.java @@ -0,0 +1,47 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package com.intellectualsites.commands.annotations; + +import javax.annotation.Nonnull; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Creates a command proxy for the command. This is similar to + * {@link com.intellectualsites.commands.Command.Builder#proxies(com.intellectualsites.commands.Command)}. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ProxiedBy { + + /** + * Syntax of the proxying command + * + * @return Proxy syntax + */ + @Nonnull String value(); + +} diff --git a/cloud-annotations/src/test/java/com/intellectualsites/commands/annotations/AnnotationParserTest.java b/cloud-annotations/src/test/java/com/intellectualsites/commands/annotations/AnnotationParserTest.java index 8060d116..a918a8a1 100644 --- a/cloud-annotations/src/test/java/com/intellectualsites/commands/annotations/AnnotationParserTest.java +++ b/cloud-annotations/src/test/java/com/intellectualsites/commands/annotations/AnnotationParserTest.java @@ -54,13 +54,15 @@ class AnnotationParserTest { void testMethodConstruction() { final Collection> commands = annotationParser.parse(this); Assertions.assertFalse(commands.isEmpty()); - manager.executeCommand(new TestCommandSender(), "test 10").join(); - manager.executeCommand(new TestCommandSender(), "t 10 o").join(); + manager.executeCommand(new TestCommandSender(), "test literal 10").join(); + manager.executeCommand(new TestCommandSender(), "t literal 10 o").join(); + manager.executeCommand(new TestCommandSender(), "proxycommand 10").join(); Assertions.assertThrows(CompletionException.class, () -> manager.executeCommand(new TestCommandSender(), "test 101").join()); } - @CommandMethod("test|t [string]") + @ProxiedBy("proxycommand") + @CommandMethod("test|t literal [string]") public void testCommand(@Nonnull final TestCommandSender sender, @Argument("int") @Range(max = "100") final int argument, @Nonnull @Argument(value = "string", defaultValue = "potato", parserName = "potato") diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/Command.java b/cloud-core/src/main/java/com/intellectualsites/commands/Command.java index eb7dabdf..3b112758 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/Command.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/Command.java @@ -410,6 +410,33 @@ public class Command { this.commandExecutionHandler, Permission.of(permission)); } + /** + * Make the current command be a proxy of the supplied command. This means that + * all of the proxied commands variable command arguments will be inserted into this + * builder instance, in the order they are declared in the proxied command. Furthermore, + * the proxied commands command handler will be showed by the command that is currently + * being built. If the current command builder does not have a permission node set, this + * too will be copied. + * + * @param command Command to proxy + * @return New builder that proxies the given command + */ + @Nonnull + public Builder proxies(@Nonnull final Command command) { + Builder builder = this; + for (final CommandArgument argument : command.getArguments()) { + if (argument instanceof StaticArgument) { + continue; + } + final CommandArgument builtArgument = argument.copy(); + builder = builder.argument(builtArgument, Description.of(command.getArgumentDescription(argument))); + } + if (this.commandPermission.toString().isEmpty()) { + builder = builder.withPermission(command.getCommandPermission()); + } + return builder.handler(command.commandExecutionHandler); + } + /** * Build a command using the builder instance * diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/arguments/CommandArgument.java b/cloud-core/src/main/java/com/intellectualsites/commands/arguments/CommandArgument.java index 0b0f55f7..3497d61b 100644 --- a/cloud-core/src/main/java/com/intellectualsites/commands/arguments/CommandArgument.java +++ b/cloud-core/src/main/java/com/intellectualsites/commands/arguments/CommandArgument.java @@ -281,6 +281,27 @@ public class CommandArgument implements Comparable> return this.valueType; } + /** + * Create a copy of the command argument + * + * @return Copied argument + */ + @Nonnull + public CommandArgument copy() { + CommandArgument.Builder builder = ofType(this.valueType, this.name); + builder = builder.withSuggestionsProvider(this.suggestionsProvider); + builder = builder.withParser(this.parser); + if (this.isRequired()) { + builder = builder.asRequired(); + } else if (this.defaultValue.isEmpty()) { + builder = builder.asOptional(); + } else { + builder = builder.asOptionalWithDefault(this.defaultValue); + } + return builder.build(); + } + + /** * Mutable builder for {@link CommandArgument} instances * diff --git a/cloud-core/src/test/java/com/intellectualsites/commands/CommandTreeTest.java b/cloud-core/src/test/java/com/intellectualsites/commands/CommandTreeTest.java index 3f7c75c8..e0fedb81 100644 --- a/cloud-core/src/test/java/com/intellectualsites/commands/CommandTreeTest.java +++ b/cloud-core/src/test/java/com/intellectualsites/commands/CommandTreeTest.java @@ -24,6 +24,7 @@ package com.intellectualsites.commands; import com.intellectualsites.commands.arguments.standard.IntegerArgument; +import com.intellectualsites.commands.arguments.standard.StringArgument; import com.intellectualsites.commands.context.CommandContext; import com.intellectualsites.commands.exceptions.NoPermissionException; import com.intellectualsites.commands.meta.SimpleCommandMeta; @@ -56,6 +57,15 @@ class CommandTreeTest { .optional("num", EXPECTED_INPUT_NUMBER)) .build()) .command(manager.commandBuilder("req").withSenderType(SpecificCommandSender.class).build()); + final Command toProxy = manager.commandBuilder("test") + .literal("unproxied") + .argument(StringArgument.required("string")) + .argument(IntegerArgument.required("int")) + .literal("anotherliteral") + .handler(c -> {}) + .build(); + manager.command(toProxy); + manager.command(manager.commandBuilder("proxy").proxies(toProxy).build()); } @Test @@ -120,6 +130,12 @@ class CommandTreeTest { .executeCommand(new TestCommandSender(), "invalid test").join()); } + @Test + void testProxy() { + manager.executeCommand(new TestCommandSender(),"test unproxied foo 10 anotherliteral").join(); + manager.executeCommand(new TestCommandSender(), "proxy foo 10").join(); + } + public static final class SpecificCommandSender extends TestCommandSender { }