/* * 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); } }