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