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&lt;IFoo&gt;, 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&lt;IFoo&gt;) fooOrProxy).setProxyCallback(new ProxyCallback&lt;IFoo&gt;() {
+ *       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();
+  }
+}