1) Flags api incompatibility whenever the api of an overridable api method evolves.
2) Adds relevant tests for issue (1).
3) Correctly handles the following case:

Api_version_1:

    public class A {
        public void foo(A self) {
      }
    }

    class B extends A {
    }


Api_version_2:

    class A as before.

    class B extends A{
       /* method overloading not overriding */
       public final void foo(B self) {
      }
    }


Api checker was incorrectly marking this case as METHOD_OVERRIDING error. Instead, it should just mark it as METHOD_OVERLOADING error.

4) Adds test case for issue (3).
5) Adds test case for  addition of unchecked exceptions.
6) Updates the config file so that 'ant apicheck' passes. I verified each change in the config file by manually inspecting the source code.  
7) Also adds conservative tests for Api-checking of classes in new Api which are sub-classes of classes in existing Api.


Review by: scottb



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3703 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt14_15userApi.conf b/tools/api-checker/config/gwt14_15userApi.conf
index 309fba7..ba8df89 100644
--- a/tools/api-checker/config/gwt14_15userApi.conf
+++ b/tools/api-checker/config/gwt14_15userApi.conf
@@ -40,7 +40,6 @@
 com.google.gwt.user.client.Event::equals(Ljava/lang/Object;) FINAL_ADDED
 com.google.gwt.user.client.Event::hashCode() FINAL_ADDED
 com.google.gwt.user.client.Event::toString() FINAL_ADDED
-com.google.gwt.user.client.History::onHistoryChanged(Ljava/lang/String;) MISSING
 com.google.gwt.user.client.rpc.core.java.lang.boolean_Array_CustomFieldSerializer MISSING
 com.google.gwt.user.client.rpc.core.java.lang.byte_Array_CustomFieldSerializer MISSING
 com.google.gwt.user.client.rpc.core.java.lang.char_Array_CustomFieldSerializer MISSING
@@ -50,61 +49,85 @@
 com.google.gwt.user.client.rpc.core.java.lang.long_Array_CustomFieldSerializer MISSING
 com.google.gwt.user.client.rpc.core.java.lang.short_Array_CustomFieldSerializer MISSING
 com.google.gwt.user.client.ui.AbstractImagePrototype::applyTo(Lcom/google/gwt/user/client/ui/Image;) OVERLOADED_METHOD_CALL
+com.google.gwt.user.client.ui.Button::Button(Ljava/lang/String;) OVERLOADED_METHOD_CALL
 com.google.gwt.user.client.ui.ChangeListenerCollection::add(ILjava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.ChangeListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.ChangeListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.ChangeListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
 com.google.gwt.user.client.ui.ChangeListenerCollection::set(ILjava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.ClickListenerCollection::add(ILjava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.ClickListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.ClickListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.ClickListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
 com.google.gwt.user.client.ui.ClickListenerCollection::set(ILjava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.FocusListenerCollection::add(ILjava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.FocusListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.FocusListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.FocusListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
 com.google.gwt.user.client.ui.FocusListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.FocusWidget::setElement(Lcom/google/gwt/user/client/Element;) FINAL_ADDED
 com.google.gwt.user.client.ui.FocusWidget::setElement(Lcom/google/gwt/user/client/Element;) OVERLOADED_METHOD_CALL
 com.google.gwt.user.client.ui.FormHandlerCollection::add(ILjava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.FormHandlerCollection::add(Ljava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.FormHandlerCollection::fireOnComplete(Ljava/lang/Object;Ljava/lang/String;) MISSING
 com.google.gwt.user.client.ui.FormHandlerCollection::fireOnSubmit(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.FormHandlerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.FormHandlerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
 com.google.gwt.user.client.ui.FormHandlerCollection::set(ILjava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.FormSubmitCompleteEvent::FormSubmitCompleteEvent(Ljava/lang/Object;Ljava/lang/String;) MISSING
 com.google.gwt.user.client.ui.FormSubmitEvent::FormSubmitEvent(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.KeyboardListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.KeyboardListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.KeyboardListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.LoadListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.LoadListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.LoadListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.MouseListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.MouseListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.MouseListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.MouseWheelListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.MouseWheelListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.MouseWheelListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.PopupListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.PopupListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.PopupListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.ScrollListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.ScrollListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.ScrollListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TabListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TabListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TabListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TableListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TableListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TableListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TreeListenerCollection::add(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TreeListenerCollection::add(Ljava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.TreeListenerCollection::set(ILjava/lang/Object;) MISSING
-com.google.gwt.user.client.ui.UIObject::setElement(Lcom/google/gwt/user/client/Element;) FINAL_ADDED
-com.google.gwt.user.client.ui.UIObject::setElement(Lcom/google/gwt/user/client/Element;) OVERLOADED_METHOD_CALL
-com.google.gwt.user.client.ui.Widget::setElement(Lcom/google/gwt/user/client/Element;) FINAL_ADDED
-com.google.gwt.user.client.ui.Widget::setElement(Lcom/google/gwt/user/client/Element;) OVERLOADED_METHOD_CALL
-com.google.gwt.user.client.ui.Button::Button(Ljava/lang/String;) OVERLOADED_METHOD_CALL
 com.google.gwt.user.client.ui.Frame::Frame(Ljava/lang/String;) OVERLOADED_METHOD_CALL
 com.google.gwt.user.client.ui.HTML::HTML(Ljava/lang/String;) OVERLOADED_METHOD_CALL
 com.google.gwt.user.client.ui.Hidden::Hidden(Ljava/lang/String;) OVERLOADED_METHOD_CALL
 com.google.gwt.user.client.ui.Image::Image(Ljava/lang/String;) OVERLOADED_METHOD_CALL
+com.google.gwt.user.client.ui.KeyboardListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.KeyboardListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.KeyboardListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.KeyboardListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.KeyboardListenerCollection::set(ILjava/lang/Object;) MISSING
 com.google.gwt.user.client.ui.Label::Label(Ljava/lang/String;) OVERLOADED_METHOD_CALL
+com.google.gwt.user.client.ui.LoadListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.LoadListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.LoadListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.LoadListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.LoadListenerCollection::set(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.MenuBar::addItem(Lcom/google/gwt/user/client/ui/MenuItem;) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.MouseListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.MouseListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.MouseListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.MouseListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.MouseListenerCollection::set(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.MouseWheelListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.MouseWheelListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.MouseWheelListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.MouseWheelListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.MouseWheelListenerCollection::set(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.PopupListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.PopupListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.PopupListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.PopupListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.PopupListenerCollection::set(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.ScrollListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.ScrollListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.ScrollListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.ScrollListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.ScrollListenerCollection::set(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TabListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TabListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TabListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.TabListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.TabListenerCollection::set(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TableListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TableListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TableListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.TableListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.TableListenerCollection::set(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TreeListenerCollection::add(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TreeListenerCollection::add(Ljava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.TreeListenerCollection::get(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.TreeListenerCollection::remove(I) OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE
+com.google.gwt.user.client.ui.TreeListenerCollection::set(ILjava/lang/Object;) MISSING
+com.google.gwt.user.client.ui.UIObject::setElement(Lcom/google/gwt/user/client/Element;) OVERLOADED_METHOD_CALL
+com.google.gwt.user.client.ui.Widget::setElement(Lcom/google/gwt/user/client/Element;) OVERLOADED_METHOD_CALL
 java.lang.Byte::compareTo(Ljava/lang/Object;) MISSING
 java.lang.Character::compareTo(Ljava/lang/Object;) MISSING
 java.lang.Class FINAL_ADDED
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
index 6164817..00fc0bc 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiAbstractMethod.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiAbstractMethod.java
@@ -54,8 +54,18 @@
     return sb.toString();
   }
 
+  /*
+   * 4 different signatures for a method: (a) internalSignature: just the
+   * JniSignature of the arguments. does not include any class name. (b)
+   * apiSignature: internalSignature, plus the className in which it was
+   * declared. (c) relativeSignature: internalSignature + the current class from
+   * which the Api method can be accessed. (d) coarseSignature: instead of
+   * jniSignature of each parameter, just record whether the parameter is a
+   * class type or a primitive type.
+   */
   final ApiClass apiClass;
   String apiSignature = null;
+  String internalSignature = null;
   final JAbstractMethod method;
   String relativeSignature = null;
 
@@ -80,6 +90,10 @@
     return false;
   }
 
+  public ApiClass getApiClass() {
+    return apiClass;
+  }
+
   public JAbstractMethod getMethod() {
     return method;
   }
@@ -114,38 +128,97 @@
     return true;
   }
 
-  // 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 boolean isOverridable();
 
   @Override
   public String toString() {
     return method.toString();
   }
 
-  protected String computeApiSignature() {
-    return computeApiSignature(method);
+  List<ApiChange> checkExceptionsAndReturnType(ApiAbstractMethod newMethod) {
+    List<ApiChange> apiChanges = checkExceptions(newMethod);
+    ApiChange returnApiChange = checkReturnTypeCompatibility(newMethod);
+    if (returnApiChange == null) {
+      return apiChanges;
+    }
+    apiChanges.add(returnApiChange);
+    return apiChanges;
+  }
+  
+  abstract ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod);
+
+  abstract List<ApiChange> getAllChangesInApi(ApiAbstractMethod newMethod);
+  
+  String getApiSignature() {
+    if (apiSignature == null) {
+      apiSignature = computeApiSignature(method);
+    }
+    return apiSignature;
   }
 
-  List<ApiChange> checkExceptions(ApiAbstractMethod newMethod) {
+  /**
+   * Gets the signature of a method distinguishing just the non-primitive types
+   * from the primitive types.
+   * <p>
+   * Useful to determine the method overloading because a null could be passed
+   * for a primitive type.
+   * <p> 
+   * Not sure if the implementation is sufficient. If need be, look at the
+   * implementation below.
+   * 
+   * <pre>
+   * public String getCoarseSignature() {
+   *   StringBuffer returnStr = new StringBuffer();
+   *   JParameter[] parameters = method.getParameters();
+   *   JArrayType jat = null;
+   *   JType type = parameter.getType();
+   *   for (JParameter parameter : parameters) {
+   *     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();
+   * }
+   * </pre>
+   *
+   * @return the coarse signature as a String
+   */
+  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();
+  }
+
+  String getInternalSignature() {
+    if (internalSignature == null) {
+      internalSignature = computeInternalSignature(method);
+    }
+    return internalSignature;
+  }
+
+  /**
+   * Find changes in modifiers. returns a possibly immutable list.
+   * 
+   */
+  abstract List<ApiChange.Status> getModifierChanges(ApiAbstractMethod newMethod);
+
+  private List<ApiChange> checkExceptions(ApiAbstractMethod newMethod) {
     ArrayList<JType> legalTypes = new ArrayList<JType>();
 
     // A throw declaration for an unchecked exception does not change the API.
@@ -178,37 +251,6 @@
     return ret;
   }
 
-  abstract ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod);
-
-  String getApiSignature() {
-    if (apiSignature == null) {
-      apiSignature = computeApiSignature();
-    }
-    return apiSignature;
-  }
-
-  // for a non-primitive type, someone can pass it null as a parameter
-  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();
-  }
-
-  /**
-   * Find changes in modifiers. returns a possibly immutable list.
-   * 
-   */
-  abstract List<ApiChange.Status> getModifierChanges(ApiAbstractMethod newMethod);
-  
   private String computeRelativeSignature() {
     String signature = computeInternalSignature(method);
     if (ApiCompatibilityChecker.DEBUG) {
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
index 8c967c2..cd5c2c0 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiChange.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiChange.java
@@ -49,6 +49,13 @@
 
     OVERLOADED_METHOD_CALL,
 
+    /*
+     * The api (argument types, return types, exceptions in throws clause) for
+     * an api method that can be overridden CANNOT change without breaking api
+     * compatibility
+     */
+    OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE, OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE, OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE,
+
     RETURN_TYPE_ERROR,
 
     STATIC_ADDED,
@@ -78,6 +85,8 @@
 
   private String stringRepresentation = null;
 
+  private String stringRepresentationWithoutMessage = null;
+
   public ApiChange(ApiElement element, Status status) {
     this(element, status, null);
   }
@@ -104,11 +113,24 @@
     return status;
   }
 
+  public String getStringRepresentationWithoutMessage() {
+    if (stringRepresentationWithoutMessage == null) {
+      stringRepresentationWithoutMessage = element.getRelativeSignature()
+          + ApiDiffGenerator.DELIMITER + status.name();
+    }
+    return stringRepresentationWithoutMessage;
+  }
+
+  public int hashCodeForDuplication() {
+    return element.hashCode() * 31 + status.hashCode() * 23
+        + (message == null ? 0 : message.hashCode());
+  }
+
   @Override
   public String toString() {
     if (stringRepresentation == null) {
-      stringRepresentation = element.getRelativeSignature()
-          + ApiDiffGenerator.DELIMITER + status.name();
+      stringRepresentation = getStringRepresentationWithoutMessage()
+          + (message == null ? "" : (ApiDiffGenerator.DELIMITER + message));
     }
     return stringRepresentation;
   }
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
index 7525380..4e04f5d 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClass.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClass.java
@@ -237,6 +237,10 @@
     }
   }
 
+  boolean isSubclassableApiClass() {
+    return isSubclassableApiClass;
+  }
+
   private JField[] getAccessibleFields() {
     Map<String, JField> fieldsBySignature = new HashMap<String, JField>();
     JClassType tempClassType = classType;
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
index 334e134..9a2c5b1 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClassDiffGenerator.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiClassDiffGenerator.java
@@ -16,7 +16,6 @@
 
 package com.google.gwt.tools.apichecker;
 
-import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 
 import java.util.ArrayList;
@@ -46,21 +45,10 @@
     sb.append("\n");
     return sb.toString();
   }
-  
-  private Set<ApiField> allIntersectingFields = new HashSet<ApiField>();
-  /**
-   * 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.
-   */
-  private EnumMap<ApiClass.MethodType, Set<ApiAbstractMethod>> allIntersectingMethods = null;
-
-  private Set<ApiField> allMissingFields = null;
-  private EnumMap<ApiClass.MethodType, Set<ApiAbstractMethod>> allMissingMethods = null;
 
   private final ApiDiffGenerator apiDiffGenerator;
   private final String className;
-  private HashMap<ApiField, Set<ApiChange.Status>> intersectingFields = null;
+  private HashMap<ApiField, Set<ApiChange>> intersectingFields = null;
 
   /**
    * Map from methods and constructors in intersection to a string describing
@@ -90,7 +78,7 @@
           + ", one of the class objects is null");
     }
 
-    intersectingFields = new HashMap<ApiField, Set<ApiChange.Status>>();
+    intersectingFields = new HashMap<ApiField, Set<ApiChange>>();
     intersectingMethods = new EnumMap<ApiClass.MethodType, Map<ApiAbstractMethod, Set<ApiChange>>>(
         ApiClass.MethodType.class);
     missingMethods = new EnumMap<ApiClass.MethodType, Set<ApiAbstractMethod>>(
@@ -110,67 +98,7 @@
     return getName().compareTo(other.getName());
   }
 
-  /**
-   * 
-   * cleanApiDiff: remove an ApiDiff message from this class, if it is present
-   * in the apiDiffs of any of the super-classes.
-   * 
-   * Compute the union of apiDiffs of all superclasses as part of this method.
-   * 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.
-   */
-  void cleanApiDiff() {
-    // check if unions have already been computed.
-    if (allMissingMethods != null) {
-      return;
-    }
-    ApiClassDiffGenerator other = getSuperclassApiClassDiffGenerator();
-    // compute 'all*' fields for the 'other' object.
-    if (other != null) {
-      other.cleanApiDiff();
-    }
-    allIntersectingMethods = new EnumMap<ApiClass.MethodType, Set<ApiAbstractMethod>>(
-        ApiClass.MethodType.class);
-    allMissingMethods = new EnumMap<ApiClass.MethodType, Set<ApiAbstractMethod>>(
-        ApiClass.MethodType.class);
-
-    for (ApiClass.MethodType methodType : ApiClass.MethodType.values()) {
-      // for methods/constructors: clean the current apiDiffs
-      if (other != null) {
-        removeAll(intersectingMethods.get(methodType),
-            other.allIntersectingMethods.get(methodType));
-        missingMethods.get(methodType).removeAll(
-            other.allMissingMethods.get(methodType));
-      }
-      // for methods/constructors: compute the allIntersecting*, allMissing*
-      HashSet<ApiAbstractMethod> tempSet1 = new HashSet<ApiAbstractMethod>(
-          intersectingMethods.get(methodType).keySet());
-      HashSet<ApiAbstractMethod> tempSet2 = new HashSet<ApiAbstractMethod>(
-          missingMethods.get(methodType));
-      if (other != null) {
-        tempSet1.addAll(other.allIntersectingMethods.get(methodType));
-        tempSet2.addAll(other.allMissingMethods.get(methodType));
-      }
-      allIntersectingMethods.put(methodType, tempSet1);
-      allMissingMethods.put(methodType, tempSet2);
-    }
-
-    // for fields: clean the current apiDiffs
-    if (other != null) {
-      removeAll(intersectingFields, other.allIntersectingFields);
-      missingFields.removeAll(other.allMissingFields);
-    }
-    // for fields: compute allIntersectingFields, allMissingFields
-    allIntersectingFields = new HashSet<ApiField>(intersectingFields.keySet());
-    allMissingFields = new HashSet<ApiField>(missingFields);
-    if (other != null) {
-      allIntersectingFields.addAll(other.allIntersectingFields);
-      allMissingFields.addAll(other.allMissingFields);
-    }
-  }
-
-  // TODO(amitmanjhi): for methods, think about variable length arguments
+  // TODO(amitmanjhi): handle methods with variable length arguments
   void computeApiDiff() {
     Set<String> newFieldNames = newClass.getApiFieldNames();
     Set<String> oldFieldNames = oldClass.getApiFieldNames();
@@ -192,13 +120,6 @@
 
   Collection<ApiChange> getApiDiff() {
     Collection<ApiChange.Status> apiStatusChanges = oldClass.getModifierChanges(newClass);
-    /*
-     * int totalSize = missingFields.size() + intersectingFields.size() +
-     * apiStatusChanges.size(); for (ApiClass.MethodType methodType :
-     * ApiClass.MethodType.values()) { totalSize +=
-     * (missingMethods.get(methodType).size() + intersectingMethods.get(
-     * methodType).size()); } if (totalSize == 0) { return EMPTY_COLLECTION; }
-     */
     Collection<ApiChange> apiChangeCollection = new ArrayList<ApiChange>();
     for (ApiChange.Status apiStatus : apiStatusChanges) {
       apiChangeCollection.add(new ApiChange(oldClass, apiStatus));
@@ -225,12 +146,6 @@
    */
   private <T> void addProperty(Map<T, Set<ApiChange>> hashMap, T key,
       ApiChange property) {
-    /*
-     * if (!ApiCompatibilityChecker.PRINT_COMPATIBLE && (property.getStatus() ==
-     * ApiChange.Status.COMPATIBLE)) { return; } if
-     * (!ApiCompatibilityChecker.PRINT_COMPATIBLE_WITH && property.getStatus() ==
-     * ApiChange.Status.COMPATIBLE_WITH) { return; }
-     */
     Set<ApiChange> value = hashMap.get(key);
     if (value == null) {
       value = new HashSet<ApiChange>();
@@ -245,8 +160,8 @@
         intersectingFields.keySet());
     Collections.sort(intersectingFieldsList);
     for (ApiField apiField : intersectingFieldsList) {
-      for (ApiChange.Status status : intersectingFields.get(apiField)) {
-        collection.add(new ApiChange(apiField, status));
+      for (ApiChange apiChange : intersectingFields.get(apiField)) {
+        collection.add(apiChange);
       }
     }
     return collection;
@@ -275,22 +190,6 @@
     return collection;
   }
 
-  /**
-   * 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.getQualifiedSourceName());
-      if (other != null) {
-        return other;
-      }
-    }
-    return null;
-  }
-
   private boolean isIncompatibileDueToMethodOverloading(
       Set<ApiAbstractMethod> methodsInNew,
       Set<ApiAbstractMethod> methodsInExisting) {
@@ -308,6 +207,12 @@
     return numMatchingSignature > 1;
   }
 
+  /**
+   * Processes elements in intersection, checking for incompatibilities.
+   * 
+   * @param intersection
+   * @param methodType
+   */
   private void processElementsInIntersection(Set<String> intersection,
       ApiClass.MethodType methodType) {
 
@@ -332,39 +237,113 @@
             "Many methods in the new API with similar signatures. Methods = "
                 + methodsInNew + " This might break API source compatibility"));
       }
-      // 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
+
+      /*
+       * 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. Keep track of a method that has the
+       * same exact argument types as the old method. If such a method exists,
+       * check Api compatibility with just that method. Otherwise, check api
+       * compatibility with ALL methods that might be compatible. (This
+       * conservative estimate will work as long as we do not change the Api in
+       * pathological ways.)
+       */
       for (ApiAbstractMethod methodInExisting : methodsInExisting) {
+        Set<ApiChange> allPossibleApiChanges = new HashSet<ApiChange>();
+        ApiAbstractMethod sameSignatureMethod = null;
         for (ApiAbstractMethod methodInNew : methodsInNew) {
+          Set<ApiChange> currentApiChange = new HashSet<ApiChange>();
+          boolean hasSameSignature = false;
           if (methodInExisting.isCompatible(methodInNew)) {
-            ApiChange returnType = methodInExisting.checkReturnTypeCompatibility(methodInNew);
-            if (returnType != null) {
-              addProperty(intersectingElements, methodInExisting, returnType);
-            }
-            for (ApiChange apiChange : methodInExisting.checkExceptions(methodInNew)) {
-              addProperty(intersectingElements, methodInExisting, apiChange);
+            if (methodInExisting.isOverridable()) {
+              // check if the new method's api is exactly the same
+              currentApiChange.addAll(methodInExisting.getAllChangesInApi(methodInNew));
+            } else {
+              // check for changes to return type and exceptions
+              currentApiChange.addAll(methodInExisting.checkExceptionsAndReturnType(methodInNew));
             }
             for (ApiChange.Status status : methodInExisting.getModifierChanges(methodInNew)) {
-              addProperty(intersectingElements, methodInExisting,
-                  new ApiChange(methodInExisting, status));
+              currentApiChange.add(new ApiChange(methodInExisting, status));
             }
-            onlyInNew.remove(methodInNew);
-            onlyInExisting.remove(methodInExisting);
-            String signatureInNew = methodInNew.getApiSignature();
-            String signatureInExisting = methodInExisting.getApiSignature();
-            if (signatureInNew.equals(signatureInExisting)) {
-              commonSignature.add(signatureInNew);
-              addProperty(intersectingElements, methodInExisting,
-                  new ApiChange(methodInExisting, ApiChange.Status.COMPATIBLE));
+            if (methodInNew.getInternalSignature().equals(
+                methodInExisting.getInternalSignature())) {
+              currentApiChange.add(new ApiChange(methodInExisting,
+                  ApiChange.Status.COMPATIBLE));
+              hasSameSignature = true;
             } else {
-              addProperty(intersectingElements, methodInExisting,
-                  new ApiChange(methodInExisting,
-                      ApiChange.Status.COMPATIBLE_WITH, signatureInNew));
+              currentApiChange.add(new ApiChange(methodInExisting,
+                  ApiChange.Status.COMPATIBLE_WITH,
+                  methodInNew.getApiSignature()));
+            }
+          }
+
+          if (currentApiChange.size() > 0) {
+            if (hasSameSignature) {
+              allPossibleApiChanges = currentApiChange;
+              sameSignatureMethod = methodInNew;
+            } else if (sameSignatureMethod == null) {
+              allPossibleApiChanges.addAll(currentApiChange);
+            }
+          }
+        }
+        // put the best Api match
+        if (allPossibleApiChanges.size() > 0) {
+          onlyInExisting.remove(methodInExisting);
+          String signatureInExisting = methodInExisting.getInternalSignature();
+          if (sameSignatureMethod != null
+              && signatureInExisting.equals(sameSignatureMethod.getInternalSignature())) {
+            commonSignature.add(signatureInExisting);
+          }
+          for (ApiChange apiChange : allPossibleApiChanges) {
+            addProperty(intersectingElements, methodInExisting, apiChange);
+          }
+        }
+      }
+
+      /**
+       * Look for incompatiblities that might result due to new methods
+       * over-loading existing methods. Instead of applying JLS to determine the
+       * best match, just be conservative and report all possible
+       * incompatibilities if there is no old method with the exact same
+       * signature.
+       * 
+       * <pre>
+       * class A { // old version 
+       *   final void foo(Set<String> p1, Set<String> p2); 
+       * }
+       * 
+       * class A { // new version 
+       *   final void foo(Set<String> p1, Set<String> p2); 
+       *   void foo(HashSet<String> p1, Set<String> p2) throws ...; 
+       * }
+       * </pre>
+       */
+      for (ApiAbstractMethod methodInNew : methodsInNew) {
+        ApiAbstractMethod sameSignatureMethod = null;
+        for (ApiAbstractMethod methodInExisting : methodsInExisting) {
+          if (methodInNew.getInternalSignature().equals(
+              methodInExisting.getInternalSignature())) {
+            sameSignatureMethod = methodInExisting;
+            break;
+          }
+        }
+
+        // do not look for incompatibilities with overloaded methods, if exact
+        // match exists.
+        if (sameSignatureMethod != null) {
+          continue;
+        }
+        for (ApiAbstractMethod methodInExisting : methodsInExisting) {
+          if (methodInNew.isCompatible(methodInExisting)) {
+            // new method is going to be called instead of existing method,
+            // determine incompatibilities
+            for (ApiChange apiChange : methodInExisting.checkExceptionsAndReturnType(methodInNew)) {
+              addProperty(intersectingElements, methodInExisting, apiChange);
             }
           }
         }
       }
+
       // printOutput(commonSignature, onlyInExisting, onlyInNew);
     }
     missingElements.addAll(onlyInExisting);
@@ -374,17 +353,11 @@
     for (String fieldName : intersection) {
       ApiField newField = newClass.getApiFieldByName(fieldName);
       ApiField oldField = oldClass.getApiFieldByName(fieldName);
-      Set<ApiChange.Status> apiChanges = oldField.getModifierChanges(newField);
+      Set<ApiChange> apiChanges = oldField.getModifierChanges(newField);
       if (apiChanges.size() > 0) {
         intersectingFields.put(oldField, apiChanges);
       }
     }
   }
 
-  private <E, V> void removeAll(Map<E, Set<V>> tempMap, Set<E> removeKeys) {
-    for (E element : removeKeys) {
-      tempMap.remove(element);
-    }
-  }
-
 }
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
index 3d01fd0..516b3ce 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiCompatibilityChecker.java
@@ -38,27 +38,27 @@
  * <p>
  * To compute API diffs, follow these 2 steps:
  * <ol>
- * <li> for each of the two repositories, construct an {@link ApiContainer}
- * <li> call getApiDiff on the {@code ApiDiffGenerator}
+ * <li>for each of the two repositories, construct an {@link ApiContainer}
+ * <li>call getApiDiff on the {@code ApiDiffGenerator}
  * </ol>
  * </p>
  * 
  * <p>
  * An {@code ApiContainer} object is a list of {@link ApiPackage} objects.
  * {@code ApiPackage} objects themselves are list of {@link ApiClass} objects.
- * {@code ApiClass} objects contain list of {@code ApiConstructor},
- * {@code ApiMethod}, and {@code JField} objects.
+ * {@code ApiClass} objects contain list of {@code ApiConstructor}, {@code
+ * ApiMethod}, and {@code JField} objects.
  * </p>
  * 
  * <p>
  * Each {@code ApiDiffGenerator} object computes the list of intersecting and
- * missing {@link ApiPackageDiffGenerator} objects. Each
- * {@code ApiPackageDiffGenerator} object in turn computes the list of
- * intersecting and missing {@link ApiClassDiffGenerator} objects. Each
- * {@code ApiClassDiffGenerator} object in turn computes the list of
- * intersecting and missing API members. The members are represented by
- * {@link ApiConstructor} for constructors, {@link ApiMethod} for methods, and
- * {@link ApiField} for fields.
+ * missing {@link ApiPackageDiffGenerator} objects. Each {@code
+ * ApiPackageDiffGenerator} object in turn computes the list of intersecting and
+ * missing {@link ApiClassDiffGenerator} objects. Each {@code
+ * ApiClassDiffGenerator} object in turn computes the list of intersecting and
+ * missing API members. The members are represented by {@link ApiConstructor}
+ * for constructors, {@link ApiMethod} for methods, and {@link ApiField} for
+ * fields.
  * </p>
  * 
  * <p>
@@ -82,7 +82,8 @@
   // currently doing only source_compatibility. true by default.
   public static final boolean API_SOURCE_COMPATIBILITY = true;
 
-  // prints which class the member was declared in, false by default
+  // prints which class the member was declared in plus the string message in
+  // ApiChange, false by default
   public static final boolean DEBUG = false;
 
   // prints the API of the two containers, false by default.
@@ -100,6 +101,10 @@
   // true by default
   public static final boolean REMOVE_NON_SUBCLASSABLE_ABSTRACT_CLASS_FROM_API = true;
 
+  public static final boolean FILTER_DUPLICATES = true;
+
+  public static final boolean DEBUG_DUPLICATE_REMOVAL = false;
+
   // Tweak for log output.
   public static final TreeLogger.Type type = TreeLogger.ERROR;
 
@@ -107,7 +112,7 @@
   public static Collection<ApiChange> getApiDiff(ApiContainer newApi,
       ApiContainer existingApi, Set<String> whiteList) throws NotFoundException {
     ApiDiffGenerator apiDiff = new ApiDiffGenerator(newApi, existingApi);
-    return getApiDiff(apiDiff, whiteList, true);
+    return getApiDiff(apiDiff, whiteList, FILTER_DUPLICATES);
   }
 
   // Call APIBuilders for each of the 2 source trees
@@ -199,10 +204,14 @@
   // interface for testing, since do not want to build ApiDiff frequently
   static Collection<ApiChange> getApiDiff(ApiDiffGenerator apiDiff,
       Set<String> whiteList, boolean removeDuplicates) throws NotFoundException {
-    Collection<ApiChange> collection = apiDiff.getApiDiff(removeDuplicates);
-    Set<ApiChange> prunedCollection = new HashSet<ApiChange>();
+    Collection<ApiChange> collection = apiDiff.getApiDiff();
+    if (removeDuplicates) {
+      collection = apiDiff.removeDuplicates(collection);
+    }
+
+    Collection<ApiChange> prunedCollection = new ArrayList<ApiChange>();
     for (ApiChange apiChange : collection) {
-      String apiChangeAsString = apiChange.toString();
+      String apiChangeAsString = apiChange.getStringRepresentationWithoutMessage();
       apiChangeAsString = apiChangeAsString.trim();
       if (whiteList.remove(apiChangeAsString)) {
         continue;
@@ -221,9 +230,11 @@
     if (whiteList.size() > 0) {
       List<String> al = new ArrayList<String>(whiteList);
       Collections.sort(al);
-      System.err.println("ApiChanges "
-          + al
-          + ",  not found. Are you using a properly formatted configuration file?");
+      System.err.println("ApiChanges [");
+      for (String apiChange : al) {
+        System.err.println(apiChange);
+      }
+      System.err.println("],  not found. Are you using a properly formatted configuration file?");
     }
     List<ApiChange> apiChangeList = new ArrayList<ApiChange>(prunedCollection);
     Collections.sort(apiChangeList);
@@ -237,8 +248,6 @@
    * represented as the string obtained by invoking the getRelativeSignature()
    * method on {@link ApiElement}.
    * 
-   * @param fileName
-   * @return
    */
   private static Set<String> readWhiteListFromFile(String fileName)
       throws IOException {
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
index 8390145..f5de9f7 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiConstructor.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiConstructor.java
@@ -30,10 +30,20 @@
   }
 
   @Override
+  public boolean isOverridable() {
+    return false;
+  }
+
+  @Override
   ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod) {
     return null;
   }
 
+  @Override
+  List<ApiChange> getAllChangesInApi(ApiAbstractMethod newMethod) {
+    return Collections.emptyList();
+  }
+
   /**
    * returns an immutable List.
    */
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
index 60afc44..a8797ca 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiDiffGenerator.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiDiffGenerator.java
@@ -34,8 +34,15 @@
  */
 public final class ApiDiffGenerator {
 
+  private static enum Relation {
+    NONE, SUBCLASS, SUPERCLASS;
+  }
+
   public static final String DELIMITER = " ";
 
+  /* variable just for debugging -- find why an apiChange is being removed */
+  static final String HAY_API_CHANGE = "";
+
   /**
    * The two types might belong to different typeOracles.
    */
@@ -61,7 +68,6 @@
     return firstClassType.isAssignableTo(secondClassType);
   }
 
-  @SuppressWarnings("unchecked")
   static Set<String> removeIntersection(Set<String> s1, Set<String> s2) {
     Set<String> intersection = new HashSet<String>(s1);
     intersection.retainAll(s2);
@@ -72,7 +78,9 @@
 
   Map<String, ApiPackageDiffGenerator> intersectingPackages = new HashMap<String, ApiPackageDiffGenerator>();
   Set<String> missingPackageNames;
+
   final ApiContainer newApi;
+
   final ApiContainer oldApi;
 
   ApiDiffGenerator(ApiContainer newApi, ApiContainer oldApi) {
@@ -80,23 +88,6 @@
     this.oldApi = oldApi;
   }
 
-  Collection<ApiChange> getApiDiff(boolean removeDuplicates)
-      throws NotFoundException {
-    computeApiDiff();
-    if (removeDuplicates) {
-      cleanApiDiff();
-    }
-    Collection<ApiChange> collection = new ArrayList<ApiChange>();
-    Set<ApiPackage> missingPackages = oldApi.getApiPackagesBySet(missingPackageNames);
-    for (ApiPackage missingPackage : missingPackages) {
-      collection.add(new ApiChange(missingPackage, ApiChange.Status.MISSING));
-    }
-    for (ApiPackageDiffGenerator intersectingPackage : intersectingPackages.values()) {
-      collection.addAll(intersectingPackage.getApiDiff());
-    }
-    return collection;
-  }
-
   ApiClassDiffGenerator findApiClassDiffGenerator(String className) {
     int i = className.length() - 1;
     while (i >= 0) {
@@ -144,6 +135,21 @@
     return intersectingPackages.get(key);
   }
 
+  Collection<ApiChange> getApiDiff() throws NotFoundException {
+
+    computeApiDiff();
+
+    Collection<ApiChange> collection = new ArrayList<ApiChange>();
+    Set<ApiPackage> missingPackages = oldApi.getApiPackagesBySet(missingPackageNames);
+    for (ApiPackage missingPackage : missingPackages) {
+      collection.add(new ApiChange(missingPackage, ApiChange.Status.MISSING));
+    }
+    for (ApiPackageDiffGenerator intersectingPackage : intersectingPackages.values()) {
+      collection.addAll(intersectingPackage.getApiDiff());
+    }
+    return collection;
+  }
+
   ApiContainer getNewApiContainer() {
     return newApi;
   }
@@ -152,10 +158,63 @@
     return oldApi;
   }
 
-  private void cleanApiDiff() {
-    for (ApiPackageDiffGenerator intersectingPackage : intersectingPackages.values()) {
-      intersectingPackage.cleanApiDiff();
+  /**
+   * Remove any apiChange x if there is another apiChange y such that the
+   * apiElement and status for both x and y are the same and the apiClass for x
+   * is a subclass of the apiClass for y.
+   * 
+   * @param originalCollection collection with duplicates.
+   * @return collection minus duplicates.
+   */
+  Collection<ApiChange> removeDuplicates(
+      Collection<ApiChange> originalCollection) {
+    /*
+     * Map from the hashCode of an apiChange to the list of ApiChanges. There
+     * can be multiple ApiChanges that have the same hashCode, but neither is a
+     * subset of another. Example: if B and C both extend A, and there is an
+     * ApiChange in B and C due to an api element of A.
+     */
+    Map<Integer, Collection<ApiChange>> apiChangeMap = new HashMap<Integer, Collection<ApiChange>>();
+    for (ApiChange apiChange : originalCollection) {
+      String apiChangeStr = apiChange.getApiElement().getRelativeSignature();
+      Collection<ApiChange> apiChangesSameHashCode = apiChangeMap.get(apiChange.hashCodeForDuplication());
+      if (apiChangesSameHashCode == null) {
+        apiChangesSameHashCode = new HashSet<ApiChange>();
+        apiChangeMap.put(apiChange.hashCodeForDuplication(),
+            apiChangesSameHashCode);
+      }
+      Collection<ApiChange> apiChangesToRemove = new HashSet<ApiChange>();
+      boolean addNewElement = true;
+
+      for (ApiChange oldApiChange : apiChangesSameHashCode) {
+        String oldApiChangeStr = oldApiChange.getApiElement().getRelativeSignature();
+        Relation relation = getRelationOfApiClassOfFirstArgToThatOfSecond(
+            apiChange.getApiElement(), oldApiChange.getApiElement());
+        if (relation == Relation.SUPERCLASS) {
+          apiChangesToRemove.add(oldApiChange);
+          if (ApiCompatibilityChecker.DEBUG_DUPLICATE_REMOVAL
+              && oldApiChangeStr.indexOf(HAY_API_CHANGE) != -1) {
+            System.out.println(oldApiChangeStr + " replaced by " + apiChangeStr
+                + ", status = " + oldApiChange.getStatus());
+          }
+        } else if (relation == Relation.SUBCLASS) {
+          addNewElement = false;
+          if (ApiCompatibilityChecker.DEBUG_DUPLICATE_REMOVAL
+              && apiChangeStr.indexOf(HAY_API_CHANGE) != -1) {
+            System.out.println(apiChangeStr + " replaced by " + oldApiChangeStr);
+          }
+        }
+      }
+      apiChangesSameHashCode.removeAll(apiChangesToRemove);
+      if (addNewElement) {
+        apiChangesSameHashCode.add(apiChange);
+      }
     }
+    Collection<ApiChange> prunedCollection = new HashSet<ApiChange>();
+    for (Integer hashCode : apiChangeMap.keySet()) {
+      prunedCollection.addAll(apiChangeMap.get(hashCode));
+    }
+    return prunedCollection;
   }
 
   /**
@@ -178,4 +237,34 @@
     }
   }
 
+  /**
+   * Returns how ApiClass for first element is "related" to the ApiClass for
+   * secondElement.
+   */
+  private Relation getRelationOfApiClassOfFirstArgToThatOfSecond(
+      ApiElement firstApiElement, ApiElement secondApiElement) {
+    JClassType firstClassType = null;
+    JClassType secondClassType = null;
+    if (firstApiElement instanceof ApiField) {
+      firstClassType = ((ApiField) firstApiElement).getApiClass().getClassObject();
+      secondClassType = ((ApiField) secondApiElement).getApiClass().getClassObject();
+    }
+    if (firstApiElement instanceof ApiAbstractMethod) {
+      firstClassType = ((ApiAbstractMethod) firstApiElement).getApiClass().getClassObject();
+      secondClassType = ((ApiAbstractMethod) secondApiElement).getApiClass().getClassObject();
+    }
+    if (firstClassType != null && secondClassType != null) {
+      if (secondClassType.isAssignableTo(firstClassType)) {
+        return Relation.SUPERCLASS;
+      }
+      if (firstClassType.isAssignableTo(secondClassType)) {
+        return Relation.SUBCLASS;
+      }
+      return Relation.NONE;
+    }
+    throw new RuntimeException(
+        "Inconsistent types for ApiElements: newApiElement " + firstApiElement
+            + ", oldApiElement : " + secondApiElement);
+  }
+
 }
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiElement.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiElement.java
index c7654b1..d261788 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiElement.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiElement.java
@@ -16,7 +16,8 @@
 package com.google.gwt.tools.apichecker;
 
 /**
- * An interface encapsulating any API elements.
+ * An interface encapsulating any API elements. All Api elements implement this
+ * interface.
  */
 public interface ApiElement {
   String getRelativeSignature();
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiField.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiField.java
index 9884ebc..a7778c7 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiField.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiField.java
@@ -22,7 +22,8 @@
 import java.util.Set;
 
 /**
- * Immutable class that encapsulates an API Field. Useful for set-operations.
+ * Immutable class that encapsulates an API Field. Useful for set-operations. An
+ * ApiField is attached to an ApiClass.
  */
 final class ApiField implements Comparable<ApiField>, ApiElement {
 
@@ -59,6 +60,10 @@
     return false;
   }
 
+  public ApiClass getApiClass() {
+    return apiClass;
+  }
+
   public String getRelativeSignature() {
     if (relativeSignature == null) {
       relativeSignature = computeRelativeSignature();
@@ -87,13 +92,13 @@
     return field;
   }
 
-  Set<ApiChange.Status> getModifierChanges(ApiField newField) {
-    Set<ApiChange.Status> statuses = new HashSet<ApiChange.Status>();
+  Set<ApiChange> getModifierChanges(ApiField newField) {
+    Set<ApiChange> statuses = new HashSet<ApiChange>();
     if (!field.isFinal() && newField.getField().isFinal()) {
-      statuses.add(ApiChange.Status.FINAL_ADDED);
+      statuses.add(new ApiChange(this, ApiChange.Status.FINAL_ADDED));
     }
     if ((field.isStatic() && !newField.getField().isStatic())) {
-      statuses.add(ApiChange.Status.STATIC_REMOVED);
+      statuses.add(new ApiChange(this, ApiChange.Status.STATIC_REMOVED));
     }
     return statuses;
   }
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
index f49d232..d911d3a 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiMethod.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiMethod.java
@@ -16,11 +16,19 @@
 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.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Encapsulates an API method. Useful for set-operations.
@@ -32,6 +40,15 @@
   }
 
   @Override
+  public boolean isOverridable() {
+    JMethod methodType = (JMethod) method;
+    if (methodType.isStatic() || methodType.isFinal()) {
+      return false;
+    }
+    return apiClass.isSubclassableApiClass();
+  }
+
+  @Override
   ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod)
       throws TypeNotPresentException {
     JType firstType, secondType;
@@ -53,7 +70,7 @@
     if (compatible) {
       return null;
     }
-    sb.append("Return type changed from ");
+    sb.append(" from ");
     sb.append(firstType.getQualifiedSourceName());
     sb.append(" to ");
     sb.append(secondType.getQualifiedSourceName());
@@ -61,6 +78,81 @@
         sb.toString());
   }
 
+  /**
+   * check for changes in: (i) argument types, (ii) return type, (iii)
+   * exceptions thrown. use getJNISignature() for type equality, it does type
+   * erasure
+   */
+  @Override
+  List<ApiChange> getAllChangesInApi(ApiAbstractMethod newApiMethod) {
+    if (!(newApiMethod.getMethod() instanceof JMethod)) {
+      return Collections.emptyList();
+    }
+    List<ApiChange> changeApis = new ArrayList<ApiChange>();
+    JMethod existingMethod = (JMethod) method;
+    JMethod newMethod = (JMethod) newApiMethod.getMethod();
+    // check return type
+    if (!existingMethod.getReturnType().getJNISignature().equals(
+        newMethod.getReturnType().getJNISignature())) {
+      changeApis.add(new ApiChange(this,
+          ApiChange.Status.OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE, " from "
+              + existingMethod.getReturnType() + " to "
+              + newMethod.getReturnType()));
+    }
+    // check argument type
+    JParameter[] newParametersList = newMethod.getParameters();
+    JParameter[] existingParametersList = existingMethod.getParameters();
+    if (newParametersList.length != existingParametersList.length) {
+      changeApis.add(new ApiChange(this,
+          ApiChange.Status.OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE,
+          "number of parameters changed"));
+    } else {
+      int length = newParametersList.length;
+      for (int i = 0; i < length; i++) {
+        if (!existingParametersList[i].getType().getJNISignature().equals(
+            newParametersList[i].getType().getJNISignature())) {
+          changeApis.add(new ApiChange(this,
+              ApiChange.Status.OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE,
+              " at position " + i + " from "
+                  + existingParametersList[i].getType() + " to "
+                  + newParametersList[i].getType()));
+        }
+      }
+    }
+
+    // check exceptions
+    Set<String> newExceptionsSet = new HashSet<String>();
+    Map<String, JType> newExceptionsMap = new HashMap<String, JType>();
+    for (JType newType : newMethod.getThrows()) {
+      String jniSignature = newType.getJNISignature();
+      newExceptionsMap.put(jniSignature, newType);
+      newExceptionsSet.add(jniSignature);
+    }
+
+    Set<String> existingExceptionsSet = new HashSet<String>();
+    Map<String, JType> existingExceptionsMap = new HashMap<String, JType>();
+    for (JType existingType : existingMethod.getThrows()) {
+      String jniSignature = existingType.getJNISignature();
+      existingExceptionsMap.put(jniSignature, existingType);
+      existingExceptionsSet.add(jniSignature);
+    }
+    ApiDiffGenerator.removeIntersection(existingExceptionsSet, newExceptionsSet);
+    removeUncheckedExceptions(newMethod, newExceptionsSet, newExceptionsMap);
+    removeUncheckedExceptions(existingMethod, existingExceptionsSet,
+        existingExceptionsMap);
+    if (existingExceptionsSet.size() > 0) {
+      changeApis.add(new ApiChange(this,
+          ApiChange.Status.OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE,
+          "existing method had more exceptions: " + existingExceptionsSet));
+    }
+    if (newExceptionsSet.size() > 0) {
+      changeApis.add(new ApiChange(this,
+          ApiChange.Status.OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE,
+          "new method has more exceptions: " + newExceptionsSet));
+    }
+    return changeApis;
+  }
+
   /*
    * check for: (i) added 'final' or 'abstract', (ii) removed 'static', adding
    * the 'static' keyword is fine.
@@ -94,6 +186,30 @@
     }
     return statuses;
   }
+
+  // remove Error.class, RuntimeException.class, and their sub-classes
+  private void removeUncheckedExceptions(JMethod method,
+      Set<String> exceptionsSet, Map<String, JType> exceptionsMap) {
+    if (exceptionsSet.size() == 0) {
+      return;
+    }
+    TypeOracle typeOracle = method.getEnclosingType().getOracle();
+    JClassType errorType = typeOracle.findType(Error.class.getName());
+    JClassType rteType = typeOracle.findType(RuntimeException.class.getName());
+    Set<String> exceptionsToRemove = new HashSet<String>();
+    for (String exceptionString : exceptionsSet) {
+      JType exception = exceptionsMap.get(exceptionString);
+      assert (exception != null);
+      boolean remove = (errorType != null && ApiDiffGenerator.isFirstTypeAssignableToSecond(
+          exception, errorType))
+          || (rteType != null && ApiDiffGenerator.isFirstTypeAssignableToSecond(
+              exception, rteType));
+      if (remove) {
+        exceptionsToRemove.add(exceptionString);
+      }
+    }
+    exceptionsSet.removeAll(exceptionsToRemove);
+  }
 }
 /*
  * final class TestB { static protected int i = 5; }
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
index c48e06e..d8841a4 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiPackageDiffGenerator.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiPackageDiffGenerator.java
@@ -56,12 +56,6 @@
     return this.getName().compareTo(other.getName());
   }
 
-  void cleanApiDiff() {
-    for (ApiClassDiffGenerator intersectingClass : intersectingClasses.values()) {
-      intersectingClass.cleanApiDiff();
-    }
-  }
-
   void computeApiDiff() throws NotFoundException {
     Set<String> newClassNames = newPackage.getApiClassNames();
     missingClassNames = oldPackage.getApiClassNames();
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
index 678f0ea..cc4cba9 100644
--- a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
+++ b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
@@ -40,6 +40,7 @@
 public class ApiCompatibilityTest extends TestCase {
 
   // These cups are slightly different from the cups in ApiContainerTest
+  private static final boolean DEBUG = false;
 
   private static StaticCompilationUnit[] getScuArray() {
     return new StaticCompilationUnit[] {
@@ -51,7 +52,11 @@
             getSourceForTestClass()),
         new StaticCompilationUnit("java.lang.Object", getSourceForObject()),
         new StaticCompilationUnit("java.lang.Throwable",
-            getSourceForThrowable()),};
+            getSourceForThrowable()),
+        new StaticCompilationUnit("test.apicontainer.OneMoreApiClass",
+            getSourceForOneMoreApiClass()),
+        new StaticCompilationUnit("java.lang.RuntimeException",
+            getSourceForRuntimeException()),};
   }
 
   private static char[] getSourceForApiClass() {
@@ -60,6 +65,7 @@
     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("\tpublic final void checkParametersAndReturnTypesFinalVersion(java.lang.Object x) throws java.lang.Throwable { };\n");
     sb.append("};\n");
     return sb.toString().toCharArray();
   }
@@ -83,6 +89,8 @@
     sb.append("package java.lang;\n");
     sb.append("public class Object {\n");
     sb.append("\tpublic void apiMethod() { }\n");
+    sb.append("\tprotected void checkOverloadedAndOverridableDetection(java.lang.Object b) { }\n");
+    sb.append("\tprotected final void checkOverloadedMethodAccounted(java.lang.Object b) { }\n");
     sb.append("\tprivate void internalMethod() { }\n");
     sb.append("\tprotected final void protectedMethod() { }\n");
     sb.append("\tpublic final int apiField = 0;\n");
@@ -92,6 +100,25 @@
     return sb.toString().toCharArray();
   }
 
+  private static char[] getSourceForOneMoreApiClass() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package test.apicontainer;\n");
+    sb.append("public class OneMoreApiClass extends java.lang.Object {\n");
+    sb.append("\tprotected void checkOverloadedAndOverridableDetection(test.apicontainer.OneMoreApiClass b) { }\n");
+    sb.append("\tprotected final void checkOverloadedMethodAccounted(test.apicontainer.ApiClass b) throws java.lang.Throwable { }\n");
+    sb.append("\tpublic void testUncheckedExceptions() throws RuntimeException { }\n");
+    sb.append("};\n");
+    return sb.toString().toCharArray();
+  }
+
+  private static char[] getSourceForRuntimeException() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package java.lang;\n");
+    sb.append("public class RuntimeException extends Throwable {\n");
+    sb.append("}\n");
+    return sb.toString().toCharArray();
+  }
+
   private static char[] getSourceForTestClass() {
     StringBuffer sb = new StringBuffer();
     sb.append("package test.nonapipackage;\n");
@@ -141,31 +168,37 @@
     assertEquals(0, ApiCompatibilityChecker.getApiDiff(api1, apiSameAs1,
         hashSet).size());
     ApiDiffGenerator apiDiff = new ApiDiffGenerator(api2, api1);
-    boolean removeDuplicates = false;
     String strWithDuplicates = getStringRepresentation(ApiCompatibilityChecker.getApiDiff(
-        apiDiff, hashSet, removeDuplicates));
+        apiDiff, hashSet, !ApiCompatibilityChecker.FILTER_DUPLICATES));
+    if (DEBUG) {
+      System.out.println("computing apiDiff, now with duplicates");
+      System.out.println(strWithDuplicates);
+    }
     String strWithoutDuplicates = getStringRepresentation(ApiCompatibilityChecker.getApiDiff(
-        apiDiff, hashSet, !removeDuplicates));
+        apiDiff, hashSet, ApiCompatibilityChecker.FILTER_DUPLICATES));
+    if (DEBUG) {
+      System.out.println("computing apiDiff, now without duplicates");
+      System.out.println(strWithoutDuplicates);
+    }
 
     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));
+    String statusString = "java.newpackage" + delimiter
+        + ApiChange.Status.MISSING;
+    assertEquals(1, countPresence(statusString, strWithDuplicates));
+    assertEquals(1, countPresence(statusString, 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 staill reported even if class becomes abstract (as
+    // test if methods are still reported even if class becomes abstract (as
     // long as it is sub-classable)
     assertEquals(0, countPresence(
         "test.apicontainer.NonApiClass.ApiClassInNonApiClass::ApiClassInNonApiClass()"
@@ -181,32 +214,67 @@
         + delimiter + ApiChange.Status.FINAL_ADDED, strWithoutDuplicates));
 
     // test if duplicates are weeded out from intersecting methods
-    assertEquals(3, countPresence("protectedMethod()" + delimiter
+    assertEquals(4, countPresence("protectedMethod()" + delimiter
         + ApiChange.Status.FINAL_ADDED, strWithDuplicates));
     assertEquals(1, countPresence("protectedMethod()" + delimiter
         + ApiChange.Status.FINAL_ADDED, strWithoutDuplicates));
 
     // test if duplicates are weeded out from missing fields
-    assertEquals(3, countPresence("apiFieldWillBeMissing" + delimiter
+    assertEquals(4, countPresence("apiFieldWillBeMissing" + delimiter
         + ApiChange.Status.MISSING, strWithDuplicates));
     assertEquals(1, countPresence("apiFieldWillBeMissing" + delimiter
         + ApiChange.Status.MISSING, strWithoutDuplicates));
 
-    // test returnType error
-    String methodSignature = "checkParametersAndReturnTypes(Ltest/apicontainer/ApiClass;)";
-    assertEquals(1, countPresence(methodSignature + delimiter
+    // test error in non-final version
+    String nonFinalMethodSignature = "checkParametersAndReturnTypes(Ltest/apicontainer/ApiClass;)";
+    for (ApiChange.Status status : new ApiChange.Status[] {
+        ApiChange.Status.OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE,
+        ApiChange.Status.OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE,
+        ApiChange.Status.OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE}) {
+      assertEquals(1, countPresence(nonFinalMethodSignature + delimiter
+          + status, strWithoutDuplicates));
+    }
+    // test return type and exception type error in final version
+    String finalMethodSignature = "checkParametersAndReturnTypesFinalVersion(Ltest/apicontainer/ApiClass;)";
+    assertEquals(1, countPresence(finalMethodSignature + delimiter
         + ApiChange.Status.RETURN_TYPE_ERROR, strWithoutDuplicates));
-    // test method exceptions
-    assertEquals(1, countPresence(methodSignature + delimiter
+    assertEquals(1, countPresence(finalMethodSignature + delimiter
         + ApiChange.Status.EXCEPTION_TYPE_ERROR, strWithoutDuplicates));
 
     // checking if changes in parameter types were okay
-    assertEquals(2, countPresence(methodSignature, strWithoutDuplicates));
+    assertEquals(2, countPresence(finalMethodSignature, strWithoutDuplicates));
 
     // test method_overloading
-    methodSignature = "methodInNonApiClass(Ljava/lang/Object;)";
+    finalMethodSignature = "methodInNonApiClass(Ljava/lang/Object;)";
+    assertEquals(1, countPresence(finalMethodSignature + delimiter
+        + ApiChange.Status.OVERLOADED_METHOD_CALL, strWithoutDuplicates));
+
+    // test unchecked exceptions
+    assertEquals(0, countPresence("testUncheckedExceptions",
+        strWithoutDuplicates));
+
+    // test overloaded and overridable detection
+    String methodSignature = "test.apicontainer.OneMoreApiClass::checkOverloadedAndOverridableDetection(Ljava/lang/Object;)";
+    for (ApiChange.Status status : new ApiChange.Status[] {
+        ApiChange.Status.OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE,
+        ApiChange.Status.OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE,
+        ApiChange.Status.OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE}) {
+      assertEquals(0, countPresence(methodSignature + delimiter + status,
+          strWithoutDuplicates));
+    }
+
     assertEquals(1, countPresence(methodSignature + delimiter
         + ApiChange.Status.OVERLOADED_METHOD_CALL, strWithoutDuplicates));
+
+    // the method should be satisfied by the method in the super-class
+    methodSignature = "test.apicontainer.OneMoreApiClass::checkOverloadedMethodAccounted(Ltest/apicontainer/OneMoreApiClass;)";
+    assertEquals(0, countPresence(methodSignature + delimiter
+        + ApiChange.Status.MISSING, strWithoutDuplicates));
+
+    // the method should throw unchecked exceptions error
+    methodSignature = "test.apicontainer.OneMoreApiClass::checkOverloadedMethodAccounted(Ljava/lang/Object;)";
+    assertEquals(1, countPresence(methodSignature + delimiter
+        + ApiChange.Status.EXCEPTION_TYPE_ERROR, strWithoutDuplicates));
   }
 
   private void checkWhiteList() throws NotFoundException {
@@ -236,9 +304,7 @@
   private String getStringRepresentation(Collection<ApiChange> collection) {
     StringBuffer sb = new StringBuffer();
     for (ApiChange apiChange : collection) {
-      sb.append(apiChange.getApiElement().getRelativeSignature());
-      sb.append(ApiDiffGenerator.DELIMITER);
-      sb.append(apiChange.getStatus());
+      sb.append(apiChange);
       sb.append("\n");
     }
     return sb.toString();
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
index 76b50c3..b7c87d2 100644
--- a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
+++ b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
@@ -121,6 +121,8 @@
         new StaticCompilationUnit("test.nonapipackage.TestClass",
             getSourceForTestClass()),
         new StaticCompilationUnit("java.lang.Object", getSourceForObject()),
+        new StaticCompilationUnit("test.apicontainer.OneMoreApiClass",
+            getSourceForOneMoreApiClass()),
         new StaticCompilationUnit("java.newpackage.Test", getSourceForTest()),};
   }
 
@@ -135,6 +137,7 @@
     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("\tpublic final java.lang.Object checkParametersAndReturnTypesFinalVersion(ApiClass a) { return this; };\n");
     sb.append("};\n");
     return sb.toString().toCharArray();
   }
@@ -174,6 +177,8 @@
     sb.append("\tpublic void apiMethod() { }\n");
     sb.append("\tprivate void internalMethod() { }\n");
     sb.append("\tprotected native long protectedMethod();\n");
+    sb.append("\tprotected void checkOverloadedAndOverridableDetection(java.lang.Object b) { }\n");
+    sb.append("\tprotected final void checkOverloadedMethodAccounted(java.lang.Object b) { }\n");
     sb.append("\tpublic int apiField = 0;\n");
     sb.append("\tprotected transient int apiFieldWillBeMissing = 1;\n");
     sb.append("\tprivate int internalField = 0;\n");
@@ -182,6 +187,16 @@
     return sb.toString().toCharArray();
   }
 
+  private static char[] getSourceForOneMoreApiClass() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("package test.apicontainer;\n");
+    sb.append("public class OneMoreApiClass extends java.lang.Object {\n");
+    sb.append("\tprotected final void checkOverloadedMethodAccounted(test.apicontainer.OneMoreApiClass b) { }\n");
+    sb.append("\tpublic void testUncheckedExceptions () { }\n");
+    sb.append("};\n");
+    return sb.toString().toCharArray();
+  }
+
   private static char[] getSourceForTest() {
     StringBuffer sb = new StringBuffer();
     sb.append("package java.newpackage;\n");
@@ -242,7 +257,7 @@
     assertNotNull(package2.getApiClass("test.apicontainer.NonApiClass.ApiClassInNonApiClass"));
     assertNotNull(package2.getApiClass("test.apicontainer.NonApiClass.AnotherApiClassInNonApiClass"));
     assertEquals(1, package1.getApiClassNames().size());
-    assertEquals(3, package2.getApiClassNames().size());
+    assertEquals(4, package2.getApiClassNames().size());
   }
 
   /**
@@ -260,6 +275,8 @@
         "test.apicontainer.ApiClass");
     ApiClass innerClass = apiCheck.getApiPackage("test.apicontainer").getApiClass(
         "test.apicontainer.NonApiClass.ApiClassInNonApiClass");
+    ApiClass oneMoreApiClass = apiCheck.getApiPackage("test.apicontainer").getApiClass(
+        "test.apicontainer.OneMoreApiClass");
 
     // constructors
     assertEquals(1, innerClass.getApiMemberNames(
@@ -268,10 +285,11 @@
     // fields
     assertEquals(3, object.getApiFieldNames().size());
     assertEquals(4, apiClass.getApiFieldNames().size());
+    assertEquals(3, oneMoreApiClass.getApiFieldNames().size());
 
     // methods
-    assertEquals(2, object.getApiMemberNames(ApiClass.MethodType.METHOD).size());
-    assertEquals(4,
+    assertEquals(4, object.getApiMemberNames(ApiClass.MethodType.METHOD).size());
+    assertEquals(7,
         apiClass.getApiMemberNames(ApiClass.MethodType.METHOD).size());
     // the method definition lowest in the class hierarchy is kept
     assertNotSame(getMethodByName("apiMethod0", apiClass), getMethodByName(
@@ -279,6 +297,19 @@
     assertEquals(getMethodByName("protectedMethod0", apiClass),
         getMethodByName("protectedMethod0", object));
     assertNotNull(getMethodByName("methodInNonApiClass1", apiClass));
+
+    assertEquals(5, oneMoreApiClass.getApiMemberNames(
+        ApiClass.MethodType.METHOD).size());
+    Set<String> methodNames = new HashSet<String>(
+        Arrays.asList(new String[] {"checkOverloadedAndOverridableDetection1"}));
+    assertEquals(1, oneMoreApiClass.getApiMembersBySet(methodNames,
+        ApiClass.MethodType.METHOD).size());
+
+    // checkOverloadedMethodAccounted should appear twice.
+    methodNames = new HashSet<String>(
+        Arrays.asList(new String[] {"checkOverloadedMethodAccounted1"}));
+    assertEquals(2, oneMoreApiClass.getApiMembersBySet(methodNames,
+        ApiClass.MethodType.METHOD).size());
   }
 
   /**