Home | History | Annotate | Download | only in annotations
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package libcore.java.lang.reflect.annotations;
     18 
     19 import java.lang.annotation.Annotation;
     20 import java.lang.annotation.ElementType;
     21 import java.lang.annotation.Inherited;
     22 import java.lang.annotation.Repeatable;
     23 import java.lang.annotation.Retention;
     24 import java.lang.annotation.RetentionPolicy;
     25 import java.lang.annotation.Target;
     26 import java.lang.reflect.AnnotatedElement;
     27 import java.lang.reflect.Parameter;
     28 import java.util.Arrays;
     29 import java.util.HashSet;
     30 import java.util.Set;
     31 import java.util.StringJoiner;
     32 
     33 import static junit.framework.Assert.assertEquals;
     34 import static junit.framework.Assert.assertFalse;
     35 import static junit.framework.Assert.assertNotNull;
     36 import static junit.framework.Assert.assertNull;
     37 import static junit.framework.Assert.assertTrue;
     38 import static junit.framework.Assert.fail;
     39 
     40 /**
     41  * Utility methods and annotation definitions for use when testing implementations of
     42  * AnnotatedElement.
     43  *
     44  * <p>For compactness, the repeated annotation methods that take strings use a format based on Java
     45  * syntax rather than the toString() of annotations. For example, "@Repeated(1)" rather than
     46  * "@libcore.java.lang.reflect.annotations.AnnotatedElementTestSupport.Repeated(value=1)". Use
     47  * {@link #EXPECT_EMPTY} to indicate "no annotationed expected".
     48  */
     49 public class AnnotatedElementTestSupport {
     50 
     51     @Retention(RetentionPolicy.RUNTIME)
     52     public @interface AnnotationA {}
     53 
     54     @Inherited
     55     @Retention(RetentionPolicy.RUNTIME)
     56     public @interface AnnotationB {}
     57 
     58     @Retention(RetentionPolicy.RUNTIME)
     59     public @interface AnnotationC {}
     60 
     61     @Retention(RetentionPolicy.RUNTIME)
     62     public @interface AnnotationD {}
     63 
     64     @Retention(RetentionPolicy.RUNTIME)
     65     @Inherited
     66     @Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD,
     67             ElementType.PARAMETER, ElementType.PACKAGE })
     68     public @interface Container {
     69         Repeated[] value();
     70     }
     71 
     72     @Repeatable(Container.class)
     73     @Retention(RetentionPolicy.RUNTIME)
     74     @Inherited
     75     @Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD,
     76             ElementType.PARAMETER, ElementType.PACKAGE })
     77     public @interface Repeated {
     78         int value();
     79     }
     80 
     81     /**
     82      * A named constant that can be used with assert methods below that take
     83      * "String[] expectedAnnotationStrings" as their final argument to indicate "none".
     84      */
     85     public static final String[] EXPECT_EMPTY = new String[0];
     86 
     87     private AnnotatedElementTestSupport() {
     88     }
     89 
     90     /**
     91      * Test the {@link AnnotatedElement} methods associated with "presence". i.e. methods that
     92      * deal with annotations being "present" (i.e. "direct" or "inherited" annotations, not
     93      * "indirect").
     94      *
     95      * <p>Asserts that calling {@link AnnotatedElement#getAnnotations()} on the supplied element
     96      * returns annotations of the supplied expected classes.
     97      *
     98      * <p>Where the expected classes contains some subset from
     99      * {@link AnnotationA}, {@link AnnotationB}, {@link AnnotationC}, {@link AnnotationD} this
    100      * method also asserts that {@link AnnotatedElement#isAnnotationPresent(Class)} and
    101      * {@link AnnotatedElement#getAnnotation(Class)} works as expected.
    102      *
    103      * <p>This method also confirms that {@link AnnotatedElement#isAnnotationPresent(Class)} and
    104      * {@link AnnotatedElement#getAnnotation(Class)} work correctly with a {@code null} argument.
    105      */
    106     static void checkAnnotatedElementPresentMethods(
    107             AnnotatedElement element, Class<? extends Annotation>... expectedAnnotations) {
    108         Set<Class<? extends Annotation>> actualTypes = annotationsToTypes(element.getAnnotations());
    109         Set<Class<? extends Annotation>> expectedTypes = set(expectedAnnotations);
    110         assertEquals(expectedTypes, actualTypes);
    111 
    112         // getAnnotations() should be consistent with isAnnotationPresent() and getAnnotation()
    113         assertPresent(expectedTypes.contains(AnnotationA.class), element, AnnotationA.class);
    114         assertPresent(expectedTypes.contains(AnnotationB.class), element, AnnotationB.class);
    115         assertPresent(expectedTypes.contains(AnnotationC.class), element, AnnotationC.class);
    116         assertPresent(expectedTypes.contains(AnnotationD.class), element, AnnotationD.class);
    117 
    118         try {
    119             element.isAnnotationPresent(null);
    120             fail();
    121         } catch (NullPointerException expected) {
    122         }
    123 
    124         try {
    125             element.getAnnotation(null);
    126             fail();
    127         } catch (NullPointerException expected) {
    128         }
    129     }
    130 
    131     /**
    132      * Test the {@link AnnotatedElement} methods associated with "direct" annotations.
    133      *
    134      * <p>Asserts that calling {@link AnnotatedElement#getDeclaredAnnotations()} on the supplied
    135      * element returns annotations of the supplied expected classes.
    136      *
    137      * <p>Where the expected classes contains some subset from
    138      * {@link AnnotationA}, {@link AnnotationB} and {@link AnnotationC}, this method also asserts
    139      * that {@link AnnotatedElement#getDeclaredAnnotation(Class)} works as expected.
    140      *
    141      * <p>This method also confirms that {@link AnnotatedElement#isAnnotationPresent(Class)} and
    142      * {@link AnnotatedElement#getAnnotation(Class)} work correctly with a {@code null} argument.
    143      */
    144     static void checkAnnotatedElementDirectMethods(
    145             AnnotatedElement element,
    146             Class<? extends Annotation>... expectedDeclaredAnnotations) {
    147         Set<Class<? extends Annotation>> actualTypes = annotationsToTypes(element.getDeclaredAnnotations());
    148         Set<Class<? extends Annotation>> expectedTypes = set(expectedDeclaredAnnotations);
    149         assertEquals(expectedTypes, actualTypes);
    150 
    151         assertDeclared(expectedTypes.contains(AnnotationA.class), element, AnnotationA.class);
    152         assertDeclared(expectedTypes.contains(AnnotationB.class), element, AnnotationB.class);
    153         assertDeclared(expectedTypes.contains(AnnotationC.class), element, AnnotationC.class);
    154 
    155         try {
    156             element.getDeclaredAnnotation(null);
    157             fail();
    158         } catch (NullPointerException expected) {
    159         }
    160     }
    161 
    162     /**
    163      * Extracts the annotation types ({@link Annotation#annotationType()} from the supplied
    164      * annotations.
    165      */
    166     static Set<Class<? extends Annotation>> annotationsToTypes(Annotation[] annotations) {
    167         Set<Class<? extends Annotation>> result = new HashSet<Class<? extends Annotation>>();
    168         for (Annotation annotation : annotations) {
    169             result.add(annotation.annotationType());
    170         }
    171         return result;
    172     }
    173 
    174     private static void assertPresent(boolean present, AnnotatedElement element,
    175             Class<? extends Annotation> annotation) {
    176         if (present) {
    177             assertNotNull(element.getAnnotation(annotation));
    178             assertTrue(element.isAnnotationPresent(annotation));
    179         } else {
    180             assertNull(element.getAnnotation(annotation));
    181             assertFalse(element.isAnnotationPresent(annotation));
    182         }
    183     }
    184 
    185     private static void assertDeclared(boolean present, AnnotatedElement element,
    186             Class<? extends Annotation> annotation) {
    187         if (present) {
    188             assertNotNull(element.getDeclaredAnnotation(annotation));
    189         } else {
    190             assertNull(element.getDeclaredAnnotation(annotation));
    191         }
    192     }
    193 
    194     @SafeVarargs
    195     static <T> Set<T> set(T... instances) {
    196         return new HashSet<>(Arrays.asList(instances));
    197     }
    198 
    199     /**
    200      * Asserts that {@link AnnotatedElement#isAnnotationPresent(Class)} returns the expected result.
    201      */
    202     static void assertIsAnnotationPresent(
    203             AnnotatedElement element, Class<? extends Annotation> annotationType,
    204             boolean expected) {
    205         assertEquals("element.isAnnotationPresent() for " + element + " and " + annotationType,
    206                 expected, element.isAnnotationPresent(annotationType));
    207     }
    208 
    209     /**
    210      * Asserts that {@link AnnotatedElement#getDeclaredAnnotation(Class)} returns the expected
    211      * result. The result is specified using a String. See {@link AnnotatedElementTestSupport} for
    212      * the string syntax.
    213      */
    214     static void assertGetDeclaredAnnotation(AnnotatedElement annotatedElement,
    215             Class<? extends Annotation> annotationType, String expectedAnnotationString) {
    216         Annotation annotation = annotatedElement.getDeclaredAnnotation(annotationType);
    217         assertAnnotationMatches(annotation, expectedAnnotationString);
    218     }
    219 
    220     /**
    221      * Asserts that {@link AnnotatedElement#getDeclaredAnnotationsByType(Class)} returns the
    222      * expected result. The result is specified using a String. See
    223      * {@link AnnotatedElementTestSupport} for the string syntax.
    224      */
    225     static void assertGetDeclaredAnnotationsByType(
    226             AnnotatedElement annotatedElement, Class<? extends Annotation> annotationType,
    227             String[] expectedAnnotationStrings) {
    228         Annotation[] annotations = annotatedElement.getDeclaredAnnotationsByType(annotationType);
    229         assertAnnotationsMatch(annotations, expectedAnnotationStrings);
    230     }
    231 
    232     /**
    233      * Asserts that {@link AnnotatedElement#getAnnotationsByType(Class)} returns the
    234      * expected result. The result is specified using a String. See
    235      * {@link AnnotatedElementTestSupport} for the string syntax.
    236      */
    237     static void assertGetAnnotationsByType(AnnotatedElement annotatedElement,
    238             Class<? extends Annotation> annotationType, String[] expectedAnnotationStrings)
    239             throws Exception {
    240         Annotation[] annotations = annotatedElement.getAnnotationsByType(annotationType);
    241         assertAnnotationsMatch(annotations, expectedAnnotationStrings);
    242     }
    243 
    244     private static void assertAnnotationMatches(
    245             Annotation annotation, String expectedAnnotationString) {
    246         if (expectedAnnotationString == null) {
    247             assertNull(annotation);
    248         } else {
    249             assertNotNull(annotation);
    250             assertEquals(expectedAnnotationString, createAnnotationTestString(annotation));
    251         }
    252     }
    253 
    254     /**
    255      * Asserts that the supplied annotations match the expectation Strings. See
    256      * {@link AnnotatedElementTestSupport} for the string syntax.
    257      */
    258     static void assertAnnotationsMatch(Annotation[] annotations,
    259             String[] expectedAnnotationStrings) {
    260 
    261         // Due to Android's dex format insisting that Annotations are sorted by name the ordering of
    262         // annotations is determined by the (simple?) name of the Annotation, not just the order
    263         // that they are defined in the source. Tests have to be sensitive to that when handling
    264         // mixed usage of "Container" and "Repeated" - the "Container" annotations will be
    265         // discovered before "Repeated" due to their sort ordering.
    266         //
    267         // This code assumes that repeated annotations with the same name will be specified in the
    268         // source their natural sort order when attributes are considered, just to make the testing
    269         // simpler.
    270         // e.g. @Repeated(1) @Repeated(2), never @Repeated(2) @Repeated(1)
    271 
    272         // Sorting the expected and actual strings _should_ work providing the assumptions above
    273         // hold. It may mask random ordering issues but it's harder to deal with that while the
    274         // source ordering is no observed. Providing no developers are ascribing meaning to the
    275         // relative order of annotations things should be ok.
    276         Arrays.sort(expectedAnnotationStrings);
    277 
    278         String[] actualAnnotationStrings = createAnnotationTestStrings(annotations);
    279         Arrays.sort(actualAnnotationStrings);
    280 
    281         assertEquals(
    282                 Arrays.asList(expectedAnnotationStrings),
    283                 Arrays.asList(actualAnnotationStrings));
    284     }
    285 
    286     private static String[] createAnnotationTestStrings(Annotation[] annotations) {
    287         String[] annotationStrings = new String[annotations.length];
    288         for (int i = 0; i < annotations.length; i++) {
    289             annotationStrings[i] = createAnnotationTestString(annotations[i]);
    290         }
    291         return annotationStrings;
    292     }
    293 
    294     private static String createAnnotationTestString(Annotation annotation) {
    295         return "@" + annotation.annotationType().getSimpleName()
    296                 + createArgumentsTestString(annotation);
    297     }
    298 
    299     private static String createArgumentsTestString(Annotation annotation) {
    300         if (annotation instanceof Repeated) {
    301             Repeated repeated = (Repeated) annotation;
    302             return "(" + repeated.value() + ")";
    303         } else if (annotation instanceof Container) {
    304             Container container = (Container) annotation;
    305             String[] repeatedValues = createAnnotationTestStrings(container.value());
    306             StringJoiner joiner = new StringJoiner(", ", "{", "}");
    307             for (String repeatedValue : repeatedValues) {
    308                 joiner.add(repeatedValue);
    309             }
    310             String repeatedValuesString = joiner.toString();
    311             return "(" +  repeatedValuesString + ")";
    312         }
    313         return "";
    314     }
    315 }
    316