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");