Initial check-in of the API compatibility checker.

Patch by: amitmanjhi
Review by: spoon

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2508 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JArrayType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JArrayType.java
index 910f5b8..40753b0 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JArrayType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JArrayType.java
@@ -306,6 +306,12 @@
     return null;
   }
 
+  // Refer the documentation for java.lang.Class::getModifiers()
+  @Override
+  public boolean isFinal() {
+    return true;
+  }
+
   @Override
   public JGenericType isGenericType() {
     return null;
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
index 82760f3..1b00a58 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
@@ -467,6 +467,8 @@
    */
   public abstract boolean isDefaultInstantiable();
 
+  public abstract boolean isFinal();
+
   public abstract JGenericType isGenericType();
 
   @Override
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JDelegatingClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JDelegatingClassType.java
index 7f422d0..35cd296 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JDelegatingClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JDelegatingClassType.java
@@ -271,6 +271,11 @@
   }
 
   @Override
+  public boolean isFinal() {
+    return baseType.isFinal();
+  }
+
+  @Override
   public JClassType isInterface() {
     if (baseType.isInterface() != null) {
       return this;
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JField.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JField.java
index 1de133b..56e0269 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JField.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JField.java
@@ -149,7 +149,11 @@
     if (names.length > 0) {
       sb.append(" ");
     }
-    sb.append(type.getQualifiedSourceName());
+    if (type == null) {
+      sb.append("NULL");
+    } else {
+      sb.append(type.getQualifiedSourceName());
+    }
     sb.append(" ");
     sb.append(getName());
     return sb.toString();
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JMethod.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JMethod.java
index 3cf2040..dd72ad1 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JMethod.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JMethod.java
@@ -29,7 +29,8 @@
 
   public JMethod(JClassType enclosingType, String name, int declStart,
       int declEnd, int bodyStart, int bodyEnd) {
-    this(enclosingType, name, declStart, declEnd, bodyStart, bodyEnd, null, null);
+    this(enclosingType, name, declStart, declEnd, bodyStart, bodyEnd, null,
+        null);
   }
 
   public JMethod(JClassType enclosingType, String name, int declStart,
@@ -129,7 +130,11 @@
       toStringTypeParams(sb);
       sb.append(" ");
     }
-    sb.append(returnType.getParameterizedQualifiedSourceName());
+    if (returnType == null) {
+      sb.append("NULL");
+    } else {
+      sb.append(returnType.getParameterizedQualifiedSourceName());
+    }
     sb.append(" ");
     sb.append(getName());
 
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
index 4e744ea..42dc892 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
@@ -353,6 +353,11 @@
   }
 
   @Override
+  public boolean isFinal() {
+    return 0 != (getModifierBits() & TypeOracle.MOD_FINAL);
+  }
+
+  @Override
   public JGenericType isGenericType() {
     return null;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java b/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
index 8def4ac..cd604d4 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
@@ -76,9 +76,16 @@
   }
 
   private final CompilationUnitDeclarationCache cachedResults = new CompilationUnitDeclarationCache();
-
+  private final boolean disableChecks;
+  
+  public AstCompiler(SourceOracle sourceOracle, boolean disableChecks) {
+    super(sourceOracle, false);
+    this.disableChecks = disableChecks;
+  }
+  
   public AstCompiler(SourceOracle sourceOracle) {
     super(sourceOracle, false);
+    this.disableChecks = false;
   }
 
   public CompilationUnitDeclaration[] getChangedCompilationUnitDeclarations(
@@ -121,8 +128,10 @@
   @Override
   protected void doCompilationUnitDeclarationValidation(
       CompilationUnitDeclaration cud, TreeLogger logger) {
-    JSORestrictionsChecker.check(cud);
-    LongFromJSNIChecker.check(cud);
-    BinaryTypeReferenceRestrictionsChecker.check(cud);
+    if (!disableChecks) {
+      JSORestrictionsChecker.check(cud);
+      LongFromJSNIChecker.check(cud);
+      BinaryTypeReferenceRestrictionsChecker.check(cud);
+    }
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java b/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java
index 76e8211..29c0485 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java
@@ -611,6 +611,10 @@
    * null, caching across reloads will be disabled.
    */
   public CacheManager(String cacheDir, TypeOracle oracle) {
+    this(cacheDir, oracle, false); 
+  }
+  
+  public CacheManager(String cacheDir, TypeOracle oracle, boolean disableChecks) {
     if (oracle == null) {
       this.oracle = new TypeOracle();
     } else {
@@ -627,7 +631,7 @@
       byteCodeCache = new DiskCache(null);
     }
     SourceOracleOnTypeOracle sooto = new SourceOracleOnTypeOracle(this.oracle);
-    astCompiler = new AstCompiler(sooto);
+    astCompiler = new AstCompiler(sooto, disableChecks);
 
     astCompiler.setCachePolicy(new AstCompiler.CachePolicy() {
       public boolean shouldProcess(CompilationUnitDeclaration cud) {
diff --git a/tools/api-checker/build.xml b/tools/api-checker/build.xml
new file mode 100755
index 0000000..9251196
--- /dev/null
+++ b/tools/api-checker/build.xml
@@ -0,0 +1,57 @@
+<project name="api-checker" default="build" basedir=".">
+
+        <property name="gwt.root" location="../.." />
+	<property name="project.tail" value="tools/api-checker" />
+	<import file="${gwt.root}/common.ant.xml" />
+
+        <property name="tools.build" value="${gwt.build.out}/${project.tail}" />
+
+	<!-- Platform shouldn't matter here, just picking one -->
+	<property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev-linux.jar" />
+	<property.ensure name="gwt.user.jar" location="${gwt.build.lib}/gwt-user.jar" />
+
+	<target name="compile" description="Compile all class files">
+		<mkdir dir="${javac.out}" />
+		<gwt.javac>
+			<classpath>
+				<pathelement location="${gwt.dev.jar}" />
+				<pathelement location="${gwt.user.jar}" />
+			</classpath>
+		</gwt.javac>
+	</target>
+
+	<target name="compile.tests" description="Compiles the test code for this project">
+		<mkdir dir="${javac.junit.out}" />
+		<gwt.javac srcdir="test" destdir="${javac.junit.out}">
+			<classpath>
+				<pathelement location="${javac.out}" />
+				<pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
+				<pathelement location="${gwt.dev.staging.jar}" />
+				<pathelement location="${gwt.dev.jar}" />
+				<pathelement location="${gwt.user.jar}" />
+			</classpath>
+		</gwt.javac>
+	</target>
+        
+        <target name="build" depends="compile" description="Build and package this project">
+		<mkdir dir="${gwt.build.lib}" />
+                    
+		<gwt.jar>
+			<fileset dir="src" excludes="**/package.html" />
+			<fileset dir="${javac.out}" />
+		</gwt.jar>
+	</target>
+
+
+	<target name="checkstyle" description="Static analysis of source">
+		<gwt.checkstyle>
+			<fileset dir="src"/>
+		</gwt.checkstyle>
+	</target>
+
+	<target name="clean" description="Cleans this project's intermediate and output files">
+		<delete dir="${project.build}" />
+		<delete file="${project.lib}" />
+	</target>
+</project>
+
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiAbstractMethod.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiAbstractMethod.java
new file mode 100644
index 0000000..4d86526
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiAbstractMethod.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import java.util.ArrayList;
+
+/**
+ * abstract super-class for ApiMethod and ApiConstructor.
+ */
+public abstract class ApiAbstractMethod {
+
+  public static String computeApiSignature(JAbstractMethod method) {
+    String className = method.getEnclosingType().getQualifiedSourceName();
+    StringBuffer sb = new StringBuffer();
+    sb.append(className);
+    sb.append("::");
+    sb.append(computeInternalSignature(method));
+    return sb.toString();
+  }
+
+  public static String computeInternalSignature(JAbstractMethod method) {
+    StringBuffer sb = new StringBuffer();
+    sb.append(method.getName());
+    sb.append("(");
+    JParameter[] params = method.getParameters();
+    for (int j = 0; j < params.length; j++) {
+      JParameter param = params[j];
+      String typeSig = param.getType().getJNISignature();
+      sb.append(typeSig);
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  ApiClass apiClass;
+
+  String apiSignature = null;
+
+  JAbstractMethod method;
+
+  public ApiAbstractMethod(JAbstractMethod method, ApiClass apiClass) {
+    this.method = method;
+    this.apiClass = apiClass;
+  }
+
+  public String getApiSignature() {
+    if (apiSignature == null) {
+      apiSignature = computeApiSignature();
+    }
+    return apiSignature;
+  }
+
+  // for a non-primitive type, someone can pass it null as a parameter
+  public String getCoarseSignature() {
+    StringBuffer returnStr = new StringBuffer();
+    JParameter[] parameters = method.getParameters();
+    for (JParameter parameter : parameters) {
+      JType type = parameter.getType();
+      if (type.isPrimitive() != null) {
+        returnStr.append(type.getJNISignature());
+      } else {
+        returnStr.append("c");
+      }
+      returnStr.append(";"); // to mark the end of a type
+    }
+    return returnStr.toString();
+  }
+
+  public JAbstractMethod getMethodObject() {
+    return method;
+  }
+
+  // Not sure the above implementation is sufficient. If need be, look at the
+  // implementation below.
+  // public String getCoarseSignature() {
+  // StringBuffer returnStr = new StringBuffer();
+  // JParameter[] parameters = method.getParameters();
+  // JArrayType jat = null;
+  // for (JParameter parameter : parameters) {
+  // JType type = parameter.getType();
+  // while ((jat = type.isArray()) != null) {
+  // returnStr.append("a");
+  // type = jat.getComponentType();
+  // }
+  // if (type.isPrimitive() != null) {
+  // returnStr.append("p");
+  // } else {
+  // returnStr.append("c");
+  // }
+  // returnStr.append(";"); // to mark the end of a type
+  // }
+  // return returnStr.toString();
+  // }
+
+  public abstract ArrayList<ApiChange.Status> getModifierChanges(
+      ApiAbstractMethod newMethod);
+
+  public boolean isCompatible(ApiAbstractMethod methodInNew) {
+    JParameter[] parametersInNew = methodInNew.getMethodObject().getParameters();
+    int length = parametersInNew.length;
+    if (length != method.getParameters().length) {
+      return false;
+    }
+    for (int i = 0; i < length; i++) {
+      if (!ApiDiffGenerator.isFirstTypeAssignableToSecond(
+          method.getParameters()[i].getType(), parametersInNew[i].getType())) {
+        return false;
+      }
+    }
+    // Control reaches here iff methods are compatible with respect to
+    // parameters and return type. For source compatibility, I do not need to
+    // check in which classes the methods are declared.
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    return method.toString();
+  }
+
+  protected String computeApiSignature() {
+    return computeApiSignature(method);
+  }
+
+  abstract ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod);
+
+}
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiChange.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiChange.java
new file mode 100644
index 0000000..2bdd6dc
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiChange.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+/**
+ * 
+ * An ApiChange message.Each message is a Status followed by an optional String
+ * description.
+ * 
+ */
+
+public class ApiChange {
+
+  // add specific changes. For example, add FINAL_ADDED and
+  // ABSTRACT_ADDED instead of API_ATTRIBUTES_CHANGED
+  static enum Status {
+    ABSTRACT_ADDED("ABSTRACT_ADDED"),
+
+    ATTRIBUTES_WARNING("ATTRIBUTES_CHANGED_WARNING"),
+
+    COMPATIBLE("COMPATIBLE"),
+
+    COMPATIBLE_WITH("COMPATIBLE_WITH"),
+
+    EXCEPTIONS_ERROR("EXCEPTION_TYPE_ERROR"),
+
+    FINAL_ADDED("FINAL_ADDED"),
+
+    MISSING("MISSING"),
+
+    NONABSTRACT_CLASS_MADE_INTERFACE("NONABSTRACT_CLASS_MADE_INTERFACE"),
+
+    OVERLOADED("OVERLOADED_METHOD_CALL"),
+
+    RETURN_TYPE_ERROR("RETURN_TYPE_ERROR"),
+
+    STATIC_ADDED("STATIC_ADDED"),
+
+    STATIC_REMOVED("STATIC_REMOVED"),
+
+    SUBCLASSABLE_API_CLASS_MADE_INTERFACE("SUBCLASSABLE_CLASS_MADE_INTERFACE"),
+
+    SUBCLASSABLE_API_INTERFACE_MADE_CLASS("SUBCLASSABLE_INTERFACE_MADE_CLASS");
+
+    private final String str;
+
+    Status(String str) {
+      this.str = str;
+    }
+
+    @Override
+    public final String toString() {
+      return str;
+    }
+  }
+
+  private String message = null;
+  private Status status = null;
+
+  public ApiChange(Status status) {
+    this.status = status;
+  }
+
+  public ApiChange(Status status, String message) {
+    this.status = status;
+    this.message = message;
+  }
+
+  public Status getStatus() {
+    return status;
+  }
+ 
+  @Override
+  public String toString() {
+    if (message != null) {
+      return status + " " + message;
+    }
+    return status.toString();
+  }
+
+}
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClass.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClass.java
new file mode 100644
index 0000000..f2a0d74
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClass.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JConstructor;
+import com.google.gwt.core.ext.typeinfo.JField;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * Encapsulates an API class.
+ */
+public class ApiClass {
+  /**
+   * Enum for indexing the common storage used for methods and constructors
+   * 
+   */
+  public static enum MethodType {
+    CONSTRUCTOR(0), METHOD(1);
+
+    private final int id;
+
+    MethodType(int id) {
+      this.id = id;
+    }
+
+    public int getId() {
+      return id;
+    }
+  }
+
+  public static String computeFieldApiSignature(JField field) {
+    return field.getEnclosingType().getQualifiedSourceName() + "::"
+        + field.getName();
+  }
+
+  /**
+   * Assumption: Clients may sub-class an API class, but they will not add their
+   * class to the package.
+   * 
+   * Notes: -- A class with only private constructors can be an Api class.
+   */
+  public static boolean isApiClass(JClassType classType) {
+    // check for outer classes
+    if (isPublicOuterClass(classType)) {
+      return true;
+    }
+    // if classType is not a member type, return false
+    if (!classType.isMemberType()) {
+      return false;
+    }
+    JClassType enclosingType = classType.getEnclosingType();
+    if (classType.isPublic()) {
+      return isApiClass(enclosingType) || isAnySubtypeAnApiClass(enclosingType);
+    }
+    if (classType.isProtected()) {
+      return isSubclassableApiClass(enclosingType)
+          || isAnySubtypeASubclassableApiClass(enclosingType);
+    }
+    return false;
+  }
+
+  public static boolean isInstantiableApiClass(JClassType classType) {
+    return !classType.isAbstract()
+        && hasPublicOrProtectedConstructor(classType);
+  }
+
+  public static boolean isNotsubclassableApiClass(JClassType classType) {
+    return isApiClass(classType) && !isSubclassable(classType);
+  }
+
+  /**
+   * @return returns true if classType is public AND an outer class
+   */
+  public static boolean isPublicOuterClass(JClassType classType) {
+    return classType.isPublic() && !classType.isMemberType();
+  }
+
+  public static boolean isSubclassable(JClassType classType) {
+    return !classType.isFinal() && hasPublicOrProtectedConstructor(classType);
+  }
+
+  public static boolean isSubclassableApiClass(JClassType classType) {
+    return isApiClass(classType) && isSubclassable(classType);
+  }
+
+  private static boolean hasPublicOrProtectedConstructor(JClassType classType) {
+    JConstructor[] constructors = classType.getConstructors();
+    for (JConstructor constructor : constructors) {
+      if (constructor.isPublic() || constructor.isProtected()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static boolean isAnySubtypeAnApiClass(JClassType classType) {
+    JClassType subTypes[] = classType.getSubtypes();
+    for (JClassType tempType : subTypes) {
+      if (isApiClass(tempType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static boolean isAnySubtypeASubclassableApiClass(JClassType classType) {
+    JClassType subTypes[] = classType.getSubtypes();
+    for (JClassType tempType : subTypes) {
+      if (isSubclassableApiClass(tempType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private HashMap<String, JField> apiFields = null;
+
+  /**
+   * 2 entries in the list: one for CONSTRUCTOR, and the other for METHOD. Each
+   * entry is a mapping from MethodName#args to a set of ApiAbstractMethod
+   */
+  private ArrayList<HashMap<String, HashSet<ApiAbstractMethod>>> apiMembersByName = new ArrayList<HashMap<String, HashSet<ApiAbstractMethod>>>(
+      MethodType.values().length);
+
+  private ApiPackage apiPackage = null;
+
+  private JClassType classType = null;
+
+  private boolean isInstantiableApiClass = false;
+
+  private boolean isNotsubclassableApiClass = false;
+  private boolean isSubclassableApiClass = false;
+  private TreeLogger logger = null;
+
+  public ApiClass(JClassType classType, ApiPackage apiPackage) {
+    this.classType = classType;
+    this.apiPackage = apiPackage;
+    logger = apiPackage.getApiContainer().getLogger();
+    isSubclassableApiClass = isSubclassableApiClass(classType);
+    isNotsubclassableApiClass = isNotsubclassableApiClass(classType);
+    isInstantiableApiClass = isInstantiableApiClass(classType);
+  }
+
+  public <E> String computeRelativeSignature(E element) {
+    String signature = element.toString();
+    JClassType enclosingType = null;
+    if (element instanceof JField) {
+      JField field = (JField) element;
+      signature = field.getName();
+      enclosingType = field.getEnclosingType();
+    }
+    if (element instanceof JAbstractMethod) {
+      JAbstractMethod jam = (JAbstractMethod) element;
+      signature = ApiAbstractMethod.computeInternalSignature(jam);
+      enclosingType = jam.getEnclosingType();
+    }
+    if (ApiCompatibilityChecker.DEBUG) {
+      return classType.getQualifiedSourceName()
+          + "::"
+          + signature
+          + " defined in "
+          + (enclosingType == null ? "null enclosing type "
+              : enclosingType.getQualifiedSourceName());
+    }
+    return classType.getQualifiedSourceName() + "::" + signature;
+  }
+
+  public JField getApiFieldByName(String name) {
+    return apiFields.get(name);
+  }
+
+  public HashSet<String> getApiFieldNames() {
+    if (apiFields == null) {
+      initializeApiFields();
+    }
+    return new HashSet<String>(apiFields.keySet());
+  }
+
+  public HashSet<JField> getApiFieldsBySet(HashSet<String> names) {
+    HashSet<JField> ret = new HashSet<JField>();
+    String tempStrings[] = names.toArray(new String[0]);
+    for (String temp : tempStrings) {
+      ret.add(apiFields.get(temp));
+    }
+    return ret;
+  }
+
+  public HashSet<String> getApiMemberNames(MethodType type) {
+    if (apiMembersByName.size() == 0) {
+      initializeApiConstructorsAndFields();
+    }
+    return new HashSet<String>(apiMembersByName.get(type.getId()).keySet());
+  }
+
+  public HashSet<ApiAbstractMethod> getApiMembersBySet(
+      HashSet<String> methodNames, MethodType type) {
+    Iterator<String> iteratorString = methodNames.iterator();
+    HashMap<String, HashSet<ApiAbstractMethod>> current = apiMembersByName.get(type.getId());
+    HashSet<ApiAbstractMethod> tempMethods = new HashSet<ApiAbstractMethod>();
+    while (iteratorString.hasNext()) {
+      tempMethods.addAll(current.get(iteratorString.next()));
+    }
+    return tempMethods;
+  }
+
+  public HashSet<ApiAbstractMethod> getApiMethodsByName(String name,
+      MethodType type) {
+    return apiMembersByName.get(type.getId()).get(name);
+  }
+
+  public JClassType getClassObject() {
+    return classType;
+  }
+
+  public String getFullName() {
+    return classType.getQualifiedSourceName();
+  }
+
+  /**
+   * compute the modifier changes. check for: (i) added 'final' or 'abstract' or
+   * 'static' (ii) removed 'static' or 'non-abstract class made into interface'
+   * (if a non-abstract class is made into interface, the client class/interface
+   * inheriting from it would need to change)
+   */
+  public ArrayList<ApiChange.Status> getModifierChanges(ApiClass newClass) {
+    JClassType newClassType = newClass.getClassObject();
+    ArrayList<ApiChange.Status> statuses = new ArrayList<ApiChange.Status>(5);
+
+    // check for addition of 'final', 'abstract', 'static'
+    if (!classType.isFinal() && newClassType.isFinal()) {
+      statuses.add(ApiChange.Status.FINAL_ADDED);
+    }
+    if (!classType.isAbstract() && newClassType.isAbstract()) {
+      statuses.add(ApiChange.Status.ABSTRACT_ADDED);
+    }
+    if (!classType.isStatic() && newClassType.isStatic()) {
+      statuses.add(ApiChange.Status.STATIC_ADDED);
+    }
+
+    // removed 'static'
+    if (classType.isStatic() && !newClassType.isStatic()) {
+      statuses.add(ApiChange.Status.STATIC_REMOVED);
+    }
+
+    if (!classType.isAbstract() && (newClassType.isInterface() != null)) {
+      statuses.add(ApiChange.Status.NONABSTRACT_CLASS_MADE_INTERFACE);
+    }
+    if (isSubclassableApiClass(classType)) {
+      if ((classType.isClass() != null) && (newClassType.isInterface() != null)) {
+        statuses.add(ApiChange.Status.SUBCLASSABLE_API_CLASS_MADE_INTERFACE);
+      }
+      if ((classType.isInterface() != null) && (newClassType.isClass() != null)) {
+        statuses.add(ApiChange.Status.SUBCLASSABLE_API_INTERFACE_MADE_CLASS);
+      }
+    }
+    return statuses;
+  }
+
+  public String getName() {
+    return classType.getName();
+  }
+
+  public ApiPackage getPackage() {
+    return apiPackage;
+  }
+
+  public void initializeApiFields() {
+    apiFields = new HashMap<String, JField>();
+    ArrayList<String> notAddedFields = new ArrayList<String>();
+    JField fields[] = getAccessibleFields();
+    for (JField field : fields) {
+      if (isApiMember(field)) {
+        apiFields.put(computeFieldApiSignature(field), field);
+      } else {
+        notAddedFields.add(field.toString());
+      }
+    }
+    if (notAddedFields.size() > 0) {
+      logger.log(TreeLogger.SPAM, "class " + getName() + " " + ", not adding "
+          + notAddedFields.size() + " nonApi fields: " + notAddedFields, null);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return classType.toString();
+  }
+
+  private JField[] getAccessibleFields() {
+    Map<String, JField> fieldsBySignature = new HashMap<String, JField>();
+    JClassType tempClassType = classType;
+    do {
+      JField declaredFields[] = tempClassType.getFields();
+      for (JField field : declaredFields) {
+        if (field.isPrivate()) {
+          continue;
+        }
+        String signature = field.toString();
+        JField existing = fieldsBySignature.put(signature, field);
+        if (existing != null) {
+          // TODO(amitmanjhi): Consider whether this is sufficient
+          fieldsBySignature.put(signature, existing);
+        }
+      }
+      tempClassType = tempClassType.getSuperclass();
+    } while (tempClassType != null);
+    return fieldsBySignature.values().toArray(new JField[0]);
+  }
+
+  private JAbstractMethod[] getAccessibleMembers(MethodType member) {
+    switch (member) {
+      case CONSTRUCTOR:
+        return classType.getConstructors();
+      case METHOD:
+        return getAccessibleMethods();
+    }
+    throw new AssertionError("Unknown value : " + member.getId());
+  }
+
+  // TODO(amitmanjhi): to optimize, cache results
+  private JMethod[] getAccessibleMethods() {
+    boolean isInterface = false;
+    if (classType.isInterface() != null) {
+      isInterface = true;
+    }
+    Map<String, JMethod> methodsBySignature = new HashMap<String, JMethod>();
+    LinkedList<JClassType> classesToBeProcessed = new LinkedList<JClassType>();
+    classesToBeProcessed.add(classType);
+    JClassType tempClassType = null;
+    while (classesToBeProcessed.peek() != null) {
+      tempClassType = classesToBeProcessed.remove();
+      JMethod declaredMethods[] = tempClassType.getMethods();
+      for (JMethod method : declaredMethods) {
+        if (method.isPrivate()) {
+          continue;
+        }
+        String signature = ApiAbstractMethod.computeInternalSignature(method);
+        JMethod existing = methodsBySignature.put(signature, method);
+        if (existing != null) {
+          // decide which implementation to keep
+          if (existing.getEnclosingType().isAssignableTo(
+              method.getEnclosingType())) {
+            methodsBySignature.put(signature, existing);
+          }
+        }
+      }
+      if (isInterface) {
+        classesToBeProcessed.addAll(Arrays.asList(tempClassType.getImplementedInterfaces()));
+      } else {
+        classesToBeProcessed.add(tempClassType.getSuperclass());
+      }
+    }
+    return methodsBySignature.values().toArray(new JMethod[0]);
+  }
+
+  private void initializeApiConstructorsAndFields() {
+    for (MethodType member : MethodType.values()) {
+      apiMembersByName.add(member.getId(),
+          new HashMap<String, HashSet<ApiAbstractMethod>>());
+      HashMap<String, HashSet<ApiAbstractMethod>> pointer = apiMembersByName.get(member.getId());
+      ArrayList<String> notAddedMembers = new ArrayList<String>();
+      JAbstractMethod jams[] = getAccessibleMembers(member);
+      for (JAbstractMethod jam : jams) {
+        if (isApiMember(jam)) {
+          String tempName = jam.getName() + jam.getParameters().length;
+          HashSet<ApiAbstractMethod> existingMembers = pointer.get(tempName);
+          if (existingMembers == null) {
+            existingMembers = new HashSet<ApiAbstractMethod>();
+          }
+          switch (member) {
+            case CONSTRUCTOR:
+              existingMembers.add(new ApiConstructor(jam, this));
+              break;
+            case METHOD:
+              existingMembers.add(new ApiMethod(jam, this));
+              break;
+            default:
+              throw new AssertionError("Unknown memberType : " + member);
+          }
+          pointer.put(tempName, existingMembers);
+        } else {
+          notAddedMembers.add(jam.toString());
+        }
+      }
+      if (notAddedMembers.size() > 0) {
+        logger.log(TreeLogger.SPAM, "class " + getName() + ", removing "
+            + notAddedMembers.size() + " nonApi members: " + notAddedMembers,
+            null);
+      }
+    }
+  }
+
+  /**
+   * Note: Instance members of a class that is not instantiable are not api
+   * members.
+   */
+  private <E> boolean isApiMember(final E member) {
+    boolean isPublic = false;
+    boolean isPublicOrProtected = false;
+    boolean isStatic = false;
+
+    if (member instanceof JField) {
+      JField field = (JField) member;
+      isPublic = field.isPublic();
+      isPublicOrProtected = isPublic || field.isProtected();
+      isStatic = field.isStatic();
+    }
+    if (member instanceof JAbstractMethod) {
+      JAbstractMethod method = (JAbstractMethod) member;
+      isPublic = method.isPublic();
+      isPublicOrProtected = isPublic || method.isProtected();
+      if (method instanceof JMethod) {
+        JMethod temp = (JMethod) method;
+        isStatic = temp.isStatic();
+      } else {
+        isStatic = false; // constructors can't be static
+      }
+    }
+    if (ApiCompatibilityChecker.REMOVE_ABSTRACT_CLASS_FROM_API) {
+      if (!isInstantiableApiClass && !isStatic) {
+        return false;
+      }
+    }
+    return (isSubclassableApiClass && isPublicOrProtected)
+        || (isNotsubclassableApiClass && isPublic);
+  }
+}
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClassDiffGenerator.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClassDiffGenerator.java
new file mode 100644
index 0000000..9477a0b
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClassDiffGenerator.java
@@ -0,0 +1,581 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * Produces the diff between the API of two apiClasses.
+ */
+public class ApiClassDiffGenerator {
+  abstract static class DuplicateDetector<E> {
+    ApiDiffGenerator apiDiffGenerator;
+    JClassType currentClass;
+    int initialValue = 1;
+    ApiClass.MethodType methodType;
+
+    DuplicateDetector(JClassType currentClass,
+        ApiDiffGenerator apiDiffGenerator, ApiClass.MethodType methodType) {
+      this.currentClass = currentClass;
+      this.apiDiffGenerator = apiDiffGenerator;
+      initialValue++;
+      this.methodType = methodType;
+    }
+
+    JClassType getClassType(E element) {
+      if (element instanceof JAbstractMethod) {
+        JAbstractMethod jam = (JAbstractMethod) element;
+        return jam.getEnclosingType();
+      }
+      if (element instanceof JField) {
+        JField field = (JField) element;
+        return field.getEnclosingType();
+      }
+      return null;
+    }
+
+    abstract HashSet<E> getElements(ApiClassDiffGenerator other);
+
+    boolean isDuplicate(E element) {
+      JClassType classType = getClassType(element);
+      if (classType == currentClass) {
+        return false;
+      }
+      ApiClassDiffGenerator other = apiDiffGenerator.findApiClassDiffGenerator(classType);
+      if (other == null) {
+        return false;
+      }
+      return getElements(other).contains(element);
+    }
+  }
+
+  private static ArrayList<ApiChange> checkExceptions(
+      JAbstractMethod newMethod, JAbstractMethod oldMethod) {
+    JType oldExceptions[] = oldMethod.getThrows();
+    JType newExceptions[] = newMethod.getThrows();
+    ArrayList<ApiChange> ret = new ArrayList<ApiChange>();
+    for (JType newException : newExceptions) {
+      boolean isSubclass = false;
+      for (JType oldException : oldExceptions) {
+        if (ApiDiffGenerator.isFirstTypeAssignableToSecond(newException,
+            oldException)) {
+          isSubclass = true;
+          break;
+        }
+      }
+      if (!isSubclass) {
+        ret.add(new ApiChange(ApiChange.Status.EXCEPTIONS_ERROR,
+            "unhandled exception in new code " + newException.toString()));
+      }
+    }
+    return ret;
+  }
+
+  HashSet<JField> allIntersectingFields = new HashSet<JField>();
+
+  /**
+   * Find all constructors, methods, fields that are present in either
+   * intersection or missing members of this class or any superclass. Total of 6
+   * things to keep track of. These variables are useful for memoization.
+   */
+  ArrayList<HashSet<JAbstractMethod>> allIntersectingMethods = new ArrayList<HashSet<JAbstractMethod>>(
+      2);
+
+  ArrayList<HashSet<JAbstractMethod>> allMissingMethods = new ArrayList<HashSet<JAbstractMethod>>(
+      2);
+  ApiDiffGenerator apiDiffGenerator = null;
+  ApiPackageDiffGenerator apiPackageDiffGenerator = null;
+  String className = null;
+
+  HashMap<JField, HashSet<ApiChange.Status>> intersectingFields = null;
+
+  /**
+   * Map from methods and constructors in intersection to a string describing
+   * how they have changed. The description could be the addition/removal of a
+   * static/abstract/final keyword.
+   */
+  ArrayList<HashMap<JAbstractMethod, HashSet<ApiChange>>> intersectingMethods;
+  HashSet<JField> missingFields = null;
+  /**
+   * list of missing constructors and methods.
+   */
+  ArrayList<HashSet<JAbstractMethod>> missingMethods;
+  ApiClass newClass = null;
+
+  ApiClass oldClass = null;
+  private HashSet<JField> allMissingFields = new HashSet<JField>();
+
+  public ApiClassDiffGenerator(String className,
+      ApiPackageDiffGenerator apiPackageDiffGenerator) throws NotFoundException {
+    this.className = className;
+    this.apiPackageDiffGenerator = apiPackageDiffGenerator;
+    apiDiffGenerator = apiPackageDiffGenerator.getApiDiffGenerator();
+    this.newClass = apiPackageDiffGenerator.getNewApiPackage().getApiClass(
+        className);
+    this.oldClass = apiPackageDiffGenerator.getOldApiPackage().getApiClass(
+        className);
+    if (newClass == null || oldClass == null) {
+      throw new NotFoundException("for class " + className
+          + ", one of the class objects is null");
+    }
+    intersectingFields = new HashMap<JField, HashSet<ApiChange.Status>>();
+    intersectingMethods = new ArrayList<HashMap<JAbstractMethod, HashSet<ApiChange>>>(
+        ApiClass.MethodType.values().length);
+    missingMethods = new ArrayList<HashSet<JAbstractMethod>>(
+        ApiClass.MethodType.values().length);
+    for (ApiClass.MethodType methodType : ApiClass.MethodType.values()) {
+      intersectingMethods.add(methodType.getId(),
+          new HashMap<JAbstractMethod, HashSet<ApiChange>>());
+    }
+  }
+
+  public void cleanApiDiff() {
+    /**
+     * Two different ways of eliminating duplicates from apiDiffs. 1.
+     * computeUnionsAndCleandApiDiff: remove an ApiDiff message from this class,
+     * if it is present in the apiDiffs of any of the superclasses. (Not sure if
+     * the implementation always yield the correct result. Therefore, I have not
+     * removed the cleanApiDiff2 implementation.)
+     * 
+     * 2. cleanApiDiff2: If the ApiDiff message is about member 'x', remove the
+     * ApiDiff message from this class, if the class defining 'x' also contains
+     * this message.
+     */
+    if (true) {
+      computeUnionsAndCleanApiDiff();
+    } else {
+      cleanApiDiff2();
+    }
+  }
+
+  public void cleanApiDiff2() {
+    DuplicateDetector<JField> fieldRemover = new DuplicateDetector<JField>(
+        oldClass.getClassObject(), apiDiffGenerator,
+        ApiClass.MethodType.CONSTRUCTOR) {
+      @Override
+      HashSet<JField> getElements(ApiClassDiffGenerator other) {
+        return other.getMissingFields();
+      }
+    };
+    missingFields.removeAll(getDuplicateElements(missingFields.iterator(),
+        fieldRemover));
+
+    DuplicateDetector<JField> intersectingFieldRemover = new DuplicateDetector<JField>(
+        oldClass.getClassObject(), apiDiffGenerator,
+        ApiClass.MethodType.CONSTRUCTOR) {
+      @Override
+      HashSet<JField> getElements(ApiClassDiffGenerator other) {
+        return other.getIntersectingFields();
+      }
+    };
+    removeAll(intersectingFields, getDuplicateElements(
+        intersectingFields.keySet().iterator(), intersectingFieldRemover));
+
+    for (ApiClass.MethodType methodType : ApiClass.MethodType.values()) {
+      DuplicateDetector<JAbstractMethod> missingMemberRemover = new DuplicateDetector<JAbstractMethod>(
+          oldClass.getClassObject(), apiDiffGenerator, methodType) {
+        @Override
+        HashSet<JAbstractMethod> getElements(final ApiClassDiffGenerator other) {
+          return other.getMissingMethods(methodType);
+        }
+      };
+      missingMethods.get(methodType.getId()).removeAll(
+          getDuplicateElements(
+              missingMethods.get(methodType.getId()).iterator(),
+              missingMemberRemover));
+
+      DuplicateDetector<JAbstractMethod> intersectingMemberRemover = new DuplicateDetector<JAbstractMethod>(
+          oldClass.getClassObject(), apiDiffGenerator, methodType) {
+        @Override
+        HashSet<JAbstractMethod> getElements(final ApiClassDiffGenerator other) {
+          return other.getIntersectingMethods(methodType);
+        }
+      };
+      removeAll(intersectingMethods.get(methodType.getId()),
+          getDuplicateElements(
+              intersectingMethods.get(methodType.getId()).keySet().iterator(),
+              intersectingMemberRemover));
+    }
+  }
+
+  public void computeApiDiff() {
+    HashSet<String> newFieldNames = newClass.getApiFieldNames();
+    HashSet<String> oldFieldNames = oldClass.getApiFieldNames();
+    HashSet<String> intersection = ApiDiffGenerator.findCommonElements(
+        newFieldNames, oldFieldNames, className);
+    missingFields = oldClass.getApiFieldsBySet(oldFieldNames);
+    processFieldsInIntersection(intersection);
+
+    for (ApiClass.MethodType methodType : ApiClass.MethodType.values()) {
+      HashSet<String> newMethodNames = newClass.getApiMemberNames(methodType);
+      HashSet<String> oldMethodNames = oldClass.getApiMemberNames(methodType);
+      intersection = ApiDiffGenerator.findCommonElements(newMethodNames,
+          oldMethodNames, className);
+      missingMethods.add(methodType.getId(),
+          getAbstractMethodObjects(oldClass.getApiMembersBySet(oldMethodNames,
+              methodType)));
+      processElementsInIntersection(intersection, methodType);
+    }
+  }
+
+  /**
+   * Compute the union of apiDiffs of all superclasses. Must be invoked only
+   * after intersectingMembers et al. have been computed. Algorithm: - Find the
+   * immediate superclass that has computed these apiDiffs. - Compute the union
+   * of apiDiffs of superClass with own apiDiffs.
+   */
+  public void computeUnionsAndCleanApiDiff() {
+    // check if unions have already been computed.
+    if (allMissingMethods.size() > 0) {
+      return;
+    }
+    ApiClassDiffGenerator other = getSuperclassApiClassDiffGenerator();
+    // compute 'all*' fields for the 'other' object.
+    if (other != null) {
+      other.computeUnionsAndCleanApiDiff();
+    }
+
+    for (ApiClass.MethodType methodType : ApiClass.MethodType.values()) {
+      // clean the current apiDiffs
+      if (other != null) {
+        removeAll(intersectingMethods.get(methodType.getId()),
+            other.allIntersectingMethods.get(methodType.getId()));
+        missingMethods.get(methodType.getId()).removeAll(
+            other.allMissingMethods.get(methodType.getId()));
+      }
+      // compute the union
+      HashSet<JAbstractMethod> tempSet1 = new HashSet<JAbstractMethod>(
+          intersectingMethods.get(methodType.getId()).keySet());
+      HashSet<JAbstractMethod> tempSet2 = new HashSet<JAbstractMethod>(
+          missingMethods.get(methodType.getId()));
+      if (other != null) {
+        tempSet1.addAll(other.allIntersectingMethods.get(methodType.getId()));
+        tempSet2.addAll(other.allMissingMethods.get(methodType.getId()));
+      }
+      allIntersectingMethods.add(methodType.getId(), tempSet1);
+      allMissingMethods.add(methodType.getId(), tempSet2);
+    }
+    // clean the current apiDiffs
+    if (other != null) {
+      removeAll(intersectingFields, other.allIntersectingFields);
+      missingFields.removeAll(other.allMissingFields);
+    }
+    // compute the union
+    allIntersectingFields = new HashSet<JField>(intersectingFields.keySet());
+    allMissingFields = new HashSet<JField>(missingFields);
+    if (other != null) {
+      allIntersectingFields.addAll(other.allIntersectingFields);
+      allMissingFields.addAll(other.allMissingFields);
+    }
+  }
+
+  public HashSet<JAbstractMethod> getAbstractMethodObjects(
+      HashSet<? extends ApiAbstractMethod> temp) {
+    Iterator<? extends ApiAbstractMethod> apiMethodsIterator = temp.iterator();
+    HashSet<JAbstractMethod> returnSet = new HashSet<JAbstractMethod>();
+    while (apiMethodsIterator.hasNext()) {
+      returnSet.add(apiMethodsIterator.next().getMethodObject());
+    }
+    return returnSet;
+  }
+
+  public <T> HashSet<T> getDuplicateElements(Iterator<T> iterator,
+      DuplicateDetector<T> detector) {
+    HashSet<T> returnSet = new HashSet<T>();
+    while (iterator.hasNext()) {
+      T element = iterator.next();
+      if (detector.isDuplicate(element)) {
+        returnSet.add(element);
+      }
+    }
+    return returnSet;
+  }
+
+  public HashSet<JField> getIntersectingFields() {
+    return new HashSet<JField>(intersectingFields.keySet());
+  }
+
+  public HashSet<JAbstractMethod> getIntersectingMethods(
+      ApiClass.MethodType methodType) {
+    return new HashSet<JAbstractMethod>(intersectingMethods.get(
+        methodType.getId()).keySet());
+  }
+
+  public HashSet<JField> getMissingFields() {
+    return missingFields;
+  }
+
+  public HashSet<JAbstractMethod> getMissingMethods(
+      ApiClass.MethodType methodType) {
+    return missingMethods.get(methodType.getId());
+  }
+
+  public HashSet<ApiChange.Status> getModifierChangesForField(JField newField,
+      JField oldField) {
+    HashSet<ApiChange.Status> statuses = new HashSet<ApiChange.Status>();
+    if (!oldField.isFinal() && newField.isFinal()) {
+      statuses.add(ApiChange.Status.FINAL_ADDED);
+    }
+    if ((oldField.isStatic() && !newField.isStatic())) {
+      statuses.add(ApiChange.Status.STATIC_REMOVED);
+    }
+    return statuses;
+  }
+
+  public String printApiDiff() {
+    ArrayList<ApiChange.Status> apiChanges = oldClass.getModifierChanges(newClass);
+    int totalSize = missingFields.size() + intersectingFields.size()
+        + apiChanges.size();
+    for (ApiClass.MethodType methodType : ApiClass.MethodType.values()) {
+      totalSize += (missingMethods.get(methodType.getId()).size() + intersectingMethods.get(
+          methodType.getId()).size());
+    }
+    if (totalSize == 0) {
+      return "";
+    }
+    StringBuffer sb = new StringBuffer();
+    Iterator<ApiChange.Status> apiChangeIterator = apiChanges.iterator();
+    while (apiChangeIterator.hasNext()) {
+      sb.append("\t\t" + oldClass.getFullName() + " "
+          + apiChangeIterator.next() + "\n");
+    }
+    if (apiChanges.size() == 0) {
+      sb.append("\t\tclass " + oldClass.getFullName() + "\n");
+    }
+    sb.append(printCollectionElements(missingFields.iterator()));
+    sb.append(printCollectionElements2(intersectingFields));
+    for (ApiClass.MethodType methodType : ApiClass.MethodType.values()) {
+      sb.append(printCollectionElements(missingMethods.get(methodType.getId()).iterator()));
+      sb.append(printCollectionElements(intersectingMethods.get(methodType.getId())));
+    }
+    sb.append("\n");
+    return sb.toString();
+  }
+
+  public <E, V> void removeAll(HashMap<E, HashSet<V>> tempMap,
+      HashSet<E> removeKeys) {
+    Iterator<E> keyIterator = removeKeys.iterator();
+    while (keyIterator.hasNext()) {
+      tempMap.remove(keyIterator.next());
+    }
+  }
+
+  private <T> void addProperty(HashMap<T, HashSet<ApiChange>> hashMap, T key,
+      ApiChange property) {
+    if (!ApiCompatibilityChecker.PRINT_INTERSECTIONS
+        && (property.getStatus() == ApiChange.Status.COMPATIBLE)) {
+      return;
+    }
+    if (!ApiCompatibilityChecker.PRINT_COMPATIBLE_WITH
+        && property.getStatus() == ApiChange.Status.COMPATIBLE_WITH) {
+      return;
+    }
+    HashSet<ApiChange> value = hashMap.get(key);
+    if (value == null) {
+      value = new HashSet<ApiChange>();
+    }
+    value.add(property);
+    hashMap.put(key, value);
+  }
+
+  /**
+   * return the ApiClassDiffGenerator object for the "closest" ancestor of
+   * oldClass. return null if no ancestor of oldClass has ApiClassDiffGenerator
+   */
+  private ApiClassDiffGenerator getSuperclassApiClassDiffGenerator() {
+    ApiClassDiffGenerator other = null;
+    JClassType classType = oldClass.getClassObject();
+    while ((classType = classType.getSuperclass()) != null) {
+      other = apiDiffGenerator.findApiClassDiffGenerator(classType);
+      if (other != null) {
+        return other;
+      }
+    }
+    return null;
+  }
+
+  private boolean isIncompatibileDueToMethodOverloading(
+      HashSet<ApiAbstractMethod> methodsInNew,
+      HashSet<ApiAbstractMethod> methodsInExisting) {
+    if (!ApiCompatibilityChecker.API_SOURCE_COMPATIBILITY
+        || methodsInExisting.size() != 1 || methodsInNew.size() <= 1) {
+      return false;
+    }
+    String signature = methodsInExisting.toArray(new ApiAbstractMethod[0])[0].getCoarseSignature();
+    Iterator<ApiAbstractMethod> newMethodsIterator = methodsInNew.iterator();
+    int numMatchingSignature = 0;
+    while (newMethodsIterator.hasNext() && numMatchingSignature < 2) {
+      ApiAbstractMethod current = newMethodsIterator.next();
+      if (current.getCoarseSignature().equals(signature)) {
+        ++numMatchingSignature;
+      }
+    }
+    return numMatchingSignature > 1;
+  }
+
+  private <V> String printCollectionElements(
+      HashMap<JAbstractMethod, HashSet<V>> tempHashMap) {
+    StringBuffer sb = new StringBuffer();
+    Iterator<JAbstractMethod> tempIterator = tempHashMap.keySet().iterator();
+    while (tempIterator.hasNext()) {
+      JAbstractMethod element = tempIterator.next();
+      String identifier = oldClass.computeRelativeSignature(element);
+      Iterator<V> tempIterator2 = tempHashMap.get(element).iterator();
+      while (tempIterator2.hasNext()) {
+        sb.append("\t\t\t" + identifier + ApiDiffGenerator.DELIMITER
+            + tempIterator2.next() + "\n");
+      }
+    }
+    return sb.toString();
+  }
+
+  private <E> String printCollectionElements(Iterator<E> temp) {
+    StringBuffer sb = new StringBuffer();
+    while (temp.hasNext()) {
+      sb.append("\t\t\t" + oldClass.computeRelativeSignature(temp.next())
+          + ApiDiffGenerator.DELIMITER + ApiChange.Status.MISSING + "\n");
+    }
+    return sb.toString();
+  }
+
+  private <V> String printCollectionElements2(
+      HashMap<JField, HashSet<V>> tempHashMap) {
+    StringBuffer sb = new StringBuffer();
+    Iterator<JField> tempIterator = tempHashMap.keySet().iterator();
+    while (tempIterator.hasNext()) {
+      JField element = tempIterator.next();
+      String identifier = oldClass.computeRelativeSignature(element);
+      Iterator<V> tempIterator2 = tempHashMap.get(element).iterator();
+      while (tempIterator2.hasNext()) {
+        sb.append("\t\t\t" + identifier + ApiDiffGenerator.DELIMITER
+            + tempIterator2.next() + "\n");
+      }
+    }
+    return sb.toString();
+  }
+
+  private void processElementsInIntersection(HashSet<String> intersection,
+      ApiClass.MethodType methodType) {
+    if (intersection.size() == 0) {
+      return;
+    }
+    HashSet<JAbstractMethod> missingElements = missingMethods.get(methodType.getId());
+    HashMap<JAbstractMethod, HashSet<ApiChange>> intersectingElements = intersectingMethods.get(methodType.getId());
+
+    HashSet<ApiAbstractMethod> onlyInExisting = new HashSet<ApiAbstractMethod>();
+    HashSet<ApiAbstractMethod> onlyInNew = new HashSet<ApiAbstractMethod>();
+    HashSet<String> commonSignature = new HashSet<String>();
+    Iterator<String> intersectionNames = intersection.iterator();
+
+    while (intersectionNames.hasNext()) {
+      String tempName = intersectionNames.next();
+      HashSet<ApiAbstractMethod> methodsInNew = newClass.getApiMethodsByName(
+          tempName, methodType);
+      HashSet<ApiAbstractMethod> methodsInExisting = oldClass.getApiMethodsByName(
+          tempName, methodType);
+      onlyInNew.addAll(methodsInNew);
+      onlyInExisting.addAll(methodsInExisting);
+      if (isIncompatibileDueToMethodOverloading(methodsInNew, methodsInExisting)) {
+        addProperty(
+            intersectingElements,
+            methodsInExisting.toArray(new ApiAbstractMethod[0])[0].getMethodObject(),
+            new ApiChange(ApiChange.Status.OVERLOADED,
+                "Many methods in the new API with similar signatures. Methods = "
+                    + methodsInNew
+                    + " This might break API source compatibility"));
+      }
+      Iterator<ApiAbstractMethod> iterator1 = methodsInExisting.iterator();
+      // We want to find out which method calls that the current API supports
+      // will succeed even with the new API. Determine this by iterating over
+      // the methods of the current API
+      while (iterator1.hasNext()) {
+        ApiAbstractMethod methodInExisting = iterator1.next();
+        Iterator<ApiAbstractMethod> iterator2 = methodsInNew.iterator();
+        while (iterator2.hasNext()) {
+          ApiAbstractMethod methodInNew = iterator2.next();
+          if (methodInExisting.isCompatible(methodInNew)) {
+            ApiChange returnType = methodInExisting.checkReturnTypeCompatibility(methodInNew);
+            if (returnType != null) {
+              addProperty(intersectingElements,
+                  methodInExisting.getMethodObject(), returnType);
+            }
+            Iterator<ApiChange> apiChangeIterator = checkExceptions(
+                methodInNew.getMethodObject(),
+                methodInExisting.getMethodObject()).iterator();
+            while (apiChangeIterator.hasNext()) {
+              addProperty(intersectingElements,
+                  methodInExisting.getMethodObject(), apiChangeIterator.next());
+            }
+            Iterator<ApiChange.Status> apiChanges = methodInExisting.getModifierChanges(
+                methodInNew).iterator();
+            while (apiChanges.hasNext()) {
+              addProperty(intersectingElements,
+                  methodInExisting.getMethodObject(), new ApiChange(
+                      apiChanges.next()));
+            }
+            onlyInNew.remove(methodInNew);
+            onlyInExisting.remove(methodInExisting);
+            String signatureInNew = methodInNew.getApiSignature();
+            String signatureInExisting = methodInExisting.getApiSignature();
+            if (signatureInNew.equals(signatureInExisting)) {
+              commonSignature.add(signatureInNew);
+              addProperty(intersectingElements,
+                  methodInExisting.getMethodObject(), new ApiChange(
+                      ApiChange.Status.COMPATIBLE));
+            } else {
+              addProperty(intersectingElements,
+                  methodInExisting.getMethodObject(), new ApiChange(
+                      ApiChange.Status.COMPATIBLE_WITH, " compatible with "
+                          + signatureInNew));
+            }
+          }
+        }
+      }
+      // printOutput(commonSignature, onlyInExisting, onlyInNew);
+    }
+    missingElements.addAll(getAbstractMethodObjects(onlyInExisting));
+  }
+
+  private void processFieldsInIntersection(HashSet<String> intersection) {
+    if (intersection.size() == 0) {
+      return;
+    }
+    Iterator<String> intersectionNames = intersection.iterator();
+
+    while (intersectionNames.hasNext()) {
+      String tempName = intersectionNames.next();
+      JField newField = newClass.getApiFieldByName(tempName);
+      JField oldField = oldClass.getApiFieldByName(tempName);
+      HashSet<ApiChange.Status> apiChanges = getModifierChangesForField(
+          newField, oldField);
+      if (apiChanges.size() > 0) {
+        intersectingFields.put(oldField, getModifierChangesForField(newField,
+            oldField));
+      }
+    }
+  }
+
+}
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
new file mode 100644
index 0000000..d67c296
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashSet;
+
+/**
+ * Checks if the new API is compatible with the existing API.
+ * 
+ * 
+ * To compute Api diffs, follow these 5 steps: i) for each of the two
+ * repositories, construct an ApiContainer, ii) construct an ApiDiffGenerator,
+ * iii) call computeApiDiff on the ApiDiffGenerator iv) call cleanApiDiff on the
+ * ApiDiffGenerator v) call printApiDiff on the ApiDiffGenerator
+ * 
+ * An apicontainer object has a list of apiPackage objects. ApiPackage objects
+ * themselves are list of ApiClass objects. ApiClass objects contain list of
+ * ApiConstructor, ApiMethod, and JField objects.
+ * 
+ * Each ApiDiffGenerator object has a list of intersecting and missing
+ * ApiPackageDiffGenerator objects. Each ApiPackageDiffGenerator object has a
+ * list of intersecting and missing ApiClassDiffGenerator objects. Each
+ * ApiClassDiffGenerator object has a list of intersecting and missing
+ * apiMembers, where these members are constructors, methods, and fields.
+ * 
+ * For each intersecting apiMember, a list of ApiChange objects is stored. Each
+ * ApiChange object encodes a specific Api change like adding the 'final'
+ * keyword to an apiMethod.
+ * 
+ */
+public class ApiCompatibilityChecker {
+
+  public static final boolean API_SOURCE_COMPATIBILITY = true;
+  public static final boolean REMOVE_ABSTRACT_CLASS_FROM_API = true;
+  public static final boolean IGNORE_WHITELIST = false;
+  public static final boolean PRINT_COMPATIBLE_WITH = false;
+  public static final boolean PRINT_INTERSECTIONS = false;
+  public static final boolean REMOVE_DUPLICATES = true;
+  public static final boolean DEBUG = false;
+  public static final boolean DISABLE_CHECKS = true;
+
+  private static final AbstractTreeLogger logger1 = createTreeLogger();
+
+  public static String getApiDiff(ApiDiffGenerator temp,
+      HashSet<String> whiteList, boolean removeDuplicates)
+      throws NotFoundException {
+    temp.computeApiDiff();
+    if (removeDuplicates) {
+      temp.cleanApiDiff();
+    }
+    String apiDifferences = temp.printApiDiff();
+    return removeWhiteListMatches(apiDifferences, whiteList);
+  }
+
+  // Call APIBuilders for each of the 2 source trees
+  public static void main(String args[]) {
+
+    try {
+      ApiContainer newApi = null, existingApi = null;
+      boolean processNewApi = true;
+      boolean processExistingApi = true;
+
+      if (args.length < 1) {
+        printHelp();
+        Runtime.getRuntime().exit(-1);
+      }
+
+      if (processNewApi) {
+        newApi = new ApiContainer(args[0], "_new", logger1);
+      }
+      if (processExistingApi) {
+        existingApi = new ApiContainer(args[0], "_old", logger1);
+      }
+
+      if ((processNewApi && processExistingApi)
+          && (newApi != null && existingApi != null)) {
+        HashSet<String> whiteList = new HashSet<String>();
+        if (!IGNORE_WHITELIST) {
+          whiteList = readStringFromFile(args[0]);
+        }
+
+        ApiDiffGenerator apiDiff = new ApiDiffGenerator(newApi, existingApi);
+        String apiDifferences = getApiDiff(apiDiff, whiteList,
+            REMOVE_DUPLICATES);
+        System.out.println(apiDifferences);
+        System.out.println("\t\t\t\tApi Compatibility Checker tool, Copyright Google Inc. 2008");
+      }
+
+    } catch (Exception e) {
+      System.err.println("Exception " + e.getMessage()
+          + ", printing stacktrace");
+      e.printStackTrace();
+      Runtime.getRuntime().exit(-1);
+    }
+  }
+
+  public static void printHelp() {
+    System.out.println("java ApiCompatibilityChecker configFile\n");
+    System.out.println("The ApiCompatibilityChecker tool requires a config file as an argument. "
+        + "The config file must specify two repositories of java source files "
+        + "that must be compared for API source compatibility. Each repository "
+        + "must specify three properties: 'name', 'sourceFiles', and 'excludeFiles.' "
+        + "A suffix of '_old' is attached to properties of the first repository, "
+        + "while a suffix of '_new' is attached to properties of the second "
+        + "repository. An optional whitelist can also be present at the end of "
+        + "the config file. The format of the whitelist is same as the output of "
+        + "the tool without the whitelist.");
+    System.out.println();
+    System.out.println("The name property specifies the api name that should "
+        + "be used in the output. The sourceFiles property, a colon-separated "
+        + "list of files/directories, specifies the roots of the the filesystem "
+        + "trees that must be included. The excludeFiles property, "
+        + "a colon-separated lists of files/directories specifies the roots of "
+        + "the filesystem trees that must be excluded.");
+    System.out.println();
+    System.out.println();
+    System.out.println("Example api.conf file:\n"
+
+        + "name_old gwtEmulator\n"
+        + "sourceFiles_old dev/core/super/com/google/gwt/dev/jjs/intrinsic/:user/super/com/google/gwt/emul/:user/src/com/google/gwt/core/client\n"
+        + "excludeFiles_old \n\n"
+
+        + "name_new gwtEmulatorCopy\n"
+        + "sourceFiles_new dev/core/super/com/google/gwt/dev/jjs/intrinsic/:user/super/com/google/gwt/emul/:user/src/com/google/gwt/core/client\n"
+        + "excludeFiles_new \n\n");
+  }
+
+  /**
+   * Tweak this for the log output.
+   */
+  private static AbstractTreeLogger createTreeLogger() {
+    AbstractTreeLogger logger = new PrintWriterTreeLogger();
+    int choice = 3; // 1, 2, 3
+    switch (choice) {
+      case 1:
+        logger.setMaxDetail(TreeLogger.ALL);
+        break;
+      case 2:
+        logger.setMaxDetail(null);
+        break;
+      default:
+        logger.setMaxDetail(TreeLogger.ERROR);
+    }
+    return logger;
+  }
+
+  private static HashSet<String> readStringFromFile(String fileName)
+      throws IOException {
+    if (fileName == null) {
+      throw new IllegalArgumentException("fileName is null");
+    }
+    HashSet<String> hashSet = new HashSet<String>();
+    FileReader fr = new FileReader(fileName);
+    BufferedReader br = new BufferedReader(fr);
+    String str = null;
+    while ((str = br.readLine()) != null) {
+      str = str.trim();
+      // ignore comments
+      if (str.startsWith("#")) {
+        continue;
+      }
+      String splits[] = str.split(" ");
+      if (splits.length > 1) {
+        hashSet.add(splits[0] + " " + splits[1]);
+      }
+    }
+    return hashSet;
+  }
+
+  private static String removeWhiteListMatches(String apiDifferences,
+      HashSet<String> whiteList) {
+    String apiDifferencesArray[] = apiDifferences.split("\n");
+    String whiteListArray[] = whiteList.toArray(new String[0]);
+    for (int i = 0; i < apiDifferencesArray.length; i++) {
+      String temp = apiDifferencesArray[i].trim();
+      for (String whiteListElement : whiteListArray) {
+        if (temp.startsWith(whiteListElement)) {
+          apiDifferencesArray[i] = "";
+        }
+      }
+    }
+
+    StringBuffer sb = new StringBuffer();
+    for (String temp : apiDifferencesArray) {
+      if (temp.length() > 0) {
+        sb.append(temp);
+        sb.append("\n");
+      }
+    }
+    return sb.toString();
+  }
+
+}
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiConstructor.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiConstructor.java
new file mode 100644
index 0000000..fd57b57
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiConstructor.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+
+import java.util.ArrayList;
+
+/**
+ * Encapsulates an API constructor.
+ */
+public class ApiConstructor extends ApiAbstractMethod {
+
+  public ApiConstructor(JAbstractMethod method, ApiClass apiClass) {
+    super(method, apiClass);
+  }
+
+  @Override
+  public ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod) {
+    return null;
+  }
+
+  @Override
+  public ArrayList<ApiChange.Status> getModifierChanges(
+      ApiAbstractMethod newMethod) {
+    return new ArrayList<ApiChange.Status>(0);
+  }
+
+}
\ No newline at end of file
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java
new file mode 100644
index 0000000..c7d1c20
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
+import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.jdt.CacheManager;
+import com.google.gwt.dev.jdt.TypeOracleBuilder;
+import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Vector;
+
+/**
+ * Encapsulates an API.
+ * 
+ */
+public class ApiContainer {
+  private HashMap<String, ApiPackage> apiPackages = new HashMap<String, ApiPackage>();
+  private HashMap<String, String> excludedFiles = null;
+  private TreeLogger logger = null;
+  private String name = null;
+  private int numFilesCount = 0;
+  private Collection<File> sourceTrees = null;
+  private TypeOracle typeOracle = null;
+
+  public ApiContainer(String fileName, String suffix, TreeLogger logger)
+      throws IllegalArgumentException, MalformedURLException,
+      FileNotFoundException, IOException, NotFoundException,
+      UnableToCompleteException {
+    this.logger = logger;
+    if (fileName == null) {
+      throw new IllegalArgumentException("fileName is null");
+    }
+    FileInputStream fis = new FileInputStream(fileName);
+    Properties config = new Properties();
+    config.load(fis);
+    String apiName = config.getProperty("name" + suffix);
+    String allSourceFiles = config.getProperty("sourceFiles" + suffix);
+    String allExcludedFiles = config.getProperty("excludedFiles" + suffix);
+
+    if (allExcludedFiles == null) {
+      allExcludedFiles = "";
+    }
+    if (apiName == null || allSourceFiles == null) {
+      throw new IllegalArgumentException(
+          "in apiContainer constructor, either name (" + apiName
+              + ") or sourceFiles (" + allSourceFiles + ") is null");
+    }
+    logger.log(TreeLogger.DEBUG, "read from config file " + fileName
+        + ", name = " + apiName + ", allSourceFiles = " + allSourceFiles
+        + ", allExcludedFiles = " + allExcludedFiles, null);
+
+    String sourceFilesArray[] = allSourceFiles.split(":");
+    Collection<File> fileCollection = new Vector<File>();
+    for (String tempStr : sourceFilesArray) {
+      tempStr = tempStr.trim();
+      fileCollection.add(new File(tempStr));
+    }
+    this.sourceTrees = fileCollection;
+    if (allExcludedFiles.equals("")) {
+      this.excludedFiles = generateCanonicalHashmap(new String[0]);
+    } else {
+      String excludedFilesArray[] = allExcludedFiles.split(":");
+      this.excludedFiles = generateCanonicalHashmap(excludedFilesArray);
+    }
+    this.name = apiName;
+    createTypeOracleFromSources();
+    initializeApiPackages();
+  }
+
+  // constructor is used while testing
+  ApiContainer(String name, TreeLogger logger, TypeOracle typeOracle) {
+    this.name = name;
+    this.logger = logger;
+    this.typeOracle = typeOracle;
+    initializeApiPackages();
+  }
+
+  public ApiPackage getApiPackage(String packageName) {
+    return apiPackages.get(packageName);
+  }
+
+  public HashSet<String> getApiPackageNames() {
+    return new HashSet<String>(apiPackages.keySet());
+  }
+
+  public TreeLogger getLogger() {
+    return logger;
+  }
+ 
+  private void addCompilationUnitsInPath(TypeOracleBuilder builder,
+      File sourcePathEntry) throws NotFoundException, IOException,
+      UnableToCompleteException {
+    File[] files = sourcePathEntry.listFiles();
+    if (files == null) {
+      // No files found.
+      return;
+    }
+
+    for (int i = 0; i < files.length; i++) {
+      File file = files[i];
+      // Ignore files like .svn and .cvs
+      if (file.getName().startsWith(".") || file.getName().equals("CVS")) {
+        continue;
+      }
+      if (isExcludedFile(file.getCanonicalPath())) {
+        // do not process the subtree
+        logger.log(TreeLogger.DEBUG, "not traversing "
+            + file.toURL().toString(), null);
+        continue;
+      }
+      if (file.isFile()) {
+        String pkgName = null;
+        if (file.getName().endsWith("java")) {
+          pkgName = extractPackageNameFromFile(file);
+          logger.log(TreeLogger.DEBUG, "pkgName = " + pkgName + ", file = "
+              + file.toString(), null);
+        }
+        if (isValidPackage(pkgName, sourcePathEntry.toURL().toString())) {
+          // Add if it's a source file and the package and fileNames are okay
+          URL location = file.toURL();
+          CompilationUnitProvider cup = new URLCompilationUnitProvider(
+              location, pkgName);
+          logger.log(TreeLogger.DEBUG, "+ to CompilationUnit" + ", location="
+              + location + ", pkgName=" + pkgName, null);
+          builder.addCompilationUnit(cup);
+          numFilesCount++;
+        } else {
+          logger.log(TreeLogger.SPAM, " not adding file " + file.toURL(), null);
+        }
+      } else {
+        // Recurse into subDirs
+        addCompilationUnitsInPath(builder, file);
+      }
+    }
+  }
+
+  private void createTypeOracleFromSources() throws NotFoundException,
+      IOException, UnableToCompleteException {
+
+    numFilesCount = 0;
+    TypeOracleBuilder builder = new TypeOracleBuilder(new CacheManager(null,
+        null, ApiCompatibilityChecker.DISABLE_CHECKS));
+    for (Iterator<File> i = sourceTrees.iterator(); i.hasNext();) {
+      addCompilationUnitsInPath(builder, i.next());
+    }
+    typeOracle = builder.build(logger);
+    logger.log(TreeLogger.INFO, "API " + name
+        + ", Finished with building typeOracle, added " + numFilesCount
+        + " files", null);
+  }
+
+  private String extractPackageNameFromFile(File file) {
+    if (!file.exists()) {
+      return null;
+    }
+    String fileName = null;
+    try {
+      fileName = file.toURL().toString();
+      FileReader fr = new FileReader(file);
+      BufferedReader br = new BufferedReader(fr);
+      String str = null;
+      while ((str = br.readLine()) != null) {
+        if (str.indexOf("package") != 0) {
+          continue;
+        }
+        String parts[] = str.split("[\b\t\n\r ]+");
+        if ((parts.length == 2) && parts[0].equals("package")) {
+          return parts[1].substring(0, parts[1].length() - 1); // the ; char
+        }
+      }
+      return null;
+    } catch (Exception ex) {
+      logger.log(TreeLogger.ERROR,
+          "error in parsing and obtaining the packageName from file "
+              + fileName + "error's message " + ex.getMessage(), ex);
+      return null;
+    }
+  }
+
+  /**
+   * Convert a set into a HashMap for faster lookups.
+   */
+  private HashMap<String, String> generateCanonicalHashmap(String strArray[])
+      throws IOException {
+    HashMap<String, String> tempMap = new HashMap<String, String>();
+    if (strArray == null) {
+      return tempMap;
+    }
+    for (String str : strArray) {
+      str = str.trim();
+      File tempFile = new File(str);
+      str = tempFile.getCanonicalPath();
+      tempMap.put(str, str);
+    }
+    return tempMap;
+  }
+
+  /**
+   * Purge non API packages.
+   */
+  private void initializeApiPackages() {
+    HashSet<JPackage> allPackages = new HashSet<JPackage>(
+        Arrays.asList(typeOracle.getPackages()));
+    Iterator<JPackage> packagesIterator = allPackages.iterator();
+    HashSet<String> packagesNotAdded = new HashSet<String>();
+    while (packagesIterator.hasNext()) {
+      JPackage packageObject = packagesIterator.next();
+      if (ApiPackage.isApiPackage(packageObject)) {
+        ApiPackage apiPackageObj = new ApiPackage(packageObject, this);
+        apiPackages.put(apiPackageObj.getName(), apiPackageObj);
+      } else {
+        packagesNotAdded.add(packageObject.toString());
+      }
+    }
+    if (packagesNotAdded.size() > 0) {
+      logger.log(TreeLogger.DEBUG, "API " + name + ": not added "
+          + packagesNotAdded.size() + " packages: " + packagesNotAdded, null);
+    }
+    if (apiPackages.size() > 0) {
+      logger.log(TreeLogger.INFO, "API " + name + apiPackages.size()
+          + " Api packages: " + apiPackages.keySet(), null);
+    }
+  }
+
+  private boolean isExcludedFile(String fileName) {
+    String pattern = "file:";
+    if (fileName.indexOf(pattern) == 0) {
+      fileName = fileName.substring(pattern.length());
+    }
+    return (excludedFiles.get(fileName) != null);
+  }
+
+  private boolean isValidPackage(String packageName, String filePath) {
+    logger.log(TreeLogger.SPAM, "packageName = " + packageName
+        + ", filePath = " + filePath, null);
+    if (packageName == null) {
+      return false;
+    }
+    int lastSlashPosition = filePath.lastIndexOf("/");
+    if (lastSlashPosition == -1) {
+      return false;
+    }
+    String dirPath = filePath.substring(0, lastSlashPosition);
+    String packageNameAsPath = packageName.replace('.', '/');
+    logger.log(TreeLogger.SPAM, "packageNameAsPath " + packageNameAsPath
+        + ", dirPath = " + dirPath, null);
+    return dirPath.endsWith(packageNameAsPath);
+  }
+}
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiDiffGenerator.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiDiffGenerator.java
new file mode 100644
index 0000000..3405fb4
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiDiffGenerator.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * encapsulates a class that produces the diff between two api's.
+ */
+public class ApiDiffGenerator {
+
+  public static final String DELIMITER = " ";
+
+  @SuppressWarnings("unchecked")
+  public static HashSet<String> findCommonElements(HashSet<String> s1,
+      HashSet<String> s2, String name) {
+    HashSet<String> intersection = (HashSet<String>) s1.clone();
+    intersection.retainAll(s2);
+    s1.removeAll(intersection);
+    s2.removeAll(intersection);
+    return intersection;
+  }
+
+  /**
+   * The two types might belong to different typeOracles.
+   */
+  public static boolean isFirstTypeAssignableToSecond(JType firstType,
+      JType secondType) {
+
+    // getJNISignature() does TypeErasure
+    if (firstType.getJNISignature().equals(secondType.getJNISignature())) {
+      return true;
+    }
+    JClassType classType1 = firstType.isClassOrInterface();
+    JClassType classType2 = secondType.isClassOrInterface();
+    if (classType1 == null || classType2 == null) {
+      return false;
+    }
+    TypeOracle newApiTypeOracle = classType2.getOracle();
+    // get the appropriate classObject in the newApi
+    JClassType firstClassType = newApiTypeOracle.findType(classType1.getQualifiedSourceName());
+    JClassType secondClassType = newApiTypeOracle.findType(classType2.getQualifiedSourceName());
+    // The types might not necessarily exist in the newApi
+    if (firstClassType == null || secondClassType == null) {
+      return false;
+    }
+    return firstClassType.isAssignableTo(secondClassType);
+  }
+
+  HashMap<String, ApiPackageDiffGenerator> intersectingPackages = new HashMap<String, ApiPackageDiffGenerator>();
+
+  HashSet<String> missingPackageNames = null;
+  ApiContainer newApi = null;
+
+  ApiContainer oldApi = null;
+
+  ApiDiffGenerator(ApiContainer newApi, ApiContainer oldApi) {
+    this.newApi = newApi;
+    this.oldApi = oldApi;
+  }
+
+  public void cleanApiDiff() {
+    Iterator<ApiPackageDiffGenerator> tempIterator = intersectingPackages.values().iterator();
+    while (tempIterator.hasNext()) {
+      tempIterator.next().cleanApiDiff();
+    }
+  }
+
+  /**
+   * Compares 2 APIs for source compatibility. Algorithm: First find packages
+   * that are in one but not in another. Then, look at at classes in the common
+   * packages. Look at public classes.
+   * 
+   */
+  public void computeApiDiff() throws NotFoundException {
+    HashSet<String> newApiPackageNames = newApi.getApiPackageNames();
+    missingPackageNames = oldApi.getApiPackageNames();
+    HashSet<String> intersection = findCommonElements(newApiPackageNames,
+        missingPackageNames, "ROOT");
+    // Inspect each of the classes in each of the packages in the intersection
+    Iterator<String> tempIterator = intersection.iterator();
+    while (tempIterator.hasNext()) {
+      String packageName = tempIterator.next();
+      ApiPackageDiffGenerator temp = new ApiPackageDiffGenerator(packageName,
+          this);
+      intersectingPackages.put(packageName, temp);
+      temp.computeApiDiff();
+    }
+  }
+
+  public ApiClassDiffGenerator findApiClassDiffGenerator(JClassType classType) {
+    String className = classType.getQualifiedSourceName();
+    int i = className.length() - 1;
+    while (i >= 0) {
+      int dot = className.lastIndexOf('.', i);
+      String pkgName = "";
+      String typeName = className;
+      if (dot != -1) {
+        pkgName = className.substring(0, dot);
+        typeName = className.substring(dot + 1);
+        i = dot - 1;
+      } else {
+        i = -1;
+      }
+      ApiClassDiffGenerator result = findApiClassDiffGenerator(pkgName,
+          typeName);
+      if (result != null) {
+        return result;
+      }
+    }
+    return null;
+  }
+
+  // TODO(amitmanjhi): cache the results
+  /**
+   * Finds a type given its package-relative name. For nested classes, use its
+   * source name rather than its binary name (that is, use a "." rather than a
+   * "$").
+   * 
+   * @return <code>null</code> if the type is not found
+   */
+  public ApiClassDiffGenerator findApiClassDiffGenerator(String pkgName,
+      String typeName) {
+    ApiPackageDiffGenerator pkg = findApiPackageDiffGenerator(pkgName);
+    if (pkg != null) {
+      ApiClassDiffGenerator type = pkg.findApiClassDiffGenerator(pkgName + "."
+          + typeName);
+      if (type != null) {
+        return type;
+      }
+    }
+    return null;
+  }
+
+  public ApiPackageDiffGenerator findApiPackageDiffGenerator(String key) {
+    return intersectingPackages.get(key);
+  }
+
+  public ApiContainer getNewApiContainer() {
+    return newApi;
+  }
+
+  public ApiContainer getOldApiContainer() {
+    return oldApi;
+  }
+
+  public String printApiDiff() {
+    StringBuffer sb = new StringBuffer();
+    Iterator<String> missingPackagesIterator = missingPackageNames.iterator();
+    while (missingPackagesIterator.hasNext()) {
+      sb.append(missingPackagesIterator.next() + DELIMITER
+          + ApiChange.Status.MISSING + "\n");
+    }
+    Iterator<ApiPackageDiffGenerator> tempIterator = intersectingPackages.values().iterator();
+    while (tempIterator.hasNext()) {
+      sb.append(tempIterator.next().printApiDiff());
+    }
+    return sb.toString();
+  }
+
+}
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiMethod.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiMethod.java
new file mode 100644
index 0000000..677099f
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiMethod.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import java.util.ArrayList;
+
+/**
+ * Encapsulates an API method.
+ */
+public class ApiMethod extends ApiAbstractMethod {
+
+  public ApiMethod(JAbstractMethod method, ApiClass apiClass) {
+    super(method, apiClass);
+  }
+
+  @Override
+  public ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod)
+      throws TypeNotPresentException {
+    JType firstType, secondType;
+    if (newMethod.getMethodObject() instanceof JMethod
+        && method instanceof JMethod) {
+      firstType = ((JMethod) method).getReturnType();
+      secondType = ((JMethod) newMethod.getMethodObject()).getReturnType();
+    } else {
+      throw new AssertionError("Different types for method = "
+          + method.getClass() + ", and newMethodObject = "
+          + newMethod.getMethodObject().getClass() + ", signature = "
+          + getApiSignature());
+    }
+    StringBuffer sb = new StringBuffer();
+    if (firstType.getSimpleSourceName().indexOf("void") != -1) {
+      return null;
+    }
+    boolean compatible = ApiDiffGenerator.isFirstTypeAssignableToSecond(
+        secondType, firstType);
+    if (compatible) {
+      return null;
+    }
+    sb.append("Return type changed from ");
+    sb.append(firstType.getQualifiedSourceName());
+    sb.append(" to ");
+    sb.append(secondType.getQualifiedSourceName());
+    return new ApiChange(ApiChange.Status.RETURN_TYPE_ERROR, sb.toString());
+  }
+
+  /*
+   * check for: (i) added 'final' or 'abstract', (ii) removed 'static' adding
+   * the 'static' keyword is fine.
+   * 
+   * A private, static, or final method can't be made 'abstract' (Java language
+   * specification).
+   */
+  @Override
+  public ArrayList<ApiChange.Status> getModifierChanges(
+      final ApiAbstractMethod newMethod) {
+    JMethod newjmethod = null;
+    JMethod oldjmethod = null;
+
+    if (newMethod.getMethodObject() instanceof JMethod
+        && method instanceof JMethod) {
+      newjmethod = (JMethod) newMethod.getMethodObject();
+      oldjmethod = (JMethod) method;
+    } else {
+      throw new AssertionError("Different types for method = "
+          + method.getClass() + " and newMethod = "
+          + newMethod.getMethodObject().getClass() + ", signature = "
+          + getApiSignature());
+    }
+    ArrayList<ApiChange.Status> statuses = new ArrayList<ApiChange.Status>();
+    if (!oldjmethod.isFinal() && newjmethod.isFinal()) {
+      statuses.add(ApiChange.Status.FINAL_ADDED);
+    }
+    if (!oldjmethod.isAbstract() && newjmethod.isAbstract()) {
+      statuses.add(ApiChange.Status.ABSTRACT_ADDED);
+    }
+    if ((oldjmethod.isStatic() && !newjmethod.isStatic())) {
+      statuses.add(ApiChange.Status.STATIC_REMOVED);
+    }
+    return statuses;
+  }
+}
+/*
+ * final class TestB { static protected int i = 5; }
+ * 
+ * class TestC { public int j = TestB.i + 10; }
+ */
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiPackage.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiPackage.java
new file mode 100644
index 0000000..ca8a224
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiPackage.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JPackage;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * Encapsulates an API package.
+ */
+public class ApiPackage {
+  /**
+   * A package is an API package if it contains at least one API class. Refer
+   * http://wiki.eclipse.org/index.php/Evolving_Java-based_APIs This definition
+   * boils down to "a package is an API package iff it contains at least one API
+   * class that is not enclosed in any other class."
+   * 
+   * @return return true if and only if the packageObject is an apiPackage
+   */
+  public static boolean isApiPackage(JPackage packageObject) {
+    JClassType classTypes[] = packageObject.getTypes();
+    for (JClassType classType : classTypes) {
+      if (ApiClass.isPublicOuterClass(classType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private HashMap<String, ApiClass> apiClasses = new HashMap<String, ApiClass>();
+  private ApiContainer container = null;
+
+  private TreeLogger logger = null;
+  private String name = null;
+
+  private JPackage packageObject = null;
+
+  public ApiPackage(JPackage obj, ApiContainer container) {
+    packageObject = obj;
+    this.container = container;
+    if (logger == null) {
+      logger = container.getLogger();
+    }
+    name = obj.getName();
+    initialize();
+  }
+
+  public ArrayList<JClassType> getAllClasses() {
+    ArrayList<JClassType> allClasses = new ArrayList<JClassType>(
+        Arrays.asList(packageObject.getTypes()));
+    logger.log(TreeLogger.SPAM, "API " + packageObject + " has "
+        + allClasses.size() + " outer classes", null);
+    int index = 0;
+    while (index < allClasses.size()) {
+      JClassType classObject = allClasses.get(index);
+      allClasses.addAll(Arrays.asList(classObject.getNestedTypes()));
+      index++;
+    }
+    logger.log(TreeLogger.SPAM, "API " + packageObject + " has "
+        + allClasses.size() + " total classes", null);
+    return allClasses;
+  }
+
+  public ApiClass getApiClass(String className) {
+    return apiClasses.get(className);
+  }
+
+  public HashSet<String> getApiClassNames() {
+    return new HashSet<String>(apiClasses.keySet());
+  }
+
+  public ApiContainer getApiContainer() {
+    return container;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  private void initialize() {
+    Iterator<JClassType> allClassesIterator = getAllClasses().iterator();
+    ArrayList<String> notAddedClassNames = new ArrayList<String>();
+    while (allClassesIterator.hasNext()) {
+      JClassType classType = allClassesIterator.next();
+      if (ApiClass.isApiClass(classType)) {
+        ApiClass apiClass = new ApiClass(classType, this);
+        apiClasses.put(classType.getQualifiedSourceName(), apiClass);
+      } else {
+        notAddedClassNames.add(classType.toString());
+      }
+    }
+    if (notAddedClassNames.size() > 0) {
+      logger.log(TreeLogger.SPAM, "API " + name + ", package: " + name
+          + ", not adding " + notAddedClassNames.size() + " nonApi classes: "
+          + notAddedClassNames, null);
+    }
+  }
+
+}
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiPackageDiffGenerator.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiPackageDiffGenerator.java
new file mode 100644
index 0000000..4d43d08
--- /dev/null
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiPackageDiffGenerator.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * encapsulates a class that produces the diff between the api of two packages.
+ */
+public class ApiPackageDiffGenerator {
+  ApiDiffGenerator apiDiffGenerator = null;
+  HashMap<String, ApiClassDiffGenerator> intersectingClasses = new HashMap<String, ApiClassDiffGenerator>();
+  HashSet<String> missingClassNames = null;
+  String name = null;
+  ApiPackage newPackage = null;
+  ApiPackage oldPackage = null;
+
+  public ApiPackageDiffGenerator(String packageName,
+      ApiDiffGenerator apiDiffGenerator) throws NotFoundException {
+    this.apiDiffGenerator = apiDiffGenerator;
+    name = packageName;
+    newPackage = apiDiffGenerator.getNewApiContainer().getApiPackage(
+        packageName);
+    oldPackage = apiDiffGenerator.getOldApiContainer().getApiPackage(
+        packageName);
+    if (newPackage == null || oldPackage == null) {
+      throw new NotFoundException("for package " + packageName
+          + ", one of the package objects is null");
+    }
+  }
+
+  public void cleanApiDiff() {
+    Iterator<ApiClassDiffGenerator> tempIterator = intersectingClasses.values().iterator();
+    while (tempIterator.hasNext()) {
+      tempIterator.next().cleanApiDiff();
+    }
+  }
+
+  public void computeApiDiff() throws NotFoundException {
+    HashSet<String> newClassNames = newPackage.getApiClassNames();
+    missingClassNames = oldPackage.getApiClassNames();
+    HashSet<String> intersection = ApiDiffGenerator.findCommonElements(
+        newClassNames, missingClassNames, name);
+
+    /* Inspect each of the classes in each of the packages in the intersection */
+    Iterator<String> tempIterator = intersection.iterator();
+    while (tempIterator.hasNext()) {
+      String className = tempIterator.next();
+      ApiClassDiffGenerator temp = new ApiClassDiffGenerator(className, this);
+      intersectingClasses.put(className, temp);
+      temp.computeApiDiff();
+    }
+  }
+
+  public ApiClassDiffGenerator findApiClassDiffGenerator(String key) {
+    return intersectingClasses.get(key);
+  }
+
+  public ApiDiffGenerator getApiDiffGenerator() {
+    return apiDiffGenerator;
+  }
+
+  public ApiPackage getNewApiPackage() {
+    return newPackage;
+  }
+
+  public ApiPackage getOldApiPackage() {
+    return oldPackage;
+  }
+
+  public String printApiDiff() {
+    int totalSize = missingClassNames.size() + intersectingClasses.size();
+    if (totalSize == 0) {
+      return "";
+    }
+    StringBuffer sb = new StringBuffer();
+    Iterator<String> missingClassesIterator = missingClassNames.iterator();
+    while (missingClassesIterator.hasNext()) {
+      sb.append("\t\t" + missingClassesIterator.next()
+          + ApiDiffGenerator.DELIMITER + ApiChange.Status.MISSING + "\n");
+    }
+    Iterator<ApiClassDiffGenerator> tempIterator = intersectingClasses.values().iterator();
+    while (tempIterator.hasNext()) {
+      sb.append(tempIterator.next().printApiDiff());
+    }
+    if (sb.length() == 0) {
+      return "";
+    }
+    return "\tpackage " + name + "\n" + sb.toString() + "\n";
+  }
+
+}
diff --git a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
new file mode 100644
index 0000000..f6fdbd9
--- /dev/null
+++ b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.jdt.CacheManager;
+import com.google.gwt.dev.jdt.TypeOracleBuilder;
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
+
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+
+/**
+ * 
+ * Test 2 ApiContainers for compatibility
+ * 
+ * test compatibility output if returnType changes, parameters change, method
+ * overload compatibility, exception compatibility; abstract added, final added
+ * to both ApiClass, apiMethods.
+ * 
+ * test white-list support.
+ */
+public class ApiCompatibilityTest extends TestCase {
+  // These cups are slightly different from the cups in ApiContainerTest
+  static StaticCompilationUnitProvider cuApiClass = new StaticCompilationUnitProvider(
+      "test.apicontainer", "ApiClass", getSourceForApiClass());
+  static StaticCompilationUnitProvider cuNonApiClass = new StaticCompilationUnitProvider(
+      "test.apicontainer", "NonApiClass", getSourceForNonApiClass());
+  static StaticCompilationUnitProvider cuNonApiPackage = new StaticCompilationUnitProvider(
+      "test.nonapipackage", "TestClass", getSourceForTestClass());
+  static StaticCompilationUnitProvider cuObject = new StaticCompilationUnitProvider(
+      "java.lang", "Object", getSourceForObject());
+
+  static StaticCompilationUnitProvider cuThrowable = new StaticCompilationUnitProvider(
+      "java.lang", "Throwable", getSourceForThrowable());
+
+  private static char[] getSourceForApiClass() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package test.apicontainer;\n");
+    sb.append("public class ApiClass extends NonApiClass {\n");
+    sb.append("\tpublic void apiMethod() { };\n");
+    sb.append("\tpublic void checkParametersAndReturnTypes(java.lang.Object x) throws java.lang.Throwable { };\n");
+    sb.append("};\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForNonApiClass() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package test.apicontainer;\n");
+    sb.append("class NonApiClass extends java.lang.Object {\n");
+    sb.append("\tpublic void methodInNonApiClass(java.lang.Object o) { };\n");
+    sb.append("\tpublic void methodInNonApiClass(test.apicontainer.NonApiClass t) { };\n");
+    sb.append("\tpublic int fieldInNonApiClass = 3;\n");
+    sb.append("\tprotected abstract class ApiClassInNonApiClass {\n");
+    sb.append("\tprotected ApiClassInNonApiClass() { }\n");
+    sb.append("\t}\n");
+    sb.append("}\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForObject() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package java.lang;\n");
+    sb.append("public class Object {\n");
+    sb.append("\tpublic void apiMethod() { }\n");
+    sb.append("\tprivate void internalMethod() { }\n");
+    sb.append("\tprotected final void protectedMethod() { }\n");
+    sb.append("\tpublic final int apiField = 0;\n");
+    sb.append("\tprivate int internalField = 0;\n");
+    sb.append("\tprotected static int protectedField=2;\n");
+    sb.append("}\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForTestClass() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package test.nonapipackage;\n");
+    sb.append("class TestClass extends java.lang.Object {\n");
+    sb.append("\tpublic void method() { }\n");
+    sb.append("}\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForThrowable() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package java.lang;\n");
+    sb.append("public class Throwable extends Object {\n");
+    sb.append("}\n");
+    return sb.toString().toCharArray();
+  }
+
+  ApiContainer api1 = null;
+  ApiContainer api2 = null;
+
+  ApiContainer apiSameAs1 = null;
+
+  public TypeOracle getNewTypeOracleWithCompilationUnitsAdded(
+      AbstractTreeLogger logger) throws UnableToCompleteException {
+    TypeOracleBuilder builder1 = new TypeOracleBuilder(new CacheManager(null,
+        null, ApiCompatibilityChecker.DISABLE_CHECKS));
+    builder1.addCompilationUnit(cuObject);
+    builder1.addCompilationUnit(cuNonApiClass);
+    builder1.addCompilationUnit(cuApiClass);
+    builder1.addCompilationUnit(cuNonApiPackage);
+    builder1.addCompilationUnit(cuThrowable);
+    return builder1.build(logger);
+  }
+
+  @Override
+  public void setUp() {
+    AbstractTreeLogger logger = new PrintWriterTreeLogger();
+    logger.setMaxDetail(TreeLogger.ERROR);
+    try {
+      api1 = new ApiContainer("Api1", logger,
+          new ApiContainerTest().getNewTypeOracleWithCompilationUnitsAdded());
+      apiSameAs1 = new ApiContainer("Api2", logger,
+          new ApiContainerTest().getNewTypeOracleWithCompilationUnitsAdded());
+      api2 = new ApiContainer("Api2", logger,
+          getNewTypeOracleWithCompilationUnitsAdded(logger));
+    } catch (Exception ex) {
+      assertEquals("JSNI checks are probably active", "failed");
+    }
+  }
+
+  public void testBasicStuff() throws NotFoundException {
+    HashSet<String> hashSet = new HashSet<String>();
+    assertEquals("", ApiCompatibilityChecker.getApiDiff(new ApiDiffGenerator(
+        api1, apiSameAs1), hashSet, false));
+    ApiDiffGenerator apiDiff = new ApiDiffGenerator(api2, api1);
+    boolean removeDuplicates = false;
+    String strWithDuplicates = ApiCompatibilityChecker.getApiDiff(apiDiff,
+        hashSet, removeDuplicates);
+    String strWithoutDuplicates = ApiCompatibilityChecker.getApiDiff(apiDiff,
+        hashSet, !removeDuplicates);
+
+    String delimiter = ApiDiffGenerator.DELIMITER;
+    // test if missing packages are reported correctly
+    String status = "java.newpackage" + delimiter + ApiChange.Status.MISSING;
+    assertEquals(1, countPresence(status, strWithDuplicates));
+    assertEquals(1, countPresence(status, strWithoutDuplicates));
+
+    // test if missing classes are reported correctly
+    assertEquals(1, countPresence(
+        "test.apicontainer.NonApiClass.AnotherApiClassInNonApiClass"
+            + delimiter + ApiChange.Status.MISSING, strWithoutDuplicates));
+    // System.out.println(strWithDuplicates);
+    // System.out.println(strWithoutDuplicates);
+
+    // test if modifier changes of a class are reported
+    assertEquals(1, countPresence(
+        "test.apicontainer.NonApiClass.ApiClassInNonApiClass" + delimiter
+            + ApiChange.Status.ABSTRACT_ADDED, strWithoutDuplicates));
+
+    // test if methods are reported as missing due to class becoming abstract
+    if (ApiCompatibilityChecker.REMOVE_ABSTRACT_CLASS_FROM_API) {
+      assertEquals(1, countPresence(
+          "test.apicontainer.NonApiClass.ApiClassInNonApiClass::ApiClassInNonApiClass()"
+              + delimiter + ApiChange.Status.MISSING, strWithoutDuplicates));
+      assertEquals(1, countPresence(
+          "test.apicontainer.NonApiClass.ApiClassInNonApiClass::protectedMethod()"
+              + delimiter + ApiChange.Status.MISSING, strWithoutDuplicates));
+    }
+
+    // test if modifier changes of fields and methods are reported
+    assertEquals(1, countPresence("java.lang.Object::apiField" + delimiter
+        + ApiChange.Status.FINAL_ADDED, strWithoutDuplicates));
+    assertEquals(1, countPresence("java.lang.Object::protectedMethod()"
+        + delimiter + ApiChange.Status.FINAL_ADDED, strWithoutDuplicates));
+
+    // test if duplicates are weeded out.
+    if (ApiCompatibilityChecker.REMOVE_ABSTRACT_CLASS_FROM_API) {
+      assertEquals(2, countPresence("protectedMethod()" + delimiter
+          + ApiChange.Status.FINAL_ADDED, strWithDuplicates));
+    } else {
+      assertEquals(3, countPresence("protectedMethod()" + delimiter
+          + ApiChange.Status.FINAL_ADDED, strWithDuplicates));
+    }
+    // test returnType error
+    String methodSignature = "checkParametersAndReturnTypes(Ltest/apicontainer/ApiClass;)";
+    assertEquals(1, countPresence(methodSignature + delimiter
+        + ApiChange.Status.RETURN_TYPE_ERROR, strWithoutDuplicates));
+    // test method exceptions
+    assertEquals(1, countPresence(methodSignature + delimiter
+        + ApiChange.Status.EXCEPTIONS_ERROR, strWithoutDuplicates));
+
+    // checking if changes in parameter types were okay
+    assertEquals(2, countPresence(methodSignature, strWithoutDuplicates));
+
+    // test method_overloading
+    methodSignature = "methodInNonApiClass(Ljava/lang/Object;)";
+    assertEquals(1, countPresence(methodSignature + delimiter
+        + ApiChange.Status.OVERLOADED, strWithoutDuplicates));
+  }
+
+  public void testWhiteList() throws NotFoundException {
+    ApiDiffGenerator apiDiff = new ApiDiffGenerator(api2, api1);
+    boolean removeDuplicates = false;
+    String whiteList = "java.newpackage" + ApiDiffGenerator.DELIMITER
+        + ApiChange.Status.MISSING;
+    HashSet<String> hashSet = new HashSet<String>();
+    hashSet.add(whiteList);
+    String strWithoutDuplicates = ApiCompatibilityChecker.getApiDiff(apiDiff,
+        hashSet, !removeDuplicates);
+
+    // test if missing packages are reported correctly
+    assertEquals(0, countPresence(whiteList, strWithoutDuplicates));
+  }
+
+  private int countPresence(String needle, String hay) {
+    int count = 0;
+    int needleLength = needle.length();
+    int index = -needleLength;
+    while ((index = hay.indexOf(needle, index + needleLength)) != -1) {
+      count++;
+    }
+    return count;
+  }
+
+}
+
+// abstract methods can't be static
+
+class TestAA {
+  static int j = 10;
+
+  static void printX() {
+    System.err.println("2");
+  }
+
+  int i = 5;
+
+  private TestAA() {
+  }
+}
+
+class TestAB {
+  static void test() {
+    TestAA.printX();
+  }
+}
diff --git a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
new file mode 100644
index 0000000..5b02e02
--- /dev/null
+++ b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2008 Google Inc.
+ * 
+ * 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.google.gwt.tools.apichecker;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.jdt.CacheManager;
+import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
+import com.google.gwt.dev.jdt.TypeOracleBuilder;
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import junit.framework.TestCase;
+
+/**
+ * Test ApiContainer.
+ */
+public class ApiContainerTest extends TestCase {
+  class TestA {
+    public TestA(String args) {
+    }
+
+    protected TestA(TestA a) {
+    }
+
+    public String valueOf(Object o) {
+      return "a";
+    }
+
+    public String valueOf(String s) {
+      return "";
+    }
+  }
+
+  class TestB extends TestA {
+    public TestB(TestA a) {
+      super(a);
+    }
+
+    public void main(String args[]) {
+      TestA x1 = new TestA("test");
+      x1.valueOf("ab");
+      TestB x2 = new TestB(x1);
+      new TestB(x2);
+    }
+  }
+
+  static StaticCompilationUnitProvider cuApiClass = new StaticCompilationUnitProvider(
+      "test.apicontainer", "ApiClass", getSourceForApiClass());
+
+  static StaticCompilationUnitProvider cuNewPackage = new StaticCompilationUnitProvider(
+      "java.newpackage", "Test", getSourceForTest());
+
+  static StaticCompilationUnitProvider cuNonApiClass = new StaticCompilationUnitProvider(
+      "test.apicontainer", "NonApiClass", getSourceForNonApiClass());
+  static StaticCompilationUnitProvider cuNonApiPackage = new StaticCompilationUnitProvider(
+      "test.nonapipackage", "TestClass", getSourceForTestClass());
+
+  static StaticCompilationUnitProvider cuObject = new StaticCompilationUnitProvider(
+      "java.lang", "Object", getSourceForObject());
+
+  private static JAbstractMethod getMethodByName(String name, ApiClass apiClass) {
+    return (apiClass.getApiMethodsByName(name, ApiClass.MethodType.METHOD).toArray(
+        new ApiAbstractMethod[0])[0]).getMethodObject();
+  }
+
+  private static char[] getSourceForApiClass() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package test.apicontainer;\n");
+    sb.append("public class ApiClass extends NonApiClass {\n");
+    sb.append("\tpublic void apiMethod() { };\n");
+    sb.append("\tpublic java.lang.Object checkParametersAndReturnTypes(ApiClass a) { return this; };\n");
+    sb.append("};\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForNonApiClass() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package test.apicontainer;\n");
+    sb.append("class NonApiClass extends java.lang.Object {\n");
+    sb.append("\tpublic void methodInNonApiClass(java.lang.Object a) { };\n");
+    sb.append("\tpublic int fieldInNonApiClass = 3;\n");
+    sb.append("\tprotected class ApiClassInNonApiClass {\n");
+    sb.append("\tprotected ApiClassInNonApiClass() { }\n");
+    sb.append("\t}\n");
+    sb.append("\tprotected final class AnotherApiClassInNonApiClass {\n");
+    sb.append("\tprivate AnotherApiClassInNonApiClass() { }\n");
+    sb.append("\t}\n");
+    sb.append("}\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForObject() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package java.lang;\n");
+    sb.append("public class Object {\n");
+    sb.append("\tpublic void apiMethod() { }\n");
+    sb.append("\tprivate void internalMethod() { }\n");
+    sb.append("\tprotected native long protectedMethod();\n");
+    sb.append("\tpublic int apiField = 0;\n");
+    sb.append("\tprivate int internalField = 0;\n");
+    sb.append("\tprotected int protectedField=2;\n");
+    sb.append("}\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForTest() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package java.newpackage;\n");
+    sb.append("public class Test { }\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForTestClass() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package test.nonapipackage;\n");
+    sb.append("class TestClass extends java.lang.Object {\n");
+    sb.append("\tpublic void method() { }\n");
+    sb.append("}\n");
+    return sb.toString().toCharArray();
+  }
+
+  ApiContainer api1 = null;
+
+  // TODO (amitmanjhi): Try using UnitTestTreeLogger to capture log messages
+  public TypeOracle getNewTypeOracleWithCompilationUnitsAdded()
+      throws UnableToCompleteException {
+
+    // Build onto an empty type oracle.
+    TypeOracleBuilder builder1 = new TypeOracleBuilder(new CacheManager(null,
+        null, ApiCompatibilityChecker.DISABLE_CHECKS));
+    builder1.addCompilationUnit(cuObject);
+    builder1.addCompilationUnit(cuNonApiClass);
+    builder1.addCompilationUnit(cuApiClass);
+    builder1.addCompilationUnit(cuNonApiPackage);
+    builder1.addCompilationUnit(cuNewPackage);
+    AbstractTreeLogger logger = new PrintWriterTreeLogger();
+    logger.setMaxDetail(TreeLogger.ERROR);
+    TypeOracle typeOracle = builder1.build(logger);
+    return typeOracle;
+  }
+
+  /**
+   * Class hierarchy. public java.lang.Object -- test.apicontainer.NonApiClass
+   * (encloses ApiClassInNonApiClass and AnotherApiClassInNonApiClass) -- public
+   * test.apicontainer.ApiClass -- test.nonapipackage.TestClass
+   */
+  @Override
+  public void setUp() {
+    AbstractTreeLogger logger1 = new PrintWriterTreeLogger();
+    logger1.setMaxDetail(TreeLogger.ERROR);
+
+    try {
+      api1 = new ApiContainer("ApiContainerTest", logger1,
+          getNewTypeOracleWithCompilationUnitsAdded());
+    } catch (Exception ex) {
+      // JSNI checks are active.
+      assertEquals("jsni checks are probably active", "failed");
+    }
+  }
+
+  /**
+   * Check if apiClasses are determined correctly. Check if inner classes are
+   * classified correctly as api classes.
+   */
+  public void testApiClass() {
+    ApiPackage package1 = api1.getApiPackage("java.lang");
+    ApiPackage package2 = api1.getApiPackage("test.apicontainer");
+    assertNotNull(package1);
+    assertNotNull(package2);
+
+    assertNull(package2.getApiClass("test.apicontainer.NonApiClass"));
+    assertNotNull(package1.getApiClass("java.lang.Object"));
+    assertNotNull(package2.getApiClass("test.apicontainer.ApiClass"));
+    assertNotNull(package2.getApiClass("test.apicontainer.NonApiClass.ApiClassInNonApiClass"));
+    assertNotNull(package2.getApiClass("test.apicontainer.NonApiClass.AnotherApiClassInNonApiClass"));
+    assertEquals(1, package1.getApiClassNames().size());
+    assertEquals(3, package2.getApiClassNames().size());
+  }
+
+  /**
+   * Since constructors and methods use the same code, check methods in most
+   * cases. Also need to check apiFields.
+   * 
+   * For methods, check if: (a) inherited methods are identified correctly as
+   * apiMethods, (b) method overloading is done correctly
+   * 
+   */
+  public void testApiMembers() {
+    ApiClass object = api1.getApiPackage("java.lang").getApiClass(
+        "java.lang.Object");
+    ApiClass apiClass = api1.getApiPackage("test.apicontainer").getApiClass(
+        "test.apicontainer.ApiClass");
+    ApiClass innerClass = api1.getApiPackage("test.apicontainer").getApiClass(
+        "test.apicontainer.NonApiClass.ApiClassInNonApiClass");
+
+    // constructors
+    assertEquals(1, innerClass.getApiMemberNames(
+        ApiClass.MethodType.CONSTRUCTOR).size());
+
+    // fields
+    assertEquals(2, object.getApiFieldNames().size());
+    assertEquals(3, apiClass.getApiFieldNames().size());
+
+    // methods
+    assertEquals(2, object.getApiMemberNames(ApiClass.MethodType.METHOD).size());
+    assertEquals(4,
+        apiClass.getApiMemberNames(ApiClass.MethodType.METHOD).size());
+    // the method definition lowest in the class hierarchy is kept
+    assertNotSame(getMethodByName("apiMethod0", apiClass), getMethodByName(
+        "apiMethod0", object));
+    assertEquals(getMethodByName("protectedMethod0", apiClass),
+        getMethodByName("protectedMethod0", object));
+    assertNotNull(getMethodByName("methodInNonApiClass1", apiClass));
+  }
+
+  /**
+   * Test if apiPackages are identified correctly.
+   */
+  public void testApiPackages() {
+    assertNotNull(api1.getApiPackage("java.lang"));
+    assertNotNull(api1.getApiPackage("test.apicontainer"));
+    assertEquals(3, api1.getApiPackageNames().size());
+  }
+
+}
diff --git a/tools/build.xml b/tools/build.xml
index 065d592..f271a9e 100755
--- a/tools/build.xml
+++ b/tools/build.xml
@@ -10,7 +10,11 @@
                 <gwt.ant dir="benchmark-viewer" />
         </target>
 
-	<target name="-do" depends="benchmark-viewer" description="Run all subfolders" />
+        <target name="api-checker" depends="" description="Compile api-checker">
+                <gwt.ant dir="api-checker" />
+        </target>
+        
+	<target name="-do" depends="benchmark-viewer,api-checker" description="Run all subfolders" />
 
 	<target name="build" description="Build each subfolder">
 		<antcall target="-do">