AnnotationUtil.java

package net.morimekta.testing.junit5;

import org.junit.jupiter.api.extension.ExtensionContext;

import java.lang.annotation.Annotation;
import java.util.Optional;
import java.util.stream.Stream;

public final class AnnotationUtil {
    public static boolean isAnnotationPresent(ExtensionContext context, Class<? extends Annotation> annotation) {
        return context.getTestMethod()
                      .map(m -> m.isAnnotationPresent(annotation))
                      .orElseGet(() -> context.getTestClass()
                                              .map(c -> isAnnotationPresent(c, annotation))
                                              .orElse(false));
    }

    public static <T extends Annotation> Optional<T> getTopAnnotation(
            ExtensionContext context, Class<T> annotation) {
        return context
                .getTestMethod()
                .map(m -> m.getDeclaredAnnotation(annotation))
                .or(() -> context.getTestClass().flatMap(t -> getTopAnnotation(t, annotation)));
    }

    public static <T extends Annotation> Stream<T> getAnnotationsBottomUp(
            ExtensionContext context, Class<T> annotation) {
        return Stream.concat(
                context.getTestClass()
                       .stream()
                       .flatMap(t -> getAnnotationsBottomUp(t, annotation)),
                context.getTestMethod()
                       .map(m -> m.getDeclaredAnnotation(annotation))
                       .stream());
    }

    // --- Private ---

    private static boolean isAnnotationPresent(Class<?> type, Class<? extends Annotation> annotation) {
        return type.isAnnotationPresent(annotation) ||
               Optional.ofNullable(type.getSuperclass())
                       .map(c -> isAnnotationPresent(c, annotation))
                       .orElse(false);
    }

    private static <T extends Annotation> Optional<T> getTopAnnotation(
            Class<?> type, Class<T> annotation) {
        return Optional.ofNullable(type.getDeclaredAnnotation(annotation))
                       .or(() -> Optional.ofNullable(type.getSuperclass())
                                         .flatMap(t -> getTopAnnotation(t, annotation)));
    }

    private static <T extends Annotation> Stream<T> getAnnotationsBottomUp(
            Class<?> type, Class<T> annotation) {
        return Stream.concat(
                Optional.ofNullable(type.getSuperclass())
                        .stream()
                        .flatMap(t -> getAnnotationsBottomUp(t, annotation)),
                Optional.ofNullable(type.getDeclaredAnnotation(annotation))
                        .stream());
    }

    private AnnotationUtil() {}
}