blob: be2c1b1c87a8fbb368184617a441abb50a35beb9 [file] [log] [blame]
/*
* Copyright (C) 2010 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 com.android.apkcheck;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Container representing a class or interface with fields and methods.
*/
public class ClassInfo {
private String mName;
// methods are hashed on name:descriptor
private HashMap<String,MethodInfo> mMethodList;
// fields are hashed on name:type
private HashMap<String,FieldInfo> mFieldList;
private String mSuperclassName;
// is this a static inner class?
private String mIsStatic;
// holds the name of the superclass and all declared interfaces
private ArrayList<String> mSuperNames;
// is this an enumerated type?
private boolean mIsEnum;
// is this an annotation type?
private boolean mIsAnnotation;
private boolean mFlattening = false;
private boolean mFlattened = false;
/**
* Constructs a new ClassInfo with the provided class name.
*
* @param className Binary class name without the package name,
* e.g. "AlertDialog$Builder".
* @param superclassName Fully-qualified binary or non-binary superclass
* name (e.g. "java.lang.Enum").
* @param isStatic Class static attribute, may be "true", "false", or null.
*/
public ClassInfo(String className, String superclassName, String isStatic) {
mName = className;
mMethodList = new HashMap<String,MethodInfo>();
mFieldList = new HashMap<String,FieldInfo>();
mSuperNames = new ArrayList<String>();
mIsStatic = isStatic;
/*
* Record the superclass name, and add it to the interface list
* since we'll need to do the same "flattening" work on it.
*
* Interfaces and java.lang.Object have a null value.
*/
if (superclassName != null) {
mSuperclassName = superclassName;
mSuperNames.add(superclassName);
}
}
/**
* Returns the name of the class.
*/
public String getName() {
return mName;
}
/**
* Returns the name of the superclass.
*/
public String getSuperclassName() {
return mSuperclassName;
}
/**
* Returns the "static" attribute.
*
* This is actually tri-state:
* "true" means it is static
* "false" means it's not static
* null means it's unknown
*
* The "unknown" state is associated with the APK input, while the
* known states are from the public API definition.
*
* This relates to the handling of the "secret" first parameter to
* constructors of non-static inner classes.
*/
public String getStatic() {
return mIsStatic;
}
/**
* Returns whether or not this class is an enumerated type.
*/
public boolean isEnum() {
assert mFlattened;
return mIsEnum;
}
/**
* Returns whether or not this class is an annotation type.
*/
public boolean isAnnotation() {
assert mFlattened;
return mIsAnnotation;
}
/**
* Adds a field to the list.
*/
public void addField(FieldInfo fieldInfo) {
mFieldList.put(fieldInfo.getNameAndType(), fieldInfo);
}
/**
* Retrives a field from the list.
*
* @param nameAndType fieldName:type
*/
public FieldInfo getField(String nameAndType) {
return mFieldList.get(nameAndType);
}
/**
* Returns an iterator over all known fields.
*/
public Iterator<FieldInfo> getFieldIterator() {
return mFieldList.values().iterator();
}
/**
* Adds a method to the list.
*/
public void addMethod(MethodInfo methInfo) {
mMethodList.put(methInfo.getNameAndDescriptor(), methInfo);
}
/**
* Returns an iterator over all known methods.
*/
public Iterator<MethodInfo> getMethodIterator() {
return mMethodList.values().iterator();
}
/**
* Retrieves a method from the list.
*
* @param nameAndDescr methodName:descriptor
*/
public MethodInfo getMethod(String nameAndDescr) {
return mMethodList.get(nameAndDescr);
}
/**
* Retrieves a method from the list, matching on the part of the key
* before the return type.
*
* The API file doesn't include an entry for a method that overrides
* a method in the superclass. Ordinarily this is a good thing, but
* if the override uses a covariant return type then the reference
* to it in the APK won't match.
*
* @param nameAndDescr methodName:descriptor
*/
public MethodInfo getMethodIgnoringReturn(String nameAndDescr) {
String shortKey = nameAndDescr.substring(0, nameAndDescr.indexOf(')')+1);
Iterator<MethodInfo> iter = getMethodIterator();
while (iter.hasNext()) {
MethodInfo methInfo = iter.next();
String nad = methInfo.getNameAndDescriptor();
if (nad.startsWith(shortKey))
return methInfo;
}
return null;
}
/**
* Returns true if the method and field lists are empty.
*/
public boolean hasNoFieldMethod() {
return mMethodList.size() == 0 && mFieldList.size() == 0;
}
/**
* Adds an interface to the list of classes implemented by this class.
*/
public void addInterface(String interfaceName) {
mSuperNames.add(interfaceName);
}
/**
* Flattens a class. This involves copying all methods and fields
* declared by the superclass and interfaces (and, recursively, their
* superclasses and interfaces) into the local structure.
*
* The public API file must be fully parsed before calling here.
*
* This also detects if we're an Enum or Annotation.
*/
public void flattenClass(ApiList apiList) {
if (mFlattened)
return;
/*
* Recursive class definitions aren't allowed in Java code, but
* there could be one in the API definition file.
*/
if (mFlattening) {
throw new RuntimeException("Recursive invoke; current class is "
+ mName);
}
mFlattening = true;
/*
* Normalize the ambiguous types. This requires regenerating the
* field and method lists, because the signature is used as the
* hash table key.
*/
normalizeTypes(apiList);
/*
* Figure out if this class is an enumerated type.
*/
mIsEnum = "java.lang.Enum".equals(mSuperclassName);
/*
* Figure out if this class is an annotation type. We expect it
* to extend Object, implement java.lang.annotation.Annotation,
* and declare no fields or methods. (If the API XML file is
* fixed, it will declare methods; but at that point having special
* handling for annotations will be unnecessary.)
*/
if ("java.lang.Object".equals(mSuperclassName) &&
mSuperNames.contains("java.lang.annotation.Annotation") &&
hasNoFieldMethod())
{
mIsAnnotation = true;
}
/*
* Flatten our superclass and interfaces.
*/
for (int i = 0; i < mSuperNames.size(); i++) {
/*
* The contents of mSuperNames are in an ambiguous form.
* Normalize it to binary form before working with it.
*/
String interfaceName = TypeUtils.ambiguousToBinaryName(mSuperNames.get(i),
apiList);
ClassInfo classInfo = lookupClass(interfaceName, apiList);
if (classInfo == null) {
ApkCheck.apkWarning("Class " + interfaceName +
" not found (super of " + mName + ")");
continue;
}
/* flatten it */
classInfo.flattenClass(apiList);
/* copy everything from it in here */
mergeFrom(classInfo);
}
mFlattened = true;
}
/**
* Normalizes the type names used in field and method descriptors.
*
* We call the field/method normalization function, which updates how
* it thinks of itself (and may be called multiple times from different
* classes). We then have to re-add it to the hash map because the
* key may have changed. (We're using an iterator, so we create a
* new hashmap and replace the old.)
*/
private void normalizeTypes(ApiList apiList) {
Iterator<String> keyIter;
HashMap<String,FieldInfo> tmpFieldList = new HashMap<String,FieldInfo>();
keyIter = mFieldList.keySet().iterator();
while (keyIter.hasNext()) {
String key = keyIter.next();
FieldInfo fieldInfo = mFieldList.get(key);
fieldInfo.normalizeType(apiList);
tmpFieldList.put(fieldInfo.getNameAndType(), fieldInfo);
}
mFieldList = tmpFieldList;
HashMap<String,MethodInfo> tmpMethodList = new HashMap<String,MethodInfo>();
keyIter = mMethodList.keySet().iterator();
while (keyIter.hasNext()) {
String key = keyIter.next();
MethodInfo methodInfo = mMethodList.get(key);
methodInfo.normalizeTypes(apiList);
tmpMethodList.put(methodInfo.getNameAndDescriptor(), methodInfo);
}
mMethodList = tmpMethodList;
}
/**
* Merges the fields and methods from "otherClass" into this class.
*
* Redundant entries will be merged. We don't specify who the winner
* will be.
*/
private void mergeFrom(ClassInfo otherClass) {
/*System.out.println("merging into " + getName() + ": fields=" +
mFieldList.size() + "/" + otherClass.mFieldList.size() +
", methods=" +
mMethodList.size() + "/" + otherClass.mMethodList.size());*/
mFieldList.putAll(otherClass.mFieldList);
mMethodList.putAll(otherClass.mMethodList);
/*System.out.println(" now fields=" + mFieldList.size() +
", methods=" + mMethodList.size());*/
}
/**
* Finds the named class in the ApiList.
*
* @param className Fully-qualified dot notation (e.g. "java.lang.String")
* @param apiList The hierarchy to search in.
* @return The class or null if not found.
*/
private static ClassInfo lookupClass(String fullname, ApiList apiList) {
String packageName = TypeUtils.packageNameOnly(fullname);
String className = TypeUtils.classNameOnly(fullname);
PackageInfo pkg = apiList.getPackage(packageName);
if (pkg == null)
return null;
return pkg.getClass(className);
}
}