This patch addresses issue #17 by getting RebindOracle out of the business of worrying about instantiability and only trying to instantiate the entry point class if onModuleLoad() is non-static.  In addition to allowing uninstantiable classes to be used as entry points, it also clears up a discrepancy between hosted mode and web mode; in web mode classes with static entry points are not auto-instantiated, but in hosted mode they were being instantiated before the static onModuleLoad was called.  Also resolves an issue where a superclass implementing onModuleLoad would not register if a subclass was the entry point.

- Removed a stray System.err.println() that really isn't needed.
- Doc tweak to EntryPoint.onModuleLoad()

Review by: bruce


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@463 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jdt/RebindOracle.java b/dev/core/src/com/google/gwt/dev/jdt/RebindOracle.java
index 4d79f3f..6681196 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/RebindOracle.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/RebindOracle.java
@@ -24,9 +24,12 @@
 public interface RebindOracle {
 
   /**
-   * Determines which type should be substituted for the requested type.
+   * Determines which type should be substituted for the requested type. The
+   * caller must ensure that the result type is instantiable.
    * 
-   * @return the substitute type name, which may be the requested type itself
+   * @return the substitute type name, which may be the requested type itself;
+   *         this method must not return <code>null</code> if sourceTypeName
+   *         is not <code>null</code>
    */
   String rebind(TreeLogger logger, String sourceTypeName)
       throws UnableToCompleteException;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index f78c8db..eabc993 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -79,7 +79,7 @@
 public class JavaToJavaScriptCompiler {
 
   private static void findEntryPoints(TreeLogger logger,
-      String[] mainClassNames, JProgram program)
+      RebindOracle rebindOracle, String[] mainClassNames, JProgram program)
       throws UnableToCompleteException {
     JMethod bootStrapMethod = program.createMethod(null, "init".toCharArray(),
         null, program.getTypeVoid(), false, true, true, false, false);
@@ -96,73 +96,77 @@
         throw new UnableToCompleteException();
       }
 
-      if (!(referenceType instanceof JClassType)) {
-        logger.log(TreeLogger.ERROR, "Module entry point class '"
-            + mainClassName + "' must be a class", null);
-        throw new UnableToCompleteException();
-      }
-
-      JClassType mainClass = (JClassType) referenceType;
-
-      JMethod mainMethod = null;
-      outer : for (JClassType it = mainClass; it != null; it = it.extnds) {
-        for (int j = 0; j < it.methods.size(); ++j) {
-          JMethod method = (JMethod) it.methods.get(j);
-          if (method.getName().equals("onModuleLoad")) {
-            mainMethod = method;
-            break outer;
-          }
-        }
-      }
-
-      if (mainMethod == null) {
-        logger.log(TreeLogger.ERROR,
-            "Could not find entry method 'onModuleLoad' method in entry-point class "
-                + mainClassName, null);
-        throw new UnableToCompleteException();
-      }
-
-      if (mainMethod.params.size() > 0) {
-        logger.log(TreeLogger.ERROR,
-            "Entry method 'onModuleLoad' in entry-point class " + mainClassName
-                + "must take zero arguments", null);
-        throw new UnableToCompleteException();
-      }
-
-      if (mainMethod.isAbstract()) {
-        logger.log(TreeLogger.ERROR,
-            "Entry method 'onModuleLoad' in entry-point class " + mainClassName
-                + "must not be abstract", null);
-        throw new UnableToCompleteException();
-      }
-
       JExpression qualifier = null;
-      if (!mainMethod.isStatic()) {
-        // Find the appropriate (noArg) constructor
-        JMethod noArgCtor = null;
-        for (int j = 0; j < mainClass.methods.size(); ++j) {
-          JMethod ctor = (JMethod) mainClass.methods.get(j);
-          if (ctor.getName().equals(mainClass.getShortName())) {
-            if (ctor.params.size() == 0) {
-              noArgCtor = ctor;
-            }
-          }
-        }
-        if (noArgCtor == null) {
-          logger.log(
-              TreeLogger.ERROR,
-              "No default (zero argument) constructor could be found in entry-point class "
-                  + mainClassName
-                  + " to qualify a call to non-static entry method 'onModuleLoad'",
-              null);
+      JMethod mainMethod = findMainMethod(referenceType);
+      if (mainMethod == null || !mainMethod.isStatic()) {
+        // Couldn't find a static main method; must rebind the class
+        String originalClassName = mainClassName;
+        mainClassName = rebindOracle.rebind(logger, originalClassName);
+        referenceType = program.getFromTypeMap(mainClassName);
+        if (referenceType == null) {
+          logger.log(TreeLogger.ERROR,
+              "Could not find module entry point class '" + mainClassName
+                  + "' after rebinding from '" + originalClassName + "'", null);
           throw new UnableToCompleteException();
         }
 
-        // Construct a new instance of the class to qualify the non-static call
-        JNewInstance newInstance = new JNewInstance(program, null, mainClass);
-        qualifier = new JMethodCall(program, null, newInstance, noArgCtor);
+        if (!(referenceType instanceof JClassType)) {
+          logger.log(TreeLogger.ERROR, "Module entry point class '"
+              + mainClassName + "' must be a class", null);
+          throw new UnableToCompleteException();
+        }
+
+        JClassType mainClass = (JClassType) referenceType;
+        if (mainClass.isAbstract()) {
+          logger.log(TreeLogger.ERROR, "Module entry point class '"
+              + mainClassName + "' must not be abstract", null);
+          throw new UnableToCompleteException();
+        }
+
+        mainMethod = findMainMethodRecurse(referenceType);
+        if (mainMethod == null) {
+          logger.log(TreeLogger.ERROR,
+              "Could not find entry method 'onModuleLoad()' method in entry point class '"
+                  + mainClassName + "'", null);
+          throw new UnableToCompleteException();
+        }
+
+        if (mainMethod.isAbstract()) {
+          logger.log(TreeLogger.ERROR,
+              "Entry method 'onModuleLoad' in entry point class '"
+                  + mainClassName + "' must not be abstract", null);
+          throw new UnableToCompleteException();
+        }
+
+        if (!mainMethod.isStatic()) {
+          // Find the appropriate (noArg) constructor
+          JMethod noArgCtor = null;
+          for (int j = 0; j < mainClass.methods.size(); ++j) {
+            JMethod ctor = (JMethod) mainClass.methods.get(j);
+            if (ctor.getName().equals(mainClass.getShortName())) {
+              if (ctor.params.size() == 0) {
+                noArgCtor = ctor;
+              }
+            }
+          }
+          if (noArgCtor == null) {
+            logger.log(
+                TreeLogger.ERROR,
+                "No default (zero argument) constructor could be found in entry point class '"
+                    + mainClassName
+                    + "' to qualify a call to non-static entry method 'onModuleLoad'",
+                null);
+            throw new UnableToCompleteException();
+          }
+
+          // Construct a new instance of the class to qualify the non-static
+          // call
+          JNewInstance newInstance = new JNewInstance(program, null, mainClass);
+          qualifier = new JMethodCall(program, null, newInstance, noArgCtor);
+        }
       }
 
+      // qualifier will be null if onModuleLoad is static
       JMethodCall onModuleLoadCall = new JMethodCall(program, null, qualifier,
           mainMethod);
       bootStrapMethod.body.statements.add(new JExpressionStatement(program,
@@ -171,16 +175,33 @@
     program.addEntryMethod(bootStrapMethod);
   }
 
+  private static JMethod findMainMethod(JReferenceType referenceType) {
+    for (int j = 0; j < referenceType.methods.size(); ++j) {
+      JMethod method = (JMethod) referenceType.methods.get(j);
+      if (method.getName().equals("onModuleLoad")) {
+        if (method.params.size() == 0) {
+          return method;
+        }
+      }
+    }
+    return null;
+  }
+
+  private static JMethod findMainMethodRecurse(JReferenceType referenceType) {
+    for (JReferenceType it = referenceType; it != null; it = it.extnds) {
+      JMethod result = findMainMethod(it);
+      if (result != null) {
+        return result;
+      }
+    }
+    return null;
+  }
+
   private final String[] declEntryPoints;
-
   private final CompilationUnitDeclaration[] goldenCuds;
-
   private long lastModified;
-
   private final boolean obfuscate;
-
   private final boolean prettyNames;
-
   private final Set/* <IProblem> */problemSet = new HashSet/* <IProblem> */();
 
   public JavaToJavaScriptCompiler(final TreeLogger logger,
@@ -321,11 +342,7 @@
 
       // Rebind each entry point.
       //
-      String[] actualEntryPoints = new String[declEntryPoints.length];
-      for (int i = 0; i < declEntryPoints.length; i++) {
-        actualEntryPoints[i] = rebindOracle.rebind(logger, declEntryPoints[i]);
-      }
-      findEntryPoints(logger, actualEntryPoints, jprogram);
+      findEntryPoints(logger, rebindOracle, declEntryPoints, jprogram);
 
       // (4) Optimize the normalized Java AST
       boolean didChange;
diff --git a/dev/core/src/com/google/gwt/dev/shell/JavaScriptHost.java b/dev/core/src/com/google/gwt/dev/shell/JavaScriptHost.java
index 4d67523..2b703a7 100644
--- a/dev/core/src/com/google/gwt/dev/shell/JavaScriptHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/JavaScriptHost.java
@@ -158,7 +158,6 @@
     } catch (Throwable e) {
       String msg = "Deferred binding failed for '" + className
         + "' (did you forget to inherit a required module?)";
-      System.err.println(msg);
       throw new RuntimeException(msg, e);
     }
   }
diff --git a/dev/core/src/com/google/gwt/dev/shell/Messages.java b/dev/core/src/com/google/gwt/dev/shell/Messages.java
index 85e595b..004da0d 100644
--- a/dev/core/src/com/google/gwt/dev/shell/Messages.java
+++ b/dev/core/src/com/google/gwt/dev/shell/Messages.java
@@ -26,10 +26,6 @@
  */
 public final class Messages {
 
-  public static final Message1String REBIND_RESULT_TYPE_IS_NOT_INSTANTIABLE = new Message1String(
-      TreeLogger.WARN,
-      "Deferred binding result type '$0' is not instantiable; expect subsequent failure");
-
   public static final Message1ToString TRACE_CHECKING_RULE = new Message1ToString(
       TreeLogger.DEBUG, "Checking rule $0");
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java b/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
index e0c2835..75dc42a 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ModuleSpace.java
@@ -21,6 +21,7 @@
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 
 /**
  * The interface to the low-level browser, this class serves as a 'domain' for a
@@ -29,9 +30,8 @@
  */
 public abstract class ModuleSpace implements ShellJavaScriptHost {
 
-  protected static ThreadLocal sThrownJavaExceptionObject = new ThreadLocal();
-
   protected static ThreadLocal sCaughtJavaExceptionObject = new ThreadLocal();
+  protected static ThreadLocal sThrownJavaExceptionObject = new ThreadLocal();
 
   /**
    * Logger is thread local.
@@ -132,9 +132,23 @@
       if (entryPoints.length > 0) {
         for (int i = 0; i < entryPoints.length; i++) {
           entryPointTypeName = entryPoints[i];
-          Object module = rebindAndCreate(entryPointTypeName);
-          Method onModuleLoad = module.getClass().getMethod("onModuleLoad",
-              null);
+          Class clazz = loadClassFromSourceName(entryPointTypeName);
+          Method onModuleLoad = null;
+          try {
+            onModuleLoad = clazz.getMethod("onModuleLoad", null);
+            if (!Modifier.isStatic(onModuleLoad.getModifiers())) {
+              // it's non-static, so we need to rebind the class
+              onModuleLoad = null;
+            }
+          } catch (NoSuchMethodException e) {
+            // okay, try rebinding it; maybe the rebind result will have one
+          }
+          Object module = null;
+          if (onModuleLoad == null) {
+            module = rebindAndCreate(entryPointTypeName);
+            onModuleLoad = module.getClass().getMethod("onModuleLoad", null);
+          }
+          onModuleLoad.setAccessible(true);
           onModuleLoad.invoke(module, null);
         }
       } else {
@@ -164,16 +178,24 @@
   public Object rebindAndCreate(String requestedClassName)
       throws UnableToCompleteException {
     Throwable caught = null;
+    String msg = null;
+    String resultName = null;
     try {
       // Rebind operates on source-level names.
       //
       String sourceName = requestedClassName.replace('$', '.');
-      String resultName = rebind(sourceName);
+      resultName = rebind(sourceName);
       Class resolvedClass = loadClassFromSourceName(resultName);
-      Constructor ctor = resolvedClass.getDeclaredConstructor(null);
-      ctor.setAccessible(true);
-      return ctor.newInstance(null);
+      if (Modifier.isAbstract(resolvedClass.getModifiers())) {
+        msg = "Deferred binding result type '" + resultName
+            + "' should not be abstract";
+      } else {
+        Constructor ctor = resolvedClass.getDeclaredConstructor(null);
+        ctor.setAccessible(true);
+        return ctor.newInstance(null);
+      }
     } catch (ClassNotFoundException e) {
+      msg = "Could not load deferred binding result type '" + resultName + "'";
       caught = e;
     } catch (InstantiationException e) {
       caught = e;
@@ -182,6 +204,8 @@
     } catch (ExceptionInInitializerError e) {
       caught = e.getException();
     } catch (NoSuchMethodException e) {
+      msg = "Rebind result '" + resultName
+          + "' has no default (zero argument) constructors.";
       caught = e;
     } catch (InvocationTargetException e) {
       caught = e.getTargetException();
@@ -190,10 +214,11 @@
     // Always log here because sometimes this method gets called from static
     // initializers and other unusual places, which can obscure the problem.
     //
-    String msg = "Failed to create an instance of '" + requestedClassName
-        + "' via deferred binding ";
+    if (msg == null) {
+      msg = "Failed to create an instance of '" + requestedClassName
+          + "' via deferred binding ";
+    }
     host.getLogger().log(TreeLogger.ERROR, msg, caught);
-
     throw new UnableToCompleteException();
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
index e00406e..99cf68b 100644
--- a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
@@ -171,25 +171,10 @@
 
     Messages.TRACE_TOPLEVEL_REBIND_RESULT.log(logger, result, null);
 
-    if (!isKnownToBeUninstantiable(result)) {
-      return result;
-    } else {
-      Messages.REBIND_RESULT_TYPE_IS_NOT_INSTANTIABLE.log(logger, result, null);
-      throw new UnableToCompleteException();
-    }
+    return result;
   }
 
   protected void onGeneratedTypes(String result, JClassType[] genTypes) {
   }
 
-  private boolean isKnownToBeUninstantiable(String name) {
-    JClassType type = typeOracle.findType(name);
-    if (type != null) {
-      if (!type.isDefaultInstantiable()) {
-        return true;
-      }
-    }
-    return false;
-  }
-
 }
diff --git a/user/src/com/google/gwt/core/client/EntryPoint.java b/user/src/com/google/gwt/core/client/EntryPoint.java
index 2e46143..7edacf0 100644
--- a/user/src/com/google/gwt/core/client/EntryPoint.java
+++ b/user/src/com/google/gwt/core/client/EntryPoint.java
@@ -23,7 +23,7 @@
 
   /**
    * The entry point method, called automatically by loading a module that
-   * declares an implementing class as an entry-point.
+   * declares an implementing class as an entry point.
    */
   void onModuleLoad();
 }