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