diff options
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.java | 119 |
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(); + } + } } |