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 {
value()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 {
value()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 
AnnotatedElementTestSupport()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      */
checkAnnotatedElementPresentMethods( AnnotatedElement element, Class<? extends Annotation>... expectedAnnotations)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      */
checkAnnotatedElementDirectMethods( AnnotatedElement element, Class<? extends Annotation>... expectedDeclaredAnnotations)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      */
annotationsToTypes(Annotation[] annotations)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 
assertPresent(boolean present, AnnotatedElement element, Class<? extends Annotation> annotation)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 
assertDeclared(boolean present, AnnotatedElement element, Class<? extends Annotation> annotation)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
set(T... instances)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      */
assertIsAnnotationPresent( AnnotatedElement element, Class<? extends Annotation> annotationType, boolean expected)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      */
assertGetDeclaredAnnotation(AnnotatedElement annotatedElement, Class<? extends Annotation> annotationType, String expectedAnnotationString)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      */
assertGetDeclaredAnnotationsByType( AnnotatedElement annotatedElement, Class<? extends Annotation> annotationType, String[] expectedAnnotationStrings)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      */
assertGetAnnotationsByType(AnnotatedElement annotatedElement, Class<? extends Annotation> annotationType, String[] expectedAnnotationStrings)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 
assertAnnotationMatches( Annotation annotation, String expectedAnnotationString)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      */
assertAnnotationsMatch(Annotation[] annotations, String[] expectedAnnotationStrings)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 
createAnnotationTestStrings(Annotation[] annotations)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 
createAnnotationTestString(Annotation annotation)294     private static String createAnnotationTestString(Annotation annotation) {
295         return "@" + annotation.annotationType().getSimpleName()
296                 + createArgumentsTestString(annotation);
297     }
298 
createArgumentsTestString(Annotation annotation)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