1 /* 2 * Copyright (C) 2022 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 android.car.test.util; 18 19 import static com.google.common.truth.Truth.assertWithMessage; 20 21 import android.car.annotation.AddedInOrBefore; 22 23 import java.lang.reflect.Field; 24 import java.lang.reflect.Method; 25 import java.lang.reflect.Modifier; 26 import java.util.ArrayList; 27 import java.util.List; 28 29 30 // TODO(b/237565347): Refactor this class so that 'field' and 'method' code is not repeated. 31 public class AnnotationHelper { 32 checkForAnnotation(String[] classes, Class... annotationClasses)33 public static void checkForAnnotation(String[] classes, Class... annotationClasses) 34 throws Exception { 35 List<String> errorsNoAnnotation = new ArrayList<>(); 36 List<String> errorsExtraAnnotation = new ArrayList<>(); 37 38 for (int i = 0; i < classes.length; i++) { 39 String className = classes[i]; 40 Field[] fields = Class.forName(className).getDeclaredFields(); 41 for (int j = 0; j < fields.length; j++) { 42 Field field = fields[j]; 43 boolean isAnnotated = containsAddedInAnnotation(field, annotationClasses); 44 boolean isPrivate = Modifier.isPrivate(field.getModifiers()); 45 46 if (isPrivate && isAnnotated) { 47 errorsExtraAnnotation.add(className + " FIELD: " + field.getName()); 48 } 49 50 if (!isPrivate && !isAnnotated) { 51 errorsNoAnnotation.add(className + " FIELD: " + field.getName()); 52 } 53 } 54 55 Method[] methods = Class.forName(className).getDeclaredMethods(); 56 for (int j = 0; j < methods.length; j++) { 57 Method method = methods[j]; 58 59 // These are some internal methods 60 if (method.getName().contains("$")) continue; 61 62 boolean isAnnotated = containsAddedInAnnotation(method, annotationClasses); 63 boolean isPrivate = Modifier.isPrivate(method.getModifiers()); 64 65 if (isPrivate && isAnnotated) { 66 errorsExtraAnnotation.add(className + " METHOD: " + method.getName()); 67 } 68 69 if (!isPrivate && !isAnnotated) { 70 errorsNoAnnotation.add(className + " METHOD: " + method.getName()); 71 } 72 } 73 } 74 75 StringBuilder errorFlatten = new StringBuilder(); 76 if (!errorsNoAnnotation.isEmpty()) { 77 // TODO(b/240343308): remove @AddedIn once all usages have been replaced 78 errorFlatten.append("Errors:\nMissing ApiRequirements (or AddedIn) annotation for-\n"); 79 errorFlatten.append(String.join("\n", errorsNoAnnotation)); 80 } 81 82 if (!errorsExtraAnnotation.isEmpty()) { 83 // TODO(b/240343308): remove @AddedIn once all usages have been replaced 84 errorFlatten.append("\nErrors:\nApiRequirements (or AddedIn) annotation used for " 85 + "private members/methods-\n"); 86 errorFlatten.append(String.join("\n", errorsExtraAnnotation)); 87 } 88 89 assertWithMessage(errorFlatten.toString()) 90 .that(errorsExtraAnnotation.size() + errorsNoAnnotation.size()).isEqualTo(0); 91 } 92 93 @SuppressWarnings("unchecked") containsAddedInAnnotation(Field field, Class... annotationClasses)94 private static boolean containsAddedInAnnotation(Field field, Class... annotationClasses) { 95 for (int i = 0; i < annotationClasses.length; i++) { 96 if (field.getAnnotation(annotationClasses[i]) != null) { 97 validatedAddInOrBeforeAnnotation(field); 98 return true; 99 } 100 } 101 return false; 102 } 103 104 @SuppressWarnings("unchecked") containsAddedInAnnotation(Method method, Class... annotationClasses)105 private static boolean containsAddedInAnnotation(Method method, Class... annotationClasses) { 106 for (int i = 0; i < annotationClasses.length; i++) { 107 if (method.getAnnotation(annotationClasses[i]) != null) { 108 validatedAddInOrBeforeAnnotation(method); 109 return true; 110 } 111 } 112 return false; 113 } 114 validatedAddInOrBeforeAnnotation(Field field)115 private static void validatedAddInOrBeforeAnnotation(Field field) { 116 AddedInOrBefore annotation = field.getAnnotation(AddedInOrBefore.class); 117 if (annotation != null) { 118 assertWithMessage(field.getDeclaringClass() + ", field:" + field.getName() 119 + " should not use AddedInOrBefore annotation. The annotation was reserved only" 120 + " for APIs added in or before majorVersion:33, minorVersion:0") 121 .that(annotation.majorVersion()).isEqualTo(33); 122 assertWithMessage(field.getDeclaringClass() + ", field:" + field.getName() 123 + " should not use AddedInOrBefore annotation. The annotation was reserved only" 124 + " for APIs added in or before majorVersion:33, minorVersion:0") 125 .that(annotation.minorVersion()).isEqualTo(0); 126 } 127 } 128 validatedAddInOrBeforeAnnotation(Method method)129 private static void validatedAddInOrBeforeAnnotation(Method method) { 130 AddedInOrBefore annotation = method.getAnnotation(AddedInOrBefore.class); 131 if (annotation != null) { 132 assertWithMessage(method.getDeclaringClass() + ", method:" + method.getName() 133 + " should not use AddedInOrBefore annotation. The annotation was reserved only" 134 + " for APIs added in or before majorVersion:33, minorVersion:0") 135 .that(annotation.majorVersion()).isEqualTo(33); 136 assertWithMessage(method.getDeclaringClass() + ", method:" + method.getName() 137 + " should not use AddedInOrBefore annotation. The annotation was reserved only" 138 + " for APIs added in or before majorVersion:33, minorVersion:0") 139 .that(annotation.minorVersion()).isEqualTo(0); 140 } 141 } 142 } 143