Allow for recursive annotations (#97)

Co-authored-by: Mariell <proximyst@proximyst.com>
This commit is contained in:
Alexander Söderberg 2020-10-24 19:28:56 +02:00
parent e26d01388d
commit a68bc0bea7
3 changed files with 99 additions and 6 deletions

View file

@ -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)

View file

@ -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(

View file

@ -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 {
}
} }