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