upload android base code part7
This commit is contained in:
parent
4e516ec6ed
commit
841ae54672
25229 changed files with 1709508 additions and 0 deletions
33
android/development/tools/apkcheck/Android.mk
Normal file
33
android/development/tools/apkcheck/Android.mk
Normal file
|
@ -0,0 +1,33 @@
|
|||
# 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.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# the execution script
|
||||
# ============================================================
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_IS_HOST_MODULE := true
|
||||
LOCAL_MODULE_CLASS := EXECUTABLES
|
||||
LOCAL_MODULE := apkcheck
|
||||
LOCAL_SRC_FILES := etc/apkcheck
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES := $(HOST_OUT_JAVA_LIBRARIES)/apkcheck$(COMMON_JAVA_PACKAGE_SUFFIX)
|
||||
include $(BUILD_PREBUILT)
|
||||
|
||||
# the other stuff
|
||||
# ============================================================
|
||||
subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
|
||||
src \
|
||||
))
|
||||
|
||||
include $(subdirs)
|
179
android/development/tools/apkcheck/README.txt
Normal file
179
android/development/tools/apkcheck/README.txt
Normal file
|
@ -0,0 +1,179 @@
|
|||
Android APK Checker
|
||||
|
||||
This compares the set of classes, fields, and methods used by an Android
|
||||
application against the published API. It identifies and reports the
|
||||
use of any unpublished members or methods.
|
||||
|
||||
The public API description files live in the source tree, in
|
||||
frameworks/base/api/. The tip-of-tree version is in "current.xml",
|
||||
and each officially released API has a numbered file (e.g. "6.xml").
|
||||
They're generated from the sources, and can take into acount javadoc
|
||||
annotations like "@hide" in comments.
|
||||
|
||||
The dependency set for an APK can be generated with "dexdeps". It finds
|
||||
all classes, fields, and methods that are referenced by classes.dex but not
|
||||
defined locally. The tool can't easily tell anything about a dependency
|
||||
beyond the name (e.g. whether a class is a static or non-static inner
|
||||
class), so while the output from dexdeps is similar in structure to the
|
||||
API XML file, it has much less detail.
|
||||
|
||||
|
||||
==== Usage ====
|
||||
|
||||
% apkcheck [options] public-api.xml apk1.xml ...
|
||||
|
||||
Provide the public API data file of choice, and one or more XML files
|
||||
generated by dexdeps. The time required to parse and manipulate the
|
||||
public API XML file is generally much larger than the time required to
|
||||
analyze the APK, so if you have a large set of APKs it's best to run them
|
||||
through in large batches.
|
||||
|
||||
Options:
|
||||
|
||||
--help
|
||||
Show options summary.
|
||||
|
||||
--uses-library=<lib.xml>
|
||||
Load additional public API list. This is intended for APKs that
|
||||
use "uses-library" directives to pull in external libraries. Since
|
||||
the external libraries are not part of the public API, their use
|
||||
would otherwise be flagged as illegal by apkcheck.
|
||||
|
||||
--ignore-package=<package-name>
|
||||
Ignore errors generated by references to the named package (e.g.
|
||||
"com.google.android.maps"). Warnings will be generated instead.
|
||||
Useful for ignoring references to shared library content when
|
||||
XML API data is not available.
|
||||
|
||||
--[no-]warn
|
||||
Enable or disable warning messages. These are disabled by default.
|
||||
|
||||
--[no-]error
|
||||
Enable or disable error messages. These are enabled by default. If
|
||||
you disable both warnings and errors you will only see a summary.
|
||||
|
||||
In some cases involving generic signatures it may not be possible
|
||||
to accurately reconstruct the public API. Some popular cases have
|
||||
been hard-coded into the program. They can be included by specifying
|
||||
"--uses-library=BUILTIN".
|
||||
|
||||
Example use:
|
||||
|
||||
% dexdeps out/target/product/sapphire/system/app/Gmail.apk > Gmail.apk.xml
|
||||
% apkcheck --uses-library=BUILTIN frameworks/base/api/current.xml Gmail.apk.xml
|
||||
Gmail.apk.xml: summary: 0 errors, 15 warnings
|
||||
|
||||
|
||||
==== Limitations ====
|
||||
|
||||
The API XML files have some ambiguous entries and are missing important
|
||||
pieces. A summary of the issues follows.
|
||||
|
||||
(1) Class names are not in binary form
|
||||
|
||||
Example:
|
||||
|
||||
type="android.os.Parcelable.Creator"
|
||||
|
||||
This could be a Creator class in the package android.os.Parcelable,
|
||||
or Parcelable.Creator in the package android.os. We can guess based on
|
||||
capitalization, but that's unreliable.
|
||||
|
||||
The API XML does specify each package in a <package> tag, so we should have
|
||||
the full set of packages available. From this we can remove one element
|
||||
at a time from the right until we match a known package. This will work
|
||||
unless "android.os" and "android.os.Parcelable" are both valid packages.
|
||||
|
||||
|
||||
(2) Public enums are not enumerated
|
||||
|
||||
Enumeration classes are included, and always have two methods ("valueOf"
|
||||
and "values"). What isn't included are entries for the fields representing
|
||||
the enumeration values. This makes it look like an APK is referring
|
||||
to non-public fields in the class.
|
||||
|
||||
If apkcheck sees a reference to an unknown field, and the field's defining
|
||||
class appears to be an Enum (the superclass is java.lang.Enum), we emit
|
||||
a warning instead of an error.
|
||||
|
||||
|
||||
(3) Public annotation methods are not listed
|
||||
|
||||
Annotation classes have trivial entries that show only the class name
|
||||
and "implements java.lang.annotation.Annotation". It is not possible
|
||||
to verify that a method call on an annotation is valid.
|
||||
|
||||
If apkcheck sees a method call to an unknown method, and the class appears
|
||||
to be an annotation (extends Object, implements Annotation, defines no
|
||||
fields or methods), we emit a warning instead of an error.
|
||||
|
||||
|
||||
(4) Covariant return types
|
||||
|
||||
Suppose a class defines a method "public Foo gimmeFoo()". Any subclass
|
||||
that overrides that method must also return Foo, so it would seem that
|
||||
there's no need to emit a method entry for gimmeFoo() in the subclasses.
|
||||
|
||||
However, it's possible to override gimmeFoo with "public MegaFoo
|
||||
gimmeFoo()" so long as MegaFoo is an instance of Foo. In that case it
|
||||
is necessary to emit a new method entry, but the public API XML generator
|
||||
does not.
|
||||
|
||||
If apkcheck can't find an exact match for a method reference, but can
|
||||
find a method that matches on everything but the return type, it will
|
||||
emit a warning instead of an error. (We could be more thorough and try
|
||||
to verify that the return types are related, but that's more trouble than
|
||||
it's worth.)
|
||||
|
||||
|
||||
(5) Generic signatures
|
||||
|
||||
When generic signatures are used, the public API file will contain
|
||||
entries like these:
|
||||
|
||||
<parameter name="key" type="K">
|
||||
<parameter name="others" type="E...">
|
||||
<parameter name="map" type="java.util.Map<? extends K, ? extends V>">
|
||||
|
||||
The generic types are generally indistinguishable from classes in the
|
||||
default package (i.e. that have no package name). In most cases they're
|
||||
a single letter, so apkcheck includes a kluge that converts single-letter
|
||||
class names to java.lang.Object.
|
||||
|
||||
This often works, but falls apart in a few cases. For example:
|
||||
|
||||
public <T extends Parcelable> T getParcelableExtra(String name) {
|
||||
return mExtras == null ? null : mExtras.<T>getParcelable(name);
|
||||
}
|
||||
|
||||
This is emitted as:
|
||||
|
||||
<method name="getParcelableExtra" return="T">
|
||||
|
||||
which gets converted to java.lang.Object. Unfortunately the APK wants
|
||||
a method with a more specific return type (android.os.Parcelable), so
|
||||
the lookup fails.
|
||||
|
||||
There is no way to recover the actual type, because the generic signature
|
||||
details are not present in the XML. This particular case will be handled
|
||||
as a covariant return type. When the generic type is in the parameter
|
||||
list, though, this isn't handled so easily.
|
||||
|
||||
These cases are relatively few, so they were handled by baking the
|
||||
signatures into the code (--uses-library=BUILTIN). (At some point it
|
||||
may be worthwhile to try a little harder here.)
|
||||
|
||||
|
||||
(6) Use of opaque non-public types
|
||||
|
||||
Some classes are not meant for public consumption, but are still referred
|
||||
to by application code. For example, an opaque type might be passed to
|
||||
the app as a cookie.
|
||||
|
||||
Another example is the Dalvik annotation classes, like
|
||||
dalvik.annotation.InnerClass. These are emitted by "dx", and referenced
|
||||
from the DEX file, but not intended to be used by application code.
|
||||
|
||||
If an APK refers to a non-public class, but doesn't access any fields
|
||||
or methods, a warning is emitted instead of an error.
|
||||
|
46
android/development/tools/apkcheck/etc/apkcheck
Normal file
46
android/development/tools/apkcheck/etc/apkcheck
Normal file
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Set up prog to be the path of this script, including following symlinks,
|
||||
# and set up progdir to be the fully-qualified pathname of its directory.
|
||||
prog="$0"
|
||||
while [ -h "${prog}" ]; do
|
||||
newProg=`/bin/ls -ld "${prog}"`
|
||||
newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
|
||||
if expr "x${newProg}" : 'x/' >/dev/null; then
|
||||
prog="${newProg}"
|
||||
else
|
||||
progdir=`dirname "${prog}"`
|
||||
prog="${progdir}/${newProg}"
|
||||
fi
|
||||
done
|
||||
oldwd=`pwd`
|
||||
progdir=`dirname "${prog}"`
|
||||
cd "${progdir}"
|
||||
progdir=`pwd`
|
||||
prog="${progdir}"/`basename "${prog}"`
|
||||
cd "${oldwd}"
|
||||
|
||||
libdir=`dirname $progdir`/framework
|
||||
|
||||
javaOpts=""
|
||||
while expr "x$1" : 'x-J' >/dev/null; do
|
||||
opt=`expr "$1" : '-J\(.*\)'`
|
||||
javaOpts="${javaOpts} -${opt}"
|
||||
shift
|
||||
done
|
||||
|
||||
exec java $javaOpts -jar $libdir/apkcheck.jar "$@"
|
2
android/development/tools/apkcheck/etc/manifest.txt
Normal file
2
android/development/tools/apkcheck/etc/manifest.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Manifest-Version: 1.0
|
||||
Main-Class: com.android.apkcheck.ApkCheck
|
28
android/development/tools/apkcheck/src/Android.mk
Normal file
28
android/development/tools/apkcheck/src/Android.mk
Normal file
|
@ -0,0 +1,28 @@
|
|||
# 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.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
|
||||
# apkcheck java library
|
||||
# ============================================================
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
LOCAL_JAR_MANIFEST := ../etc/manifest.txt
|
||||
|
||||
LOCAL_MODULE:= apkcheck
|
||||
|
||||
include $(BUILD_HOST_JAVA_LIBRARY)
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 org.xml.sax.*;
|
||||
import org.xml.sax.helpers.*;
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/**
|
||||
* Provides implementation for SAX parser.
|
||||
*/
|
||||
class ApiDescrHandler extends DefaultHandler {
|
||||
/*
|
||||
* Uber-container.
|
||||
*/
|
||||
private ApiList mApiList;
|
||||
|
||||
/*
|
||||
* Temporary objects, used as containers while we accumulate the
|
||||
* innards.
|
||||
*/
|
||||
private PackageInfo mCurrentPackage = null;
|
||||
private ClassInfo mCurrentClass = null;
|
||||
private MethodInfo mCurrentMethod = null;
|
||||
|
||||
/**
|
||||
* Constructs an ApiDescrHandler.
|
||||
*
|
||||
* @param fileName Source file name, used for debugging.
|
||||
*/
|
||||
public ApiDescrHandler(ApiList apiList) {
|
||||
mApiList = apiList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ApiList in its current state. Generally only
|
||||
* makes sense to call here after parsing is completed.
|
||||
*/
|
||||
public ApiList getApiList() {
|
||||
return mApiList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes start tags. If the file is malformed we will likely
|
||||
* NPE, but this is captured by the caller.
|
||||
*
|
||||
* We currently assume that packages and classes only appear once,
|
||||
* so all classes associated with a package are wrapped in a singular
|
||||
* instance of <package>. We may want to remove this assumption
|
||||
* by attempting to find an existing package/class with the same name.
|
||||
*/
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName,
|
||||
Attributes attributes) {
|
||||
|
||||
if (qName.equals("package")) {
|
||||
/* top-most element */
|
||||
mCurrentPackage = mApiList.getOrCreatePackage(
|
||||
attributes.getValue("name"));
|
||||
} else if (qName.equals("class") || qName.equals("interface")) {
|
||||
/* get class, gather fields/methods and interfaces */
|
||||
mCurrentClass = mCurrentPackage.getOrCreateClass(
|
||||
attributes.getValue("name"),
|
||||
attributes.getValue("extends"),
|
||||
attributes.getValue("static"));
|
||||
} else if (qName.equals("implements")) {
|
||||
/* add name of interface to current class */
|
||||
mCurrentClass.addInterface(attributes.getValue("name"));
|
||||
} else if (qName.equals("method")) {
|
||||
/* hold object while we gather parameters */
|
||||
mCurrentMethod = new MethodInfo(attributes.getValue("name"),
|
||||
attributes.getValue("return"));
|
||||
} else if (qName.equals("constructor")) {
|
||||
/* like "method", but has no name or return type */
|
||||
mCurrentMethod = new MethodInfo("<init>", "void");
|
||||
|
||||
/*
|
||||
* If this is a non-static inner class, we want to add the
|
||||
* "hidden" outer class parameter as the first parameter.
|
||||
* We can tell if it's an inner class because the class name
|
||||
* will include a '$' (it has been normalized already).
|
||||
*/
|
||||
String staticClass = mCurrentClass.getStatic();
|
||||
if (staticClass == null) {
|
||||
/*
|
||||
* We're parsing an APK file, which means we can't know
|
||||
* if the class we're referencing is static or not. We
|
||||
* also already have the "secret" first parameter
|
||||
* represented in the method parameter list, so we don't
|
||||
* need to insert it here.
|
||||
*/
|
||||
} else if ("false".equals(staticClass)) {
|
||||
String className = mCurrentClass.getName();
|
||||
int dollarIndex = className.indexOf('$');
|
||||
if (dollarIndex >= 0) {
|
||||
String outerClass = className.substring(0, dollarIndex);
|
||||
//System.out.println("--- inserting " +
|
||||
// mCurrentPackage.getName() + "." + outerClass +
|
||||
// " into constructor for " + className);
|
||||
mCurrentMethod.addParameter(mCurrentPackage.getName() +
|
||||
"." + outerClass);
|
||||
}
|
||||
}
|
||||
} else if (qName.equals("field")) {
|
||||
/* add to current class */
|
||||
FieldInfo fInfo = new FieldInfo(attributes.getValue("name"),
|
||||
attributes.getValue("type"));
|
||||
mCurrentClass.addField(fInfo);
|
||||
} else if (qName.equals("parameter")) {
|
||||
/* add to current method */
|
||||
mCurrentMethod.addParameter(attributes.getValue("type"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes end tags. Generally these add the under-construction
|
||||
* item to the appropriate container.
|
||||
*/
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
if (qName.equals("method") || qName.equals("constructor")) {
|
||||
/* add method to class */
|
||||
mCurrentClass.addMethod(mCurrentMethod);
|
||||
mCurrentMethod = null;
|
||||
} else if (qName.equals("class") || qName.equals("interface")) {
|
||||
mCurrentClass = null;
|
||||
} else if (qName.equals("package")) {
|
||||
mCurrentPackage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Holds a list of API members, including classes, fields, and methods.
|
||||
*/
|
||||
public class ApiList {
|
||||
private HashMap<String,PackageInfo> mPackageList;
|
||||
private String mDebugString;
|
||||
private int mWarnings, mErrors;
|
||||
|
||||
/**
|
||||
* Constructs an ApiList.
|
||||
*
|
||||
* @param debugString Identification string useful for debugging.
|
||||
*/
|
||||
public ApiList(String debugString) {
|
||||
mPackageList = new HashMap<String,PackageInfo>();
|
||||
mDebugString = debugString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source filename. Useful for debug messages only.
|
||||
*/
|
||||
public String getDebugString() {
|
||||
return mDebugString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the number of warnings associated with this API list.
|
||||
*/
|
||||
public void incrWarnings() {
|
||||
mWarnings++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the errors of warnings associated with this API list.
|
||||
*/
|
||||
public void incrErrors() {
|
||||
mErrors++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of warnings associated with this API list.
|
||||
*/
|
||||
public int getWarningCount() {
|
||||
return mWarnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of errors associated with this API list.
|
||||
*/
|
||||
public int getErrorCount() {
|
||||
return mErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the named package.
|
||||
*
|
||||
* @return the package, or null if no match was found
|
||||
*/
|
||||
public PackageInfo getPackage(String name) {
|
||||
return mPackageList.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the named package, creating it if it doesn't already
|
||||
* exist.
|
||||
*/
|
||||
public PackageInfo getOrCreatePackage(String name) {
|
||||
PackageInfo pkgInfo = mPackageList.get(name);
|
||||
if (pkgInfo == null) {
|
||||
pkgInfo = new PackageInfo(name);
|
||||
mPackageList.put(name, pkgInfo);
|
||||
}
|
||||
return pkgInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for the set of known packages.
|
||||
*/
|
||||
public Iterator<PackageInfo> getPackageIterator() {
|
||||
return mPackageList.values().iterator();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,431 @@
|
|||
/*
|
||||
* 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 org.xml.sax.*;
|
||||
import org.xml.sax.helpers.*;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
/**
|
||||
* Checks an APK's dependencies against the published API specification.
|
||||
*
|
||||
* We need to read two XML files (spec and APK) and perform some operations
|
||||
* on the elements. The file formats are similar but not identical, so
|
||||
* we distill it down to common elements.
|
||||
*
|
||||
* We may also want to read some additional API lists representing
|
||||
* libraries that would be included with a "uses-library" directive.
|
||||
*
|
||||
* For performance we want to allow processing of multiple APKs so
|
||||
* we don't have to re-parse the spec file each time.
|
||||
*/
|
||||
public class ApkCheck {
|
||||
/* keep track of current APK file name, for error messages */
|
||||
private static ApiList sCurrentApk;
|
||||
|
||||
/* show warnings? */
|
||||
private static boolean sShowWarnings = false;
|
||||
/* show errors? */
|
||||
private static boolean sShowErrors = true;
|
||||
|
||||
/* names of packages we're allowed to ignore */
|
||||
private static HashSet<String> sIgnorablePackages = new HashSet<String>();
|
||||
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
ApiList apiDescr = new ApiList("public-api");
|
||||
|
||||
if (args.length < 2) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
/* process args */
|
||||
int idx;
|
||||
for (idx = 0; idx < args.length; idx++) {
|
||||
if (args[idx].equals("--help")) {
|
||||
usage();
|
||||
return;
|
||||
} else if (args[idx].startsWith("--uses-library=")) {
|
||||
String libName = args[idx].substring(args[idx].indexOf('=')+1);
|
||||
if ("BUILTIN".equals(libName)) {
|
||||
Reader reader = Builtin.getReader();
|
||||
if (!parseXml(apiDescr, reader, "BUILTIN"))
|
||||
return;
|
||||
} else {
|
||||
if (!parseApiDescr(apiDescr, libName))
|
||||
return;
|
||||
}
|
||||
} else if (args[idx].startsWith("--ignore-package=")) {
|
||||
String pkgName = args[idx].substring(args[idx].indexOf('=')+1);
|
||||
sIgnorablePackages.add(pkgName);
|
||||
} else if (args[idx].equals("--warn")) {
|
||||
sShowWarnings = true;
|
||||
} else if (args[idx].equals("--no-warn")) {
|
||||
sShowWarnings = false;
|
||||
} else if (args[idx].equals("--error")) {
|
||||
sShowErrors = true;
|
||||
} else if (args[idx].equals("--no-error")) {
|
||||
sShowErrors = false;
|
||||
|
||||
} else if (args[idx].startsWith("--")) {
|
||||
if (args[idx].equals("--")) {
|
||||
// remainder are filenames, even if they start with "--"
|
||||
idx++;
|
||||
break;
|
||||
} else {
|
||||
// unknown option specified
|
||||
System.err.println("ERROR: unknown option " +
|
||||
args[idx] + " (use \"--help\" for usage info)");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx > args.length - 2) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
/* parse base API description */
|
||||
if (!parseApiDescr(apiDescr, args[idx++]))
|
||||
return;
|
||||
|
||||
/* "flatten" superclasses and interfaces */
|
||||
sCurrentApk = apiDescr;
|
||||
flattenInherited(apiDescr);
|
||||
|
||||
/* walk through list of libs we want to scan */
|
||||
for ( ; idx < args.length; idx++) {
|
||||
ApiList apkDescr = new ApiList(args[idx]);
|
||||
sCurrentApk = apkDescr;
|
||||
boolean success = parseApiDescr(apkDescr, args[idx]);
|
||||
if (!success) {
|
||||
if (idx < args.length-1)
|
||||
System.err.println("Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
check(apiDescr, apkDescr);
|
||||
System.out.println(args[idx] + ": summary: " +
|
||||
apkDescr.getErrorCount() + " errors, " +
|
||||
apkDescr.getWarningCount() + " warnings\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints usage statement.
|
||||
*/
|
||||
static void usage() {
|
||||
System.err.println("Android APK checker v1.0");
|
||||
System.err.println("Copyright (C) 2010 The Android Open Source Project\n");
|
||||
System.err.println("Usage: apkcheck [options] public-api.xml apk1.xml ...\n");
|
||||
System.err.println("Options:");
|
||||
System.err.println(" --help show this message");
|
||||
System.err.println(" --uses-library=lib.xml load additional public API list");
|
||||
System.err.println(" --ignore-package=pkg don't show errors for references to this package");
|
||||
System.err.println(" --[no-]warn enable or disable display of warnings");
|
||||
System.err.println(" --[no-]error enable or disable display of errors");
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the file and passes it to parseXml.
|
||||
*
|
||||
* TODO: allow '-' as an alias for stdin?
|
||||
*/
|
||||
static boolean parseApiDescr(ApiList apiList, String fileName) {
|
||||
boolean result = false;
|
||||
|
||||
try {
|
||||
FileReader fileReader = new FileReader(fileName);
|
||||
result = parseXml(apiList, fileReader, fileName);
|
||||
fileReader.close();
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error opening " + fileName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an XML file holding an API description.
|
||||
*
|
||||
* @param fileReader Data source.
|
||||
* @param apiList Container to add stuff to.
|
||||
* @param fileName Input file name, only used for debug messages.
|
||||
*/
|
||||
static boolean parseXml(ApiList apiList, Reader reader,
|
||||
String fileName) {
|
||||
//System.out.println("--- parsing " + fileName);
|
||||
try {
|
||||
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
|
||||
ApiDescrHandler handler = new ApiDescrHandler(apiList);
|
||||
xmlReader.setContentHandler(handler);
|
||||
xmlReader.setErrorHandler(handler);
|
||||
xmlReader.parse(new InputSource(reader));
|
||||
|
||||
//System.out.println("--- parsing complete");
|
||||
//dumpApi(apiList);
|
||||
return true;
|
||||
} catch (SAXParseException ex) {
|
||||
System.err.println("Error parsing " + fileName + " line " +
|
||||
ex.getLineNumber() + ": " + ex.getMessage());
|
||||
} catch (Exception ex) {
|
||||
System.err.println("Error while reading " + fileName + ": " +
|
||||
ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
// failed
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands lists of fields and methods to recursively include superclass
|
||||
* and interface entries.
|
||||
*
|
||||
* The API description files have entries for every method a class
|
||||
* declares, even if it's present in the superclass (e.g. toString()).
|
||||
* Removal of one of these methods doesn't constitute an API change,
|
||||
* though, so if we don't find a method in a class we need to hunt
|
||||
* through its superclasses.
|
||||
*
|
||||
* We can walk up the hierarchy while analyzing the target APK,
|
||||
* or we can "flatten" the methods declared by the superclasses and
|
||||
* interfaces before we begin the analysis. Expanding up front can be
|
||||
* beneficial if we're analyzing lots of APKs in one go, but detrimental
|
||||
* to startup time if we just want to look at one small APK.
|
||||
*
|
||||
* It also means filling the field/method hash tables with lots of
|
||||
* entries that never get used, possibly worsening the hash table
|
||||
* hit rate.
|
||||
*
|
||||
* We only need to do this for the public API list. The dexdeps output
|
||||
* doesn't have this sort of information anyway.
|
||||
*/
|
||||
static void flattenInherited(ApiList pubList) {
|
||||
Iterator<PackageInfo> pkgIter = pubList.getPackageIterator();
|
||||
while (pkgIter.hasNext()) {
|
||||
PackageInfo pubPkgInfo = pkgIter.next();
|
||||
|
||||
Iterator<ClassInfo> classIter = pubPkgInfo.getClassIterator();
|
||||
while (classIter.hasNext()) {
|
||||
ClassInfo pubClassInfo = classIter.next();
|
||||
|
||||
pubClassInfo.flattenClass(pubList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the APK against the public API.
|
||||
*
|
||||
* Run through and find the mismatches.
|
||||
*
|
||||
* @return true if all is well
|
||||
*/
|
||||
static boolean check(ApiList pubList, ApiList apkDescr) {
|
||||
|
||||
Iterator<PackageInfo> pkgIter = apkDescr.getPackageIterator();
|
||||
while (pkgIter.hasNext()) {
|
||||
PackageInfo apkPkgInfo = pkgIter.next();
|
||||
PackageInfo pubPkgInfo = pubList.getPackage(apkPkgInfo.getName());
|
||||
boolean badPackage = false;
|
||||
|
||||
if (pubPkgInfo == null) {
|
||||
// "illegal package" not a tremendously useful message
|
||||
//apkError("Illegal package ref: " + apkPkgInfo.getName());
|
||||
badPackage = true;
|
||||
}
|
||||
|
||||
Iterator<ClassInfo> classIter = apkPkgInfo.getClassIterator();
|
||||
while (classIter.hasNext()) {
|
||||
ClassInfo apkClassInfo = classIter.next();
|
||||
|
||||
if (badPackage) {
|
||||
/*
|
||||
* The package is not present in the public API file,
|
||||
* but simply saying "bad package" isn't all that
|
||||
* useful, so we emit the names of each of the classes.
|
||||
*/
|
||||
if (isIgnorable(apkPkgInfo)) {
|
||||
apkWarning("Ignoring class ref: " +
|
||||
apkPkgInfo.getName() + "." + apkClassInfo.getName());
|
||||
} else {
|
||||
apkError("Illegal class ref: " +
|
||||
apkPkgInfo.getName() + "." + apkClassInfo.getName());
|
||||
}
|
||||
} else {
|
||||
checkClass(pubPkgInfo, apkClassInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the class against the public API. We check the class
|
||||
* itself and then any fields and methods.
|
||||
*/
|
||||
static boolean checkClass(PackageInfo pubPkgInfo, ClassInfo classInfo) {
|
||||
|
||||
ClassInfo pubClassInfo = pubPkgInfo.getClass(classInfo.getName());
|
||||
|
||||
if (pubClassInfo == null) {
|
||||
if (isIgnorable(pubPkgInfo)) {
|
||||
apkWarning("Ignoring class ref: " +
|
||||
pubPkgInfo.getName() + "." + classInfo.getName());
|
||||
} else if (classInfo.hasNoFieldMethod()) {
|
||||
apkWarning("Hidden class referenced: " +
|
||||
pubPkgInfo.getName() + "." + classInfo.getName());
|
||||
} else {
|
||||
apkError("Illegal class ref: " +
|
||||
pubPkgInfo.getName() + "." + classInfo.getName());
|
||||
// could list specific fields/methods used
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the contents of classInfo against pubClassInfo.
|
||||
*/
|
||||
Iterator<FieldInfo> fieldIter = classInfo.getFieldIterator();
|
||||
while (fieldIter.hasNext()) {
|
||||
FieldInfo apkFieldInfo = fieldIter.next();
|
||||
String nameAndType = apkFieldInfo.getNameAndType();
|
||||
FieldInfo pubFieldInfo = pubClassInfo.getField(nameAndType);
|
||||
if (pubFieldInfo == null) {
|
||||
if (pubClassInfo.isEnum()) {
|
||||
apkWarning("Enum field ref: " + pubPkgInfo.getName() +
|
||||
"." + classInfo.getName() + "." + nameAndType);
|
||||
} else {
|
||||
apkError("Illegal field ref: " + pubPkgInfo.getName() +
|
||||
"." + classInfo.getName() + "." + nameAndType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<MethodInfo> methodIter = classInfo.getMethodIterator();
|
||||
while (methodIter.hasNext()) {
|
||||
MethodInfo apkMethodInfo = methodIter.next();
|
||||
String nameAndDescr = apkMethodInfo.getNameAndDescriptor();
|
||||
MethodInfo pubMethodInfo = pubClassInfo.getMethod(nameAndDescr);
|
||||
if (pubMethodInfo == null) {
|
||||
pubMethodInfo = pubClassInfo.getMethodIgnoringReturn(nameAndDescr);
|
||||
if (pubMethodInfo == null) {
|
||||
if (pubClassInfo.isAnnotation()) {
|
||||
apkWarning("Annotation method ref: " +
|
||||
pubPkgInfo.getName() + "." + classInfo.getName() +
|
||||
"." + nameAndDescr);
|
||||
} else {
|
||||
apkError("Illegal method ref: " + pubPkgInfo.getName() +
|
||||
"." + classInfo.getName() + "." + nameAndDescr);
|
||||
}
|
||||
} else {
|
||||
apkWarning("Possibly covariant method ref: " +
|
||||
pubPkgInfo.getName() + "." + classInfo.getName() +
|
||||
"." + nameAndDescr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the package is in the "ignored" list.
|
||||
*/
|
||||
static boolean isIgnorable(PackageInfo pkgInfo) {
|
||||
return sIgnorablePackages.contains(pkgInfo.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a warning message about an APK problem.
|
||||
*/
|
||||
public static void apkWarning(String msg) {
|
||||
if (sShowWarnings) {
|
||||
System.out.println("(warn) " + sCurrentApk.getDebugString() +
|
||||
": " + msg);
|
||||
}
|
||||
sCurrentApk.incrWarnings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints an error message about an APK problem.
|
||||
*/
|
||||
public static void apkError(String msg) {
|
||||
if (sShowErrors) {
|
||||
System.out.println(sCurrentApk.getDebugString() + ": " + msg);
|
||||
}
|
||||
sCurrentApk.incrErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively dumps the contents of the API. Sort order is not
|
||||
* specified.
|
||||
*/
|
||||
private static void dumpApi(ApiList apiList) {
|
||||
Iterator<PackageInfo> iter = apiList.getPackageIterator();
|
||||
while (iter.hasNext()) {
|
||||
PackageInfo pkgInfo = iter.next();
|
||||
dumpPackage(pkgInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpPackage(PackageInfo pkgInfo) {
|
||||
Iterator<ClassInfo> iter = pkgInfo.getClassIterator();
|
||||
System.out.println("PACKAGE " + pkgInfo.getName());
|
||||
while (iter.hasNext()) {
|
||||
ClassInfo classInfo = iter.next();
|
||||
dumpClass(classInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpClass(ClassInfo classInfo) {
|
||||
System.out.println(" CLASS " + classInfo.getName());
|
||||
Iterator<FieldInfo> fieldIter = classInfo.getFieldIterator();
|
||||
while (fieldIter.hasNext()) {
|
||||
FieldInfo fieldInfo = fieldIter.next();
|
||||
dumpField(fieldInfo);
|
||||
}
|
||||
Iterator<MethodInfo> methIter = classInfo.getMethodIterator();
|
||||
while (methIter.hasNext()) {
|
||||
MethodInfo methInfo = methIter.next();
|
||||
dumpMethod(methInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpMethod(MethodInfo methInfo) {
|
||||
System.out.println(" METHOD " + methInfo.getNameAndDescriptor());
|
||||
}
|
||||
|
||||
private static void dumpField(FieldInfo fieldInfo) {
|
||||
System.out.println(" FIELD " + fieldInfo.getNameAndType());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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.io.StringReader;
|
||||
|
||||
/**
|
||||
* Class containing "built-in" API description entries.
|
||||
*
|
||||
* There are some bugs in the API description file that we can't work around
|
||||
* (notably some ambiguity with generic types). The easiest way to cope
|
||||
* is to supply the correct definitions in an add-on file. Rather than
|
||||
* cart around an extra file, we bake them in here.
|
||||
*/
|
||||
public class Builtin {
|
||||
private Builtin() {}
|
||||
|
||||
private static final String BUILTIN =
|
||||
"<api>\n" +
|
||||
" <package name=\"java.util\">\n" +
|
||||
" <class name=\"EnumSet\"\n" +
|
||||
" extends=\"java.util.AbstractSet\">\n" +
|
||||
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
|
||||
" <parameter name=\"e\" type=\"java.lang.Enum\"/>\n" +
|
||||
" </method>\n" +
|
||||
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
|
||||
" <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
|
||||
" </method>\n" +
|
||||
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
|
||||
" <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
|
||||
" </method>\n" +
|
||||
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
|
||||
" <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e4\" type=\"java.lang.Enum\"/>\n" +
|
||||
" </method>\n" +
|
||||
" <method name=\"of\" return=\"java.util.EnumSet\">\n" +
|
||||
" <parameter name=\"e1\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e2\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e3\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e4\" type=\"java.lang.Enum\"/>\n" +
|
||||
" <parameter name=\"e5\" type=\"java.lang.Enum\"/>\n" +
|
||||
" </method>\n" +
|
||||
" </class>\n" +
|
||||
|
||||
" </package>\n" +
|
||||
" <package name=\"android.os\">\n" +
|
||||
|
||||
" <class name=\"RemoteCallbackList\"\n" +
|
||||
" extends=\"java.lang.Object\">\n" +
|
||||
" <method name=\"register\" return=\"boolean\">\n" +
|
||||
" <parameter name=\"callback\" type=\"android.os.IInterface\"/>\n" +
|
||||
" </method>\n" +
|
||||
" <method name=\"unregister\" return=\"boolean\">\n" +
|
||||
" <parameter name=\"callback\" type=\"android.os.IInterface\"/>\n" +
|
||||
" </method>\n" +
|
||||
" </class>\n" +
|
||||
|
||||
" <class name=\"AsyncTask\"\n" +
|
||||
" extends=\"java.lang.Object\">\n" +
|
||||
" <method name=\"onPostExecute\" return=\"void\">\n" +
|
||||
" <parameter name=\"result\" type=\"java.lang.Object\"/>\n" +
|
||||
" </method>\n" +
|
||||
" <method name=\"onProgressUpdate\" return=\"void\">\n" +
|
||||
" <parameter name=\"values\" type=\"java.lang.Object[]\"/>\n" +
|
||||
" </method>\n" +
|
||||
" <method name=\"execute\" return=\"android.os.AsyncTask\">\n" +
|
||||
" <parameter name=\"params\" type=\"java.lang.Object[]\"/>\n" +
|
||||
" </method>\n" +
|
||||
" </class>\n" +
|
||||
|
||||
" </package>\n" +
|
||||
" <package name=\"android.widget\">\n" +
|
||||
|
||||
" <class name=\"AutoCompleteTextView\"\n" +
|
||||
" extends=\"android.widget.EditText\">\n" +
|
||||
" <method name=\"setAdapter\" return=\"void\">\n" +
|
||||
" <parameter name=\"adapter\" type=\"android.widget.ListAdapter\"/>\n" +
|
||||
" </method>\n" +
|
||||
" </class>\n" +
|
||||
|
||||
" </package>\n" +
|
||||
"</api>\n"
|
||||
;
|
||||
|
||||
/**
|
||||
* Returns the built-in definition "file".
|
||||
*/
|
||||
public static StringReader getReader() {
|
||||
return new StringReader(BUILTIN);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Container representing a method with parameters.
|
||||
*/
|
||||
public class FieldInfo {
|
||||
private String mName;
|
||||
private String mType;
|
||||
private String mNameAndType;
|
||||
private boolean mTypeNormalized;
|
||||
|
||||
/**
|
||||
* Constructs a FieldInfo.
|
||||
*
|
||||
* @param name Field name.
|
||||
* @param type Fully-qualified binary or non-binary type name.
|
||||
*/
|
||||
public FieldInfo(String name, String type) {
|
||||
mName = name;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the combined name and type. This value is used as a hash
|
||||
* table key.
|
||||
*/
|
||||
public String getNameAndType() {
|
||||
if (mNameAndType == null)
|
||||
mNameAndType = mName + ":" + TypeUtils.typeToDescriptor(mType);
|
||||
return mNameAndType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the type used in fields.
|
||||
*/
|
||||
public void normalizeType(ApiList apiList) {
|
||||
if (!mTypeNormalized) {
|
||||
String type = TypeUtils.ambiguousToBinaryName(mType, apiList);
|
||||
if (!type.equals(mType)) {
|
||||
/* name changed, force regen on name+type */
|
||||
mType = type;
|
||||
mNameAndType = null;
|
||||
}
|
||||
mTypeNormalized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Container representing a method with parameters.
|
||||
*/
|
||||
public class MethodInfo {
|
||||
private String mName;
|
||||
private String mReturn;
|
||||
private String mNameAndDescriptor;
|
||||
private ArrayList<String> mParameters;
|
||||
private boolean mParametersNormalized;
|
||||
|
||||
/**
|
||||
* Constructs MethodInfo. Tuck the method return type away for
|
||||
* later construction of the signature.
|
||||
*/
|
||||
public MethodInfo(String name, String returnType) {
|
||||
mName = name;
|
||||
mReturn = returnType;
|
||||
mParameters = new ArrayList<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method signature. This is generated when needed.
|
||||
*/
|
||||
public String getNameAndDescriptor() {
|
||||
if (mNameAndDescriptor == null) {
|
||||
StringBuilder newSig = new StringBuilder(mName);
|
||||
newSig.append(":(");
|
||||
for (int i = 0; i < mParameters.size(); i++) {
|
||||
String humanType = mParameters.get(i);
|
||||
String sigType = TypeUtils.typeToDescriptor(humanType);
|
||||
newSig.append(sigType);
|
||||
}
|
||||
newSig.append(")");
|
||||
newSig.append(TypeUtils.typeToDescriptor(mReturn));
|
||||
mNameAndDescriptor = newSig.toString();
|
||||
}
|
||||
return mNameAndDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a parameter to the method. The "type" is a primitive or
|
||||
* object type, formatted in human-centric form. For now we just
|
||||
* store it.
|
||||
*/
|
||||
public void addParameter(String type) {
|
||||
mParameters.add(type);
|
||||
if (mNameAndDescriptor != null) {
|
||||
System.err.println("WARNING: late add of params to method");
|
||||
mNameAndDescriptor = null; // force regen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the types in parameter lists to unambiguous binary form.
|
||||
*
|
||||
* The public API file must be fully parsed before calling here,
|
||||
* because we need the full set of package names.
|
||||
*/
|
||||
public void normalizeTypes(ApiList apiList) {
|
||||
if (!mParametersNormalized) {
|
||||
mReturn = TypeUtils.ambiguousToBinaryName(mReturn, apiList);
|
||||
|
||||
for (int i = 0; i < mParameters.size(); i++) {
|
||||
String fixed = TypeUtils.ambiguousToBinaryName(mParameters.get(i),
|
||||
apiList);
|
||||
mParameters.set(i, fixed);
|
||||
}
|
||||
|
||||
mNameAndDescriptor = null; // force regen
|
||||
mParametersNormalized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Container representing a package of classes and interfaces.
|
||||
*/
|
||||
public class PackageInfo {
|
||||
private String mName;
|
||||
private HashMap<String,ClassInfo> mClassList;
|
||||
|
||||
public PackageInfo(String name) {
|
||||
mName = name;
|
||||
mClassList = new HashMap<String,ClassInfo>();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the named class.
|
||||
*
|
||||
* @return the package, or null if no match was found
|
||||
*/
|
||||
public ClassInfo getClass(String name) {
|
||||
return mClassList.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the named class, creating it if it doesn't already
|
||||
* exist.
|
||||
*
|
||||
* @param className Binary or non-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 getOrCreateClass(String className, String superclassName,
|
||||
String isStatic) {
|
||||
String fixedName = TypeUtils.simpleClassNameToBinary(className);
|
||||
ClassInfo classInfo = mClassList.get(fixedName);
|
||||
if (classInfo == null) {
|
||||
//System.out.println("--- creating entry for class " + fixedName +
|
||||
// " (super=" + superclassName + ")");
|
||||
classInfo = new ClassInfo(fixedName, superclassName, isStatic);
|
||||
mClassList.put(fixedName, classInfo);
|
||||
} else {
|
||||
//System.out.println("--- returning existing class " + name);
|
||||
}
|
||||
return classInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for the set of classes in this package.
|
||||
*/
|
||||
public Iterator<ClassInfo> getClassIterator() {
|
||||
return mClassList.values().iterator();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* 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.HashMap;
|
||||
|
||||
public class TypeUtils {
|
||||
private void TypeUtils() {}
|
||||
|
||||
/*
|
||||
* Conversions for the primitive types, as well as a few things
|
||||
* that show up a lot so we can avoid the string manipulation.
|
||||
*/
|
||||
private static final HashMap<String,String> sQuickConvert;
|
||||
static {
|
||||
sQuickConvert = new HashMap<String,String>();
|
||||
|
||||
sQuickConvert.put("boolean", "Z");
|
||||
sQuickConvert.put("byte", "B");
|
||||
sQuickConvert.put("char", "C");
|
||||
sQuickConvert.put("short", "S");
|
||||
sQuickConvert.put("int", "I");
|
||||
sQuickConvert.put("float", "F");
|
||||
sQuickConvert.put("long", "J");
|
||||
sQuickConvert.put("double", "D");
|
||||
sQuickConvert.put("void", "V");
|
||||
sQuickConvert.put("java.lang.Object", "Ljava/lang/Object;");
|
||||
sQuickConvert.put("java.lang.String", "Ljava/lang/String;");
|
||||
sQuickConvert.put("java.util.ArrayList", "Ljava/util/ArrayList;");
|
||||
sQuickConvert.put("java.util.HashMap", "Ljava/util/HashMap;");
|
||||
};
|
||||
|
||||
/*
|
||||
* Convert a human-centric type into something suitable for a method
|
||||
* signature. Examples:
|
||||
*
|
||||
* int --> I
|
||||
* float[] --> [F
|
||||
* java.lang.String --> Ljava/lang/String;
|
||||
*/
|
||||
public static String typeToDescriptor(String type) {
|
||||
String quick = sQuickConvert.get(type);
|
||||
if (quick != null)
|
||||
return quick;
|
||||
|
||||
int arrayDepth = 0;
|
||||
int firstPosn = -1;
|
||||
int posn = -1;
|
||||
while ((posn = type.indexOf('[', posn+1)) != -1) {
|
||||
if (firstPosn == -1)
|
||||
firstPosn = posn;
|
||||
arrayDepth++;
|
||||
}
|
||||
|
||||
/* if we found an array, strip the brackets off */
|
||||
if (firstPosn != -1)
|
||||
type = type.substring(0, firstPosn);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while (arrayDepth-- > 0)
|
||||
builder.append("[");
|
||||
|
||||
/* retry quick convert */
|
||||
quick = sQuickConvert.get(type);
|
||||
if (quick != null) {
|
||||
builder.append(quick);
|
||||
} else {
|
||||
builder.append("L");
|
||||
builder.append(type.replace('.', '/'));
|
||||
builder.append(";");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a "simple" class name into a "binary" class name. For
|
||||
* example:
|
||||
*
|
||||
* SharedPreferences.Editor --> SharedPreferences$Editor
|
||||
*
|
||||
* Do not use this on fully-qualified class names.
|
||||
*/
|
||||
public static String simpleClassNameToBinary(String className) {
|
||||
return className.replace('.', '$');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class name portion of a fully-qualified binary class name.
|
||||
*/
|
||||
static String classNameOnly(String typeName) {
|
||||
int start = typeName.lastIndexOf(".");
|
||||
if (start < 0) {
|
||||
return typeName;
|
||||
} else {
|
||||
return typeName.substring(start+1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package portion of a fully-qualified binary class name.
|
||||
*/
|
||||
static String packageNameOnly(String typeName) {
|
||||
int end = typeName.lastIndexOf(".");
|
||||
if (end < 0) {
|
||||
/* lives in default package */
|
||||
return "";
|
||||
} else {
|
||||
return typeName.substring(0, end);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes a full class name to binary form.
|
||||
*
|
||||
* For example, "android.view.View.OnClickListener" could be in
|
||||
* the "android.view" package or the "android.view.View" package.
|
||||
* Checking capitalization is unreliable. We do have a full list
|
||||
* of package names from the file though, so there's an excellent
|
||||
* chance that we can identify the package that way. (Of course, we
|
||||
* can only do this after we have finished parsing the file.)
|
||||
*
|
||||
* If the name has two or more dots, we need to compare successively
|
||||
* shorter strings until we find a match in the package list.
|
||||
*
|
||||
* Do not call this on previously-returned output, as that may
|
||||
* confuse the code that deals with generic signatures.
|
||||
*/
|
||||
public static String ambiguousToBinaryName(String typeName, ApiList apiList) {
|
||||
/*
|
||||
* In some cases this can be a generic signature:
|
||||
* <parameter name="collection" type="java.util.Collection<? extends E>">
|
||||
* <parameter name="interfaces" type="java.lang.Class<?>[]">
|
||||
* <parameter name="object" type="E">
|
||||
*
|
||||
* If we see a '<', strip out everything from it to the '>'. That
|
||||
* does pretty much the right thing, though we have to deal with
|
||||
* nested stuff like "<? extends Map<String>>".
|
||||
*
|
||||
* Handling the third item is ugly. If the string is a single
|
||||
* character, change it to java.lang.Object. This is generally
|
||||
* insufficient and also ambiguous with respect to classes in the
|
||||
* default package, but we don't have much choice here, and it gets
|
||||
* us through the standard collection classes. Note this is risky
|
||||
* if somebody tries to normalize a string twice, since we could be
|
||||
* "promoting" a primitive type.
|
||||
*/
|
||||
typeName = stripAngleBrackets(typeName);
|
||||
if (typeName.length() == 1) {
|
||||
//System.out.println("converting X to Object: " + typeName);
|
||||
typeName = "java.lang.Object";
|
||||
} else if (typeName.length() == 3 &&
|
||||
typeName.substring(1, 3).equals("[]")) {
|
||||
//System.out.println("converting X[] to Object[]: " + typeName);
|
||||
typeName = "java.lang.Object[]";
|
||||
} else if (typeName.length() == 4 &&
|
||||
typeName.substring(1, 4).equals("...")) {
|
||||
//System.out.println("converting X... to Object[]: " + typeName);
|
||||
typeName = "java.lang.Object[]";
|
||||
}
|
||||
|
||||
/*
|
||||
* Catch-all for varargs, which come in different varieties:
|
||||
* java.lang.Object...
|
||||
* java.lang.Class...
|
||||
* java.lang.CharSequence...
|
||||
* int...
|
||||
* Progress...
|
||||
*
|
||||
* The latter is a generic type that we didn't catch above because
|
||||
* it's not using a single-character descriptor.
|
||||
*
|
||||
* The method reference for "java.lang.Class..." will be looking
|
||||
* for java.lang.Class[], not java.lang.Object[], so we don't want
|
||||
* to do a blanket conversion. Similarly, "int..." turns into int[].
|
||||
*
|
||||
* There's not much we can do with "Progress...", unless we want
|
||||
* to write off the default package and filter out primitive types.
|
||||
* Probably easier to fix it up elsewhere.
|
||||
*/
|
||||
int ellipsisIndex = typeName.indexOf("...");
|
||||
if (ellipsisIndex >= 0) {
|
||||
String newTypeName = typeName.substring(0, ellipsisIndex) + "[]";
|
||||
//System.out.println("vararg " + typeName + " --> " + newTypeName);
|
||||
typeName = newTypeName;
|
||||
}
|
||||
|
||||
/*
|
||||
* It's possible the code that generates API definition files
|
||||
* has been fixed. If we see a '$', just return the original.
|
||||
*/
|
||||
if (typeName.indexOf('$') >= 0)
|
||||
return typeName;
|
||||
|
||||
int lastDot = typeName.lastIndexOf('.');
|
||||
if (lastDot < 0)
|
||||
return typeName;
|
||||
|
||||
/*
|
||||
* What we have looks like some variation of these:
|
||||
* package.Class
|
||||
* Class.InnerClass
|
||||
* long.package.name.Class
|
||||
* long.package.name.Class.InnerClass
|
||||
*
|
||||
* We cut it off at the last '.' and test to see if it's a known
|
||||
* package name. If not, keep moving left until we run out of dots.
|
||||
*/
|
||||
int nextDot = lastDot;
|
||||
while (nextDot >= 0) {
|
||||
String testName = typeName.substring(0, nextDot);
|
||||
if (apiList.getPackage(testName) != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
nextDot = typeName.lastIndexOf('.', nextDot-1);
|
||||
}
|
||||
|
||||
if (nextDot < 0) {
|
||||
/* no package name found, convert all dots */
|
||||
System.out.println("+++ no pkg name found on " + typeName + typeName.length());
|
||||
typeName = typeName.replace('.', '$');
|
||||
} else if (nextDot == lastDot) {
|
||||
/* class name is last element; original string is fine */
|
||||
} else {
|
||||
/* in the middle; zap the dots in the inner class name */
|
||||
String oldClassName = typeName;
|
||||
typeName = typeName.substring(0, nextDot+1) +
|
||||
typeName.substring(nextDot+1).replace('.', '$');
|
||||
//System.out.println("+++ " + oldClassName + " --> " + typeName);
|
||||
}
|
||||
|
||||
return typeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips out everything between '<' and '>'. This will handle
|
||||
* nested brackets, but we're not expecting to see multiple instances
|
||||
* in series (i.e. "<foo<bar>>" is expected, but
|
||||
* "<foo>STUFF<bar> is not).
|
||||
*
|
||||
* @return the stripped string
|
||||
*/
|
||||
private static String stripAngleBrackets(String str) {
|
||||
/*
|
||||
* Since we only expect to see one "run", we can just find the
|
||||
* first '<' and the last '>'. Ideally we'd verify that they're
|
||||
* not mismatched, but we're assuming the input file is generally
|
||||
* correct.
|
||||
*/
|
||||
int ltIndex = str.indexOf('<');
|
||||
if (ltIndex < 0)
|
||||
return str;
|
||||
|
||||
int gtIndex = str.lastIndexOf('>');
|
||||
if (gtIndex < 0) {
|
||||
System.err.println("ERROR: found '<' without '>': " + str);
|
||||
return str; // not much we can do
|
||||
}
|
||||
|
||||
return str.substring(0, ltIndex) + str.substring(gtIndex+1);
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue