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