✨ Allow for recursive annotations (#97)
Co-authored-by: Mariell <proximyst@proximyst.com>
This commit is contained in:
parent
e26d01388d
commit
a68bc0bea7
3 changed files with 99 additions and 6 deletions
|
|
@ -25,7 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Allow for combined presence flags, such that `-a -b -c` is equivalent to `-abc`
|
- Allow for combined presence flags, such that `-a -b -c` is equivalent to `-abc`
|
||||||
- Allow for class annotations as a default for when an annotation is not present on a method.
|
- Allow for class annotations as a default for when an annotation is not present on a method
|
||||||
|
- Allow for annotated annotations
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix arguments with no required children not being executors (cloud-brigadier)
|
- Fix arguments with no required children not being executors (cloud-brigadier)
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,13 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
|
@ -106,16 +108,40 @@ public final class AnnotationParser<C> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static <A extends Annotation> @Nullable A getAnnotationRecursively(
|
||||||
|
final @NonNull Annotation[] annotations,
|
||||||
|
final @NonNull Class<A> clazz,
|
||||||
|
final @NonNull Set<Class<? extends Annotation>> checkedAnnotations
|
||||||
|
) {
|
||||||
|
A innerCandidate = null;
|
||||||
|
for (final Annotation annotation : annotations) {
|
||||||
|
if (!checkedAnnotations.add(annotation.annotationType())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (annotation.annotationType().equals(clazz)) {
|
||||||
|
return (A) annotation;
|
||||||
|
}
|
||||||
|
if (annotation.annotationType().getPackage().getName().startsWith("java.lang")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final A inner = getAnnotationRecursively(annotation.annotationType().getAnnotations(), clazz, checkedAnnotations);
|
||||||
|
if (inner != null) {
|
||||||
|
innerCandidate = inner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return innerCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
static <A extends Annotation> @Nullable A getMethodOrClassAnnotation(
|
static <A extends Annotation> @Nullable A getMethodOrClassAnnotation(
|
||||||
final @NonNull Method method,
|
final @NonNull Method method,
|
||||||
final @NonNull Class<A> clazz
|
final @NonNull Class<A> clazz
|
||||||
) {
|
) {
|
||||||
if (method.isAnnotationPresent(clazz)) {
|
A annotation = getAnnotationRecursively(method.getAnnotations(), clazz, new HashSet<>());
|
||||||
return method.getAnnotation(clazz);
|
if (annotation == null) {
|
||||||
} else if (method.getDeclaringClass().isAnnotationPresent(clazz)) {
|
annotation = getAnnotationRecursively(method.getDeclaringClass().getAnnotations(), clazz, new HashSet<>());
|
||||||
return method.getDeclaringClass().getAnnotation(clazz);
|
|
||||||
}
|
}
|
||||||
return null;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
static <A extends Annotation> boolean methodOrClassHasAnnotation(
|
static <A extends Annotation> boolean methodOrClassHasAnnotation(
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -76,6 +81,34 @@ class AnnotationParserTest {
|
||||||
Assertions.assertEquals(NAMED_SUGGESTIONS, manager.suggest(new TestCommandSender(), "namedsuggestions "));
|
Assertions.assertEquals(NAMED_SUGGESTIONS, manager.suggest(new TestCommandSender(), "namedsuggestions "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAnnotationResolver() throws Exception {
|
||||||
|
final Class<AnnotatedClass> annotatedClass = AnnotatedClass.class;
|
||||||
|
final Method annotatedMethod = annotatedClass.getDeclaredMethod("annotatedMethod");
|
||||||
|
|
||||||
|
System.out.println("Looking for @CommandDescription");
|
||||||
|
final CommandDescription commandDescription = AnnotationParser.getMethodOrClassAnnotation(annotatedMethod,
|
||||||
|
CommandDescription.class);
|
||||||
|
Assertions.assertNotNull(commandDescription);
|
||||||
|
Assertions.assertEquals("Hello World!", commandDescription.value());
|
||||||
|
|
||||||
|
System.out.println("Looking for @CommandPermission");
|
||||||
|
final CommandPermission commandPermission = AnnotationParser.getMethodOrClassAnnotation(annotatedMethod,
|
||||||
|
CommandPermission.class);
|
||||||
|
Assertions.assertNotNull(commandPermission);
|
||||||
|
Assertions.assertEquals("some.permission", commandPermission.value());
|
||||||
|
|
||||||
|
System.out.println("Looking for @CommandMethod");
|
||||||
|
final CommandMethod commandMethod = AnnotationParser.getMethodOrClassAnnotation(annotatedMethod,
|
||||||
|
CommandMethod.class);
|
||||||
|
Assertions.assertNotNull(commandMethod);
|
||||||
|
Assertions.assertEquals("method", commandMethod.value());
|
||||||
|
|
||||||
|
System.out.println("Looking for @Regex");
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
final Regex regex = AnnotationParser.getMethodOrClassAnnotation(annotatedMethod, Regex.class);
|
||||||
|
}
|
||||||
|
|
||||||
@ProxiedBy("proxycommand")
|
@ProxiedBy("proxycommand")
|
||||||
@CommandMethod("test|t literal <int> [string]")
|
@CommandMethod("test|t literal <int> [string]")
|
||||||
public void testCommand(
|
public void testCommand(
|
||||||
|
|
@ -103,4 +136,37 @@ class AnnotationParserTest {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@CommandPermission("some.permission")
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
private @interface AnnotatedAnnotation {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bad1
|
||||||
|
@CommandDescription("Hello World!")
|
||||||
|
private static class AnnotatedClass {
|
||||||
|
|
||||||
|
@CommandMethod("method")
|
||||||
|
@AnnotatedAnnotation
|
||||||
|
public static void annotatedMethod() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bad2
|
||||||
|
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
private @interface Bad1 {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bad1
|
||||||
|
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
private @interface Bad2 {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue