summaryrefslogtreecommitdiffstats
path: root/tools/apicheck
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commitb6c1cf6de79035f58b512f4400db458c8401379a (patch)
tree68979db37c85b499bc384e4ac337ed1424baab51 /tools/apicheck
downloadbuild-b6c1cf6de79035f58b512f4400db458c8401379a.zip
build-b6c1cf6de79035f58b512f4400db458c8401379a.tar.gz
build-b6c1cf6de79035f58b512f4400db458c8401379a.tar.bz2
Initial Contribution
Diffstat (limited to 'tools/apicheck')
-rw-r--r--tools/apicheck/Android.mk44
-rw-r--r--tools/apicheck/etc/apicheck46
-rw-r--r--tools/apicheck/src/Android.mk28
-rw-r--r--tools/apicheck/src/MANIFEST.mf2
-rw-r--r--tools/apicheck/src/com/android/apicheck/AbstractMethodInfo.java24
-rw-r--r--tools/apicheck/src/com/android/apicheck/ApiCheck.java253
-rw-r--r--tools/apicheck/src/com/android/apicheck/ApiInfo.java81
-rw-r--r--tools/apicheck/src/com/android/apicheck/ClassInfo.java276
-rw-r--r--tools/apicheck/src/com/android/apicheck/ConstructorInfo.java130
-rw-r--r--tools/apicheck/src/com/android/apicheck/Errors.java151
-rw-r--r--tools/apicheck/src/com/android/apicheck/FieldInfo.java120
-rw-r--r--tools/apicheck/src/com/android/apicheck/MethodInfo.java180
-rw-r--r--tools/apicheck/src/com/android/apicheck/PackageInfo.java78
-rw-r--r--tools/apicheck/src/com/android/apicheck/ParameterInfo.java35
-rw-r--r--tools/apicheck/src/com/android/apicheck/SourcePositionInfo.java122
15 files changed, 1570 insertions, 0 deletions
diff --git a/tools/apicheck/Android.mk b/tools/apicheck/Android.mk
new file mode 100644
index 0000000..a2ff8a2
--- /dev/null
+++ b/tools/apicheck/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2007-2008 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)
+
+# We use copy-file-to-new-target so that the installed
+# script file's timestamp is at least as new as the
+# .jar file it wraps.
+
+#TODO(dbort): add a template to do this stuff; share with jx
+
+# the hat script
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := apicheck
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/apicheck$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/apicheck | $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
+
+# the other stuff
+# ============================================================
+subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
+ src \
+ ))
+
+include $(subdirs)
diff --git a/tools/apicheck/etc/apicheck b/tools/apicheck/etc/apicheck
new file mode 100644
index 0000000..9c00e25
--- /dev/null
+++ b/tools/apicheck/etc/apicheck
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# Copyright (C) 2005, 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/apicheck.jar "$@"
diff --git a/tools/apicheck/src/Android.mk b/tools/apicheck/src/Android.mk
new file mode 100644
index 0000000..c4e7c6e
--- /dev/null
+++ b/tools/apicheck/src/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2008 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)
+
+
+# apicheck java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_MODULE:= apicheck
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/tools/apicheck/src/MANIFEST.mf b/tools/apicheck/src/MANIFEST.mf
new file mode 100644
index 0000000..e6dc263
--- /dev/null
+++ b/tools/apicheck/src/MANIFEST.mf
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.apicheck.ApiCheck
diff --git a/tools/apicheck/src/com/android/apicheck/AbstractMethodInfo.java b/tools/apicheck/src/com/android/apicheck/AbstractMethodInfo.java
new file mode 100644
index 0000000..ca90820
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/AbstractMethodInfo.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+
+public interface AbstractMethodInfo {
+
+ public void addException(String exec);
+ public void addParameter(ParameterInfo p);
+
+}
diff --git a/tools/apicheck/src/com/android/apicheck/ApiCheck.java b/tools/apicheck/src/com/android/apicheck/ApiCheck.java
new file mode 100644
index 0000000..f78117c
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/ApiCheck.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.*;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Stack;
+
+public class ApiCheck {
+ // parse out and consume the -whatever command line flags
+ private static ArrayList<String[]> parseFlags(ArrayList<String> allArgs) {
+ ArrayList<String[]> ret = new ArrayList<String[]>();
+
+ int i;
+ for (i = 0; i < allArgs.size(); i++) {
+ // flags with one value attached
+ String flag = allArgs.get(i);
+ if (flag.equals("-error")
+ || flag.equals("-warning")
+ || flag.equals("-hide")) {
+ String[] arg = new String[2];
+ arg[0] = flag;
+ arg[1] = allArgs.get(++i);
+ ret.add(arg);
+ } else {
+ // we've consumed all of the -whatever args, so we're done
+ break;
+ }
+ }
+
+ // i now points to the first non-flag arg; strip what came before
+ for (; i > 0; i--) {
+ allArgs.remove(0);
+ }
+ return ret;
+ }
+
+ public static void main(String[] originalArgs) {
+ // translate to an ArrayList<String> for munging
+ ArrayList<String> args = new ArrayList<String>(originalArgs.length);
+ for (String a: originalArgs) {
+ args.add(a);
+ }
+
+ ArrayList<String[]> flags = ApiCheck.parseFlags(args);
+ for (String[] a: flags) {
+ if (a[0].equals("-error") || a[0].equals("-warning")
+ || a[0].equals("-hide")) {
+ try {
+ int level = -1;
+ if (a[0].equals("-error")) {
+ level = Errors.ERROR;
+ }
+ else if (a[0].equals("-warning")) {
+ level = Errors.WARNING;
+ }
+ else if (a[0].equals("-hide")) {
+ level = Errors.HIDDEN;
+ }
+ Errors.setErrorLevel(Integer.parseInt(a[1]), level);
+ }
+ catch (NumberFormatException e) {
+ System.err.println("Bad argument: " + a[0] + " " + a[1]);
+ System.exit(2);
+ }
+ }
+ }
+
+ String xmlFileName = args.get(0);
+ String xmlFileNameNew = args.get(1);
+ XMLReader xmlreader = null;
+ try {
+ // parse the XML files into our data structures
+ xmlreader = XMLReaderFactory.createXMLReader();
+ ApiCheck acheck = new ApiCheck();
+ MakeHandler handler = acheck.new MakeHandler();
+ xmlreader.setContentHandler(handler);
+ xmlreader.setErrorHandler(handler);
+ FileReader filereader = new FileReader(xmlFileName);
+ xmlreader.parse(new InputSource(filereader));
+ FileReader filereaderNew = new FileReader(xmlFileNameNew);
+ xmlreader.parse(new InputSource(filereaderNew));
+
+ // establish the superclass relationships
+ handler.getOldApi().resolveSuperclasses();
+ handler.getNewApi().resolveSuperclasses();
+
+ // finally, run the consistency check
+ handler.getOldApi().isConsistent(handler.getNewApi());
+
+ } catch (SAXParseException e) {
+ Errors.error(Errors.PARSE_ERROR,
+ new SourcePositionInfo(xmlFileName, e.getLineNumber(), 0),
+ e.getMessage());
+ } catch (Exception e) {
+ e.printStackTrace();
+ Errors.error(Errors.PARSE_ERROR,
+ new SourcePositionInfo(xmlFileName, 0, 0),
+ e.getMessage());
+ }
+
+ Errors.printErrors();
+ System.exit(Errors.hadError ? 1 : 0);
+ }
+
+ private class MakeHandler extends DefaultHandler {
+
+ private Integer mWarningCount;
+ private ApiInfo mOriginalApi;
+ private ApiInfo mNewApi;
+ private boolean mOldApi;
+ private PackageInfo mCurrentPackage;
+ private ClassInfo mCurrentClass;
+ private AbstractMethodInfo mCurrentMethod;
+ private ConstructorInfo mCurrentConstructor;
+ private Stack<ClassInfo> mClassScope = new Stack<ClassInfo>();
+
+
+ public MakeHandler() {
+ super();
+ mOriginalApi = new ApiInfo();
+ mNewApi = new ApiInfo();
+ mOldApi = true;
+
+ }
+
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) {
+ if (qName.equals("package")) {
+ mCurrentPackage = new PackageInfo(attributes.getValue("name"),
+ SourcePositionInfo.fromXml(attributes.getValue("source")));
+ } else if (qName.equals("class")
+ || qName.equals("interface")) {
+ // push the old outer scope for later recovery, then set
+ // up the new current class object
+ mClassScope.push(mCurrentClass);
+ mCurrentClass = new ClassInfo(attributes.getValue("name"),
+ mCurrentPackage,
+ attributes.getValue("extends") ,
+ qName.equals("interface"),
+ Boolean.valueOf(
+ attributes.getValue("abstract")),
+ Boolean.valueOf(
+ attributes.getValue("static")),
+ Boolean.valueOf(
+ attributes.getValue("final")),
+ attributes.getValue("deprecated"),
+ attributes.getValue("visibility"),
+ SourcePositionInfo.fromXml(attributes.getValue("source")),
+ mCurrentClass);
+ } else if (qName.equals("method")) {
+ mCurrentMethod = new MethodInfo(attributes.getValue("name"),
+ attributes.getValue("return") ,
+ Boolean.valueOf(
+ attributes.getValue("abstract")),
+ Boolean.valueOf(
+ attributes.getValue("native")),
+ Boolean.valueOf(
+ attributes.getValue("synchronized")),
+ Boolean.valueOf(
+ attributes.getValue("static")),
+ Boolean.valueOf(
+ attributes.getValue("final")),
+ attributes.getValue("deprecated"),
+ attributes.getValue("visibility"),
+ SourcePositionInfo.fromXml(attributes.getValue("source")),
+ mCurrentClass);
+ } else if (qName.equals("constructor")) {
+ mCurrentMethod = new ConstructorInfo(attributes.getValue("name"),
+ attributes.getValue("type") ,
+ Boolean.valueOf(
+ attributes.getValue("static")),
+ Boolean.valueOf(
+ attributes.getValue("final")),
+ attributes.getValue("deprecated"),
+ attributes.getValue("visibility"),
+ SourcePositionInfo.fromXml(attributes.getValue("source")),
+ mCurrentClass);
+ } else if (qName.equals("field")) {
+ FieldInfo fInfo = new FieldInfo(attributes.getValue("name"),
+ attributes.getValue("type") ,
+ Boolean.valueOf(
+ attributes.getValue("transient")),
+ Boolean.valueOf(
+ attributes.getValue("volatile")),
+ attributes.getValue("value"),
+ Boolean.valueOf(
+ attributes.getValue("static")),
+ Boolean.valueOf(
+ attributes.getValue("final")),
+ attributes.getValue("deprecated"),
+ attributes.getValue("visibility"),
+ SourcePositionInfo.fromXml(attributes.getValue("source")),
+ mCurrentClass);
+ mCurrentClass.addField(fInfo);
+ } else if (qName.equals("parameter")) {
+ mCurrentMethod.addParameter(new ParameterInfo(attributes.getValue("type"),
+ attributes.getValue("name")));
+ } else if (qName.equals("exception")) {
+ mCurrentMethod.addException(attributes.getValue("type"));
+ } else if (qName.equals("implements")) {
+ mCurrentClass.addInterface(attributes.getValue("name"));
+ }
+ }
+ public void endElement(String uri, String localName, String qName) {
+ if (qName.equals("method")) {
+ mCurrentClass.addMethod((MethodInfo) mCurrentMethod);
+ } else if (qName.equals("constructor")) {
+ mCurrentClass.addConstructor((ConstructorInfo) mCurrentMethod);
+ } else if (qName.equals("class")
+ || qName.equals("interface")) {
+ mCurrentPackage.addClass(mCurrentClass);
+ mCurrentClass = mClassScope.pop();
+ } else if (qName.equals("package")){
+ if (mOldApi) {
+ mOriginalApi.addPackage(mCurrentPackage);
+ } else {
+ mNewApi.addPackage(mCurrentPackage);
+ }
+ }
+ }
+ public void endDocument() {
+ mOldApi = !mOldApi;
+ }
+
+ public ApiInfo getOldApi() {
+ return mOriginalApi;
+ }
+
+ public ApiInfo getNewApi() {
+ return mNewApi;
+ }
+
+
+ }
+}
diff --git a/tools/apicheck/src/com/android/apicheck/ApiInfo.java b/tools/apicheck/src/com/android/apicheck/ApiInfo.java
new file mode 100644
index 0000000..01d8f9e
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/ApiInfo.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+import java.util.*;
+
+public class ApiInfo {
+
+ private HashMap<String, PackageInfo> mPackages;
+ private HashMap<String, ClassInfo> mAllClasses;
+
+ public ApiInfo() {
+ mPackages = new HashMap<String, PackageInfo>();
+ mAllClasses = new HashMap<String, ClassInfo>();
+ }
+
+ public boolean isConsistent(ApiInfo otherApi) {
+ boolean consistent = true;
+ for (PackageInfo pInfo : mPackages.values()) {
+ if (otherApi.getPackages().containsKey(pInfo.name())) {
+ if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()))) {
+ consistent = false;
+ }
+ } else {
+ Errors.error(Errors.REMOVED_PACKAGE, pInfo.position(),
+ "Removed package " + pInfo.name());
+ consistent = false;
+ }
+ }
+ for (PackageInfo pInfo : otherApi.mPackages.values()) {
+ if (!pInfo.isInBoth()) {
+ Errors.error(Errors.ADDED_PACKAGE, pInfo.position(),
+ "Added package " + pInfo.name());
+ consistent = false;
+ }
+ }
+ return consistent;
+ }
+
+ public HashMap<String, PackageInfo> getPackages() {
+ return mPackages;
+ }
+
+ public void addPackage(PackageInfo pInfo) {
+ // track the set of organized packages in the API
+ mPackages.put(pInfo.name(), pInfo);
+
+ // accumulate a direct map of all the classes in the API
+ for (ClassInfo cl: pInfo.allClasses().values()) {
+ mAllClasses.put(cl.qualifiedName(), cl);
+ }
+ }
+
+ public void resolveSuperclasses() {
+ for (ClassInfo cl: mAllClasses.values()) {
+ // java.lang.Object has no superclass
+ if (!cl.qualifiedName().equals("java.lang.Object")) {
+ String scName = cl.superclassName();
+ if (scName == null) {
+ scName = "java.lang.Object";
+ }
+
+ ClassInfo superclass = mAllClasses.get(scName);
+ cl.setSuperClass(superclass);
+ }
+ }
+ }
+}
diff --git a/tools/apicheck/src/com/android/apicheck/ClassInfo.java b/tools/apicheck/src/com/android/apicheck/ClassInfo.java
new file mode 100644
index 0000000..858ec8a
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/ClassInfo.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+import java.util.*;
+
+public class ClassInfo {
+ private String mName;
+ private String mSuperClassName;
+ private boolean mIsInterface;
+ private boolean mIsAbstract;
+ private boolean mIsStatic;
+ private boolean mIsFinal;
+ private String mDeprecated;
+ private String mScope;
+ private List<String> mInterfaces;
+ private HashMap<String, MethodInfo> mMethods;
+ private HashMap<String, FieldInfo> mFields;
+ private HashMap<String, ConstructorInfo> mConstructors;
+ private boolean mExistsInBoth;
+ private PackageInfo mPackage;
+ private SourcePositionInfo mSourcePosition;
+ private ClassInfo mSuperClass;
+ private ClassInfo mParentClass;
+
+ public ClassInfo(String name, PackageInfo pack, String superClass, boolean isInterface,
+ boolean isAbstract, boolean isStatic, boolean isFinal, String deprecated,
+ String visibility, SourcePositionInfo source, ClassInfo parent) {
+ mName = name;
+ mPackage = pack;
+ mSuperClassName = superClass;
+ mIsInterface = isInterface;
+ mIsAbstract = isAbstract;
+ mIsStatic = isStatic;
+ mIsFinal = isFinal;
+ mDeprecated = deprecated;
+ mScope = visibility;
+ mInterfaces = new ArrayList<String>();
+ mMethods = new HashMap<String, MethodInfo>();
+ mFields = new HashMap<String, FieldInfo>();
+ mConstructors = new HashMap<String, ConstructorInfo>();
+ mExistsInBoth = false;
+ mSourcePosition = source;
+ mParentClass = parent;
+ }
+
+ public String name() {
+ return mName;
+ }
+
+ public String qualifiedName() {
+ String parentQName = (mParentClass != null)
+ ? (mParentClass.qualifiedName() + ".")
+ : "";
+ return mPackage.name() + "." + parentQName + name();
+ }
+
+ public String superclassName() {
+ return mSuperClassName;
+ }
+
+ public SourcePositionInfo position() {
+ return mSourcePosition;
+ }
+
+ public boolean isInterface() {
+ return mIsInterface;
+ }
+
+ public boolean isFinal() {
+ return mIsFinal;
+ }
+
+ // Find a superclass implementation of the given method. Looking at our superclass
+ // instead of at 'this' is unusual, but it fits the point-of-call demands well.
+ public MethodInfo overriddenMethod(MethodInfo candidate) {
+ if (mSuperClass == null) {
+ return null;
+ }
+
+ // does our immediate superclass have it?
+ ClassInfo sup = mSuperClass;
+ for (MethodInfo mi : sup.mMethods.values()) {
+ if (mi.matches(candidate)) {
+ // found it
+ return mi;
+ }
+ }
+
+ // no, so recurse
+ if (sup.mSuperClass != null) {
+ return mSuperClass.overriddenMethod(candidate);
+ }
+
+ // no parent, so we just don't have it
+ return null;
+ }
+
+ public boolean isConsistent(ClassInfo cl) {
+ cl.mExistsInBoth = true;
+ mExistsInBoth = true;
+ boolean consistent = true;
+
+ if (isInterface() != cl.isInterface()) {
+ Errors.error(Errors.CHANGED_CLASS, cl.position(),
+ "Class " + cl.qualifiedName()
+ + " changed class/interface declaration");
+ consistent = false;
+ }
+ for (String iface : mInterfaces) {
+ if (!cl.mInterfaces.contains(iface)) {
+ Errors.error(Errors.REMOVED_INTERFACE,
+ cl.position(), "Removed interface " + iface);
+ }
+ }
+ for (String iface : cl.mInterfaces) {
+ if (!mInterfaces.contains(iface)) {
+ Errors.error(Errors.ADDED_INTERFACE, cl.position(),
+ "Added interface " + iface + " to class "
+ + qualifiedName());
+ consistent = false;
+ }
+ }
+
+ for (MethodInfo mInfo : mMethods.values()) {
+ if (cl.mMethods.containsKey(mInfo.getHashableName())) {
+ if (!mInfo.isConsistent(cl.mMethods.get(mInfo.getHashableName()))) {
+ consistent = false;
+ }
+ } else {
+ /* This class formerly provided this method directly, and now does not.
+ * Check our ancestry to see if there's an inherited version that still
+ * fulfills the API requirement.
+ */
+ MethodInfo mi = mInfo.containingClass().overriddenMethod(mInfo);
+ if (mi == null) {
+ Errors.error(Errors.REMOVED_METHOD, mInfo.position(),
+ "Removed public method " + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ }
+ for (MethodInfo mInfo : cl.mMethods.values()) {
+ if (!mInfo.isInBoth()) {
+ /* Similarly to the above, do not fail if this "new" method is
+ * really an override of an existing superclass method.
+ */
+ MethodInfo mi = mInfo.containingClass().overriddenMethod(mInfo);
+ if (mi == null) {
+ Errors.error(Errors.ADDED_METHOD, mInfo.position(),
+ "Added public method " + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ }
+
+ for (ConstructorInfo mInfo : mConstructors.values()) {
+ if (cl.mConstructors.containsKey(mInfo.getHashableName())) {
+ if (!mInfo.isConsistent(cl.mConstructors.get(mInfo.getHashableName()))) {
+ consistent = false;
+ }
+ } else {
+ Errors.error(Errors.REMOVED_METHOD, mInfo.position(),
+ "Removed public constructor " + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ for (ConstructorInfo mInfo : cl.mConstructors.values()) {
+ if (!mInfo.isInBoth()) {
+ Errors.error(Errors.ADDED_METHOD, mInfo.position(),
+ "Added public constructor " + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+
+ for (FieldInfo mInfo : mFields.values()) {
+ if (cl.mFields.containsKey(mInfo.qualifiedName())) {
+ if (!mInfo.isConsistent(cl.mFields.get(mInfo.qualifiedName()))) {
+ consistent = false;
+ }
+ } else {
+ Errors.error(Errors.REMOVED_FIELD, mInfo.position(),
+ "Removed field " + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ for (FieldInfo mInfo : cl.mFields.values()) {
+ if (!mInfo.isInBoth()) {
+ Errors.error(Errors.ADDED_FIELD, mInfo.position(),
+ "Added public field " + mInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+
+ if (mIsAbstract != cl.mIsAbstract) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_ABSTRACT, cl.position(),
+ "Class " + cl.qualifiedName() + " changed abstract qualifier");
+ }
+
+ if (mIsFinal != cl.mIsFinal) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_FINAL, cl.position(),
+ "Class " + cl.qualifiedName() + " changed final qualifier");
+ }
+
+ if (mIsStatic != cl.mIsStatic) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_STATIC, cl.position(),
+ "Class " + cl.qualifiedName() + " changed static qualifier");
+ }
+
+ if (!mScope.equals(cl.mScope)) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SCOPE, cl.position(),
+ "Class " + cl.qualifiedName() + " scope changed from "
+ + mScope + " to " + cl.mScope);
+ }
+
+ if (mSuperClassName != null) {
+ if (cl.mSuperClassName == null || !mSuperClassName.equals(cl.mSuperClassName)) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SUPERCLASS, cl.position(),
+ "Class " + qualifiedName() + " superclass changed from "
+ + mSuperClassName + " to " + cl.mSuperClassName);
+ }
+ } else if (cl.mSuperClassName != null) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SUPERCLASS, cl.position(),
+ "Class " + qualifiedName() + " superclass changed from "
+ + "null to " + cl.mSuperClassName);
+ }
+
+ return consistent;
+ }
+
+ public void addInterface(String name) {
+ mInterfaces.add(name);
+ }
+
+ public void addMethod(MethodInfo mInfo) {
+ mMethods.put(mInfo.getHashableName(), mInfo);
+ }
+
+ public void addConstructor(ConstructorInfo cInfo) {
+ mConstructors.put(cInfo.getHashableName(), cInfo);
+
+ }
+
+ public void addField(FieldInfo fInfo) {
+ mFields.put(fInfo.qualifiedName(), fInfo);
+
+ }
+
+ public void setSuperClass(ClassInfo superclass) {
+ mSuperClass = superclass;
+ }
+
+ public boolean isInBoth() {
+ return mExistsInBoth;
+ }
+
+}
diff --git a/tools/apicheck/src/com/android/apicheck/ConstructorInfo.java b/tools/apicheck/src/com/android/apicheck/ConstructorInfo.java
new file mode 100644
index 0000000..5593d21
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/ConstructorInfo.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+import java.util.*;
+
+public class ConstructorInfo implements AbstractMethodInfo {
+
+ private String mName;
+ private String mType;
+ private boolean mIsStatic;
+ private boolean mIsFinal;
+ private String mDeprecated;
+ private String mScope;
+ private List<String> mExceptions;
+ private List<ParameterInfo> mParameters;
+ private boolean mExistsInBoth;
+ private SourcePositionInfo mSourcePosition;
+ private ClassInfo mClass;
+
+ public ConstructorInfo(String name, String type, boolean isStatic, boolean isFinal,
+ String deprecated, String scope, SourcePositionInfo pos, ClassInfo clazz) {
+ mName = name;
+ mType = type;
+ mIsStatic = isStatic;
+ mIsFinal = isFinal;
+ mDeprecated= deprecated;
+ mScope = scope;
+ mExistsInBoth = false;
+ mExceptions = new ArrayList<String>();
+ mParameters = new ArrayList<ParameterInfo>();
+ mSourcePosition = pos;
+ mClass = clazz;
+ }
+
+ public void addParameter(ParameterInfo pInfo) {
+ mParameters.add(pInfo);
+ }
+
+ public void addException(String exec) {
+ mExceptions.add(exec);
+ }
+
+ public String getHashableName() {
+ String returnString = qualifiedName();
+ for (ParameterInfo pInfo : mParameters) {
+ returnString += ":" + pInfo.getType();
+ }
+ return returnString;
+ }
+
+ public boolean isInBoth() {
+ return mExistsInBoth;
+ }
+
+ public SourcePositionInfo position() {
+ return mSourcePosition;
+ }
+
+ public String name() {
+ return mName;
+ }
+
+ public String qualifiedName() {
+ String baseName = (mClass != null)
+ ? (mClass.qualifiedName() + ".")
+ : "";
+ return baseName + name();
+ }
+
+ public boolean isConsistent(ConstructorInfo mInfo) {
+ mInfo.mExistsInBoth = true;
+ mExistsInBoth = true;
+ boolean consistent = true;
+
+ if (mIsFinal != mInfo.mIsFinal) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_FINAL, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " has changed 'final' qualifier");
+ }
+
+ if (mIsStatic != mInfo.mIsStatic) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_FINAL, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " has changed 'static' qualifier");
+ }
+
+ if (!mScope.equals(mInfo.mScope)) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SCOPE, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " changed scope from "
+ + mScope + " to " + mInfo.mScope);
+ }
+
+ for (String exec : mExceptions) {
+ if (!mInfo.mExceptions.contains(exec)) {
+ Errors.error(Errors.CHANGED_THROWS, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " no longer throws exception "
+ + exec);
+ consistent = false;
+ }
+ }
+
+ for (String exec : mInfo.mExceptions) {
+ if (!mExceptions.contains(exec)) {
+ Errors.error(Errors.CHANGED_THROWS, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " added thrown exception "
+ + exec);
+ consistent = false;
+ }
+ }
+
+ return consistent;
+ }
+
+
+}
diff --git a/tools/apicheck/src/com/android/apicheck/Errors.java b/tools/apicheck/src/com/android/apicheck/Errors.java
new file mode 100644
index 0000000..84d9c17
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/Errors.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+
+import java.lang.Comparable;
+import java.util.TreeSet;
+
+public class Errors
+{
+ public static boolean hadError = false;
+ private static boolean warningsAreErrors = false;
+ private static TreeSet<Message> allErrors = new TreeSet<Message>();
+
+ private static class Message implements Comparable {
+ SourcePositionInfo pos;
+ String msg;
+
+ Message(SourcePositionInfo p, String m) {
+ pos = p;
+ msg = m;
+ }
+
+ public int compareTo(Object o) {
+ Message that = (Message)o;
+ int r = this.pos.compareTo(that.pos);
+ if (r != 0) return r;
+ return this.msg.compareTo(that.msg);
+ }
+
+ public String toString() {
+ return this.pos.toString() + this.msg;
+ }
+ }
+
+ public static void error(Error error, SourcePositionInfo where, String text) {
+ if (error.level == HIDDEN) {
+ return;
+ }
+
+ String which = (!warningsAreErrors && error.level == WARNING) ? " warning " : " error ";
+ String message = which + error.code + ": " + text;
+
+ if (where == null) {
+ where = new SourcePositionInfo("unknown", 0, 0);
+ }
+
+ allErrors.add(new Message(where, message));
+
+ if (error.level == ERROR || (warningsAreErrors && error.level == WARNING)) {
+ hadError = true;
+ }
+ }
+
+ public static void printErrors() {
+ for (Message m: allErrors) {
+ System.err.println(m.toString());
+ }
+ }
+
+ public static int HIDDEN = 0;
+ public static int WARNING = 1;
+ public static int ERROR = 2;
+
+ public static void setWarningsAreErrors(boolean val) {
+ warningsAreErrors = val;
+ }
+
+ public static class Error {
+ public int code;
+ public int level;
+
+ public Error(int code, int level)
+ {
+ this.code = code;
+ this.level = level;
+ }
+ }
+
+ public static Error PARSE_ERROR = new Error(1, ERROR);
+ public static Error ADDED_PACKAGE = new Error(2, WARNING);
+ public static Error ADDED_CLASS = new Error(3, WARNING);
+ public static Error ADDED_METHOD = new Error(4, WARNING);
+ public static Error ADDED_FIELD = new Error(5, WARNING);
+ public static Error ADDED_INTERFACE = new Error(6, WARNING);
+ public static Error REMOVED_PACKAGE = new Error(7, WARNING);
+ public static Error REMOVED_CLASS = new Error(8, WARNING);
+ public static Error REMOVED_METHOD = new Error(9, WARNING);
+ public static Error REMOVED_FIELD = new Error(10, WARNING);
+ public static Error REMOVED_INTERFACE = new Error(11, WARNING);
+ public static Error CHANGED_STATIC = new Error(12, WARNING);
+ public static Error CHANGED_FINAL = new Error(13, WARNING);
+ public static Error CHANGED_TRANSIENT = new Error(14, WARNING);
+ public static Error CHANGED_VOLATILE = new Error(15, WARNING);
+ public static Error CHANGED_TYPE = new Error(16, WARNING);
+ public static Error CHANGED_VALUE = new Error(17, WARNING);
+ public static Error CHANGED_SUPERCLASS = new Error(18, WARNING);
+ public static Error CHANGED_SCOPE = new Error(19, WARNING);
+ public static Error CHANGED_ABSTRACT = new Error(20, WARNING);
+ public static Error CHANGED_THROWS = new Error(21, WARNING);
+ public static Error CHANGED_NATIVE = new Error(22, HIDDEN);
+ public static Error CHANGED_CLASS = new Error(23, WARNING);
+
+ public static Error[] ERRORS = {
+ PARSE_ERROR,
+ ADDED_PACKAGE,
+ ADDED_CLASS,
+ ADDED_METHOD,
+ ADDED_FIELD,
+ ADDED_INTERFACE,
+ REMOVED_PACKAGE,
+ REMOVED_CLASS,
+ REMOVED_METHOD,
+ REMOVED_FIELD,
+ REMOVED_INTERFACE,
+ CHANGED_STATIC,
+ CHANGED_FINAL,
+ CHANGED_TRANSIENT,
+ CHANGED_VOLATILE,
+ CHANGED_TYPE,
+ CHANGED_VALUE,
+ CHANGED_SUPERCLASS,
+ CHANGED_SCOPE,
+ CHANGED_ABSTRACT,
+ CHANGED_THROWS,
+ CHANGED_NATIVE,
+ };
+
+ public static boolean setErrorLevel(int code, int level) {
+ for (Error e: ERRORS) {
+ if (e.code == code) {
+ e.level = level;
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/tools/apicheck/src/com/android/apicheck/FieldInfo.java b/tools/apicheck/src/com/android/apicheck/FieldInfo.java
new file mode 100644
index 0000000..9b467af
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/FieldInfo.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+
+public class FieldInfo {
+
+ private String mName;
+ private String mType;
+ private boolean mIsTransient;
+ private boolean mIsVolatile;
+ private String mValue;
+ private boolean mIsStatic;
+ private boolean mIsFinal;
+ private String mDeprecated;
+ private String mScope;
+ private boolean mExistsInBoth;
+ private SourcePositionInfo mSourcePosition;
+ private ClassInfo mClass;
+
+ public FieldInfo (String name, String type, boolean isTransient, boolean isVolatile,
+ String value, boolean isStatic, boolean isFinal, String deprecated,
+ String scope, SourcePositionInfo source, ClassInfo parent) {
+ mName = name;
+ mType = type;
+ mIsTransient = isTransient;
+ mIsVolatile = isVolatile;
+ mValue = value;
+ mIsStatic = isStatic;
+ mIsFinal = isFinal;
+ mDeprecated = deprecated;
+ mScope = scope;
+ mExistsInBoth = false;
+ mSourcePosition = source;
+ mClass = parent;
+ }
+
+ public boolean isInBoth() {
+ return mExistsInBoth;
+ }
+ public SourcePositionInfo position() {
+ return mSourcePosition;
+ }
+
+ public String name() {
+ return mName;
+ }
+
+ public String qualifiedName() {
+ String parentQName = (mClass != null)
+ ? (mClass.qualifiedName() + ".")
+ : "";
+ return parentQName + name();
+ }
+
+ public boolean isConsistent(FieldInfo fInfo) {
+ fInfo.mExistsInBoth = true;
+ mExistsInBoth = true;
+ boolean consistent = true;
+ if (!mType.equals(fInfo.mType)) {
+ Errors.error(Errors.CHANGED_TYPE, fInfo.position(),
+ "Field " + fInfo.qualifiedName() + " has changed type");
+ consistent = false;
+ }
+ if ((mValue != null && !mValue.equals(fInfo.mValue)) ||
+ (mValue == null && fInfo.mValue != null)) {
+ Errors.error(Errors.CHANGED_VALUE, fInfo.position(),
+ "Field " + fInfo.qualifiedName() + " has changed value from "
+ + mValue + " to " + fInfo.mValue);
+ consistent = false;
+ }
+
+ if (!mScope.equals(fInfo.mScope)) {
+ Errors.error(Errors.CHANGED_SCOPE, fInfo.position(),
+ "Method " + fInfo.qualifiedName() + " changed scope from "
+ + mScope + " to " + fInfo.mScope);
+ consistent = false;
+ }
+
+ if (mIsStatic != fInfo.mIsStatic) {
+ Errors.error(Errors.CHANGED_STATIC, fInfo.position(),
+ "Field " + fInfo.qualifiedName() + " has changed 'static' qualifier");
+ consistent = false;
+ }
+
+ if (mIsFinal != fInfo.mIsFinal) {
+ Errors.error(Errors.CHANGED_FINAL, fInfo.position(),
+ "Field " + fInfo.qualifiedName() + " has changed 'final' qualifier");
+ consistent = false;
+ }
+
+ if (mIsTransient != fInfo.mIsTransient) {
+ Errors.error(Errors.CHANGED_TRANSIENT, fInfo.position(),
+ "Field " + fInfo.qualifiedName() + " has changed 'transient' qualifier");
+ consistent = false;
+ }
+
+ if (mIsVolatile != fInfo.mIsVolatile) {
+ Errors.error(Errors.CHANGED_VOLATILE, fInfo.position(),
+ "Field " + fInfo.qualifiedName() + " has changed 'volatile' qualifier");
+ consistent = false;
+ }
+
+ return consistent;
+ }
+
+}
diff --git a/tools/apicheck/src/com/android/apicheck/MethodInfo.java b/tools/apicheck/src/com/android/apicheck/MethodInfo.java
new file mode 100644
index 0000000..1f973dc
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/MethodInfo.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+import java.util.*;
+
+public class MethodInfo implements AbstractMethodInfo {
+
+ private String mName;
+ private String mReturn;
+ private boolean mIsAbstract;
+ private boolean mIsNative;
+ private boolean mIsSynchronized;
+ private boolean mIsStatic;
+ private boolean mIsFinal;
+ private String mDeprecated;
+ private String mScope;
+ private boolean mExistsInBoth;
+ private List<ParameterInfo> mParameters;
+ private List<String> mExceptions;
+ private SourcePositionInfo mSourcePosition;
+ private ClassInfo mClass;
+
+ public MethodInfo (String name, String returnType, boolean isAbstract, boolean isNative,
+ boolean isSynchronized, boolean isStatic, boolean isFinal, String deprecated
+ , String scope, SourcePositionInfo source, ClassInfo parent) {
+
+ mName = name;
+ mReturn = returnType;
+ mIsAbstract = isAbstract;
+ mIsNative = isNative;
+ mIsSynchronized = isSynchronized;
+ mIsStatic = isStatic;
+ mIsFinal = isFinal;
+ mDeprecated = deprecated;
+ mScope = scope;
+ mParameters = new ArrayList<ParameterInfo>();
+ mExceptions = new ArrayList<String>();
+ mExistsInBoth = false;
+ mSourcePosition = source;
+ mClass = parent;
+ }
+
+
+ public String name() {
+ return mName;
+ }
+
+ public String qualifiedName() {
+ String parentQName = (mClass != null)
+ ? (mClass.qualifiedName() + ".")
+ : "";
+ return parentQName + name();
+ }
+
+ public SourcePositionInfo position() {
+ return mSourcePosition;
+ }
+
+ public ClassInfo containingClass() {
+ return mClass;
+ }
+
+ public boolean matches(MethodInfo other) {
+ return getSignature().equals(other.getSignature());
+ }
+
+ public boolean isConsistent(MethodInfo mInfo) {
+ mInfo.mExistsInBoth = true;
+ mExistsInBoth = true;
+ boolean consistent = true;
+ if (!mReturn.equals(mInfo.mReturn)) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_TYPE, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " has changed return type from "
+ + mReturn + " to " + mInfo.mReturn);
+ }
+
+ if (mIsAbstract != mInfo.mIsAbstract) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " has changed 'abstract' qualifier");
+ }
+
+ if (mIsNative != mInfo.mIsNative) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_NATIVE, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " has changed 'native' qualifier");
+ }
+
+ if (mIsFinal != mInfo.mIsFinal) {
+ // Compiler-generated methods vary in their 'final' qual between versions of
+ // the compiler, so this check needs to be quite narrow. A change in 'final'
+ // status of a method is only relevant if (a) the method is not declared 'static'
+ // and (b) the method's class is not itself 'final'.
+ if (!mIsStatic) {
+ if ((mClass == null) || (!mClass.isFinal())) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_FINAL, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " has changed 'final' qualifier");
+ }
+ }
+ }
+
+ if (mIsStatic != mInfo.mIsStatic) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_STATIC, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " has changed 'static' qualifier");
+ }
+
+ if (!mScope.equals(mInfo.mScope)) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_SCOPE, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " changed scope from "
+ + mScope + " to " + mInfo.mScope);
+ }
+
+ for (String exec : mExceptions) {
+ if (!mInfo.mExceptions.contains(exec)) {
+ Errors.error(Errors.CHANGED_THROWS, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " no longer throws exception "
+ + exec);
+ consistent = false;
+ }
+ }
+
+ for (String exec : mInfo.mExceptions) {
+ if (!mExceptions.contains(exec)) {
+ Errors.error(Errors.CHANGED_THROWS, mInfo.position(),
+ "Method " + mInfo.qualifiedName() + " added thrown exception "
+ + exec);
+ consistent = false;
+ }
+ }
+
+ return consistent;
+ }
+
+ public void addParameter(ParameterInfo pInfo) {
+ mParameters.add(pInfo);
+ }
+
+ public void addException(String exc) {
+ mExceptions.add(exc);
+ }
+
+ public String getParameterHash() {
+ String hash = "";
+ for (ParameterInfo pInfo : mParameters) {
+ hash += ":" + pInfo.getType();
+ }
+ return hash;
+ }
+
+ public String getHashableName() {
+ return qualifiedName() + getParameterHash();
+ }
+
+ public String getSignature() {
+ return name() + getParameterHash();
+ }
+
+ public boolean isInBoth() {
+ return mExistsInBoth;
+ }
+
+}
diff --git a/tools/apicheck/src/com/android/apicheck/PackageInfo.java b/tools/apicheck/src/com/android/apicheck/PackageInfo.java
new file mode 100644
index 0000000..2262f21
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/PackageInfo.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+import java.util.*;
+
+public class PackageInfo {
+ private String mName;
+ private HashMap<String, ClassInfo> mClasses;
+ private boolean mExistsInBoth;
+ private SourcePositionInfo mPosition;
+
+ public PackageInfo(String name, SourcePositionInfo position) {
+ mName = name;
+ mClasses = new HashMap<String, ClassInfo>();
+ mExistsInBoth = false;
+ mPosition = position;
+ }
+
+ public void addClass(ClassInfo cl) {
+ mClasses.put(cl.name() , cl);
+ }
+
+ public HashMap<String, ClassInfo> allClasses() {
+ return mClasses;
+ }
+
+ public String name() {
+ return mName;
+ }
+
+ public SourcePositionInfo position() {
+ return mPosition;
+ }
+
+ public boolean isConsistent(PackageInfo pInfo) {
+ mExistsInBoth = true;
+ pInfo.mExistsInBoth = true;
+ boolean consistent = true;
+ for (ClassInfo cInfo : mClasses.values()) {
+ if (pInfo.mClasses.containsKey(cInfo.name())) {
+ if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()))) {
+ consistent = false;
+ }
+ } else {
+ Errors.error(Errors.REMOVED_CLASS, cInfo.position(),
+ "Removed public class " + cInfo.qualifiedName());
+ consistent = false;
+ }
+ }
+ for (ClassInfo cInfo : pInfo.mClasses.values()) {
+ if (!cInfo.isInBoth()) {
+ Errors.error(Errors.ADDED_CLASS, cInfo.position(),
+ "Added class " + cInfo.name() + " to package "
+ + pInfo.name());
+ consistent = false;
+ }
+ }
+ return consistent;
+ }
+
+ public boolean isInBoth() {
+ return mExistsInBoth;
+ }
+}
diff --git a/tools/apicheck/src/com/android/apicheck/ParameterInfo.java b/tools/apicheck/src/com/android/apicheck/ParameterInfo.java
new file mode 100644
index 0000000..5788814
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/ParameterInfo.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+
+public class ParameterInfo {
+ private String mType;
+ private String mName;
+
+ public ParameterInfo(String type, String name) {
+ mType = type;
+ mName = name;
+ }
+
+ public String getType() {
+ return mType;
+ }
+
+ public String getName() {
+ return mName;
+ }
+}
diff --git a/tools/apicheck/src/com/android/apicheck/SourcePositionInfo.java b/tools/apicheck/src/com/android/apicheck/SourcePositionInfo.java
new file mode 100644
index 0000000..477c1d3
--- /dev/null
+++ b/tools/apicheck/src/com/android/apicheck/SourcePositionInfo.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 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.apicheck;
+
+import java.lang.Comparable;
+
+public class SourcePositionInfo implements Comparable
+{
+ public SourcePositionInfo() {
+ this.file = "<unknown>";
+ this.line = 0;
+ this.column = 0;
+ }
+
+ public SourcePositionInfo(String file, int line, int column)
+ {
+ this.file = file;
+ this.line = line;
+ this.column = column;
+ }
+
+ public SourcePositionInfo(SourcePositionInfo that)
+ {
+ this.file = that.file;
+ this.line = that.line;
+ this.column = that.column;
+ }
+
+ /**
+ * Given this position and str which occurs at that position, as well as str an index into str,
+ * find the SourcePositionInfo.
+ *
+ * @throw StringIndexOutOfBoundsException if index &gt; str.length()
+ */
+ public static SourcePositionInfo add(SourcePositionInfo that, String str, int index)
+ {
+ if (that == null) {
+ return null;
+ }
+ int line = that.line;
+ char prev = 0;
+ for (int i=0; i<index; i++) {
+ char c = str.charAt(i);
+ if (c == '\r' || (c == '\n' && prev != '\r')) {
+ line++;
+ }
+ prev = c;
+ }
+ return new SourcePositionInfo(that.file, line, 0);
+ }
+
+ public static SourcePositionInfo findBeginning(SourcePositionInfo that, String str)
+ {
+ if (that == null) {
+ return null;
+ }
+ int line = that.line-1; // -1 because, well, it seems to work
+ int prev = 0;
+ for (int i=str.length()-1; i>=0; i--) {
+ char c = str.charAt(i);
+ if ((c == '\r' && prev != '\n') || (c == '\n')) {
+ line--;
+ }
+ prev = c;
+ }
+ return new SourcePositionInfo(that.file, line, 0);
+ }
+
+ public String toString()
+ {
+ if (this.file == null) {
+ return "(unknown)";
+ } else {
+ if (this.line == 0) {
+ return this.file + ':';
+ } else {
+ return this.file + ':' + this.line + ':';
+ }
+ }
+ }
+
+ public int compareTo(Object o) {
+ SourcePositionInfo that = (SourcePositionInfo)o;
+ int r = this.file.compareTo(that.file);
+ if (r != 0) return r;
+ return this.line - that.line;
+ }
+
+ /**
+ * Build a SourcePositionInfo from the XML source= notation
+ */
+ public static SourcePositionInfo fromXml(String source) {
+ if (source != null) {
+ for (int i = 0; i < source.length(); i++) {
+ if (source.charAt(i) == ':') {
+ return new SourcePositionInfo(source.substring(0, i),
+ Integer.parseInt(source.substring(i+1)), 0);
+ }
+ }
+ }
+
+ return new SourcePositionInfo("(unknown)", 0, 0);
+ }
+
+ public String file;
+ public int line;
+ public int column;
+}