1 /* 2 * Copyright (C) 2017 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 package android.signature.cts; 17 18 import java.lang.annotation.Annotation; 19 import java.lang.reflect.Constructor; 20 import java.lang.reflect.Field; 21 import java.lang.reflect.Method; 22 import java.util.HashMap; 23 import java.util.Map; 24 import java.util.Set; 25 26 /** 27 * Checks that the runtime representation of a class matches the API representation of a class. 28 */ 29 public class AnnotationChecker extends AbstractApiChecker { 30 31 private final Class<? extends Annotation> annotationClass; 32 33 private final Map<String, Class<?>> annotatedClassesMap = new HashMap<>(); 34 private final Map<String, Set<Constructor<?>>> annotatedConstructorsMap = new HashMap<>(); 35 private final Map<String, Set<Method>> annotatedMethodsMap = new HashMap<>(); 36 private final Map<String, Set<Field>> annotatedFieldsMap = new HashMap<>(); 37 38 /** 39 * @param annotationName name of the annotation class for the API type (e.g. 40 * android.annotation.SystemApi) 41 */ 42 public AnnotationChecker( 43 ResultObserver resultObserver, ClassProvider classProvider, String annotationName) { 44 super(classProvider, resultObserver); 45 46 annotationClass = ReflectionHelper.getAnnotationClass(annotationName); 47 classProvider.getAllClasses().forEach(clazz -> { 48 if (clazz.isAnnotationPresent(annotationClass)) { 49 annotatedClassesMap.put(clazz.getName(), clazz); 50 } 51 Set<Constructor<?>> constructors = ReflectionHelper.getAnnotatedConstructors(clazz, 52 annotationClass); 53 if (!constructors.isEmpty()) { 54 annotatedConstructorsMap.put(clazz.getName(), constructors); 55 } 56 Set<Method> methods = ReflectionHelper.getAnnotatedMethods(clazz, annotationClass); 57 if (!methods.isEmpty()) { 58 annotatedMethodsMap.put(clazz.getName(), methods); 59 } 60 Set<Field> fields = ReflectionHelper.getAnnotatedFields(clazz, annotationClass); 61 if (!fields.isEmpty()) { 62 annotatedFieldsMap.put(clazz.getName(), fields); 63 } 64 }); 65 } 66 67 @Override 68 public void checkDeferred() { 69 for (Class<?> clazz : annotatedClassesMap.values()) { 70 resultObserver.notifyFailure(FailureType.EXTRA_CLASS, clazz.getName(), 71 "Class annotated with " + annotationClass.getName() 72 + " does not exist in the documented API"); 73 } 74 for (Set<Constructor<?>> set : annotatedConstructorsMap.values()) { 75 for (Constructor<?> c : set) { 76 resultObserver.notifyFailure(FailureType.EXTRA_METHOD, c.toString(), 77 "Constructor annotated with " + annotationClass.getName() 78 + " does not exist in the API"); 79 } 80 } 81 for (Set<Method> set : annotatedMethodsMap.values()) { 82 for (Method m : set) { 83 resultObserver.notifyFailure(FailureType.EXTRA_METHOD, m.toString(), 84 "Method annotated with " + annotationClass.getName() 85 + " does not exist in the API"); 86 } 87 } 88 for (Set<Field> set : annotatedFieldsMap.values()) { 89 for (Field f : set) { 90 resultObserver.notifyFailure(FailureType.EXTRA_FIELD, f.toString(), 91 "Field annotated with " + annotationClass.getName() 92 + " does not exist in the API"); 93 } 94 } 95 } 96 97 @Override 98 protected boolean checkClass(JDiffClassDescription classDescription, Class<?> runtimeClass) { 99 // remove the class from the set if found 100 annotatedClassesMap.remove(runtimeClass.getName()); 101 return true; 102 } 103 104 @Override 105 protected void checkField(JDiffClassDescription classDescription, Class<?> runtimeClass, 106 JDiffClassDescription.JDiffField fieldDescription, Field field) { 107 // make sure that the field (or its declaring class) is annotated 108 if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(field, annotationClass)) { 109 resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION, 110 field.toString(), 111 "Annotation " + annotationClass.getName() + " is missing"); 112 } 113 114 // remove it from the set if found in the API doc 115 Set<Field> annotatedFields = annotatedFieldsMap.get(runtimeClass.getName()); 116 if (annotatedFields != null) { 117 annotatedFields.remove(field); 118 } 119 } 120 121 @Override 122 protected void checkConstructor(JDiffClassDescription classDescription, Class<?> runtimeClass, 123 JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor) { 124 Set<Constructor<?>> annotatedConstructors = annotatedConstructorsMap 125 .get(runtimeClass.getName()); 126 127 // make sure that the constructor (or its declaring class) is annotated 128 if (annotationClass != null) { 129 if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(ctor, annotationClass)) { 130 resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION, 131 ctor.toString(), 132 "Annotation " + annotationClass.getName() + " is missing"); 133 } 134 } 135 136 // remove it from the set if found in the API doc 137 if (annotatedConstructors != null) { 138 annotatedConstructors.remove(ctor); 139 } 140 } 141 142 @Override 143 protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass, 144 JDiffClassDescription.JDiffMethod methodDescription, Method method) { 145 // make sure that the method (or its declaring class) is annotated or overriding 146 // annotated method. 147 if (!ReflectionHelper.isAnnotatedOrInAnnotatedClass(method, annotationClass) 148 && !ReflectionHelper.isOverridingAnnotatedMethod(method, 149 annotationClass)) { 150 resultObserver.notifyFailure(FailureType.MISSING_ANNOTATION, 151 method.toString(), 152 "Annotation " + annotationClass.getName() + " is missing"); 153 } 154 155 // remove it from the set if found in the API doc 156 Set<Method> annotatedMethods = annotatedMethodsMap.get(runtimeClass.getName()); 157 if (annotatedMethods != null) { 158 annotatedMethods.remove(method); 159 } 160 } 161 } 162