aboutsummaryrefslogtreecommitdiff
path: root/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java
diff options
context:
space:
mode:
Diffstat (limited to 'junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java')
-rw-r--r--junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java119
1 files changed, 117 insertions, 2 deletions
diff --git a/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java b/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java
index f2a8c73..5586d7b 100644
--- a/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java
+++ b/junit4/src/main/java/com/google/testing/junit/testparameterinjector/GenericParameterContext.java
@@ -16,24 +16,86 @@ package com.google.testing.junit.testparameterinjector;
import static com.google.common.collect.Iterables.getOnlyElement;
+import com.google.common.base.Function;
import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import java.lang.annotation.Annotation;
+import java.lang.annotation.Repeatable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.Proxy;
import java.util.NoSuchElementException;
/** A value class that contains extra information about the context of a field or parameter. */
final class GenericParameterContext {
private final ImmutableList<Annotation> annotationsOnParameter;
+
+ /** Same contract as #getAnnotations */
+ private final Function<Class<? extends Annotation>, ImmutableList<? extends Annotation>>
+ getAnnotationsFunction;
+
private final Class<?> testClass;
- GenericParameterContext(ImmutableList<Annotation> annotationsOnParameter, Class<?> testClass) {
+ private GenericParameterContext(
+ ImmutableList<Annotation> annotationsOnParameter,
+ Function<Class<? extends Annotation>, ImmutableList<? extends Annotation>>
+ getAnnotationsFunction,
+ Class<?> testClass) {
this.annotationsOnParameter = annotationsOnParameter;
+ this.getAnnotationsFunction = getAnnotationsFunction;
this.testClass = testClass;
}
+ // Field.getAnnotationsByType() is not available on old Android SDKs. There is a fallback in that
+ // case in this method.
+ @SuppressWarnings("AndroidJdkLibsChecker")
+ static GenericParameterContext create(Field field, Class<?> testClass) {
+ return new GenericParameterContext(
+ ImmutableList.copyOf(field.getAnnotations()),
+ /* getAnnotationsFunction= */ annotationType -> {
+ try {
+ return ImmutableList.copyOf(field.getAnnotationsByType(annotationType));
+ } catch (NoSuchMethodError ignored) {
+ return getAnnotationsFallback(
+ ImmutableList.copyOf(field.getAnnotations()), annotationType);
+ }
+ },
+ testClass);
+ }
+
+ // Parameter is not available on old Android SDKs, and isn't desugared. That's why this method
+ // should only be called with a fallback.
+ @SuppressWarnings("AndroidJdkLibsChecker")
+ static GenericParameterContext create(Parameter parameter, Class<?> testClass) {
+ return new GenericParameterContext(
+ ImmutableList.copyOf(parameter.getAnnotations()),
+ /* getAnnotationsFunction= */ annotationType ->
+ ImmutableList.copyOf(parameter.getAnnotationsByType(annotationType)),
+ testClass);
+ }
+
+ static GenericParameterContext createWithRepeatableAnnotationsFallback(
+ Annotation[] annotationsOnParameter, Class<?> testClass) {
+ return new GenericParameterContext(
+ ImmutableList.copyOf(annotationsOnParameter),
+ /* getAnnotationsFunction= */ annotationType ->
+ getAnnotationsFallback(ImmutableList.copyOf(annotationsOnParameter), annotationType),
+ testClass);
+ }
+
+ static GenericParameterContext createWithoutParameterAnnotations(Class<?> testClass) {
+ return new GenericParameterContext(
+ /* annotationsOnParameter= */ ImmutableList.of(),
+ /* getAnnotationsFunction= */ annotationType ->
+ getAnnotationsFallback(ImmutableList.of(), annotationType),
+ testClass);
+ }
+
/**
* Returns the only annotation with the given type on the field or parameter.
*
@@ -49,7 +111,15 @@ final class GenericParameterContext {
.toList());
}
- // TODO: b/317524353 - Add support for repeated annotations
+ /**
+ * Returns the annotations with the given type on the field or parameter.
+ *
+ * <p>Returns an empty list if this there is no annotation with the given type.
+ */
+ @SuppressWarnings("unchecked") // Safe because of the getAnnotationsFunction contract
+ <A extends Annotation> ImmutableList<A> getAnnotations(Class<A> annotationType) {
+ return (ImmutableList<A>) getAnnotationsFunction.apply(annotationType);
+ }
/** The class that contains the test that is currently being run. */
Class<?> testClass() {
@@ -73,4 +143,49 @@ final class GenericParameterContext {
.join(Joiner.on(',')),
testClass().getSimpleName());
}
+
+ private static ImmutableList<Annotation> getAnnotationsFallback(
+ ImmutableList<Annotation> annotationsOnParameter,
+ Class<? extends Annotation> annotationType) {
+ ImmutableList<Annotation> candidates =
+ FluentIterable.from(annotationsOnParameter)
+ .filter(annotation -> annotation.annotationType().equals(annotationType))
+ .toList();
+ if (candidates.isEmpty() && getContainerType(annotationType).isPresent()) {
+ ImmutableList<Annotation> containerAnnotations =
+ getAnnotationsFallback(annotationsOnParameter, getContainerType(annotationType).get());
+ if (containerAnnotations.size() == 1) {
+ Annotation containerAnnotation = getOnlyElement(containerAnnotations);
+ try {
+ Method annotationValueMethod =
+ containerAnnotation.annotationType().getDeclaredMethod("value");
+ annotationValueMethod.setAccessible(true);
+ return ImmutableList.copyOf(
+ (Annotation[])
+ Proxy.getInvocationHandler(containerAnnotation)
+ .invoke(containerAnnotation, annotationValueMethod, null));
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return ImmutableList.of();
+ } else {
+ return candidates;
+ }
+ }
+
+ private static Optional<Class<? extends Annotation>> getContainerType(
+ Class<? extends Annotation> annotationType) {
+ try {
+ Repeatable repeatable = annotationType.getAnnotation(Repeatable.class);
+ if (repeatable == null) {
+ return Optional.absent();
+ } else {
+ return Optional.of(repeatable.value());
+ }
+ } catch (NoClassDefFoundError ignored) {
+ // If @Repeatable does not exist, then there is no container type by definition
+ return Optional.absent();
+ }
+ }
}