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