Initial add of the AsyncProxy type to support deferred construction and method invocation for "command-style" APIs.
Patch by: bobv
Review by: rjrjr (desk), spoon (initial)
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4347 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/AsyncProxy.gwt.xml b/user/src/com/google/gwt/user/AsyncProxy.gwt.xml
new file mode 100644
index 0000000..5cc92d2
--- /dev/null
+++ b/user/src/com/google/gwt/user/AsyncProxy.gwt.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+ <!--
+ Copyright 2008 Google Inc. Licensed under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with the
+ License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ or agreed to in writing, software distributed under the License is
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+ -->
+
+<module>
+ <generate-with class="com.google.gwt.user.rebind.AsyncProxyGenerator">
+ <when-type-assignable class="com.google.gwt.user.client.AsyncProxy" />
+ </generate-with>
+</module>
\ No newline at end of file
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 3010bef..4750266 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -21,6 +21,7 @@
<inherits name="com.google.gwt.core.Core"/>
<inherits name="com.google.gwt.event.Event"/>
<inherits name="com.google.gwt.animation.Animation"/>
+ <inherits name="com.google.gwt.user.AsyncProxy"/>
<inherits name="com.google.gwt.user.RemoteService"/>
<inherits name="com.google.gwt.user.DocumentRoot" />
<inherits name="com.google.gwt.user.DOM"/>
diff --git a/user/src/com/google/gwt/user/client/AsyncProxy.java b/user/src/com/google/gwt/user/client/AsyncProxy.java
new file mode 100644
index 0000000..e4b7a3c
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/AsyncProxy.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+import com.google.gwt.core.client.GWT;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * The AsyncProxy type is used to provide a reachability barrier between classes
+ * intended to be used with runAsync while maintaining a simple,
+ * deferred-synchronous API. The first invocation of an instance method on the
+ * AsyncProxy will trigger the instantiation of a concrete object via runAsync.
+ * All method invocations on the proxy will be recorded and played back on the
+ * newly-instantiated object after the call to runAsync returns.
+ * <p>
+ * Once method playback has finished, the proxy will continue to forward
+ * invocations onto the instantiated object, although it is recommended that the
+ * developer use {@link ProxyCallback#onComplete} to reassign the field
+ * containing the proxy.
+ * <p>
+ * Example use:
+ *
+ * <pre>
+ * interface IFoo {
+ * void doSomething(int a, int b);
+ * void anotherMethad(Object o);
+ * }
+ * class FooImpl implements IFoo { .... }
+ *
+ * {@literal @}ConcreteType(FooImpl.class)
+ * interface FooProxy extends AsyncProxy<IFoo>, IFoo {}
+ *
+ * class UserOfIFoo {
+ * private IFoo fooOrProxy = GWT.create(FooProxy.class);
+ *
+ * public UserOfIFoo() {
+ * // When the load, initialization, and playback are done, get rid of the proxy.
+ * // This is actually optional, but will improve subsequent function dispatch speed.
+ * ((AsyncProxy<IFoo>) fooOrProxy).setProxyCallback(new ProxyCallback<IFoo>() {
+ * public void onComplete(IFoo instance) {
+ * fooOrProxy = instance;
+ * }
+ * });
+ * }
+ *
+ * void makeTrouble() {
+ * // This first call triggers a runAsync load
+ * fooOrProxy.doSomething(1, 2);
+ *
+ * // and this second will also be replayed before the above onComplete is called
+ * fooOrProxy.anotherMethod("A string");
+ * }
+ * }
+ * </pre>
+ *
+ * @param <T> the type of interface that must be implemented by the derivative
+ * class.
+ */
+@AsyncProxy.DefaultValue()
+public interface AsyncProxy<T> {
+ /*
+ * NB: This type is annotated with the DefaultValue annotation so that we can
+ * always fall back on getting the default values from an instance of the
+ * annotation, as opposed to maintaining the default in two places.
+ */
+
+ /**
+ * If this annotation is applied to an AsyncProxy type, it will be legal for
+ * the parameterized type <code>T</code> to declare non-void methods. These
+ * methods will immediately return a default value of 0, false, or null if the
+ * proxy has not yet instantiated the backing object. The use of this
+ * annotation may cause surprising operation if the consuming code does not
+ * expect this behavior; for example a call to a property setter followed by a
+ * call to the getter could return null,
+ *
+ * @see DefaultValue
+ */
+ @Documented
+ @Target(ElementType.TYPE)
+ public @interface AllowNonVoid {
+ }
+
+ /**
+ * This interface should be applied to the AsyncProxy subtype in order to
+ * specify the Class literal that will be passed to {@link GWT#create(Class)}.
+ */
+ @Documented
+ @Target(ElementType.TYPE)
+ public @interface ConcreteType {
+ Class<?> value();
+ }
+
+ /**
+ * This annotation specifies the return value for primitive methods when the
+ * {@link AllowNonVoid} annotation has been applied to an AsyncProxy.
+ *
+ * The annotation may be applied to the definition of the AsyncProxy type or
+ * individual methods defined on the target interface. If the annotation is
+ * applied to the AsyncProxy type, then it will apply to all methods
+ * implemented by the proxy.
+ *
+ * The correct default value will be chosen from the value methods defined in
+ * this type based on the return type of the method.
+ */
+ @Documented
+ @Target(value = {ElementType.METHOD, ElementType.TYPE})
+ public @interface DefaultValue {
+ /*
+ * Consider adding an additional flag value that would make the generated
+ * type fail an assertion on uninitialized access. Also consider whether or
+ * not allowing a class literal to be specified for reference types would be
+ * useful.
+ */
+
+ boolean booleanValue() default false;
+
+ byte byteValue() default 0;
+
+ char charValue() default 0;
+
+ double doubleValue() default 0;
+
+ float floatValue() default 0;
+
+ int intValue() default 0;
+
+ long longValue() default 0;
+
+ short shortValue() default 0;
+ }
+
+ /**
+ * The callback used by
+ * {@link AsyncProxy#setAsyncProxyCallback(AsyncProxyCallback)}.
+ *
+ * @param <T> the interface parameterization of AsyncProxy.
+ */
+ public abstract static class ProxyCallback<T> {
+ /**
+ * This method will be invoked by the AsyncProxy after method playback is
+ * complete.
+ */
+ public void onComplete(T instance) {
+ }
+
+ /**
+ * Invokes the global uncaught exception handler.
+ */
+ public void onFailure(Throwable t) {
+ GWT.getUncaughtExceptionHandler().onUncaughtException(t);
+ }
+
+ /**
+ * This method will be called with the instance object before method replay
+ * starts. This provides the developer with the opportunity to perform
+ * secondary initialization of the backing object.
+ */
+ public void onInit(T instance) {
+ }
+ }
+
+ /**
+ * Returns the underlying proxied object if it has been instantiated or
+ * <code>null</code>.
+ */
+ T getProxiedInstance();
+
+ /**
+ * Sets a callback that can be used to influence the initialization process.
+ */
+ void setProxyCallback(ProxyCallback<T> callback);
+}
diff --git a/user/src/com/google/gwt/user/client/impl/AsyncProxyBase.java b/user/src/com/google/gwt/user/client/impl/AsyncProxyBase.java
new file mode 100644
index 0000000..d295483
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/impl/AsyncProxyBase.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.AsyncProxy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The base implementation for AsyncProxy instances.
+ *
+ * @param <T> the type to be returned from the GWT.create call
+ */
+public abstract class AsyncProxyBase<T> implements AsyncProxy<T> {
+ /**
+ * Simple parameterized command type.
+ *
+ * @param <T> as above
+ */
+ protected interface ParamCommand<T> {
+ void execute(T instance);
+ }
+
+ /*
+ * These fields are package-protected to allow for easier testing.
+ */
+ private List<ParamCommand<T>> commands = new ArrayList<ParamCommand<T>>();
+ private boolean hasAsyncBeenIssued = false;
+ private boolean hasAsyncFailed = false;
+ private boolean hasAsyncReturned = false;
+ private T instance = null;
+
+ /**
+ * Testing method to enable enqueue0 from firing the runAsync call.
+ */
+ public void enableLoadForTest0() {
+ hasAsyncBeenIssued = false;
+ hasAsyncFailed = false;
+ hasAsyncReturned = false;
+ }
+
+ public final T getProxiedInstance() {
+ return instance;
+ }
+
+ /**
+ * To be implemented by the subtype.
+ */
+ public abstract void setProxyCallback(ProxyCallback<T> callback);
+
+ /**
+ * Testing method to prevent enqueue0 from actually firing the runAsync call.
+ */
+ public void suppressLoadForTest0() {
+ hasAsyncBeenIssued = true;
+ hasAsyncFailed = false;
+ hasAsyncReturned = false;
+ }
+
+ /**
+ * To be implemented by the subtype to give the code-splitter a new starting
+ * point.
+ */
+ protected abstract void doAsync0();
+
+ /**
+ * Called by the generated subtype if the runAsync invocation fails.
+ */
+ protected final void doFailure0(Throwable t) {
+ hasAsyncFailed = true;
+ getCallback0().onFailure(t);
+ }
+
+ /**
+ * Called by the generated subtype to enqueue an action for later replay.
+ */
+ protected final void enqueue0(ParamCommand<T> cmd) {
+ try {
+ if (hasAsyncFailed) {
+ throw new IllegalStateException("runAsync load previously failed");
+
+ } else if (!hasAsyncBeenIssued) {
+ hasAsyncBeenIssued = true;
+ commands.add(cmd);
+ doAsync0();
+
+ } else if (hasAsyncReturned) {
+ assert instance != null;
+ cmd.execute(instance);
+
+ } else {
+ assert instance == null;
+ commands.add(cmd);
+ }
+ } catch (Throwable t) {
+ if (getCallback0() != null) {
+ getCallback0().onFailure(t);
+ } else {
+ GWT.getUncaughtExceptionHandler().onUncaughtException(t);
+ }
+ }
+ }
+
+ /**
+ * The callback is maintained by the generated subtype to take advantage of
+ * type-tightening.
+ */
+ protected abstract ProxyCallback<T> getCallback0();
+
+ /**
+ * Called by the generated subtype with the new instance of the object.
+ */
+ protected final void setInstance0(T instance) {
+ assert commands != null : "No commands";
+ assert hasAsyncBeenIssued : "async request not yet started";
+ hasAsyncReturned = true;
+ this.instance = instance;
+ List<ParamCommand<T>> localCommands = commands;
+ commands = null;
+
+ // Can fail below
+ if (getCallback0() != null) {
+ getCallback0().onInit(instance);
+ }
+ for (ParamCommand<T> cmd : localCommands) {
+ cmd.execute(instance);
+ }
+ if (getCallback0() != null) {
+ getCallback0().onComplete(instance);
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/user/rebind/AsyncProxyGenerator.java b/user/src/com/google/gwt/user/rebind/AsyncProxyGenerator.java
new file mode 100644
index 0000000..4bc1d21
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/AsyncProxyGenerator.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.rebind;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.RunAsyncCallback;
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.client.AsyncProxy;
+import com.google.gwt.user.client.AsyncProxy.AllowNonVoid;
+import com.google.gwt.user.client.AsyncProxy.ConcreteType;
+import com.google.gwt.user.client.AsyncProxy.DefaultValue;
+import com.google.gwt.user.client.impl.AsyncProxyBase;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Iterator;
+
+/**
+ * Generates implementation of AsyncProxy interfaces.
+ */
+public class AsyncProxyGenerator extends Generator {
+
+ public String generate(TreeLogger logger, GeneratorContext generatorContext,
+ String typeName) throws UnableToCompleteException {
+
+ // The TypeOracle knows about all types in the type system
+ TypeOracle typeOracle = generatorContext.getTypeOracle();
+
+ // Get a reference to the type that the generator should implement
+ JClassType asyncProxyType = typeOracle.findType(AsyncProxy.class.getName());
+ JClassType asyncProxyBaseType = typeOracle.findType(AsyncProxyBase.class.getName());
+ JClassType sourceType = typeOracle.findType(typeName);
+
+ // Ensure that the requested type exists
+ if (sourceType == null) {
+ logger.log(TreeLogger.ERROR, "Could not find requested typeName");
+ throw new UnableToCompleteException();
+ } else if (sourceType.isInterface() == null) {
+ // The incoming type wasn't a plain interface, we don't support
+ // abstract base classes
+ logger.log(TreeLogger.ERROR, sourceType.getQualifiedSourceName()
+ + " is not an interface.", null);
+ throw new UnableToCompleteException();
+ }
+
+ JClassType concreteType = getConcreteType(logger, typeOracle, sourceType);
+ JClassType paramType = getParamType(logger, asyncProxyType, sourceType);
+
+ validate(logger, sourceType, concreteType, paramType);
+
+ // com.foo.Bar$Proxy -> com_foo_Bar_ProxyImpl
+ String generatedSimpleSourceName = sourceType.getQualifiedSourceName().replace(
+ '.', '_').replace('$', '_')
+ + "Impl";
+
+ // Begin writing the generated source.
+ ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
+ sourceType.getPackage().getName(), generatedSimpleSourceName);
+ String createdClassName = f.getCreatedClassName();
+
+ // The generated class needs to be able to determine the module base URL
+ f.addImport(GWT.class.getName());
+ f.addImport(RunAsyncCallback.class.getName());
+
+ // Used by the map methods
+ f.setSuperclass(asyncProxyBaseType.getQualifiedSourceName() + "<"
+ + paramType.getQualifiedSourceName() + ">");
+
+ // The whole point of this exercise
+ f.addImplementedInterface(sourceType.getQualifiedSourceName());
+
+ // All source gets written through this Writer
+ PrintWriter out = generatorContext.tryCreate(logger,
+ sourceType.getPackage().getName(), generatedSimpleSourceName);
+
+ // If an implementation already exists, we don't need to do any work
+ if (out != null) {
+ SourceWriter sw = f.createSourceWriter(generatorContext, out);
+
+ // The invocation of runAsync, with a unique class for the split point
+ sw.println("protected void doAsync0() {");
+ sw.indent();
+ sw.println("GWT.runAsync(new RunAsyncCallback() {");
+ sw.indentln("public void onFailure(Throwable caught) {doFailure0(caught);}");
+ sw.indentln("public void onSuccess() {setInstance0(doCreate0());}");
+ sw.println("});");
+ sw.outdent();
+ sw.println("}");
+
+ // Field for callback, plus getter and setter
+ String proxyCallback = "ProxyCallback<"
+ + paramType.getQualifiedSourceName() + ">";
+ sw.println("private " + proxyCallback + " callback;");
+ sw.println("public void setProxyCallback(" + proxyCallback
+ + " callback) {this.callback = callback;}");
+ sw.println("protected " + proxyCallback
+ + " getCallback0() {return callback;}");
+
+ // doCreate0() method to invoke GWT.create()
+ sw.println("private " + paramType.getQualifiedSourceName()
+ + " doCreate0() {");
+ sw.indent();
+ sw.println("return GWT.create(" + concreteType.getQualifiedSourceName()
+ + ".class);");
+ sw.outdent();
+ sw.println("}");
+
+ boolean allowNonVoid = sourceType.getAnnotation(AllowNonVoid.class) != null;
+ for (JMethod method : paramType.getOverridableMethods()) {
+ DefaultValue defaults = getDefaultValue(sourceType, method);
+
+ if (method.getReturnType() != JPrimitiveType.VOID && !allowNonVoid) {
+ logger.log(TreeLogger.ERROR, "The method " + method.getName()
+ + " returns a type other than void, but "
+ + sourceType.getQualifiedSourceName() + " does not define the "
+ + AllowNonVoid.class.getSimpleName() + " annotation.");
+ throw new UnableToCompleteException();
+ }
+
+ // Print the method decl
+ sw.print("public " + method.getReturnType().getQualifiedSourceName()
+ + " " + method.getName() + "(");
+ for (Iterator<JParameter> i = Arrays.asList(method.getParameters()).iterator(); i.hasNext();) {
+ JParameter param = i.next();
+ sw.print("final " + param.getType().getQualifiedSourceName() + " "
+ + param.getName());
+ if (i.hasNext()) {
+ sw.print(", ");
+ }
+ }
+ sw.println(") {");
+ {
+ sw.indent();
+
+ // Try a direct dispatch if we have a proxy instance
+ sw.println("if (getProxiedInstance() != null) {");
+ {
+ sw.indent();
+ if (method.getReturnType() != JPrimitiveType.VOID) {
+ sw.print("return ");
+ }
+ writeInvocation(sw, "getProxiedInstance()", method);
+ sw.outdent();
+ }
+
+ // Otherwise queue up a parameterized command object
+ sw.println("} else {");
+ {
+ sw.indent();
+ sw.println("enqueue0(new ParamCommand<"
+ + paramType.getQualifiedSourceName() + ">() {");
+ {
+ sw.indent();
+ sw.println("public void execute("
+ + paramType.getQualifiedSourceName() + " t) {");
+ {
+ sw.indent();
+ writeInvocation(sw, "t", method);
+ sw.outdent();
+ }
+ sw.println("}");
+ sw.outdent();
+ }
+ sw.println("});");
+
+ if (method.getReturnType() != JPrimitiveType.VOID) {
+ sw.println("return "
+ + getDefaultExpression(defaults, method.getReturnType())
+ + ";");
+ }
+
+ sw.outdent();
+ }
+ sw.println("}");
+ sw.outdent();
+ }
+ sw.println("}");
+ }
+
+ sw.commit(logger);
+ }
+
+ // Return the name of the concrete class
+ return createdClassName;
+ }
+
+ private JClassType getConcreteType(TreeLogger logger, TypeOracle typeOracle,
+ JClassType sourceType) throws UnableToCompleteException {
+ JClassType concreteType;
+ ConcreteType concreteTypeAnnotation = sourceType.getAnnotation(ConcreteType.class);
+ if (concreteTypeAnnotation == null) {
+ logger.log(TreeLogger.ERROR, "AsnycProxy subtypes must specify a "
+ + ConcreteType.class.getSimpleName() + " annotation.");
+ throw new UnableToCompleteException();
+ }
+
+ String concreteTypeName = concreteTypeAnnotation.value().getName().replace(
+ '$', '.');
+ concreteType = typeOracle.findType(concreteTypeName);
+
+ if (concreteType == null) {
+ logger.log(TreeLogger.ERROR,
+ "Unable to find concrete type; is it in the GWT source path?");
+ throw new UnableToCompleteException();
+ }
+ return concreteType;
+ }
+
+ /**
+ * Returns a useful default expression for a type. It is an error to pass
+ * JPrimitiveType.VOID into this method.
+ */
+ private String getDefaultExpression(DefaultValue defaultValue, JType type) {
+ if (!(type instanceof JPrimitiveType)) {
+ return "null";
+ } else if (type == JPrimitiveType.BOOLEAN) {
+ return String.valueOf(defaultValue.booleanValue());
+ } else if (type == JPrimitiveType.BYTE) {
+ return String.valueOf(defaultValue.byteValue());
+ } else if (type == JPrimitiveType.CHAR) {
+ return String.valueOf((int) defaultValue.charValue());
+ } else if (type == JPrimitiveType.DOUBLE) {
+ return String.valueOf(defaultValue.doubleValue());
+ } else if (type == JPrimitiveType.FLOAT) {
+ return String.valueOf(defaultValue.floatValue()) + "F";
+ } else if (type == JPrimitiveType.INT) {
+ return String.valueOf(defaultValue.intValue());
+ } else if (type == JPrimitiveType.LONG) {
+ return String.valueOf(defaultValue.longValue());
+ } else if (type == JPrimitiveType.SHORT) {
+ return String.valueOf(defaultValue.shortValue());
+ } else if (type == JPrimitiveType.VOID) {
+ assert false : "Should not pass VOID into this method";
+ }
+ assert false : "Should never reach here";
+ return null;
+ }
+
+ private DefaultValue getDefaultValue(JClassType sourceType, JMethod method) {
+ DefaultValue toReturn = method.getAnnotation(DefaultValue.class);
+ if (toReturn == null) {
+ toReturn = sourceType.getAnnotation(DefaultValue.class);
+ }
+
+ if (toReturn == null) {
+ JClassType proxyType = sourceType.getOracle().findType(
+ AsyncProxy.class.getName());
+ toReturn = proxyType.getAnnotation(DefaultValue.class);
+ }
+
+ assert toReturn != null : "Could not find any DefaultValue instance";
+ return toReturn;
+ }
+
+ private JClassType getParamType(TreeLogger logger, JClassType asyncProxyType,
+ JClassType sourceType) throws UnableToCompleteException {
+ JClassType paramType = null;
+ for (JClassType intr : sourceType.getImplementedInterfaces()) {
+ JParameterizedType isParameterized = intr.isParameterized();
+ if (isParameterized == null) {
+ continue;
+ }
+ if (isParameterized.getBaseType().equals(asyncProxyType)) {
+ paramType = isParameterized.getTypeArgs()[0];
+ break;
+ }
+ }
+
+ if (paramType == null) {
+ logger.log(TreeLogger.ERROR, "Unable to determine parameterization type.");
+ throw new UnableToCompleteException();
+ }
+ return paramType;
+ }
+
+ private void validate(TreeLogger logger, JClassType sourceType,
+ JClassType concreteType, JClassType paramType)
+ throws UnableToCompleteException {
+ // sourceType may not define any methods
+ if (sourceType.getMethods().length > 0) {
+ logger.log(TreeLogger.ERROR,
+ "AsyncProxy subtypes may not define any additional methods");
+ throw new UnableToCompleteException();
+ }
+
+ // Make sure that sourceType implements paramType to assignments work
+ if (!sourceType.isAssignableTo(paramType)) {
+ logger.log(TreeLogger.ERROR, "Expecting "
+ + sourceType.getQualifiedSourceName() + " to implement "
+ + paramType.getQualifiedSourceName());
+ throw new UnableToCompleteException();
+ }
+
+ // Make sure that concreteType is assignable to paramType
+ if (!concreteType.isAssignableTo(paramType)) {
+ logger.log(TreeLogger.ERROR, "Expecting concrete type"
+ + concreteType.getQualifiedSourceName() + " to implement "
+ + paramType.getQualifiedSourceName());
+ throw new UnableToCompleteException();
+ }
+
+ // Must be able to GWT.create()
+ if (!concreteType.isStatic()) {
+ logger.log(TreeLogger.ERROR, "Expecting concrete type"
+ + concreteType.getQualifiedSourceName() + " to be static.");
+ throw new UnableToCompleteException();
+ }
+ }
+
+ /**
+ * Given a method and a qualifier expression, construct an invocation of the
+ * method using its default parameter names.
+ */
+ private void writeInvocation(SourceWriter sw, String qualifier, JMethod method) {
+ sw.print(qualifier + "." + method.getName() + "(");
+
+ for (Iterator<JParameter> i = Arrays.asList(method.getParameters()).iterator(); i.hasNext();) {
+ JParameter param = i.next();
+ sw.print(param.getName());
+ if (i.hasNext()) {
+ sw.print(", ");
+ }
+ }
+
+ sw.println(");");
+ }
+}
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 1f06f51..2db625e 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -16,6 +16,7 @@
package com.google.gwt.user;
import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.gwt.user.client.AsyncProxyTest;
import com.google.gwt.user.client.CommandExecutorTest;
import com.google.gwt.user.client.CookieTest;
import com.google.gwt.user.client.WindowTest;
@@ -88,6 +89,7 @@
suite.addTestSuite(AbsolutePanelTest.class);
suite.addTestSuite(AnchorTest.class);
+ suite.addTestSuite(AsyncProxyTest.class);
suite.addTestSuite(CaptionPanelTest.class);
suite.addTestSuite(CheckBoxTest.class);
suite.addTestSuite(ClippedImagePrototypeTest.class);
diff --git a/user/test/com/google/gwt/user/client/AsyncProxyTest.java b/user/test/com/google/gwt/user/client/AsyncProxyTest.java
new file mode 100644
index 0000000..94f7474
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/AsyncProxyTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.user.client.AsyncProxy.AllowNonVoid;
+import com.google.gwt.user.client.AsyncProxy.ConcreteType;
+import com.google.gwt.user.client.AsyncProxy.DefaultValue;
+import com.google.gwt.user.client.AsyncProxy.ProxyCallback;
+import com.google.gwt.user.client.impl.AsyncProxyBase;
+
+/**
+ * Tests the AsyncProxy type.
+ */
+public class AsyncProxyTest extends GWTTestCase {
+ interface Test {
+ boolean defaultBool();
+
+ byte defaultByte();
+
+ char defaultChar();
+
+ double defaultDouble();
+
+ float defaultFloat();
+
+ int defaultInt();
+
+ long defaultLong();
+
+ Object defaultObject();
+
+ short defaultShort();
+
+ String defaultString();
+
+ void one();
+
+ void three();
+
+ void two();
+ }
+
+ static class TestImpl implements Test {
+ @AllowNonVoid
+ @ConcreteType(TestImpl.class)
+ @DefaultValue(intValue = 42, longValue = 42)
+ interface Proxy extends AsyncProxy<Test>, Test {
+ }
+
+ private int value = 0;
+
+ public boolean defaultBool() {
+ return true;
+ }
+
+ public byte defaultByte() {
+ return 1;
+ }
+
+ public char defaultChar() {
+ return 1;
+ }
+
+ public double defaultDouble() {
+ return 1;
+ }
+
+ public float defaultFloat() {
+ return 1;
+ }
+
+ public int defaultInt() {
+ return 1;
+ }
+
+ public long defaultLong() {
+ return 1;
+ }
+
+ public Object defaultObject() {
+ return this;
+ }
+
+ public short defaultShort() {
+ return 1;
+ }
+
+ public String defaultString() {
+ return "";
+ }
+
+ public void one() {
+ GWTTestCase.assertEquals(0, value);
+ value = 1;
+ }
+
+ public void three() {
+ GWTTestCase.assertEquals(2, value);
+ testInstance.finishTest();
+ }
+
+ public void two() {
+ GWTTestCase.assertEquals(1, value);
+ value = 2;
+ }
+ }
+
+ private static final int TEST_FINISH_DELAY_MILLIS = 10000;
+
+ private static AsyncProxyTest testInstance;
+
+ public String getModuleName() {
+ return "com.google.gwt.user.User";
+ }
+
+ public void testProxy() {
+ // Disable in web mode for now
+ // TODO Make sure runAsync and JUnit play nicely together
+ if (GWT.isScript()) {
+ return;
+ }
+
+ testInstance = this;
+ final Test proxy = GWT.create(TestImpl.Proxy.class);
+ assertTrue(proxy instanceof AsyncProxy);
+
+ final TestImpl.Proxy asHidden = (TestImpl.Proxy) proxy;
+ assertNull(asHidden.getProxiedInstance());
+
+ asHidden.setProxyCallback(new ProxyCallback<Test>() {
+
+ @Override
+ public void onComplete(Test instance) {
+ // Check that the proxy is returning the correct values now
+ assertEquals(true, proxy.defaultBool());
+ assertEquals(1, proxy.defaultByte());
+ assertEquals(1, proxy.defaultChar());
+ assertEquals(1D, proxy.defaultDouble());
+ assertEquals(1F, proxy.defaultFloat());
+ assertEquals(1, proxy.defaultInt());
+ assertEquals(1L, proxy.defaultLong());
+ assertEquals(1, proxy.defaultShort());
+ assertEquals("", proxy.defaultString());
+ assertSame(instance, proxy.defaultObject());
+
+ instance.three();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ t.printStackTrace();
+ fail(t.getMessage());
+ }
+
+ @Override
+ public void onInit(Test instance) {
+ assertTrue(instance instanceof TestImpl);
+ assertSame(asHidden.getProxiedInstance(), instance);
+ instance.one();
+ }
+ });
+
+ // Cast to AsyncProxyBase to fiddle with internals
+ AsyncProxyBase<?> asBase = (AsyncProxyBase<?>) proxy;
+ asBase.suppressLoadForTest0();
+ assertEquals(false, proxy.defaultBool());
+ assertEquals(0, proxy.defaultByte());
+ assertEquals(0, proxy.defaultChar());
+ assertEquals(0D, proxy.defaultDouble());
+ assertEquals(0F, proxy.defaultFloat());
+ assertEquals(42, proxy.defaultInt());
+ assertEquals(42L, proxy.defaultLong());
+ assertEquals(0, proxy.defaultShort());
+ assertNull(proxy.defaultString());
+ assertNull(proxy.defaultObject());
+ asBase.enableLoadForTest0();
+
+ delayTestFinish(TEST_FINISH_DELAY_MILLIS);
+ proxy.two();
+ }
+}