From ab849ffa0961323b96978405e03cc3522b4385d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= <4096670+Citymonstret@users.noreply.github.com> Date: Tue, 31 May 2022 15:37:45 +0200 Subject: [PATCH] feat(annotations): add command containers (#364) This is the first part of the introduction of annotation processing to cloud. A new `@CommandContainer` annotation has been introduced, which can be placed on classes to have the annotation parser automatically construct & parse the classes when `AnnotationParser.parseContainers()` is invoked. A future PR will introduce another processor that will scan for `@CommandMethod` annotations and verify the integrity of the annotated methods (visibility, argument annotations, etc.). --- CHANGELOG.md | 1 + cloud-annotations/build.gradle.kts | 2 + .../annotations/AnnotationParser.java | 63 ++++++- .../processing/CommandContainer.java | 56 ++++++ .../processing/CommandContainerProcessor.java | 115 +++++++++++++ .../processing/CommandContainerVisitor.java | 159 ++++++++++++++++++ .../annotations/processing/package-info.java | 6 + .../javax.annotation.processing.Processor | 1 + .../CommandContainerProcessorTest.java | 60 +++++++ .../test/resources/TestCommandContainer.java | 6 + .../test/resources/TestCommandContainer2.java | 10 ++ examples/example-bukkit/build.gradle.kts | 2 + .../bukkit/ExampleCommandContainer.java | 53 ++++++ .../examples/bukkit/ExamplePlugin.java | 6 + gradle/libs.versions.yml | 5 + 15 files changed, 543 insertions(+), 2 deletions(-) create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainer.java create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainerProcessor.java create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainerVisitor.java create mode 100644 cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/package-info.java create mode 100644 cloud-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor create mode 100644 cloud-annotations/src/test/java/cloud/commandframework/annotations/processing/CommandContainerProcessorTest.java create mode 100644 cloud-annotations/src/test/resources/TestCommandContainer.java create mode 100644 cloud-annotations/src/test/resources/TestCommandContainer2.java create mode 100644 examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExampleCommandContainer.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f9b50feb..2985a03ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Core: Add delegating command execution handlers ([#363](https://github.com/Incendo/cloud/pull/363)) - Core: Add `builder()` getter to `Command.Builder` ([#363](https://github.com/Incendo/cloud/pull/363)) - Annotations: Annotation string processors ([#353](https://github.com/Incendo/cloud/pull/353)) +- Annotations: `@CommandContainer` annotation processing ### Fixed - Core: Fix missing caption registration for the regex caption ([#351](https://github.com/Incendo/cloud/pull/351)) diff --git a/cloud-annotations/build.gradle.kts b/cloud-annotations/build.gradle.kts index 7048adf49..9fd54536a 100644 --- a/cloud-annotations/build.gradle.kts +++ b/cloud-annotations/build.gradle.kts @@ -5,4 +5,6 @@ plugins { dependencies { implementation(projects.cloudCore) + + testImplementation(libs.compileTesting) } 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 35343af09..279ec1818 100644 --- a/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/AnnotationParser.java @@ -30,6 +30,7 @@ import cloud.commandframework.annotations.injection.RawArgs; import cloud.commandframework.annotations.parsers.MethodArgumentParser; import cloud.commandframework.annotations.parsers.Parser; +import cloud.commandframework.annotations.processing.CommandContainerProcessor; import cloud.commandframework.annotations.specifier.Completions; import cloud.commandframework.annotations.suggestions.MethodSuggestionsProvider; import cloud.commandframework.annotations.suggestions.Suggestions; @@ -48,16 +49,21 @@ import cloud.commandframework.meta.CommandMeta; import cloud.commandframework.meta.SimpleCommandMeta; import io.leangen.geantyref.TypeToken; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -66,6 +72,8 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -318,13 +326,64 @@ public void stringProcessor(final @NonNull StringProcessor stringProcessor) { return Arrays.stream(strings).map(this::processString).toArray(String[]::new); } + /** + * Parses all known {@link cloud.commandframework.annotations.processing.CommandContainer command containers}. + * + * @return Collection of parsed commands + * @throws Exception re-throws all encountered exceptions. + * @since 1.7.0 + * @see cloud.commandframework.annotations.processing.CommandContainer CommandContainer for more information. + */ + public @NonNull Collection<@NonNull Command> parseContainers() throws Exception { + final List> commands = new LinkedList<>(); + + final List classes; + try (InputStream stream = this.getClass().getClassLoader().getResourceAsStream(CommandContainerProcessor.PATH)) { + if (stream == null) { + return Collections.emptyList(); + } + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + classes = reader.lines().distinct().collect(Collectors.toList()); + } + } + + for (final String className : classes) { + final Class commandContainer = Class.forName(className); + + // We now have the class, and we now just need to decide what constructor to invoke. + // We first try to find a constructor which takes in the parser. + @MonotonicNonNull Object instance; + try { + instance = commandContainer.getConstructor(AnnotationParser.class).newInstance(this); + } catch (final NoSuchMethodException ignored) { + try { + // Then we try to find a no-arg constructor. + instance = commandContainer.getConstructor().newInstance(); + } catch (final NoSuchMethodException e) { + // If neither are found, we panic! + throw new IllegalStateException( + String.format( + "Command container %s has no valid constructors", + commandContainer + ), + e + ); + } + } + commands.addAll(this.parse(instance)); + } + + return Collections.unmodifiableList(commands); + } + /** * Scan a class instance of {@link CommandMethod} annotations and attempt to - * compile them into {@link Command} instances + * compile them into {@link Command} instances. * * @param instance Instance to scan * @param Type of the instance - * @return Collection of parsed annotations + * @return Collection of parsed commands */ @SuppressWarnings({"deprecation", "unchecked", "rawtypes"}) public @NonNull Collection<@NonNull Command> parse(final @NonNull T instance) { diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainer.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainer.java new file mode 100644 index 000000000..ddb730261 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainer.java @@ -0,0 +1,56 @@ +// +// MIT License +// +// Copyright (c) 2021 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.processing; + +import cloud.commandframework.annotations.AnnotationParser; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the class contains + * {@link cloud.commandframework.annotations.CommandMethod command metods}. + *

+ * If using cloud-annotations as an annotation processor, then the class will + * be listed in a special file under META-INF. These containers can be collectively + * parsed using {@link AnnotationParser#parseContainers()}, which will create instances + * of the containers and then call {@link AnnotationParser#parse(Object)} with the created instance. + *

+ * Every class annotated with {@link CommandContainer} needs to be {@code public}, and it + * also needs to have one of the following: + *

    + *
  • A {@code public} no-arg constructor
  • + *
  • A {@code public} constructor with {@link AnnotationParser} as the sole parameter
  • + *
+ *

+ * NOTE: For container parsing to work, you need to make sure that cloud-annotations is added + * as an annotation processor. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface CommandContainer { + String ANNOTATION_PATH = "cloud.commandframework.annotations.processing.CommandContainer"; + +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainerProcessor.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainerProcessor.java new file mode 100644 index 000000000..7356887a4 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainerProcessor.java @@ -0,0 +1,115 @@ +// +// MIT License +// +// Copyright (c) 2021 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.processing; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.StandardLocation; +import org.checkerframework.checker.nullness.qual.NonNull; + +@SupportedAnnotationTypes(CommandContainer.ANNOTATION_PATH) +public final class CommandContainerProcessor extends AbstractProcessor { + + /** + * The file in which all command container names are stored. + */ + public static final String PATH = "META-INF/commands/cloud.commandframework.annotations.processing.CommandContainer"; + + @Override + public boolean process( + final @NonNull Set annotations, + final @NonNull RoundEnvironment roundEnv + ) { + final List validTypes = new ArrayList<>(); + + final Set elements = roundEnv.getElementsAnnotatedWith(CommandContainer.class); + if (elements.isEmpty()) { + return false; // Nothing to process... + } + + for (final Element element : elements) { + if (element.getKind() != ElementKind.CLASS) { + this.processingEnv.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format( + "@CommandMethod found on unsupported element type '%s' (%s)", + element.getKind().name(), + element.getSimpleName().toString() + ), + element + ); + return false; + } + + element.accept(new CommandContainerVisitor(this.processingEnv, validTypes), null); + } + + for (final String type : validTypes) { + this.processingEnv.getMessager().printMessage( + Diagnostic.Kind.NOTE, + String.format( + "Found valid @CommandMethod annotated class: %s", + type + ) + ); + } + this.writeCommandFile(validTypes); + + // https://errorprone.info/bugpattern/DoNotClaimAnnotations + return false; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @SuppressWarnings({"unused", "try"}) + private void writeCommandFile(final @NonNull List types) { + try (BufferedWriter writer = new BufferedWriter(this.processingEnv.getFiler().createResource( + StandardLocation.CLASS_OUTPUT, + "", + PATH + ).openWriter())) { + for (final String t : types) { + writer.write(t); + writer.newLine(); + } + writer.flush(); + } catch (final IOException e) { + e.printStackTrace(); + } + } +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainerVisitor.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainerVisitor.java new file mode 100644 index 000000000..ebfe64af0 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/CommandContainerVisitor.java @@ -0,0 +1,159 @@ +// +// MIT License +// +// Copyright (c) 2021 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.processing; + +import java.util.Arrays; +import java.util.Collection; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ElementVisitor; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.tools.Diagnostic; +import org.checkerframework.checker.nullness.qual.NonNull; + +final class CommandContainerVisitor implements ElementVisitor { + + private static final Collection PERMITTED_CONSTRUCTOR_PARAMETER_TYPES = Arrays.asList( + "cloud.commandframework.annotations.AnnotationParser" + ); + + private final ProcessingEnvironment processingEnvironment; + private final Collection validTypes; + + private boolean suitableConstructorFound; + + CommandContainerVisitor( + final @NonNull ProcessingEnvironment processingEnvironment, + final @NonNull Collection<@NonNull String> validTypes + ) { + this.processingEnvironment = processingEnvironment; + this.validTypes = validTypes; + this.suitableConstructorFound = false; + } + + @Override + public Void visit(final Element e) { + return this.visit(e, null); + } + + @Override + public Void visit(final Element e, final Void unused) { + return null; + } + + @Override + public Void visitPackage(final PackageElement e, final Void unused) { + return null; + } + + @Override + public Void visitType(final TypeElement e, final Void unused) { + if (!e.getModifiers().contains(Modifier.PUBLIC)) { + this.processingEnvironment.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format("@CommandMethod annotated class must be public (%s)", e.getSimpleName()), + e + ); + } + + for (final Element enclosedElement : e.getEnclosedElements()) { + if (enclosedElement.getKind() != ElementKind.CONSTRUCTOR) { + continue; + } + + // Visit the constructor. + enclosedElement.accept(this, null); + + // If we've already found a suitable constructor, there's no + // need to search for more. + if (this.suitableConstructorFound) { + break; + } + } + + // When we've visited every constructor, we verify that we found + // at least one suitable constructor. + if (!this.suitableConstructorFound) { + this.processingEnvironment.getMessager().printMessage( + Diagnostic.Kind.ERROR, + String.format("@CommandMethod must have a suitable constructor (%s)", e.getSimpleName()), + e + ); + } + + // We know it's valid, so we'll add it to the list of valid types. + this.validTypes.add(e.asType().toString()); + + return null; + } + + @Override + public Void visitVariable(final VariableElement e, final Void unused) { + return null; + } + + @Override + public Void visitExecutable(final ExecutableElement e, final Void unused) { + // We only want to process public constructors. + if (!e.getModifiers().contains(Modifier.PUBLIC)) { + return null; + } + + // Now we need to verify that the paramters are correct. + final boolean containsIllegalParameter = e.getParameters() + .stream() + .map(parameter -> parameter.asType().toString()) + // Ignore annotations. + .map(typeString -> typeString.split(" ")) + .map(parts -> parts[parts.length - 1]) + // Ignore type parameters. + .map(part -> part.split("<")[0]) + .anyMatch(type -> !PERMITTED_CONSTRUCTOR_PARAMETER_TYPES.contains(type)); + if (containsIllegalParameter) { + return null; + } + + // We now know that there's a constructor which accepts the permitted types, + // and is public - Yay. + this.suitableConstructorFound = true; + + return null; + } + + @Override + public Void visitTypeParameter(final TypeParameterElement e, final Void unused) { + return null; + } + + @Override + public Void visitUnknown(final Element e, final Void unused) { + return null; + } +} diff --git a/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/package-info.java b/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/package-info.java new file mode 100644 index 000000000..49fa42db8 --- /dev/null +++ b/cloud-annotations/src/main/java/cloud/commandframework/annotations/processing/package-info.java @@ -0,0 +1,6 @@ +/** + * Annotation-processing related classes. + * + * @since 1.7.0 + */ +package cloud.commandframework.annotations.processing; diff --git a/cloud-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/cloud-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 000000000..1210878b9 --- /dev/null +++ b/cloud-annotations/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +cloud.commandframework.annotations.processing.CommandContainerProcessor diff --git a/cloud-annotations/src/test/java/cloud/commandframework/annotations/processing/CommandContainerProcessorTest.java b/cloud-annotations/src/test/java/cloud/commandframework/annotations/processing/CommandContainerProcessorTest.java new file mode 100644 index 000000000..d15a1d56f --- /dev/null +++ b/cloud-annotations/src/test/java/cloud/commandframework/annotations/processing/CommandContainerProcessorTest.java @@ -0,0 +1,60 @@ +// +// MIT License +// +// Copyright (c) 2021 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.processing; + +import com.google.common.truth.StringSubject; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.StandardLocation; +import org.junit.jupiter.api.Test; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; + +class CommandContainerProcessorTest { + + @Test + void testCommandContainerParsing() { + // Arrange + final Compiler compiler = javac().withProcessors(new CommandContainerProcessor()); + + // Act + final Compilation compilation = compiler.compile( + JavaFileObjects.forResource("TestCommandContainer.java"), + JavaFileObjects.forResource("TestCommandContainer2.java") + ); + + // Assert + assertThat(compilation).succeeded(); + + final StringSubject contentSubject = assertThat(compilation).generatedFile( + StandardLocation.CLASS_OUTPUT, + "" /* package */, + CommandContainerProcessor.PATH + ).contentsAsUtf8String(); + contentSubject.contains("TestCommandContainer"); + contentSubject.contains("TestCommandContainer2"); + } +} diff --git a/cloud-annotations/src/test/resources/TestCommandContainer.java b/cloud-annotations/src/test/resources/TestCommandContainer.java new file mode 100644 index 000000000..0315427a3 --- /dev/null +++ b/cloud-annotations/src/test/resources/TestCommandContainer.java @@ -0,0 +1,6 @@ +import cloud.commandframework.annotations.processing.CommandContainer; + +@CommandContainer +public class TestCommandContainer { + +} diff --git a/cloud-annotations/src/test/resources/TestCommandContainer2.java b/cloud-annotations/src/test/resources/TestCommandContainer2.java new file mode 100644 index 000000000..51eba07fe --- /dev/null +++ b/cloud-annotations/src/test/resources/TestCommandContainer2.java @@ -0,0 +1,10 @@ +import cloud.commandframework.annotations.AnnotationParser; +import cloud.commandframework.annotations.processing.CommandContainer; + +@CommandContainer +public class TestCommandContainer2 { + + public TestCommandContainer2(final AnnotationParser parser) { + // ... + } +} diff --git a/examples/example-bukkit/build.gradle.kts b/examples/example-bukkit/build.gradle.kts index 0e4c2a391..ffb61ebc7 100644 --- a/examples/example-bukkit/build.gradle.kts +++ b/examples/example-bukkit/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { implementation(libs.adventurePlatformBukkit) /* Bukkit */ compileOnly(libs.bukkit) + /* Annotation processing */ + annotationProcessor(project(":cloud-annotations")) } tasks { diff --git a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExampleCommandContainer.java b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExampleCommandContainer.java new file mode 100644 index 000000000..38eec7378 --- /dev/null +++ b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExampleCommandContainer.java @@ -0,0 +1,53 @@ +// +// MIT License +// +// Copyright (c) 2021 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.examples.bukkit; + +import cloud.commandframework.annotations.AnnotationParser; +import cloud.commandframework.annotations.CommandMethod; +import cloud.commandframework.annotations.processing.CommandContainer; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; + +@CommandContainer +public final class ExampleCommandContainer { + + /** + * The constructor. {@link AnnotationParser} is an optional parameter. + * + * @param parser the parser + */ + public ExampleCommandContainer(final @NonNull AnnotationParser parser) { + // Woo... + } + + /** + * This one gets parsed automatically! + * + * @param sender the sender + */ + @CommandMethod("container") + public void containerCommand(final CommandSender sender) { + sender.sendMessage("This is sent from a container!!"); + } +} diff --git a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java index 28a25a838..ac7fdcd14 100644 --- a/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java +++ b/examples/example-bukkit/src/main/java/cloud/commandframework/examples/bukkit/ExamplePlugin.java @@ -235,6 +235,12 @@ private void constructCommands() { // Parse all @CommandMethod-annotated methods // this.annotationParser.parse(this); + // Parse all @CommandContainer-annotated classes + try { + this.annotationParser.parseContainers(); + } catch (final Exception e) { + e.printStackTrace(); + } // // Base command builder // diff --git a/gradle/libs.versions.yml b/gradle/libs.versions.yml index 1b2762220..94f639188 100644 --- a/gradle/libs.versions.yml +++ b/gradle/libs.versions.yml @@ -54,6 +54,7 @@ versions: mockitoKotlin : 4.0.0 mockitoJupiter: 4.5.1 truth : 1.1.3 + compileTesting: 0.19 # build-logic indra: 2.1.1 @@ -223,6 +224,10 @@ dependencies: group: com.google.truth.extensions name: truth-java8-extension version: { ref: truth } + compileTesting: + group: com.google.testing.compile + name: compile-testing + version: { ref: compileTesting } # build-logic indraCommon: