blob: 54640a358f2201aee389faba77856cacdb1be78b [file] [log] [blame]
/*
* Copyright (C) 2017 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.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.List;
/**
* Uses reflection to obtain runtime representations of elements in the API.
*/
public class ReflectionHelper {
private static void loge(String message, Exception exception) {
System.err.println(String.format("%s: %s", message, exception));
}
/**
* Finds the reflected class for the class under test.
*
* @param classDescription the description of the class to find.
* @return the reflected class, or null if not found.
*/
@SuppressWarnings("unchecked")
public static Class<?> findMatchingClass(JDiffClassDescription classDescription)
throws ClassNotFoundException {
// even if there are no . in the string, split will return an
// array of length 1
String shortClassName = classDescription.getShortClassName();
String[] classNameParts = shortClassName.split("\\.");
String packageName = classDescription.getPackageName();
String currentName = packageName + "." + classNameParts[0];
Class<?> clz = Class.forName(
currentName, false, ReflectionHelper.class.getClassLoader());
String absoluteClassName = classDescription.getAbsoluteClassName();
if (clz.getCanonicalName().equals(absoluteClassName)) {
return clz;
}
// Then it must be an inner class.
for (int x = 1; x < classNameParts.length; x++) {
clz = findInnerClassByName(clz, classNameParts[x]);
if (clz == null) {
return null;
}
if (clz.getCanonicalName().equals(absoluteClassName)) {
return clz;
}
}
return null;
}
/**
* Searches the class for the specified inner class.
*
* @param clz the class to search in.
* @param simpleName the simpleName of the class to find
* @return the class being searched for, or null if it can't be found.
*/
private static Class<?> findInnerClassByName(Class<?> clz, String simpleName) {
for (Class<?> c : clz.getDeclaredClasses()) {
if (c.getSimpleName().equals(simpleName)) {
return c;
}
}
return null;
}
/**
* Searches available constructor.
*
* @param runtimeClass the class in which to search.
* @param jdiffDes constructor description to find.
* @return reflected constructor, or null if not found.
*/
@SuppressWarnings("unchecked")
static Constructor<?> findMatchingConstructor(Class<?> runtimeClass,
JDiffClassDescription.JDiffConstructor jdiffDes) {
for (Constructor<?> c : runtimeClass.getDeclaredConstructors()) {
Type[] params = c.getGenericParameterTypes();
boolean isStaticClass = ((runtimeClass.getModifiers() & Modifier.STATIC) != 0);
int startParamOffset = 0;
int numberOfParams = params.length;
// non-static inner class -> skip implicit parent pointer
// as first arg
if (runtimeClass.isMemberClass() && !isStaticClass && params.length >= 1) {
startParamOffset = 1;
--numberOfParams;
}
ArrayList<String> jdiffParamList = jdiffDes.mParamList;
if (jdiffParamList.size() == numberOfParams) {
boolean isFound = true;
// i counts jdiff params, j counts reflected params
int i = 0;
int j = startParamOffset;
while (i < jdiffParamList.size()) {
if (!compareParam(jdiffParamList.get(i), params[j])) {
isFound = false;
break;
}
++i;
++j;
}
if (isFound) {
return c;
}
}
}
return null;
}
/**
* Compares the parameter from the API and the parameter from
* reflection.
*
* @param jdiffParam param parsed from the API xml file.
* @param reflectionParamType param gotten from the Java reflection.
* @return True if the two params match, otherwise return false.
*/
private static boolean compareParam(String jdiffParam, Type reflectionParamType) {
if (jdiffParam == null) {
return false;
}
String reflectionParam = typeToString(reflectionParamType);
// Most things aren't varargs, so just do a simple compare
// first.
if (jdiffParam.equals(reflectionParam)) {
return true;
}
// Check for varargs. jdiff reports varargs as ..., while
// reflection reports them as []
int jdiffParamEndOffset = jdiffParam.indexOf("...");
int reflectionParamEndOffset = reflectionParam.indexOf("[]");
if (jdiffParamEndOffset != -1 && reflectionParamEndOffset != -1) {
jdiffParam = jdiffParam.substring(0, jdiffParamEndOffset);
reflectionParam = reflectionParam.substring(0, reflectionParamEndOffset);
return jdiffParam.equals(reflectionParam);
}
return false;
}
/**
* Finds the reflected method specified by the method description.
*
* @param runtimeClass the class in which to search.
* @param method description of the method to find
* @return the reflected method, or null if not found.
*/
@SuppressWarnings("unchecked")
static Method findMatchingMethod(Class<?> runtimeClass,
JDiffClassDescription.JDiffMethod method) {
Method[] methods = runtimeClass.getDeclaredMethods();
for (Method m : methods) {
if (matches(method, m)) {
return m;
}
}
return null;
}
/**
* Checks if the two types of methods are the same.
*
* @param jDiffMethod the jDiffMethod to compare
* @param reflectedMethod the reflected method to compare
* @return true, if both methods are the same
*/
static boolean matches(JDiffClassDescription.JDiffMethod jDiffMethod,
Method reflectedMethod) {
// If the method names aren't equal, the methods can't match.
if (!jDiffMethod.mName.equals(reflectedMethod.getName())) {
return false;
}
String jdiffReturnType = jDiffMethod.mReturnType;
String reflectionReturnType = typeToString(reflectedMethod.getGenericReturnType());
List<String> jdiffParamList = jDiffMethod.mParamList;
// Next, compare the return types of the two methods. If
// they aren't equal, the methods can't match.
if (!jdiffReturnType.equals(reflectionReturnType)) {
return false;
}
Type[] params = reflectedMethod.getGenericParameterTypes();
// Next, check the method parameters. If they have different
// parameter lengths, the two methods can't match.
if (jdiffParamList.size() != params.length) {
return false;
}
boolean piecewiseParamsMatch = true;
// Compare method parameters piecewise and return true if they all match.
for (int i = 0; i < jdiffParamList.size(); i++) {
piecewiseParamsMatch &= compareParam(jdiffParamList.get(i), params[i]);
}
if (piecewiseParamsMatch) {
return true;
}
/* NOTE: There are cases where piecewise method parameter checking
* fails even though the strings are equal, so compare entire strings
* against each other. This is not done by default to avoid a
* TransactionTooLargeException.
* Additionally, this can fail anyway due to extra
* information dug up by reflection.
*
* TODO: fix parameter equality checking and reflection matching
* See https://b.corp.google.com/issues/27726349
*/
StringBuilder reflectedMethodParams = new StringBuilder("");
StringBuilder jdiffMethodParams = new StringBuilder("");
for (int i = 0; i < jdiffParamList.size(); i++) {
jdiffMethodParams.append(jdiffParamList.get(i));
reflectedMethodParams.append(params[i]);
}
String jDiffFName = jdiffMethodParams.toString();
String refName = reflectedMethodParams.toString();
return jDiffFName.equals(refName);
}
/**
* Converts WildcardType array into a jdiff compatible string..
* This is a helper function for typeToString.
*
* @param types array of types to format.
* @return the jdiff formatted string.
*/
private static String concatWildcardTypes(Type[] types) {
StringBuilder sb = new StringBuilder();
int elementNum = 0;
for (Type t : types) {
sb.append(typeToString(t));
if (++elementNum < types.length) {
sb.append(" & ");
}
}
return sb.toString();
}
/**
* Converts a Type into a jdiff compatible String. The returned
* types from this function should match the same Strings that
* jdiff is providing to us.
*
* @param type the type to convert.
* @return the jdiff formatted string.
*/
private static String typeToString(Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
StringBuilder sb = new StringBuilder();
sb.append(typeToString(pt.getRawType()));
sb.append("<");
int elementNum = 0;
Type[] types = pt.getActualTypeArguments();
for (Type t : types) {
sb.append(typeToString(t));
if (++elementNum < types.length) {
sb.append(", ");
}
}
sb.append(">");
return sb.toString();
} else if (type instanceof TypeVariable) {
return ((TypeVariable<?>) type).getName();
} else if (type instanceof Class) {
return ((Class<?>) type).getCanonicalName();
} else if (type instanceof GenericArrayType) {
String typeName = typeToString(((GenericArrayType) type).getGenericComponentType());
return typeName + "[]";
} else if (type instanceof WildcardType) {
WildcardType wt = (WildcardType) type;
Type[] lowerBounds = wt.getLowerBounds();
if (lowerBounds.length == 0) {
String name = "? extends " + concatWildcardTypes(wt.getUpperBounds());
// Special case for ?
if (name.equals("? extends java.lang.Object")) {
return "?";
} else {
return name;
}
} else {
String name = concatWildcardTypes(wt.getUpperBounds()) +
" super " +
concatWildcardTypes(wt.getLowerBounds());
// Another special case for ?
name = name.replace("java.lang.Object", "?");
return name;
}
} else {
throw new RuntimeException("Got an unknown java.lang.Type");
}
}
}