Loosens up JSO restrictions in the TypeOracle to allow multiple JSO
implementations of an interface if the implementations share a common base
class that fully implements the interface.
Review at http://gwt-code-reviews.appspot.com/1076801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9183 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
index c701dfb..a03d724 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
@@ -33,6 +33,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -651,6 +653,45 @@
return javaSourceParser;
}
+ private List<JClassType> classChain(JClassType cls) {
+ LinkedList<JClassType> chain = new LinkedList<JClassType>();
+ while (cls != null) {
+ chain.push(cls);
+ cls = cls.getSuperclass();
+ }
+ return chain;
+ }
+
+ /**
+ * Determines whether the given class fully implements an interface (either
+ * directly or via inherited methods).
+ */
+ private boolean classFullyImplements(JClassType cls, JClassType intf) {
+ // The class must at least nominally implement the interface.
+ if (!intf.isAssignableFrom(cls)) {
+ return false;
+ }
+
+ // Check to see whether it implements all the interfaces methods.
+ for (JMethod meth : intf.getInheritableMethods()) {
+ if (!classImplementsMethod(cls, meth)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean classImplementsMethod(JClassType cls, JMethod meth) {
+ while (cls != null) {
+ JMethod found = cls.findMethod(meth.getName(), methodParamTypes(meth));
+ if ((found != null) && !found.isAbstract()) {
+ return true;
+ }
+ cls = cls.getSuperclass();
+ }
+ return false;
+ }
+
private void computeHierarchyRelationships(JClassType[] types) {
// For each type, walk up its hierarchy chain and tell each supertype
// about its subtype.
@@ -703,17 +744,69 @@
} else if (type.isAssignableTo(previousType)) {
// Do nothing
} else {
- throw new InternalCompilerException(
- "Already seen an implementing JSO subtype ("
- + previousType.getName() + ") for interface ("
- + intf.getName() + ") while examining newly-added type ("
- + type.getName() + "). This is a bug in "
- + "JSORestrictionsChecker.");
+ // Special case: If two JSOs implement the same interface, but they
+ // share a common base class that fully implements that interface,
+ // then choose that base class.
+ JClassType impl = findFullyImplementingBase(intf, type, previousType);
+ if (impl != null) {
+ jsoSingleImpls.put(intf, impl);
+ } else {
+ throw new InternalCompilerException(
+ "Already seen an implementing JSO subtype ("
+ + previousType.getName() + ") for interface ("
+ + intf.getName() + ") while examining newly-added type ("
+ + type.getName() + "). This is a bug in "
+ + "JSORestrictionsChecker.");
+ }
}
}
}
}
+ /**
+ * Determines whether both classes A and B share a common superclass which
+ * fully implements the given interface.
+ */
+ private JClassType findFullyImplementingBase(JClassType intf, JClassType a,
+ JClassType b) {
+ JClassType common = findNearestCommonBase(a, b);
+ if (classFullyImplements(common, intf)) {
+ return common;
+ }
+ return null;
+ }
+
+ /**
+ * Finds the nearest common base class of the given classes.
+ */
+ private JClassType findNearestCommonBase(JClassType a, JClassType b) {
+ List<JClassType> as = classChain(a);
+ List<JClassType> bs = classChain(b);
+
+ JClassType match = null;
+ Iterator<JClassType> ait = as.iterator();
+ Iterator<JClassType> bit = bs.iterator();
+ while (ait.hasNext() && bit.hasNext()) {
+ a = ait.next();
+ b = bit.next();
+ if (a.equals(b)) {
+ match = a;
+ } else {
+ break;
+ }
+ }
+ return match;
+ }
+
+ private JType[] methodParamTypes(JMethod meth) {
+ JParameter[] params = meth.getParameters();
+ JType[] types = new JType[params.length];
+ for (int i = 0; i < params.length; ++i) {
+ types[i] = params[i].getType();
+ }
+ return types;
+ }
+
private JType parseImpl(String type) throws NotFoundException,
ParseException, BadTypeArgsException {
if (type.endsWith("[]")) {
diff --git a/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java b/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
index 11d1005..f6f5ab9 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
@@ -37,6 +37,37 @@
}
}
+ public void testBaseClassFullyImplements() {
+ StringBuffer goodCode = new StringBuffer();
+ goodCode.append("import com.google.gwt.core.client.JavaScriptObject;\n");
+ goodCode.append("public class Buggy {\n");
+ goodCode.append(" static interface IntfA {\n");
+ goodCode.append(" void a();\n");
+ goodCode.append(" void b();\n");
+ goodCode.append(" }\n");
+ goodCode.append(" static interface IntfB {\n");
+ goodCode.append(" void c();\n");
+ goodCode.append(" }\n");
+ goodCode.append(" static abstract class BaseA extends JavaScriptObject {\n");
+ goodCode.append(" public final void a() { }\n");
+ goodCode.append(" protected BaseA() { }\n");
+ goodCode.append(" }\n");
+ goodCode.append(" static class BaseB extends BaseA implements IntfA {\n");
+ goodCode.append(" public final void b() { }\n");
+ goodCode.append(" protected BaseB() { }\n");
+ goodCode.append(" }\n");
+ goodCode.append(" static class LeafA extends BaseB {\n");
+ goodCode.append(" protected LeafA() { }\n");
+ goodCode.append(" }\n");
+ goodCode.append(" static class LeafB extends BaseB implements IntfB {\n");
+ goodCode.append(" public final void c() { }\n");
+ goodCode.append(" protected LeafB() { }\n");
+ goodCode.append(" }\n");
+ goodCode.append("}\n");
+
+ shouldGenerateNoError(goodCode);
+ }
+
public void testFinalClass() {
StringBuffer code = new StringBuffer();
code.append("import com.google.gwt.core.client.JavaScriptObject;\n");