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