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