diff --git a/CHANGELOG.md b/CHANGELOG.md index 8287c447..15fd5dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added `@Suggestions` annotated methods + - Added `@Parser` annotated methods - Type safe meta system ### Changed @@ -17,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated - String keyed command meta +### Fixed + - Fixed issue with task synchronization + ## [1.2.0] - 2020-12-07 ### Added diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java index 071660cf..f3e70d8f 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -28,6 +28,8 @@ import cloud.commandframework.CommandManager; import cloud.commandframework.Description; import cloud.commandframework.annotations.injection.ParameterInjectorRegistry; import cloud.commandframework.annotations.injection.RawArgs; +import cloud.commandframework.annotations.parsers.MethodArgumentParser; +import cloud.commandframework.annotations.parsers.Parser; import cloud.commandframework.annotations.specifier.Completions; import cloud.commandframework.annotations.suggestions.MethodSuggestionsProvider; import cloud.commandframework.annotations.suggestions.Suggestions; @@ -227,6 +229,8 @@ public final class AnnotationParser { public @NonNull Collection<@NonNull Command> parse(final @NonNull T instance) { /* Start by registering all @Suggestion annotated methods */ this.parseSuggestions(instance); + /* Then register all parsers */ + this.parseParsers(instance); /* Then construct commands from @CommandMethod annotated classes */ final Method[] methods = instance.getClass().getDeclaredMethods(); final Collection commandMethodPairs = new ArrayList<>(); @@ -291,6 +295,51 @@ public final class AnnotationParser { } } + @SuppressWarnings("deprecation") + private void parseParsers(final @NonNull T instance) { + for (final Method method : instance.getClass().getMethods()) { + final Parser parser = method.getAnnotation(Parser.class); + if (parser == null) { + continue; + } + if (!method.isAccessible()) { + method.setAccessible(true); + } + if (method.getParameterCount() != 2 + || method.getReturnType().equals(Void.class) + || !method.getParameters()[0].getType().equals(CommandContext.class) + || !method.getParameters()[1].getType().equals(Queue.class) + ) { + throw new IllegalArgumentException(String.format( + "@Parser annotated method '%s' in class '%s' does not have the correct signature", + method.getName(), + instance.getClass().getCanonicalName() + )); + } + try { + final MethodArgumentParser methodArgumentParser = new MethodArgumentParser<>( + instance, + method + ); + final Function> parserFunction = + parameters -> methodArgumentParser; + if (parser.name().isEmpty()) { + this.manager.getParserRegistry().registerParserSupplier( + TypeToken.get(method.getGenericReturnType()), + parserFunction + ); + } else { + this.manager.getParserRegistry().registerNamedParserSupplier( + parser.name(), + parserFunction + ); + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + } + @SuppressWarnings("unchecked") private @NonNull Collection<@NonNull Command> construct( final @NonNull Object instance, diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/MethodArgumentParser.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/MethodArgumentParser.java new file mode 100644 index 00000000..fe824408 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/MethodArgumentParser.java @@ -0,0 +1,76 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// 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 cloud.commandframework.annotations.parsers; + +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.context.CommandContext; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.util.Queue; + +/** + * Represents a method annotated with {@link Parser} + * + * @param Command sender type + * @param Argument type + * @since 1.3.0 + */ +public class MethodArgumentParser implements ArgumentParser { + + private final MethodHandle methodHandle; + + /** + * Create a new parser + * + * @param instance Instance that owns the method + * @param method The annotated method + * @throws Exception If the method lookup fails + */ + public MethodArgumentParser( + final @NonNull Object instance, + final @NonNull Method method + ) throws Exception { + this.methodHandle = MethodHandles.lookup().unreflect(method).bindTo(instance); + } + + @Override + @SuppressWarnings("unchecked") + public @NonNull ArgumentParseResult<@NonNull T> parse( + final @NonNull CommandContext<@NonNull C> commandContext, + final @NonNull Queue<@NonNull String> inputQueue + ) { + try { + return ArgumentParseResult.success( + (T) this.methodHandle.invokeWithArguments(commandContext, inputQueue) + ); + } catch (final Throwable t) { + return ArgumentParseResult.failure(t); + } + } + +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/Parser.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/Parser.java new file mode 100644 index 00000000..3cf2559e --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/Parser.java @@ -0,0 +1,55 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// 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 cloud.commandframework.annotations.parsers; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation allows you to create annotated methods that behave like argument parsers. + * The method must have this exact signature:
{@code
+ * ﹫Parser("name") // Name may be left out
+ * public ParsedType methodName(CommandContext sender, Queue input) {
+ * }}
+ *

+ * The method can throw exceptions, and the thrown exceptions will automatically be + * wrapped by a {@link cloud.commandframework.arguments.parser.ArgumentParseResult#failure(Throwable)} + * + * @since 1.3.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Parser { + + /** + * The name of the parser. If this is left empty, the parser will + * be registered as a default parser for the return type of the method + * + * @return Parser name + */ + String name() default ""; + +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/package-info.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/package-info.java new file mode 100644 index 00000000..ba0fe1d5 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/parsers/package-info.java @@ -0,0 +1,28 @@ +// +// MIT License +// +// Copyright (c) 2020 Alexander Söderberg & Contributors +// +// 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. +// + +/** + * Classes related to {@link cloud.commandframework.annotations.parsers.Parser} annotated methods + */ +package cloud.commandframework.annotations.parsers; diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/suggestions/MethodSuggestionsProvider.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/suggestions/MethodSuggestionsProvider.java index e43836a9..37c6b1d9 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/suggestions/MethodSuggestionsProvider.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/suggestions/MethodSuggestionsProvider.java @@ -46,7 +46,7 @@ public final class MethodSuggestionsProvider implements BiFunction{@code * ﹫Suggestions("name") * public List methodName(CommandContext sender, String input) { diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java index 744b439e..f2ba9024 100644 --- a/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/AnnotationParserTest.java @@ -25,11 +25,15 @@ package cloud.commandframework.annotations; import cloud.commandframework.Command; import cloud.commandframework.CommandManager; +import cloud.commandframework.annotations.parsers.Parser; import cloud.commandframework.annotations.specifier.Range; import cloud.commandframework.annotations.suggestions.Suggestions; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.arguments.parser.ParserParameters; import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.context.CommandContext; import cloud.commandframework.meta.SimpleCommandMeta; +import io.leangen.geantyref.TypeToken; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -43,7 +47,9 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; import java.util.concurrent.CompletionException; import java.util.function.BiFunction; @@ -141,11 +147,31 @@ class AnnotationParserTest { .contains("Stella")); } + @Test + void testAnnotatedArgumentParser() { + final ArgumentParser parser = this.manager.getParserRegistry().createParser( + TypeToken.get(CustomType.class), + ParserParameters.empty() + ).orElseThrow(() -> new NullPointerException("Could not find CustomType parser")); + Assertions.assertEquals("yay", parser.parse( + new CommandContext<>( + new TestCommandSender(), + this.manager + ), + new LinkedList<>() + ).getParsedValue().orElse(new CustomType("")).toString()); + } + @Suggestions("cows") public List cowSuggestions(final CommandContext context, final String input) { return Arrays.asList("Stella", "Bella", "Agda"); } + @Parser + public CustomType customTypeParser(final CommandContext context, final Queue input) { + return new CustomType("yay"); + } + @ProxiedBy("proxycommand") @CommandMethod("test|t literal [string]") public void testCommand( @@ -231,4 +257,20 @@ class AnnotationParserTest { } + + private static final class CustomType { + + private final String value; + + private CustomType(final String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + } + } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitSynchronizer.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitSynchronizer.java index 203d722b..d838a238 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitSynchronizer.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitSynchronizer.java @@ -31,11 +31,19 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.util.concurrent.CompletableFuture; -final class BukkitSynchronizer implements TaskSynchronizer { +/** + * {@link TaskSynchronizer} using Bukkit's {@link org.bukkit.scheduler.BukkitScheduler} + */ +public final class BukkitSynchronizer implements TaskSynchronizer { private final Plugin plugin; - BukkitSynchronizer(final @NonNull Plugin plugin) { + /** + * Create a new instance of the Bukkit synchronizer + * + * @param plugin Owning plugin + */ + public BukkitSynchronizer(final @NonNull Plugin plugin) { this.plugin = plugin; }