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