blob: 75632ac8e63cf5f3fc81320ac83681d18b3bc596 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.signature.cts;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Base class for those that process a set of API definition files and perform some checking on
* them.
*/
public abstract class AbstractApiChecker {
final ResultObserver resultObserver;
final ClassProvider classProvider;
AbstractApiChecker(ClassProvider classProvider, ResultObserver resultObserver) {
this.classProvider = classProvider;
this.resultObserver = resultObserver;
}
/**
* Checks test class's name, modifier, fields, constructors, and
* methods.
*/
public void checkSignatureCompliance(JDiffClassDescription classDescription) {
Class<?> runtimeClass = checkClassCompliance(classDescription);
if (runtimeClass != null) {
checkFieldsCompliance(classDescription, runtimeClass);
checkConstructorCompliance(classDescription, runtimeClass);
checkMethodCompliance(classDescription, runtimeClass);
}
}
/**
* Checks that the class found through reflection matches the
* specification from the API xml file.
*
* @param classDescription a description of a class in an API.
*/
private Class<?> checkClassCompliance(JDiffClassDescription classDescription) {
try {
Class<?> runtimeClass = ReflectionHelper
.findRequiredClass(classDescription, classProvider);
if (runtimeClass == null) {
// No class found, notify the observer according to the class type,
// if missing a class isn't acceptable.
if (!allowMissingClass(classDescription)) {
resultObserver.notifyFailure(FailureType.missing(classDescription),
classDescription.getAbsoluteClassName(),
"Classloader is unable to find " + classDescription
.getAbsoluteClassName());
}
return null;
}
if (!checkClass(classDescription, runtimeClass)) {
return null;
}
return runtimeClass;
} catch (Exception e) {
LogHelper.loge("Got exception when checking class compliance", e);
resultObserver.notifyFailure(
FailureType.CAUGHT_EXCEPTION,
classDescription.getAbsoluteClassName(),
"Exception while checking class compliance!");
return null;
}
}
/**
* Perform any additional checks that can only be done after all api files have been processed.
*/
public abstract void checkDeferred();
/**
* Implement to provide custom check of the supplied class description.
*
* <p>This should not peform checks on the members, those will be done separately depending
* on the result of this method.
*
* @param classDescription the class description to check
* @param runtimeClass the runtime class corresponding to the class description.
* @return true if the checks passed and the members should now be checked.
*/
protected abstract boolean checkClass(JDiffClassDescription classDescription,
Class<?> runtimeClass);
/**
* Checks that a class that exists in the API xml file but that does not exist
* in the runtime is allowed or not.
*
* @param classDescription the class description that is missing.
* @return true if missing the class is acceptable.
*/
protected boolean allowMissingClass(JDiffClassDescription classDescription) {
return false;
}
/**
* Checks all fields in test class for compliance with the API xml.
*
* @param classDescription a description of a class in an API.
* @param runtimeClass the runtime class corresponding to {@code classDescription}.
*/
private void checkFieldsCompliance(JDiffClassDescription classDescription,
Class<?> runtimeClass) {
// A map of field name to field of the fields contained in runtimeClass.
Map<String, Field> classFieldMap = buildFieldMap(runtimeClass);
for (JDiffClassDescription.JDiffField field : classDescription.getFields()) {
try {
Field f = classFieldMap.get(field.mName);
if (f == null) {
resultObserver.notifyFailure(FailureType.MISSING_FIELD,
field.toReadableString(classDescription.getAbsoluteClassName()),
"No field with correct signature found:" +
field.toSignatureString());
} else {
checkField(classDescription, runtimeClass, field, f);
}
} catch (Exception e) {
LogHelper.loge("Got exception when checking field compliance", e);
resultObserver.notifyFailure(
FailureType.CAUGHT_EXCEPTION,
field.toReadableString(classDescription.getAbsoluteClassName()),
"Exception while checking field compliance");
}
}
}
/**
* Scan a class (an its entire inheritance chain) for fields.
*
* @return a {@link Map} of fieldName to {@link Field}
*/
private static Map<String, Field> buildFieldMap(Class<?> testClass) {
Map<String, Field> fieldMap = new HashMap<>();
// Scan the superclass
if (testClass.getSuperclass() != null) {
fieldMap.putAll(buildFieldMap(testClass.getSuperclass()));
}
// Scan the interfaces
for (Class<?> interfaceClass : testClass.getInterfaces()) {
fieldMap.putAll(buildFieldMap(interfaceClass));
}
// Check the fields in the test class
for (Field field : testClass.getDeclaredFields()) {
fieldMap.put(field.getName(), field);
}
return fieldMap;
}
protected abstract void checkField(JDiffClassDescription classDescription,
Class<?> runtimeClass,
JDiffClassDescription.JDiffField fieldDescription, Field field);
/**
* Checks whether the constructor parsed from API xml file and
* Java reflection are compliant.
*
* @param classDescription a description of a class in an API.
* @param runtimeClass the runtime class corresponding to {@code classDescription}.
*/
private void checkConstructorCompliance(JDiffClassDescription classDescription,
Class<?> runtimeClass) {
for (JDiffClassDescription.JDiffConstructor con : classDescription.getConstructors()) {
try {
Constructor<?> c = ReflectionHelper.findMatchingConstructor(runtimeClass, con);
if (c == null) {
resultObserver.notifyFailure(FailureType.MISSING_CONSTRUCTOR,
con.toReadableString(classDescription.getAbsoluteClassName()),
"No constructor with correct signature found:" +
con.toSignatureString());
} else {
checkConstructor(classDescription, runtimeClass, con, c);
}
} catch (Exception e) {
LogHelper.loge("Got exception when checking constructor compliance", e);
resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION,
con.toReadableString(classDescription.getAbsoluteClassName()),
"Exception while checking constructor compliance!");
}
}
}
protected abstract void checkConstructor(JDiffClassDescription classDescription,
Class<?> runtimeClass,
JDiffClassDescription.JDiffConstructor ctorDescription, Constructor<?> ctor);
/**
* Checks that the method found through reflection matches the
* specification from the API xml file.
*
* @param classDescription a description of a class in an API.
* @param runtimeClass the runtime class corresponding to {@code classDescription}.
*/
private void checkMethodCompliance(JDiffClassDescription classDescription,
Class<?> runtimeClass) {
for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
try {
Method m = ReflectionHelper.findMatchingMethod(runtimeClass, method);
if (m == null) {
resultObserver.notifyFailure(FailureType.MISSING_METHOD,
method.toReadableString(classDescription.getAbsoluteClassName()),
"No method with correct signature found, looking for:" +
method.toSignatureString());
} else {
checkMethod(classDescription, runtimeClass, method, m);
}
} catch (Exception e) {
LogHelper.loge("Got exception when checking method compliance", e);
resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION,
method.toReadableString(classDescription.getAbsoluteClassName()),
"Exception while checking method compliance!");
}
}
}
protected abstract void checkMethod(JDiffClassDescription classDescription,
Class<?> runtimeClass,
JDiffClassDescription.JDiffMethod methodDescription, Method method);
}