1 /* 2 * Copyright (C) 2018 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.reflect.Constructor; 19 import java.lang.reflect.Field; 20 import java.lang.reflect.Method; 21 import java.util.HashMap; 22 import java.util.Map; 23 24 /** 25 * Base class for those that process a set of API definition files and perform some checking on 26 * them. 27 */ 28 public abstract class AbstractApiChecker { 29 30 final ResultObserver resultObserver; 31 32 final ClassProvider classProvider; 33 34 AbstractApiChecker(ClassProvider classProvider, ResultObserver resultObserver) { 35 this.classProvider = classProvider; 36 this.resultObserver = resultObserver; 37 } 38 39 /** 40 * Checks test class's name, modifier, fields, constructors, and 41 * methods. 42 */ 43 public void checkSignatureCompliance(JDiffClassDescription classDescription) { 44 Class<?> runtimeClass = checkClassCompliance(classDescription); 45 if (runtimeClass != null) { 46 checkFieldsCompliance(classDescription, runtimeClass); 47 checkConstructorCompliance(classDescription, runtimeClass); 48 checkMethodCompliance(classDescription, runtimeClass); 49 } 50 } 51 52 /** 53 * Checks that the class found through reflection matches the 54 * specification from the API xml file. 55 * 56 * @param classDescription a description of a class in an API. 57 */ 58 @SuppressWarnings("unchecked") 59 private Class<?> checkClassCompliance(JDiffClassDescription classDescription) { 60 try { 61 Class<?> runtimeClass = ReflectionHelper 62 .findRequiredClass(classDescription, classProvider); 63 64 if (runtimeClass == null) { 65 // No class found, notify the observer according to the class type 66 resultObserver.notifyFailure(FailureType.missing(classDescription), 67 classDescription.getAbsoluteClassName(), 68 "Classloader is unable to find " + classDescription 69 .getAbsoluteClassName()); 70 71 return null; 72 } 73 74 if (!checkClass(classDescription, runtimeClass)) { 75 return null; 76 } 77 78 return runtimeClass; 79 } catch (Exception e) { 80 LogHelper.loge("Got exception when checking class compliance", e); 81 resultObserver.notifyFailure( 82 FailureType.CAUGHT_EXCEPTION, 83 classDescription.getAbsoluteClassName(), 84 "Exception while checking class compliance!"); 85 return null; 86 } 87 } 88 89 /** 90 * Perform any additional checks that can only be done after all api files have been processed. 91 */ 92 public abstract void checkDeferred(); 93 94 /** 95 * Implement to provide custom check of the supplied class description. 96 * 97 * <p>This should not peform checks on the members, those will be done separately depending 98 * on the result of this method. 99 * 100 * @param classDescription the class description to check 101 * @param runtimeClass the runtime class corresponding to the class description. 102 * @return true if the checks passed and the members should now be checked. 103 */ 104 protected abstract boolean checkClass(JDiffClassDescription classDescription, 105 Class<?> runtimeClass); 106 107 108 /** 109 * Checks all fields in test class for compliance with the API xml. 110 * 111 * @param classDescription a description of a class in an API. 112 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 113 */ 114 @SuppressWarnings("unchecked") 115 private void checkFieldsCompliance(JDiffClassDescription classDescription, 116 Class<?> runtimeClass) { 117 // A map of field name to field of the fields contained in runtimeClass. 118 Map<String, Field> classFieldMap = buildFieldMap(runtimeClass); 119 for (JDiffClassDescription.JDiffField field : classDescription.getFields()) { 120 try { 121 Field f = classFieldMap.get(field.mName); 122 if (f == null) { 123 resultObserver.notifyFailure(FailureType.MISSING_FIELD, 124 field.toReadableString(classDescription.getAbsoluteClassName()), 125 "No field with correct signature found:" + 126 field.toSignatureString()); 127 } else { 128 checkField(classDescription, runtimeClass, field, f); 129 } 130 } catch (Exception e) { 131 LogHelper.loge("Got exception when checking field compliance", e); 132 resultObserver.notifyFailure( 133 FailureType.CAUGHT_EXCEPTION, 134 field.toReadableString(classDescription.getAbsoluteClassName()), 135 "Exception while checking field compliance"); 136 } 137 } 138 } 139 140 /** 141 * Scan a class (an its entire inheritance chain) for fields. 142 * 143 * @return a {@link Map} of fieldName to {@link Field} 144 */ 145 private static Map<String, Field> buildFieldMap(Class testClass) { 146 Map<String, Field> fieldMap = new HashMap<>(); 147 // Scan the superclass 148 if (testClass.getSuperclass() != null) { 149 fieldMap.putAll(buildFieldMap(testClass.getSuperclass())); 150 } 151 152 // Scan the interfaces 153 for (Class interfaceClass : testClass.getInterfaces()) { 154 fieldMap.putAll(buildFieldMap(interfaceClass)); 155 } 156 157 // Check the fields in the test class 158 for (Field field : testClass.getDeclaredFields()) { 159 fieldMap.put(field.getName(), field); 160 } 161 162 return fieldMap; 163 } 164 165 protected abstract void checkField(JDiffClassDescription classDescription, 166 Class<?> runtimeClass, 167 JDiffClassDescription.JDiffField fieldDescription, Field field); 168 169 170 /** 171 * Checks whether the constructor parsed from API xml file and 172 * Java reflection are compliant. 173 * 174 * @param classDescription a description of a class in an API. 175 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 176 */ 177 @SuppressWarnings("unchecked") 178 private void checkConstructorCompliance(JDiffClassDescription classDescription, 179 Class<?> runtimeClass) { 180 for (JDiffClassDescription.JDiffConstructor con : classDescription.getConstructors()) { 181 try { 182 Constructor<?> c = ReflectionHelper.findMatchingConstructor(runtimeClass, con); 183 if (c == null) { 184 resultObserver.notifyFailure(FailureType.MISSING_CONSTRUCTOR, 185 con.toReadableString(classDescription.getAbsoluteClassName()), 186 "No constructor with correct signature found:" + 187 con.toSignatureString()); 188 } else { 189 checkConstructor(classDescription, runtimeClass, con, c); 190 } 191 } catch (Exception e) { 192 LogHelper.loge("Got exception when checking constructor compliance", e); 193 resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, 194 con.toReadableString(classDescription.getAbsoluteClassName()), 195 "Exception while checking constructor compliance!"); 196 } 197 } 198 } 199 200 protected abstract void checkConstructor(JDiffClassDescription classDescription, 201 Class<?> runtimeClass, 202 JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor); 203 204 /** 205 * Checks that the method found through reflection matches the 206 * specification from the API xml file. 207 * 208 * @param classDescription a description of a class in an API. 209 * @param runtimeClass the runtime class corresponding to {@code classDescription}. 210 */ 211 private void checkMethodCompliance(JDiffClassDescription classDescription, 212 Class<?> runtimeClass) { 213 for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) { 214 try { 215 216 Method m = ReflectionHelper.findMatchingMethod(runtimeClass, method); 217 if (m == null) { 218 resultObserver.notifyFailure(FailureType.MISSING_METHOD, 219 method.toReadableString(classDescription.getAbsoluteClassName()), 220 "No method with correct signature found:" + 221 method.toSignatureString()); 222 } else { 223 checkMethod(classDescription, runtimeClass, method, m); 224 } 225 } catch (Exception e) { 226 LogHelper.loge("Got exception when checking method compliance", e); 227 resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, 228 method.toReadableString(classDescription.getAbsoluteClassName()), 229 "Exception while checking method compliance!"); 230 } 231 } 232 } 233 234 protected abstract void checkMethod(JDiffClassDescription classDescription, 235 Class<?> runtimeClass, 236 JDiffClassDescription.JDiffMethod methodDescription, Method method); 237 } 238