All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.junitpioneer.internal.PioneerAnnotationUtils Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show newest version
/*
 * Copyright 2016-2022 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * http://www.eclipse.org/legal/epl-v20.html
 */

package org.junitpioneer.internal;

import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.platform.commons.support.AnnotationSupport;
import org.junitpioneer.jupiter.cartesian.CartesianArgumentsSource;

/**
 * Pioneer-internal utility class to handle annotations.
 * DO NOT USE THIS CLASS - IT MAY CHANGE SIGNIFICANTLY IN ANY MINOR UPDATE.
 *
 * It uses the following terminology to describe annotations that are not
 * immediately present on an element:
 *
 * 
    *
  • indirectly present if a supertype of the element is annotated
  • *
  • meta-present if an annotation that is present on the element is itself annotated
  • *
  • enclosing-present if an enclosing type (think opposite of * {@link org.junit.jupiter.api.Nested @Nested}) is annotated
  • *
* * All of the above mechanisms apply recursively, meaning that, e.g., for an annotation to be * meta-present it can present on an annotation that is present on another annotation * that is present on the element. */ public class PioneerAnnotationUtils { private PioneerAnnotationUtils() { // private constructor to prevent instantiation of utility class } /** * Determines whether an annotation of the specified {@code annotationType} is either * present, indirectly present, meta-present, or * enclosing-present on the test element (method or class) belonging to the * specified {@code context}. */ public static boolean isAnnotationPresent(ExtensionContext context, Class annotationType) { return findClosestEnclosingAnnotation(context, annotationType).isPresent(); } /** * Determines whether an annotation of the specified repeatable {@code annotationType} * is either present, indirectly present, meta-present, or * enclosing-present on the test element (method or class) belonging to the specified * {@code context}. */ public static boolean isAnyRepeatableAnnotationPresent(ExtensionContext context, Class annotationType) { return findClosestEnclosingRepeatableAnnotations(context, annotationType).iterator().hasNext(); } /** * Returns the specified annotation if it is either present, meta-present, * enclosing-present, or indirectly present on the test element (method or class) belonging * to the specified {@code context}. If the annotations are present on more than one enclosing type, * the closest ones are returned. */ public static Optional findClosestEnclosingAnnotation(ExtensionContext context, Class annotationType) { return findAnnotations(context, annotationType, false, false).findFirst(); } /** * Returns the specified repeatable annotations if they are either present, * indirectly present, meta-present, or enclosing-present on the test * element (method or class) belonging to the specified {@code context}. If the annotations are * present on more than one enclosing type, the instances on the closest one are returned. */ public static Stream findClosestEnclosingRepeatableAnnotations(ExtensionContext context, Class annotationType) { return findAnnotations(context, annotationType, true, false); } /** * Returns the specified annotations if they are either present, indirectly present, * meta-present, or enclosing-present on the test element (method or class) belonging * to the specified {@code context}. If the annotations are present on more than one enclosing type, * all instances are returned. */ public static Stream findAllEnclosingAnnotations(ExtensionContext context, Class annotationType) { return findAnnotations(context, annotationType, false, true); } /** * Returns the specified repeatable annotations if they are either present, * indirectly present, meta-present, or enclosing-present on the test * element (method or class) belonging to the specified {@code context}. If the annotation is * present on more than one enclosing type, all instances are returned. */ public static Stream findAllEnclosingRepeatableAnnotations(ExtensionContext context, Class annotationType) { return findAnnotations(context, annotationType, true, true); } /** * Returns the annotations present on the {@code AnnotatedElement} * that are themselves annotated with the specified annotation. The meta-annotation can be present, * indirectly present, or meta-present. */ public static List findAnnotatedAnnotations(AnnotatedElement element, Class annotation) { boolean isRepeatable = annotation.isAnnotationPresent(Repeatable.class); return Arrays .stream(element.getDeclaredAnnotations()) // flatten @Repeatable aggregator annotations .flatMap(PioneerAnnotationUtils::flatten) .filter(a -> !(findOnType(a.annotationType(), annotation, isRepeatable, false).isEmpty())) .collect(Collectors.toList()); } private static Stream flatten(Annotation annotation) { try { if (isContainerAnnotation(annotation)) { Method value = annotation.annotationType().getDeclaredMethod("value"); Annotation[] invoke = (Annotation[]) value.invoke(annotation); return Stream.of(invoke).flatMap(PioneerAnnotationUtils::flatten); } else { return Stream.of(annotation); } } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException("Failed to flatten annotation stream.", e); //NOSONAR } } public static boolean isContainerAnnotation(Annotation annotation) { // See https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.3 try { Method value = annotation.annotationType().getDeclaredMethod("value"); return value.getReturnType().isArray() && value.getReturnType().getComponentType().isAnnotation() && isContainerAnnotationOf(annotation, value.getReturnType().getComponentType()); } catch (NoSuchMethodException e) { return false; } } private static boolean isContainerAnnotationOf(Annotation potentialContainer, Class potentialRepeatable) { Repeatable repeatable = potentialRepeatable.getAnnotation(Repeatable.class); return repeatable != null && repeatable.value().equals(potentialContainer.annotationType()); } static Stream findAnnotations(ExtensionContext context, Class annotationType, boolean findRepeated, boolean findAllEnclosing) { /* * Implementation notes: * * This method starts with the specified element and, if not happy with the results (depends on the * arguments and whether the annotation is present) kicks off a recursive search. The recursion steps * through enclosing types (if required by the arguments, thus handling _enclosing-presence_) and * eventually calls either `AnnotationSupport::findRepeatableAnnotations` or * `AnnotationSupport::findAnnotation` (depending on arguments, thus handling the repeatable case). * Both of these methods check for _meta-presence_ and _indirect presence_. */ List onMethod = context .getTestMethod() .map(method -> findOnMethod(method, annotationType, findRepeated)) .orElse(Collections.emptyList()); if (!findAllEnclosing && !onMethod.isEmpty()) return onMethod.stream(); Stream onClass = findOnOuterClasses(context.getTestClass(), annotationType, findRepeated, findAllEnclosing); return Stream.concat(onMethod.stream(), onClass); } private static List findOnMethod(Method element, Class annotationType, boolean findRepeated) { if (findRepeated) return AnnotationSupport.findRepeatableAnnotations(element, annotationType); else return AnnotationSupport .findAnnotation(element, annotationType) .map(Collections::singletonList) .orElse(Collections.emptyList()); } private static Stream findOnOuterClasses(Optional> type, Class annotationType, boolean findRepeated, boolean findAllEnclosing) { if (!type.isPresent()) return Stream.empty(); List onThisClass = Arrays.asList(type.get().getAnnotationsByType(annotationType)); if (!findAllEnclosing && !onThisClass.isEmpty()) return onThisClass.stream(); List onClass = findOnType(type.get(), annotationType, findRepeated, findAllEnclosing); Stream onParentClass = findOnOuterClasses(type.map(Class::getEnclosingClass), annotationType, findRepeated, findAllEnclosing); return Stream.concat(onClass.stream(), onParentClass); } private static List findOnType(Class element, Class annotationType, boolean findRepeated, boolean findAllEnclosing) { if (element == null || element == Object.class) return Collections.emptyList(); if (findRepeated) return AnnotationSupport.findRepeatableAnnotations(element, annotationType); List onElement = AnnotationSupport .findAnnotation(element, annotationType) .map(Collections::singletonList) .orElse(Collections.emptyList()); List onInterfaces = Arrays .stream(element.getInterfaces()) .flatMap(clazz -> findOnType(clazz, annotationType, false, findAllEnclosing).stream()) .collect(Collectors.toList()); if (!annotationType.isAnnotationPresent(Inherited.class)) { if (!findAllEnclosing) return onElement; else return Stream .of(onElement, onInterfaces) .flatMap(Collection::stream) .distinct() .collect(Collectors.toList()); } List onSuperclass = findOnType(element.getSuperclass(), annotationType, false, findAllEnclosing); return Stream .of(onElement, onInterfaces, onSuperclass) .flatMap(Collection::stream) .distinct() .collect(Collectors.toList()); } public static List findParameterArgumentsSources(Method testMethod) { return Arrays .stream(testMethod.getParameters()) .map(PioneerAnnotationUtils::collectArgumentSources) .filter(list -> !list.isEmpty()) .map(annotations -> annotations.get(0)) .collect(Collectors.toList()); } private static List collectArgumentSources(Parameter parameter) { List annotations = new ArrayList<>(); AnnotationSupport.findAnnotation(parameter, CartesianArgumentsSource.class).ifPresent(annotations::add); // ArgumentSource meta-annotations are allowed on parameters for // CartesianTest because there is no overlap with ParameterizedTest annotations.addAll(AnnotationSupport.findRepeatableAnnotations(parameter, ArgumentsSource.class)); return annotations; } public static List findMethodArgumentsSources(Method testMethod) { return Arrays .stream(testMethod.getAnnotations()) .filter(annotation -> AnnotationSupport .findAnnotation(annotation.annotationType(), CartesianArgumentsSource.class) .isPresent()) .collect(Collectors.toList()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy