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.).
This commit is contained in:
parent
d613fd0208
commit
f1582fb64e
15 changed files with 543 additions and 2 deletions
|
|
@ -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 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))
|
- 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: Annotation string processors ([#353](https://github.com/Incendo/cloud/pull/353))
|
||||||
|
- Annotations: `@CommandContainer` annotation processing
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Core: Fix missing caption registration for the regex caption ([#351](https://github.com/Incendo/cloud/pull/351))
|
- Core: Fix missing caption registration for the regex caption ([#351](https://github.com/Incendo/cloud/pull/351))
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,6 @@ plugins {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.cloudCore)
|
implementation(projects.cloudCore)
|
||||||
|
|
||||||
|
testImplementation(libs.compileTesting)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import cloud.commandframework.annotations.injection.ParameterInjectorRegistry;
|
||||||
import cloud.commandframework.annotations.injection.RawArgs;
|
import cloud.commandframework.annotations.injection.RawArgs;
|
||||||
import cloud.commandframework.annotations.parsers.MethodArgumentParser;
|
import cloud.commandframework.annotations.parsers.MethodArgumentParser;
|
||||||
import cloud.commandframework.annotations.parsers.Parser;
|
import cloud.commandframework.annotations.parsers.Parser;
|
||||||
|
import cloud.commandframework.annotations.processing.CommandContainerProcessor;
|
||||||
import cloud.commandframework.annotations.specifier.Completions;
|
import cloud.commandframework.annotations.specifier.Completions;
|
||||||
import cloud.commandframework.annotations.suggestions.MethodSuggestionsProvider;
|
import cloud.commandframework.annotations.suggestions.MethodSuggestionsProvider;
|
||||||
import cloud.commandframework.annotations.suggestions.Suggestions;
|
import cloud.commandframework.annotations.suggestions.Suggestions;
|
||||||
|
|
@ -48,16 +49,21 @@ import cloud.commandframework.extra.confirmation.CommandConfirmationManager;
|
||||||
import cloud.commandframework.meta.CommandMeta;
|
import cloud.commandframework.meta.CommandMeta;
|
||||||
import cloud.commandframework.meta.SimpleCommandMeta;
|
import cloud.commandframework.meta.SimpleCommandMeta;
|
||||||
import io.leangen.geantyref.TypeToken;
|
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.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Parameter;
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
@ -66,6 +72,8 @@ import java.util.Set;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
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.NonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
|
@ -318,13 +326,64 @@ public final class AnnotationParser<C> {
|
||||||
return Arrays.stream(strings).map(this::processString).toArray(String[]::new);
|
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<C>> parseContainers() throws Exception {
|
||||||
|
final List<Command<C>> commands = new LinkedList<>();
|
||||||
|
|
||||||
|
final List<String> 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
|
* 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 instance Instance to scan
|
||||||
* @param <T> Type of the instance
|
* @param <T> Type of the instance
|
||||||
* @return Collection of parsed annotations
|
* @return Collection of parsed commands
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"deprecation", "unchecked", "rawtypes"})
|
@SuppressWarnings({"deprecation", "unchecked", "rawtypes"})
|
||||||
public <T> @NonNull Collection<@NonNull Command<C>> parse(final @NonNull T instance) {
|
public <T> @NonNull Collection<@NonNull Command<C>> parse(final @NonNull T instance) {
|
||||||
|
|
|
||||||
|
|
@ -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}.
|
||||||
|
* <p>
|
||||||
|
* If using <i>cloud-annotations</i> 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.
|
||||||
|
* <p>
|
||||||
|
* Every class annotated with {@link CommandContainer} needs to be {@code public}, and it
|
||||||
|
* also needs to have one of the following:
|
||||||
|
* <ul>
|
||||||
|
* <li>A {@code public} no-arg constructor</li>
|
||||||
|
* <li>A {@code public} constructor with {@link AnnotationParser} as the sole parameter</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* <b>NOTE:</b> For container parsing to work, you need to make sure that <i>cloud-annotations</i> is added
|
||||||
|
* as an annotation processor.
|
||||||
|
*/
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface CommandContainer {
|
||||||
|
String ANNOTATION_PATH = "cloud.commandframework.annotations.processing.CommandContainer";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<? extends TypeElement> annotations,
|
||||||
|
final @NonNull RoundEnvironment roundEnv
|
||||||
|
) {
|
||||||
|
final List<String> validTypes = new ArrayList<>();
|
||||||
|
|
||||||
|
final Set<? extends Element> 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<String> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Void, Void> {
|
||||||
|
|
||||||
|
private static final Collection<String> PERMITTED_CONSTRUCTOR_PARAMETER_TYPES = Arrays.asList(
|
||||||
|
"cloud.commandframework.annotations.AnnotationParser"
|
||||||
|
);
|
||||||
|
|
||||||
|
private final ProcessingEnvironment processingEnvironment;
|
||||||
|
private final Collection<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Annotation-processing related classes.
|
||||||
|
*
|
||||||
|
* @since 1.7.0
|
||||||
|
*/
|
||||||
|
package cloud.commandframework.annotations.processing;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
cloud.commandframework.annotations.processing.CommandContainerProcessor
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import cloud.commandframework.annotations.processing.CommandContainer;
|
||||||
|
|
||||||
|
@CommandContainer
|
||||||
|
public class TestCommandContainer {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import cloud.commandframework.annotations.AnnotationParser;
|
||||||
|
import cloud.commandframework.annotations.processing.CommandContainer;
|
||||||
|
|
||||||
|
@CommandContainer
|
||||||
|
public class TestCommandContainer2 {
|
||||||
|
|
||||||
|
public TestCommandContainer2(final AnnotationParser parser) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,8 @@ dependencies {
|
||||||
implementation(libs.adventurePlatformBukkit)
|
implementation(libs.adventurePlatformBukkit)
|
||||||
/* Bukkit */
|
/* Bukkit */
|
||||||
compileOnly(libs.bukkit)
|
compileOnly(libs.bukkit)
|
||||||
|
/* Annotation processing */
|
||||||
|
annotationProcessor(project(":cloud-annotations"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|
|
||||||
|
|
@ -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<CommandSender> 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!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -235,6 +235,12 @@ public final class ExamplePlugin extends JavaPlugin {
|
||||||
// Parse all @CommandMethod-annotated methods
|
// Parse all @CommandMethod-annotated methods
|
||||||
//
|
//
|
||||||
this.annotationParser.parse(this);
|
this.annotationParser.parse(this);
|
||||||
|
// Parse all @CommandContainer-annotated classes
|
||||||
|
try {
|
||||||
|
this.annotationParser.parseContainers();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
//
|
//
|
||||||
// Base command builder
|
// Base command builder
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ versions:
|
||||||
mockitoKotlin : 4.0.0
|
mockitoKotlin : 4.0.0
|
||||||
mockitoJupiter: 4.5.1
|
mockitoJupiter: 4.5.1
|
||||||
truth : 1.1.3
|
truth : 1.1.3
|
||||||
|
compileTesting: 0.19
|
||||||
|
|
||||||
# build-logic
|
# build-logic
|
||||||
indra: 2.1.1
|
indra: 2.1.1
|
||||||
|
|
@ -223,6 +224,10 @@ dependencies:
|
||||||
group: com.google.truth.extensions
|
group: com.google.truth.extensions
|
||||||
name: truth-java8-extension
|
name: truth-java8-extension
|
||||||
version: { ref: truth }
|
version: { ref: truth }
|
||||||
|
compileTesting:
|
||||||
|
group: com.google.testing.compile
|
||||||
|
name: compile-testing
|
||||||
|
version: { ref: compileTesting }
|
||||||
|
|
||||||
# build-logic
|
# build-logic
|
||||||
indraCommon:
|
indraCommon:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue