Cherrypick user/{src,super,test}/com/google/web/bindery/...
Update GWT references to point to the new packages
Deprecate the old packages
git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.3@10070 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build.xml b/build.xml
index e17454d..81a956a 100755
--- a/build.xml
+++ b/build.xml
@@ -37,6 +37,7 @@
description="[action] Minimal one-platform devel build, without distro packaging">
<call-subproject subproject="dev" subtarget="build" />
<call-subproject subproject="user" subtarget="build" />
+ <call-subproject subproject="requestfactory" subtarget="build" />
<call-subproject subproject="servlet" subtarget="build" />
<call-subproject subproject="jni" subtarget="build" />
</target>
@@ -70,6 +71,11 @@
<gwt.ant dir="tools" />
</target>
+ <target name="requestfactory" description="[subdir] Builds (or runs ${target} if set) only the requestfactory jars">
+ <call-subproject subproject="user" subtarget="build" />
+ <gwt.ant dir="requestfactory" />
+ </target>
+
<target name="servlet" description="[subdir] Builds (or runs ${target} if set) only the servlet jar">
<call-subproject subproject="user" subtarget="build" />
<gwt.ant dir="servlet" />
@@ -94,18 +100,20 @@
</target>
<target name="build" description="[action] Builds GWT, including samples, but without distro packaging">
- <call-subproject subproject="dev" subtarget="build"/>
- <call-subproject subproject="user" subtarget="build"/>
- <call-subproject subproject="servlet" subtarget="build"/>
- <call-subproject subproject="tools" subtarget="build"/>
- <call-subproject subproject="jni" subtarget="build"/>
- <call-subproject subproject="samples" subtarget="build"/>
+ <call-subproject subproject="dev" subtarget="build"/>
+ <call-subproject subproject="user" subtarget="build"/>
+ <call-subproject subproject="requestfactory" subtarget="build"/>
+ <call-subproject subproject="servlet" subtarget="build"/>
+ <call-subproject subproject="tools" subtarget="build"/>
+ <call-subproject subproject="jni" subtarget="build"/>
+ <call-subproject subproject="samples" subtarget="build"/>
</target>
<target name="checkstyle" description="[action] Does static analysis of GWT source">
<call-subproject subproject="buildtools" subtarget="checkstyle" />
<call-subproject subproject="dev" subtarget="checkstyle" />
<call-subproject subproject="user" subtarget="checkstyle" />
+ <call-subproject subproject="requestfactory" subtarget="checkstyle" />
<call-subproject subproject="servlet" subtarget="checkstyle" />
<call-subproject subproject="tools" subtarget="checkstyle" />
<call-subproject subproject="samples" subtarget="checkstyle" />
@@ -116,10 +124,18 @@
<call-subproject subproject="buildtools" subtarget="test" />
<call-subproject subproject="dev" subtarget="test" />
<call-subproject subproject="user" subtarget="test" />
+ <call-subproject subproject="requestfactory" subtarget="test" />
<call-subproject subproject="servlet" subtarget="test" />
<call-subproject subproject="tools" subtarget="test" />
</target>
+ <target name="testrf" depends="dist-dev"
+ description="[action] Runs the GWT RequestFactory tests">
+ <call-subproject subproject="dev" subtarget="compile.tests" />
+ <call-subproject subproject="user" subtarget="compile.tests" />
+ <call-subproject subproject="requestfactory" subtarget="test" />
+ </target>
+
<target name="benchmark" depends="dist-dev"
description="[action] Runs all the GWT benchmarks">
<call-subproject subproject="user" subtarget="benchmark" />
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index d5ed6ed..ea04297 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -534,7 +534,7 @@
*/
JClassType implementingType = typeOracle.getSingleJsoImpl(intfMethod.getEnclosingType());
- if (implementingType == null) {
+ if (implementingType == null || implementingType.isAnnotationPresent(GwtScriptOnly.class)) {
/*
* This means that there is no concrete implementation of the
* interface by a JSO. Any implementation that might be created by a
diff --git a/requestfactory/build.xml b/requestfactory/build.xml
new file mode 100755
index 0000000..5139e8c
--- /dev/null
+++ b/requestfactory/build.xml
@@ -0,0 +1,103 @@
+<project name="requestfactory" default="build" basedir=".">
+ <property name="gwt.root" location=".." />
+ <property name="project.tail" value="requestfactory" />
+ <import file="${gwt.root}/common.ant.xml" />
+
+ <!-- Remove all output files -->
+ <target name="clean" description="Cleans this project's output files">
+ <delete file="${gwt.build.lib}/requestfactory-client.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-client-src.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-client+src.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-server.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-server-src.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-server+src.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-test+src.jar" />
+ </target>
+
+ <!-- Build a jar file containing a subset of requestfactory -->
+ <macrodef name="requestfactory-jar">
+ <!--
+ "target" should be one of {client,server,all}[(+|-)src] or test+src.
+ -src includes .java files only, +src includes .java and .class files
+ -->
+ <attribute name="target" default="client"/>
+ <sequential>
+ <java failonerror="true" fork="true"
+ classname="com.google.web.bindery.requestfactory.server.RequestFactoryJarExtractor">
+ <classpath>
+ <fileset dir="${gwt.build.lib}" includes="gwt-user.jar,gwt-dev.jar" />
+ <fileset dir="${gwt.tools.redist}" includes="json/r2_20080312/json-1.5.jar" />
+ <pathelement location="${gwt.tools.lib}/junit/junit-4.8.2.jar" />
+ <pathelement path="${gwt.build.out}/user/bin" />
+ <pathelement path="${gwt.build.out}/dev/bin-test" />
+ <pathelement path="${gwt.build.out}/user/bin-test" />
+ </classpath>
+ <arg value="@{target}"/>
+ <arg file="${gwt.build.lib}/requestfactory-@{target}.jar"/>
+ </java>
+ </sequential>
+ </macrodef>
+
+ <!-- Targets for individual jars -->
+
+ <target name="requestfactory-client" description="Build RequestFactory client jar">
+ <requestfactory-jar target="client"/>
+ </target>
+
+ <target name="requestfactory-client-src" description="Build RequestFactory client source jar">
+ <requestfactory-jar target="client-src"/>
+ </target>
+
+ <target name="requestfactory-client+src" description="Build RequestFactory client source/class jar">
+ <requestfactory-jar target="client+src"/>
+ </target>
+
+ <target name="requestfactory-server" description="Build RequestFactory server jar">
+ <requestfactory-jar target="server"/>
+ </target>
+
+ <target name="requestfactory-server-src" description="Build RequestFactory server source jar">
+ <requestfactory-jar target="server-src"/>
+ </target>
+
+ <target name="requestfactory-server+src" description="Build RequestFactory server source/class jar">
+ <requestfactory-jar target="server+src"/>
+ </target>
+
+ <!-- This target requires classes from ../build/{dev,user}/bin-test -->
+ <target name="requestfactory-test+src" description="Build RequestFactory test source/class jar">
+ <requestfactory-jar target="test+src" />
+ </target>
+
+ <!-- Build all client jars -->
+ <target name="clientjars" depends="requestfactory-client,requestfactory-client-src,requestfactory-client+src" description="Build requestfactory client jars" />
+
+ <!-- Build all server jars -->
+ <target name="serverjars" depends="requestfactory-server,requestfactory-server-src,requestfactory-server+src" description="Build requestfactory server jars" />
+
+ <!-- Default target, build client and server jars.
+ Assumes the 'user' target has been built in the trunk directory
+ -->
+ <target name="build" depends="clientjars,serverjars" />
+
+ <!-- Run RequestFactoryJreSuite from the requestfactory-test+src jar.
+ Assumes test classes have been built in the trunk directory
+ -->
+ <target name="test" depends="requestfactory-test+src" description="Run RequestFactoryJreSuite">
+ <java failonerror="true" fork="true"
+ classname="com.google.web.bindery.requestfactory.vm.RequestFactoryJreSuite">
+ <jvmarg value="-Xss8m" />
+ <classpath>
+ <fileset dir="${gwt.tools.lib}" includes="tomcat/servlet-api-2.5.jar" />
+ <fileset dir="${gwt.tools.lib}" includes="apache/log4j/log4j-1.2.16.jar" />
+ <fileset dir="${gwt.tools.lib}" includes="slf4j/slf4j-api/slf4j-api-1.6.1.jar" />
+ <fileset dir="${gwt.tools.lib}" includes="slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar" />
+ <fileset dir="${gwt.tools.lib}" includes="hibernate/validator/hibernate-validator-4.1.0.Final.jar" />
+ <fileset dir="${gwt.tools.lib}" includes="javax/validation/validation-api-1.0.0.GA.jar" />
+ <fileset dir="${gwt.tools.lib}" includes="javax/xml/bind/jaxb-api-2.1.jar" />
+ <fileset dir="${gwt.build.lib}" includes="requestfactory-test+src.jar" />
+ </classpath>
+ </java>
+ </target>
+
+</project>
diff --git a/tools/api-checker/config/gwt22_23userApi.conf b/tools/api-checker/config/gwt22_23userApi.conf
index acbb7e5..c9d3227 100644
--- a/tools/api-checker/config/gwt22_23userApi.conf
+++ b/tools/api-checker/config/gwt22_23userApi.conf
@@ -14,9 +14,10 @@
:**/rebind/**\
:**/server/**\
:**/tools/**\
+:**/vm/**\
:com/google/gwt/regexp/shared/**\
-:com/google/gwt/autobean/**/impl/**\
-:com/google/gwt/autobean/shared/ValueCodexHelper.java\
+:com/google/gwt/autobean/**\
+:com/google/web/bindery/autobean/**\
:com/google/gwt/core/client/impl/WeakMapping.java\
:com/google/gwt/core/ext/**\
:com/google/gwt/dev/*.java\
@@ -42,11 +43,8 @@
:com/google/gwt/resources/css/**\
:com/google/gwt/resources/ext/**\
:com/google/gwt/resources/rg/**\
-:com/google/gwt/requestfactory/client/impl/FindRequest.java\
-:com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java\
-:com/google/gwt/requestfactory/shared/UserInformationProxy.java\
-:com/google/gwt/requestfactory/shared/UserInformationRequest.java\
-:com/google/gwt/requestfactory/ui/client/LoginWidget.java\
+:com/google/gwt/requestfactory/**\
+:com/google/web/bindery/requestfactory/**\
:com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
:com/google/gwt/rpc/client/impl/EscapeUtil.java\
:com/google/gwt/rpc/client/impl/RpcCallbackAdapter.java\
@@ -80,9 +78,12 @@
:**/rebind/**\
:**/server/**\
:**/tools/**\
-:user/src/com/google/gwt/regexp/shared/**\
+:**/vm/**\
:user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java\
-:user/src/com/google/gwt/autobean/**/impl/**\
+:user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java\
+:user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java\
+:user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java\
+:user/src/com/google/gwt/regexp/shared/**\
:user/src/com/google/gwt/core/client/impl/WeakMapping.java\
:user/src/com/google/gwt/junit/*.java\
:user/src/com/google/gwt/junit/client/GWTTestCase.java\
@@ -92,6 +93,7 @@
:user/src/com/google/gwt/resources/ext/**\
:user/src/com/google/gwt/resources/rg/**\
:user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java\
+:user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java\
:user/src/com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
:user/src/com/google/gwt/rpc/client/impl/EscapeUtil.java\
:user/src/com/google/gwt/rpc/client/impl/RpcCallbackAdapter.java\
@@ -109,11 +111,59 @@
##############################################
#excluded packages colon separated list
excludedPackages com.google.gwt.editor.client.impl\
-:com.google.gwt.requestfactory.client.impl.messages\
-:com.google.gwt.requestfactory.client.impl\
-:com.google.gwt.requestfactory.shared.impl\
+:com/google/gwt/requestfactory\
+:com/google/gwt/requestfactory/client\
+:com/google/gwt/requestfactory/client/testing\
+:com/google/gwt/requestfactory/client/impl\
+:com/google/gwt/requestfactory/rebind\
+:com/google/gwt/requestfactory/rebind/model\
+:com/google/gwt/requestfactory/server\
+:com/google/gwt/requestfactory/server/testing\
+:com/google/gwt/requestfactory/server/impl\
+:com/google/gwt/requestfactory/shared\
+:com/google/gwt/requestfactory/shared/messages\
+:com/google/gwt/requestfactory/shared/impl\
+:com/google/gwt/requestfactory/shared/impl/posers\
+:com/google/gwt/requestfactory/ui\
+:com/google/gwt/requestfactory/ui/client\
:com.google.gwt.junit.client.impl\
:com.google.gwt.benchmarks.client.impl\
+:com/google/web/bindery/requestfactory\
+:com/google/web/bindery/requestfactory/gwt\
+:com/google/web/bindery/requestfactory/gwt/client\
+:com/google/web/bindery/requestfactory/gwt/client/impl\
+:com/google/web/bindery/requestfactory/gwt/client/testing\
+:com/google/web/bindery/requestfactory/gwt/rebind\
+:com/google/web/bindery/requestfactory/gwt/rebind/model\
+:com/google/web/bindery/requestfactory/gwt/ui\
+:com/google/web/bindery/requestfactory/gwt/ui/client\
+:com/google/web/bindery/requestfactory/server\
+:com/google/web/bindery/requestfactory/server/impl\
+:com/google/web/bindery/requestfactory/server/testing\
+:com/google/web/bindery/requestfactory/shared\
+:com/google/web/bindery/requestfactory/shared/impl\
+:com/google/web/bindery/requestfactory/shared/impl/posers\
+:com/google/web/bindery/requestfactory/shared/messages\
+:com/google/web/bindery/requestfactory/vm\
+:com/google/gwt/autobean\
+:com/google/gwt/autobean/client\
+:com/google/gwt/autobean/client/impl\
+:com/google/gwt/autobean/rebind\
+:com/google/gwt/autobean/rebind/model\
+:com/google/gwt/autobean/server\
+:com/google/gwt/autobean/server/impl\
+:com/google/gwt/autobean/shared\
+:com/google/gwt/autobean/shared/impl\
+:com/google/web/bindery/autobean\
+:com/google/web/bindery/autobean/gwt\
+:com/google/web/bindery/autobean/gwt/client\
+:com/google/web/bindery/autobean/gwt/client/impl\
+:com/google/web/bindery/autobean/gwt/rebind\
+:com/google/web/bindery/autobean/gwt/rebind/model\
+:com/google/web/bindery/autobean/shared\
+:com/google/web/bindery/autobean/shared/impl\
+:com/google/web/bindery/autobean/vm\
+:com/google/web/bindery/autobean/vm/impl\
##############################################
#Api whitelist
@@ -123,6 +173,24 @@
# Overloaded SimplePanel constructor to accept a Widget.
com.google.gwt.user.client.ui.SimplePanel::SimplePanel(Lcom/google/gwt/dom/client/Element;) OVERLOADED_METHOD_CALL
+# Overloads for legacy compatibility after introduction of com.google.web.bindery
+#
+
+com.google.gwt.event.shared.EventBus::addHandler(Lcom/google/gwt/event/shared/GwtEvent$Type;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+com.google.gwt.event.shared.EventBus::addHandlerToSource(Lcom/google/gwt/event/shared/GwtEvent$Type;Ljava/lang/Object;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+
+com.google.gwt.event.shared.ResettableEventBus::addHandler(Lcom/google/gwt/event/shared/GwtEvent$Type;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+com.google.gwt.event.shared.ResettableEventBus::addHandlerToSource(Lcom/google/gwt/event/shared/GwtEvent$Type;Ljava/lang/Object;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+
+com.google.gwt.event.shared.SimpleEventBus::addHandler(Lcom/google/gwt/event/shared/GwtEvent$Type;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+com.google.gwt.event.shared.SimpleEventBus::addHandlerToSource(Lcom/google/gwt/event/shared/GwtEvent$Type;Ljava/lang/Object;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+
+com.google.gwt.event.shared.testing.CountingEventBus::addHandler(Lcom/google/gwt/event/shared/GwtEvent$Type;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+com.google.gwt.event.shared.testing.CountingEventBus::addHandlerToSource(Lcom/google/gwt/event/shared/GwtEvent$Type;Ljava/lang/Object;Lcom/google/gwt/event/shared/EventHandler;) OVERLOADED_METHOD_CALL
+
+#
+# end com.google.web.bindery changes
+
# Renamed CellBasedWidgetImplSafari to CellBasedWidgetImplStandardBase.
com.google.gwt.user.cellview.client.CellBasedWidgetImplSafari MISSING
diff --git a/user/src/com/google/gwt/activity/shared/AbstractActivity.java b/user/src/com/google/gwt/activity/shared/AbstractActivity.java
index 59e596c..51f3abd 100644
--- a/user/src/com/google/gwt/activity/shared/AbstractActivity.java
+++ b/user/src/com/google/gwt/activity/shared/AbstractActivity.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -16,8 +16,8 @@
package com.google.gwt.activity.shared;
/**
- * Simple Activity implementation that is always willing to stop,
- * and does nothing onStop and onCancel.
+ * Simple Activity implementation that is always willing to stop, and does
+ * nothing onStop and onCancel.
*/
public abstract class AbstractActivity implements Activity {
diff --git a/user/src/com/google/gwt/activity/shared/Activity.java b/user/src/com/google/gwt/activity/shared/Activity.java
index f141171..3c473dc 100644
--- a/user/src/com/google/gwt/activity/shared/Activity.java
+++ b/user/src/com/google/gwt/activity/shared/Activity.java
@@ -49,7 +49,7 @@
* Called when the Activity should ready its widget for the user. When the
* widget is ready (typically after an RPC response has been received),
* receiver should present it by calling
- * {@link AcceptsOneWidget#setWidget(IsWidget)} on the given panel.
+ * {@link AcceptsOneWidget#setWidget} on the given panel.
* <p>
* Any handlers attached to the provided event bus will be de-registered when
* the activity is stopped, so activities will rarely need to hold on to the
diff --git a/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
index ce068ca..eb08f85 100644
--- a/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/client/impl/AbstractAutoBeanFactory.java
@@ -25,7 +25,12 @@
/**
* Provides base implementations of AutoBeanFactory methods.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public abstract class AbstractAutoBeanFactory implements AutoBeanFactory,
EnumMap {
diff --git a/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
index c89ae2d..e1129b2 100644
--- a/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/client/impl/ClientPropertyContext.java
@@ -27,13 +27,23 @@
/**
* Provides base methods for generated implementations of PropertyContext.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public final class ClientPropertyContext implements PropertyContext,
CollectionPropertyContext, MapPropertyContext {
/**
* A reference to an instance setter method.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static final class Setter extends JavaScriptObject {
/**
* Create a trivial Setter that calls {@link Map#put(Object, Object)}.
diff --git a/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java b/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java
index 67f34fe..bf137da 100644
--- a/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java
+++ b/user/src/com/google/gwt/autobean/client/impl/JsniCreatorMap.java
@@ -22,7 +22,12 @@
/**
* Used in prod-mode code to create instances of generated AutoBean subtypes via
* JSNI references to their constructor methods.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public final class JsniCreatorMap extends JavaScriptObject {
public static JsniCreatorMap createMap() {
return JavaScriptObject.createObject().cast();
diff --git a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
index c7d227f..cf26cbe 100644
--- a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
+++ b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
@@ -25,11 +25,21 @@
/**
* Implements the EntityCodex.Splittable interface using a raw JavaScriptObject.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public final class JsoSplittable extends JavaScriptObject implements Splittable {
/**
* This type is used because we can't treat Strings as JSOs.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class StringSplittable implements Splittable {
private final String value;
diff --git a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
index 509cadc..8392bb8 100644
--- a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
+++ b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
@@ -58,7 +58,12 @@
/**
* Generates implementations of AutoBeanFactory.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class AutoBeanFactoryGenerator extends Generator {
private GeneratorContext context;
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
index ee9cdae..52110d4 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryMethod.java
@@ -20,11 +20,21 @@
/**
* Represents a single method in an AutoBeanFactory interface.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class AutoBeanFactoryMethod {
/**
* Builds AutoBeanFactoryMethods.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class Builder {
private AutoBeanFactoryMethod toReturn = new AutoBeanFactoryMethod();
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
index 1ec0452..ba7cc4c 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanFactoryModel.java
@@ -44,8 +44,11 @@
import java.util.Set;
/**
- *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class AutoBeanFactoryModel {
private static final JType[] EMPTY_JTYPE = new JType[0];
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
index e936479..1c1f93a 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanMethod.java
@@ -30,11 +30,21 @@
/**
* Describes a method implemented by an AutoBean.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class AutoBeanMethod {
/**
* Creates AutoBeanMethods.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class Builder {
private AutoBeanMethod toReturn = new AutoBeanMethod();
@@ -216,4 +226,4 @@
public String toString() {
return method.toString();
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
index 5fd64e5..0a6335c 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/AutoBeanType.java
@@ -25,12 +25,22 @@
/**
* Describes an AutoBean.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class AutoBeanType {
/**
* Builder.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class Builder {
private boolean affectedByCategories;
private String beanSimpleSourceName;
@@ -165,4 +175,4 @@
public String toString() {
return peerType.toString();
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java b/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java
index 9880b61..661a585 100644
--- a/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java
+++ b/user/src/com/google/gwt/autobean/rebind/model/JBeanMethod.java
@@ -30,9 +30,14 @@
/**
* Common utility code for matching {@link JMethod} and against bean-style
* accessor semantics.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see com.google.gwt.autobean.server.impl.BeanMethod
*/
+@Deprecated
public enum JBeanMethod {
GET {
@Override
@@ -151,4 +156,4 @@
* Returns {@code true} if the BeanLikeMethod matches the method.
*/
public abstract boolean matches(JMethod method);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
index 6ab47d1..46c5312 100644
--- a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
+++ b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
@@ -31,7 +31,14 @@
* This implementation is written assuming that the AutoBeanFactory and
* associated declarations will validate if compiled and used with the
* AutoBeanFactoyModel.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
+ * @deprecated Replaced by {@link com.google.web.bindery.autobean.vm.AutoBeanFactorySource}
*/
+@Deprecated
public class AutoBeanFactoryMagic {
/*
* NB: This implementation is excessively dynamic, however the inability to
diff --git a/user/src/com/google/gwt/autobean/server/Configuration.java b/user/src/com/google/gwt/autobean/server/Configuration.java
index 8e96c36..ba95ce7 100644
--- a/user/src/com/google/gwt/autobean/server/Configuration.java
+++ b/user/src/com/google/gwt/autobean/server/Configuration.java
@@ -28,11 +28,21 @@
* Used by {@link AutoBeanFactoryMagic#createBean(Class, Configuration)}. This
* type replicates the annotations that may be applied to an AutoBeanFactory
* declaration.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class Configuration {
/**
* Builds {@link Configuration} objects.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class Builder {
private Configuration toReturn = new Configuration();
diff --git a/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
index 59e90ed..6591813 100644
--- a/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanMethod.java
@@ -24,9 +24,14 @@
/**
* Breakout of method types that an AutoBean shim interface can implement. The
* order of the values of the enum is important.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see com.google.gwt.autobean.rebind.model.JBeanMethod
*/
+@Deprecated
public enum BeanMethod {
/**
* Methods defined in Object.
@@ -226,4 +231,4 @@
* Determine if the method maches the given type.
*/
abstract boolean matches(SimpleBeanHandler<?> handler, Method method);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
index 8b1a7a5..4d0013a 100644
--- a/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/BeanPropertyContext.java
@@ -22,7 +22,12 @@
/**
* A property context that allows setters to be called on a simple peer,
* regardless of whether or not the interface actually has a setter.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class BeanPropertyContext extends MethodPropertyContext {
private final String propertyName;
private final Map<String, Object> map;
diff --git a/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java b/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
index 4d21c6d..fc1f883 100644
--- a/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/FactoryHandler.java
@@ -27,7 +27,12 @@
/**
* Handles dispatches on AutoBeanFactory interfaces.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class FactoryHandler implements InvocationHandler {
private final Configuration configuration;
@@ -126,4 +131,4 @@
return e.name();
}
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
index ba7814d..dd850af 100644
--- a/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/GetterPropertyContext.java
@@ -21,7 +21,12 @@
/**
* Used by {@link ProxyAutoBean#traverseProperties()}.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class GetterPropertyContext extends MethodPropertyContext {
private final Method setter;
private final Object shim;
@@ -66,4 +71,4 @@
throw new RuntimeException(e.getCause());
}
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
index 6dd80e3..2ce6c20 100644
--- a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
+++ b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
@@ -28,7 +28,12 @@
/**
* Uses the org.json packages to slice and dice request payloads.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class JsonSplittable implements Splittable {
public static Splittable create(String payload) {
try {
@@ -173,4 +178,4 @@
}
return new JsonSplittable(object.toString());
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java b/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
index efbf8f7..e3aae3b 100644
--- a/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/impl/MethodPropertyContext.java
@@ -28,7 +28,12 @@
/**
* A base type to handle analyzing the return value of a getter method. The
* accessor methods are implemented in subtypes.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
abstract class MethodPropertyContext implements CollectionPropertyContext,
MapPropertyContext {
private static class Data {
diff --git a/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
index 4c93baa..cbcc999 100644
--- a/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ProxyAutoBean.java
@@ -36,9 +36,14 @@
/**
* An implementation of an AutoBean that uses reflection.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> the type of interface being wrapped
*/
+@Deprecated
public class ProxyAutoBean<T> extends AbstractAutoBean<T> {
private static class Data {
final List<Method> getters = new ArrayList<Method>();
@@ -322,4 +327,4 @@
WeakMapping.set(toReturn, AutoBean.class.getName(), this);
return toReturn;
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
index 23c190d..273a53f 100644
--- a/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/ShimHandler.java
@@ -25,9 +25,14 @@
/**
* Implements an AutoBean's shim interface that intercepts calls to the backing
* object.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> the interface type of the AutoBean
*/
+@Deprecated
class ShimHandler<T> implements InvocationHandler {
private final ProxyAutoBean<T> bean;
private final Method interceptor;
@@ -128,4 +133,4 @@
bean.getFactory(), intf, bean.getConfiguration(), toReturn);
return newBean.as();
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
index d25468d..2f39e33 100644
--- a/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
+++ b/user/src/com/google/gwt/autobean/server/impl/SimpleBeanHandler.java
@@ -20,9 +20,14 @@
/**
* Dynamic implementation of an AutoBean's simple peer object.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> the type of interface the shim allows access to
*/
+@Deprecated
class SimpleBeanHandler<T> implements InvocationHandler {
private final ProxyAutoBean<T> bean;
@@ -55,4 +60,4 @@
public String toString() {
return bean.getPropertyMap().toString();
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
index c7c64ab..db60e62 100644
--- a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
+++ b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
@@ -34,7 +34,12 @@
/**
* Shared code for answering question about Class objects. This is a
* server-compatible analog to ModelUtils.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class TypeUtils {
static final Map<Class<?>, Class<?>> AUTOBOX_MAP;
static final Map<Class<?>, Object> DEFAULT_PRIMITIVE_VALUES;
diff --git a/user/src/com/google/gwt/autobean/server/package-info.java b/user/src/com/google/gwt/autobean/server/package-info.java
index 88e1b04..d761402 100644
--- a/user/src/com/google/gwt/autobean/server/package-info.java
+++ b/user/src/com/google/gwt/autobean/server/package-info.java
@@ -16,13 +16,17 @@
/**
* Contains JVM-compatible implementations of the AutoBean framework.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see <a
* href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
* wiki page</a>
* @see com.google.gwt.autobean.shared.AutoBeanFactory
* @see com.google.gwt.autobean.server.AutoBeanFactoryMagic
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.autobean.server;
-
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBean.java b/user/src/com/google/gwt/autobean/shared/AutoBean.java
index 8b8613f..b265aac 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBean.java
@@ -24,9 +24,14 @@
/**
* A controller for an implementation of a bean interface. Instances of
* AutoBeans are obtained from an {@link AutoBeanFactory}.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> the type of interface that will be wrapped.
*/
+@Deprecated
public interface AutoBean<T> {
/**
* An annotation that allows inferred property names to be overridden.
@@ -34,7 +39,12 @@
* This annotation is asymmetric, applying it to a getter will not affect the
* setter. The asymmetry allows existing users of an interface to read old
* {@link AutoBeanCodex} messages, but write new ones.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
index bcded2c..bf73dd9 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
@@ -34,13 +34,23 @@
* Utility methods for encoding an AutoBean graph into a JSON-compatible string.
* This codex intentionally does not preserve object identity, nor does it
* encode cycles, but it will detect them.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class AutoBeanCodex {
/**
* Describes a means of encoding or decoding a particular type of data to or
* from a wire format representation.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
interface Coder {
Object decode(Splittable data);
@@ -148,7 +158,12 @@
/**
* Used to stop processing.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
static class HaltException extends RuntimeException {
public HaltException(RuntimeException cause) {
super(cause);
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java b/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
index 5f1a8f9..5bf39fe 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanFactory.java
@@ -28,7 +28,7 @@
* Simple interfaces, consisting of only getters and setters, can be constructed
* with a no-arg method. Non-simple interfaces must provide a delegate object to
* implement a non-simple interface or use a {@link Category}.
- *
+ *
* <pre>
* interface MyFactory extends AutoBeanFactory {
* // A factory method for a simple bean
@@ -37,11 +37,16 @@
* AutoBean<ArbitraryInterface> wrapper(ArbitraryInterface delegate);
* }
* </pre>
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see <a
* href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
* wiki page</a>
*/
+@Deprecated
public interface AutoBeanFactory {
/**
* Allows non-property methods on simple bean implementations when applied.
@@ -73,7 +78,12 @@
* AutoBean<HasMethod> hasMethod();
* }
* </pre>
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@@ -84,7 +94,12 @@
/**
* The types specified by this annotation will not be wrapped by an AutoBean
* when returned from an AutoBean-controlled method.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NoWrap {
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
index ed1930f..3adcc47 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanUtils.java
@@ -29,7 +29,12 @@
/**
* Utility methods for working with AutoBeans.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public final class AutoBeanUtils {
/*
* TODO(bobv): Make Comparison a real type that holds a map contain the diff
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java b/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java
index 39358ac..931ce30 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanVisitor.java
@@ -20,12 +20,22 @@
/**
* Allows traversal of an AutoBean object graph.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class AutoBeanVisitor {
/**
* A PropertyContext that describes the parameterization of the Collection
* being visited.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public interface CollectionPropertyContext extends PropertyContext {
/**
* Returns the collection's element type.
@@ -37,14 +47,24 @@
/**
* Reserved for future expansion to avoid API breaks.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public interface Context {
}
/**
* A PropertyContext that describes the parameterization of the Map being
* visited.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public interface MapPropertyContext extends PropertyContext {
/**
* Returns the map's key type.
@@ -85,7 +105,12 @@
* endVisitParameter();
* endVisitType(Map.class);
* </pre>
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class ParameterizationVisitor {
/**
* Called when finished with a type parameter.
@@ -120,7 +145,12 @@
/**
* Allows properties to be reset.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public interface PropertyContext {
/**
* Allows deeper inspection of the declared parameterization of the
diff --git a/user/src/com/google/gwt/autobean/shared/Splittable.java b/user/src/com/google/gwt/autobean/shared/Splittable.java
index 65a2286..357c5c9 100644
--- a/user/src/com/google/gwt/autobean/shared/Splittable.java
+++ b/user/src/com/google/gwt/autobean/shared/Splittable.java
@@ -21,7 +21,12 @@
* This interface provides an abstraction around the underlying data model
* (JavaScriptObject, {@code org.json}, or XML) used to encode an AutoBeanCodex
* payload.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface Splittable {
/**
* Returns a string representation of the data.
@@ -81,4 +86,4 @@
* Returns the size of the list.
*/
int size();
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/autobean/shared/ValueCodex.java b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
index b4fb321..b8dc1e0 100644
--- a/user/src/com/google/gwt/autobean/shared/ValueCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
@@ -28,8 +28,19 @@
/**
* Provides unified encoding and decoding of value objects.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class ValueCodex {
+ /**
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ */
+ @Deprecated
enum Type {
BIG_DECIMAL(BigDecimal.class) {
@Override
diff --git a/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java b/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java
index c6f72c8..dbad68b 100644
--- a/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodexHelper.java
@@ -20,7 +20,12 @@
/**
* Provides reflection-based operation for server (JVM) implementation. There is
* a no-op super-source version for client (dev- and web-mode) code.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class ValueCodexHelper {
/**
* Returns {@code true} if {@code clazz} is assignable to any of the value
diff --git a/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
index ad2dc20..27d2bc7 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/AbstractAutoBean.java
@@ -29,13 +29,23 @@
/**
* Basic implementation.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> the wrapper type
*/
+@Deprecated
public abstract class AbstractAutoBean<T> implements AutoBean<T> {
/**
* Used to avoid cycles when visiting.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class OneShotContext implements Context {
private final Set<AbstractAutoBean<?>> seen = new HashSet<AbstractAutoBean<?>>();
diff --git a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
index 3b53d1b..f00ae90 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
@@ -18,11 +18,21 @@
/**
* This interface is implemented by our generated AutoBeanFactory types to
* convert enum values to strings.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface EnumMap {
/**
* Extra enums that should be included in the AutoBeanFactory.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public @interface ExtraEnums {
Class<? extends Enum<?>>[] value();
}
diff --git a/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java b/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
index 2fb4749..5a17a71 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/LazySplittable.java
@@ -22,7 +22,12 @@
/**
* Holds a string payload with the expectation that the object will be used only
* for creating a larger payload.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class LazySplittable implements Splittable {
public static final Splittable NULL = new LazySplittable("null");
diff --git a/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
index 899ed52..8568db7 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/StringQuoter.java
@@ -28,7 +28,12 @@
/**
* This class has a super-source version with a client-only implementation.
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class StringQuoter {
private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSz";
private static final DateFormat ISO8601 = new SimpleDateFormat(
diff --git a/user/src/com/google/gwt/autobean/shared/package-info.java b/user/src/com/google/gwt/autobean/shared/package-info.java
index 5c13167..9812599 100644
--- a/user/src/com/google/gwt/autobean/shared/package-info.java
+++ b/user/src/com/google/gwt/autobean/shared/package-info.java
@@ -19,13 +19,17 @@
* bean-like interfaces and a low-level serialization mechanism for those
* interfaces. AutoBeans can be used in both client and server code to improve
* code re-use.
- *
+ *
+ * <p><span style='color:red'>AutoBeans has moved to
+ * <code>com.google.web.bindery.autobeans</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see <a
* href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
* wiki page</a>
* @see com.google.gwt.autobean.shared.AutoBeanFactory
* @see com.google.gwt.autobean.server.AutoBeanFactoryMagic
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.autobean.shared;
-
diff --git a/user/src/com/google/gwt/core/client/JsonUtils.java b/user/src/com/google/gwt/core/client/JsonUtils.java
index 4fd9942..7088266 100644
--- a/user/src/com/google/gwt/core/client/JsonUtils.java
+++ b/user/src/com/google/gwt/core/client/JsonUtils.java
@@ -19,10 +19,8 @@
* Provides JSON-related utility methods.
*/
public class JsonUtils {
- @SuppressWarnings("unused")
private static JavaScriptObject escapeTable = initEscapeTable();
- @SuppressWarnings("unused")
private static final boolean hasJsonParse = hasJsonParse();
/**
@@ -62,17 +60,17 @@
try {
return JSON.parse(json);
} catch (e) {
- return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+ return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
}
} else {
if (!@com.google.gwt.core.client.JsonUtils::safeToEval(Ljava/lang/String;)(json)) {
- return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Illegal character in JSON string");
+ return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Illegal character in JSON string", json);
}
json = @com.google.gwt.core.client.JsonUtils::escapeJsonForEval(Ljava/lang/String;)(json);
try {
return eval('(' + json + ')');
} catch (e) {
- return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+ return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
}
}
}-*/;
@@ -112,15 +110,14 @@
try {
return eval('(' + escaped + ')');
} catch (e) {
- return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(Ljava/lang/String;)("Error parsing JSON: " + e);
+ return @com.google.gwt.core.client.JsonUtils::throwIllegalArgumentException(*)("Error parsing JSON: " + e, json);
}
}-*/;
- static void throwIllegalArgumentException(String message) {
- throw new IllegalArgumentException(message);
+ static void throwIllegalArgumentException(String message, String data) {
+ throw new IllegalArgumentException(message + "\n" + data);
}
- @SuppressWarnings("unused")
private static native String escapeChar(String c) /*-{
var lookedUp = @com.google.gwt.core.client.JsonUtils::escapeTable[c.charCodeAt(0)];
return (lookedUp == null) ? c : lookedUp;
diff --git a/user/src/com/google/gwt/editor/client/EditorDriver.java b/user/src/com/google/gwt/editor/client/EditorDriver.java
index 8fff6c6..414c8e2 100644
--- a/user/src/com/google/gwt/editor/client/EditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/EditorDriver.java
@@ -28,7 +28,7 @@
*
* @param <T> the type of data returned from {@link #flush()}
* @see com.google.gwt.editor.client.SimpleBeanEditorDriver
- * @see com.google.gwt.requestfactory.client.RequestFactoryEditorDriver
+ * @see com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver
*/
public interface EditorDriver<T> {
/**
@@ -59,7 +59,7 @@
/**
* Returns {@code true} if any of the Editors in the hierarchy have been
- * modified relative to the last value passed into {@link #edit(Object)}.
+ * modified relative to the last value passed into {@link SimpleBeanEditorDriver#edit(Object)}.
* <p>
* This method is not affected by {@link #flush()} to support the following
* workflow:
diff --git a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
index 310b9dd..241a1c5 100644
--- a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
+++ b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
@@ -15,7 +15,7 @@
*/
package com.google.gwt.editor.rebind.model;
-import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.ValueCodex;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
diff --git a/user/src/com/google/gwt/event/Event.gwt.xml b/user/src/com/google/gwt/event/Event.gwt.xml
index 3dafaf8..14f6dab 100644
--- a/user/src/com/google/gwt/event/Event.gwt.xml
+++ b/user/src/com/google/gwt/event/Event.gwt.xml
@@ -1,5 +1,20 @@
+<!--
+ 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>
- <inherits name="com.google.gwt.event.EventBase" />
- <inherits name="com.google.gwt.event.dom.DomEvent" />
- <inherits name="com.google.gwt.event.logical.LogicalEvent" />
+ <inherits name="com.google.gwt.event.EventBase" />
+ <inherits name="com.google.gwt.event.dom.DomEvent" />
+ <inherits name="com.google.gwt.event.logical.LogicalEvent" />
</module>
diff --git a/user/src/com/google/gwt/event/EventBase.gwt.xml b/user/src/com/google/gwt/event/EventBase.gwt.xml
index 9f0de72..895f0ee 100644
--- a/user/src/com/google/gwt/event/EventBase.gwt.xml
+++ b/user/src/com/google/gwt/event/EventBase.gwt.xml
@@ -1,5 +1,21 @@
+<!--
+ 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>
- <inherits name="com.google.gwt.core.Core" />
+ <inherits name="com.google.web.bindery.event.Event" />
+ <inherits name="com.google.gwt.core.Core" />
- <source path="shared" />
+ <source path="shared" />
</module>
diff --git a/user/src/com/google/gwt/event/shared/EventBus.java b/user/src/com/google/gwt/event/shared/EventBus.java
index 74460c3..267cef4 100644
--- a/user/src/com/google/gwt/event/shared/EventBus.java
+++ b/user/src/com/google/gwt/event/shared/EventBus.java
@@ -15,81 +15,71 @@
*/
package com.google.gwt.event.shared;
-import com.google.gwt.event.shared.GwtEvent.Type;
+import com.google.web.bindery.event.shared.Event;
/**
- * Dispatches {@link GwtEvent}s to interested parties. Eases decoupling by
- * allowing objects to interact without having direct dependencies upon one
- * another, and without requiring event sources to deal with maintaining handler
- * lists. There will typically be one EventBus per application, broadcasting
- * events that may be of general interest.
- *
- * @see SimpleEventBus
- * @see ResettableEventBus
- * @see com.google.gwt.event.shared.testing.CountingEventBus
+ * Extends {com.google.web.bindery.event.shared.EventBus} for legacy
+ * compatibility.
*/
-public abstract class EventBus implements HasHandlers {
+public abstract class EventBus extends com.google.web.bindery.event.shared.EventBus implements
+ HasHandlers {
- /**
- * Adds an unfiltered handler to receive events of this type from all sources.
- * <p>
- * It is rare to call this method directly. More typically a {@link GwtEvent}
- * subclass will provide a static <code>register</code> method, or a widget
- * will accept handlers directly.
- * <p>
- * A tip: to make a handler de-register itself, the following works:
- * <code><pre>new MyHandler() {
- * HandlerRegistration reg = MyEvent.register(eventBus, this);
- *
- * public void onMyThing(MyEvent event) {
- * {@literal /}* do your thing *{@literal /}
- * reg.removeHandler();
- * }
- * };
- * </pre></code>
- *
- * @param <H> The type of handler
- * @param type the event type associated with this handler
- * @param handler the handler
- * @return the handler registration, can be stored in order to remove the
- * handler later
- */
- public abstract <H extends EventHandler> HandlerRegistration addHandler(
- Type<H> type, H handler);
+ @Override
+ public <H> com.google.web.bindery.event.shared.HandlerRegistration addHandler(Event.Type<H> type, H handler) {
+ throw new UnsupportedOperationException("Subclass responsibility. "
+ + "This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. "
+ + "Use that directly, or try com.google.gwt.event.shared.SimpleEventBus");
+ }
+
+ public abstract <H extends EventHandler> HandlerRegistration addHandler(GwtEvent.Type<H> type, H handler);
- /**
- * Adds a handler to receive events of this type from the given source.
- * <p>
- * It is rare to call this method directly. More typically a {@link GwtEvent}
- * subclass will provide a static <code>register</code> method, or a widget
- * will accept handlers directly.
- *
- * @param <H> The type of handler
- * @param type the event type associated with this handler
- * @param source the source associated with this handler
- * @param handler the handler
- * @return the handler registration, can be stored in order to remove the
- * handler later
- */
- public abstract <H extends EventHandler> HandlerRegistration addHandlerToSource(
- Type<H> type, Object source, H handler);
+ @Override
+ public <H> com.google.web.bindery.event.shared.HandlerRegistration addHandlerToSource(Event.Type<H> type,
+ Object source, H handler) {
+ throw new UnsupportedOperationException("Subclass responsibility. "
+ + "This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. "
+ + "Use that directly, or try com.google.gwt.event.shared.SimpleEventBus");
+ }
- /**
- * Fires the event from no source. Only unfiltered handlers will receive it.
- *
- * @param event the event to fire
- */
+ public abstract <H extends EventHandler> HandlerRegistration addHandlerToSource(GwtEvent.Type<H> type,
+ Object source, H handler);
+
+ @Override
+ public void fireEvent(Event<?> event) {
+ throw new UnsupportedOperationException("Subclass responsibility. "
+ + "This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. "
+ + "Use that directly, or try com.google.gwt.event.shared.SimpleEventBus");
+ }
+
public abstract void fireEvent(GwtEvent<?> event);
- /**
- * Fires the given event to the handlers listening to the event's type.
- * <p>
- * Any exceptions thrown by handlers will be bundled into a
- * {@link UmbrellaException} and then re-thrown after all handlers have
- * completed. An exception thrown by a handler will not prevent other handlers
- * from executing.
- *
- * @param event the event to fire
- */
+
+ @Override
+ public void fireEventFromSource(Event<?> event, Object source) {
+ throw new UnsupportedOperationException("Subclass responsibility. "
+ + "This class is a legacy wrapper for com.google.web.bindery.event.shared.EventBus. "
+ + "Use that directly, or try com.google.gwt.event.shared.SimpleEventBus");
+ }
+
public abstract void fireEventFromSource(GwtEvent<?> event, Object source);
+
+ protected void castFireEvent(GwtEvent<?> event) {
+ try {
+ fireEvent((Event<?>) event);
+ } catch (com.google.web.bindery.event.shared.UmbrellaException e) {
+ throw new UmbrellaException(e.getCauses());
+ }
+ }
+
+ protected void castFireEventFromSource(GwtEvent<?> event, Object source) {
+ try {
+ fireEventFromSource((Event<?>) event, source);
+ } catch (com.google.web.bindery.event.shared.UmbrellaException e) {
+ throw new UmbrellaException(e.getCauses());
+ }
+ }
+
+ protected HandlerRegistration wrap(com.google.web.bindery.event.shared.HandlerRegistration reg) {
+ return new LegacyHandlerWrapper(reg);
+ }
}
diff --git a/user/src/com/google/gwt/event/shared/EventHandler.java b/user/src/com/google/gwt/event/shared/EventHandler.java
index 2558629..7860744 100644
--- a/user/src/com/google/gwt/event/shared/EventHandler.java
+++ b/user/src/com/google/gwt/event/shared/EventHandler.java
@@ -16,8 +16,8 @@
package com.google.gwt.event.shared;
/**
- * Marker interface for event handlers. All GWT event handlers should extend
- * {@link EventHandler}.
+ * Marker interface for event handlers. All stock GWT Widget and dom event
+ * handlers extend {@link EventHandler}.
*/
public interface EventHandler {
diff --git a/user/src/com/google/gwt/event/shared/GwtEvent.java b/user/src/com/google/gwt/event/shared/GwtEvent.java
index ef74c39..a4a8465 100644
--- a/user/src/com/google/gwt/event/shared/GwtEvent.java
+++ b/user/src/com/google/gwt/event/shared/GwtEvent.java
@@ -15,16 +15,20 @@
*/
package com.google.gwt.event.shared;
+import com.google.web.bindery.event.shared.Event;
+
/**
- * Root of all GWT events. All GWT events are considered dead and should no
- * longer be accessed once the {@link HandlerManager} which originally fired the
- * event finishes with it. That is, don't hold on to event objects outside of
- * your handler methods.
+ * Root of all GWT widget and dom events sourced by a {@link HandlerManager}.
+ * All GWT events are considered dead and should no longer be accessed once the
+ * {@link HandlerManager} which originally fired the event finishes with it.
+ * That is, don't hold on to event objects outside of your handler methods.
+ * <p>
+ * There is no need for an application's custom event types to extend GwtEvent.
+ * Prefer {@link Event} instead.
*
* @param <H> handler type
- *
*/
-public abstract class GwtEvent<H extends EventHandler> {
+public abstract class GwtEvent<H extends EventHandler> extends Event<H> {
/**
* Type class used to register events with the {@link HandlerManager}.
* <p>
@@ -34,79 +38,24 @@
*
* @param <H> handler type
*/
- public static class Type<H> {
- private static int nextHashCode;
- private final int index;
-
- /**
- * Constructor.
- */
- public Type() {
- index = ++nextHashCode;
- }
-
- // We override hash code to make it as efficient as possible.
- @Override
- public final int hashCode() {
- return index;
- }
-
- @Override
- public String toString() {
- return "Event type";
- }
+ public static class Type<H> extends com.google.web.bindery.event.shared.Event.Type<H> {
}
private boolean dead;
- private Object source;
-
/**
* Constructor.
*/
protected GwtEvent() {
}
- /**
- * Returns the type used to register this event. Used by handler manager to
- * dispatch events to the correct handlers.
- *
- * @return the type
- */
- public abstract Type<H> getAssociatedType();
+ @Override
+ public abstract GwtEvent.Type<H> getAssociatedType();
- /**
- * Returns the source that last fired this event.
- *
- * @return object representing the source of this event
- */
+ @Override
public Object getSource() {
assertLive();
- return source;
- }
-
- /**
- * This is a method used primarily for debugging. It gives a string
- * representation of the event details. This does not override the toString
- * method because the compiler cannot always optimize toString out correctly.
- * Event types should override as desired.
- *
- * @return a string representing the event's specifics.
- */
- public String toDebugString() {
- String name = this.getClass().getName();
- name = name.substring(name.lastIndexOf(".") + 1);
- return "event: " + name + ":";
- }
-
- /**
- * The toString() for abstract event is overridden to avoid accidently
- * including class literals in the the compiled output. Use {@link GwtEvent}
- * #toDebugString to get more information about the event.
- */
- @Override
- public String toString() {
- return "An event type";
+ return super.getSource();
}
/**
@@ -141,7 +90,7 @@
*/
protected void kill() {
dead = true;
- source = null;
+ setSource(null);
}
/**
@@ -149,16 +98,10 @@
*/
protected void revive() {
dead = false;
- source = null;
+ setSource(null);
}
- /**
- * Set the source that triggered this event.
- *
- * @param source the source of this event, should only be set by a
- * {@link HandlerManager}
- */
- void setSource(Object source) {
- this.source = source;
+ void overrideSource(Object source) {
+ super.setSource(source);
}
}
diff --git a/user/src/com/google/gwt/event/shared/HandlerManager.java b/user/src/com/google/gwt/event/shared/HandlerManager.java
index 73d181e..9f4d350 100644
--- a/user/src/com/google/gwt/event/shared/HandlerManager.java
+++ b/user/src/com/google/gwt/event/shared/HandlerManager.java
@@ -1,12 +1,12 @@
/*
* Copyright 2009 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
@@ -15,7 +15,7 @@
*/
package com.google.gwt.event.shared;
-import com.google.gwt.event.shared.GwtEvent.Type;
+import com.google.web.bindery.event.shared.Event;
/**
* Manager responsible for adding handlers to event sources and firing those
@@ -25,13 +25,40 @@
* While widget authors should continue to use
* {@link com.google.gwt.user.client.ui.Widget#addDomHandler(EventHandler, com.google.gwt.event.dom.client.DomEvent.Type)}
* and
- * {@link com.google.gwt.user.client.ui.Widget#addHandler(EventHandler, Type)},
- * application developers are strongly discouraged from using a HandlerManager
+ * {@link com.google.gwt.user.client.ui.Widget#addHandler(EventHandler, com.google.gwt.event.shared.GwtEvent.Type)}
+ * , application developers are strongly discouraged from using a HandlerManager
* instance as a global event dispatch mechanism.
*/
public class HandlerManager implements HasHandlers {
- private final SimpleEventBus eventBus;
+ @SuppressWarnings("deprecation")
+ private static class Bus extends com.google.web.bindery.event.shared.SimpleEventBus {
+ public Bus(boolean fireInReverseOrder) {
+ super(fireInReverseOrder);
+ }
+
+ @Override
+ protected <H> void doRemove(Event.Type<H> type, Object source, H handler) {
+ super.doRemove(type, source, handler);
+ }
+
+ @Override
+ protected <H> H getHandler(Event.Type<H> type, int index) {
+ return super.getHandler(type, index);
+ }
+
+ @Override
+ protected int getHandlerCount(Event.Type<?> eventKey) {
+ return super.getHandlerCount(eventKey);
+ }
+
+ @Override
+ protected boolean isEventHandled(Event.Type<?> eventKey) {
+ return super.isEventHandled(eventKey);
+ }
+ }
+
+ private final Bus eventBus;
// source of the events
private final Object source;
@@ -40,7 +67,7 @@
* Creates a handler manager with a source to be set on all events fired via
* {@link #fireEvent(GwtEvent)}. Handlers will be fired in the order that they
* are added.
- *
+ *
* @param source the default event source
*/
public HandlerManager(Object source) {
@@ -50,28 +77,27 @@
/**
* Creates a handler manager with the given source, specifying the order in
* which handlers are fired.
- *
+ *
* @param source the event source
* @param fireInReverseOrder true to fire handlers in reverse order
*/
- @SuppressWarnings("deprecation")
public HandlerManager(Object source, boolean fireInReverseOrder) {
- eventBus = new SimpleEventBus(fireInReverseOrder);
+ eventBus = new Bus(fireInReverseOrder);
this.source = source;
}
/**
* Adds a handler.
- *
+ *
* @param <H> The type of handler
* @param type the event type associated with this handler
* @param handler the handler
* @return the handler registration, can be stored in order to remove the
* handler later
*/
- public <H extends EventHandler> HandlerRegistration addHandler(
- GwtEvent.Type<H> type, final H handler) {
- return eventBus.addHandler(type, handler);
+ public <H extends EventHandler> HandlerRegistration addHandler(GwtEvent.Type<H> type,
+ final H handler) {
+ return new LegacyHandlerWrapper(eventBus.addHandler(type, handler));
}
/**
@@ -85,7 +111,7 @@
* Note, any subclass should be very careful about overriding this method, as
* adds/removes of handlers will not be safe except within this
* implementation.
- *
+ *
* @param event the event
*/
public void fireEvent(GwtEvent<?> event) {
@@ -94,68 +120,65 @@
event.revive();
}
Object oldSource = event.getSource();
- event.setSource(source);
+ event.overrideSource(source);
try {
// May throw an UmbrellaException.
eventBus.fireEvent(event);
-
+ } catch (com.google.web.bindery.event.shared.UmbrellaException e) {
+ throw new UmbrellaException(e.getCauses());
} finally {
if (oldSource == null) {
// This was my event, so I should kill it now that I'm done.
event.kill();
} else {
// Restoring the source for the next handler to use.
- event.setSource(oldSource);
+ event.overrideSource(oldSource);
}
}
}
/**
* Gets the handler at the given index.
- *
+ *
* @param <H> the event handler type
* @param index the index
* @param type the handler's event type
* @return the given handler
*/
- @SuppressWarnings("deprecation")
public <H extends EventHandler> H getHandler(GwtEvent.Type<H> type, int index) {
return eventBus.getHandler(type, index);
}
/**
* Gets the number of handlers listening to the event type.
- *
+ *
* @param type the event type
* @return the number of registered handlers
*/
- @SuppressWarnings("deprecation")
- public int getHandlerCount(Type<?> type) {
+ public int getHandlerCount(GwtEvent.Type<?> type) {
return eventBus.getHandlerCount(type);
}
/**
* Does this handler manager handle the given event type?
- *
+ *
* @param e the event type
* @return whether the given event type is handled
*/
- @SuppressWarnings("deprecation")
- public boolean isEventHandled(Type<?> e) {
+ public boolean isEventHandled(GwtEvent.Type<?> e) {
return eventBus.isEventHandled(e);
}
/**
* Removes the given handler from the specified event type.
- *
+ *
* @param <H> handler type
- *
+ *
* @param type the event type
* @param handler the handler
*/
- public <H extends EventHandler> void removeHandler(GwtEvent.Type<H> type,
- final H handler) {
- eventBus.doRemove(type, null, handler);
+ public <H extends EventHandler> void removeHandler(GwtEvent.Type<H> type, final H handler) {
+ eventBus.doRemove(type, null, handler);
}
}
diff --git a/user/src/com/google/gwt/event/shared/HandlerRegistration.java b/user/src/com/google/gwt/event/shared/HandlerRegistration.java
index 9031343..5d04ac9 100644
--- a/user/src/com/google/gwt/event/shared/HandlerRegistration.java
+++ b/user/src/com/google/gwt/event/shared/HandlerRegistration.java
@@ -17,19 +17,8 @@
package com.google.gwt.event.shared;
/**
- * Registration returned from a call to
- * {@link HandlerManager#addHandler(com.google.gwt.event.shared.GwtEvent.Type, EventHandler)}
- * . Use the handler registration to remove handlers when they are no longer
- * needed.
- *
- * Note, this interface is under the control of the {@link HandlerManager} class
- * and may be expanded over time, so extend {@link DefaultHandlerRegistration}
- * if you do not wish to get compiler errors if we extend the handler registry
- * functionality.
+ * Extends {com.google.bindery.event.shared.HandlerRegistration} for legacy
+ * compatibility.
*/
-public interface HandlerRegistration {
- /**
- * Removes the given handler from its manager.
- */
- void removeHandler();
+public interface HandlerRegistration extends com.google.web.bindery.event.shared.HandlerRegistration {
}
diff --git a/user/src/com/google/gwt/event/shared/LegacyHandlerWrapper.java b/user/src/com/google/gwt/event/shared/LegacyHandlerWrapper.java
new file mode 100644
index 0000000..b5396da
--- /dev/null
+++ b/user/src/com/google/gwt/event/shared/LegacyHandlerWrapper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.event.shared;
+
+class LegacyHandlerWrapper implements HandlerRegistration {
+ private final com.google.web.bindery.event.shared.HandlerRegistration real;
+
+ LegacyHandlerWrapper(com.google.web.bindery.event.shared.HandlerRegistration real) {
+ this.real = real;
+ }
+
+ public void removeHandler() {
+ real.removeHandler();
+ }
+}
diff --git a/user/src/com/google/gwt/event/shared/ResettableEventBus.java b/user/src/com/google/gwt/event/shared/ResettableEventBus.java
index 5769a79..4e12fe8 100644
--- a/user/src/com/google/gwt/event/shared/ResettableEventBus.java
+++ b/user/src/com/google/gwt/event/shared/ResettableEventBus.java
@@ -15,57 +15,83 @@
*/
package com.google.gwt.event.shared;
-import com.google.gwt.event.shared.GwtEvent.Type;
-
-import java.util.HashSet;
-import java.util.Set;
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.Event.Type;
+import com.google.web.bindery.event.shared.HandlerRegistration;
/**
- * Wraps an EventBus to hold on to any HandlerRegistrations, so that they can
- * easily all be cleared at once.
+ * Wraps {com.google.web.bindery.event.shared.ResettableEventBus} for legacy
+ * compatibility.
*/
public class ResettableEventBus extends EventBus {
- private final EventBus wrapped;
- private final Set<HandlerRegistration> registrations = new HashSet<HandlerRegistration>();
+ private static class TestableResettableEventBus extends com.google.web.bindery.event.shared.ResettableEventBus {
+ /**
+ * @param wrappedBus
+ */
+ public TestableResettableEventBus(EventBus wrappedBus) {
+ super(wrappedBus);
+ }
+
+ @Override
+ public int getRegistrationSize() {
+ return super.getRegistrationSize();
+ }
+ }
+
+ private final TestableResettableEventBus real;
public ResettableEventBus(EventBus wrappedBus) {
- this.wrapped = wrappedBus;
+ real = new TestableResettableEventBus(wrappedBus);
+ }
+
+ public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandler(
+ GwtEvent.Type<H> type, H handler) {
+ return wrap(addHandler((Event.Type<H>) type, handler));
}
@Override
- public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type,
- H handler) {
- HandlerRegistration rtn = wrapped.addHandler(type, handler);
- registrations.add(rtn);
- return rtn;
+ public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+ return real.addHandler(type, handler);
}
- @Override
- public <H extends EventHandler> HandlerRegistration addHandlerToSource(
+ public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandlerToSource(
GwtEvent.Type<H> type, Object source, H handler) {
- HandlerRegistration rtn = wrapped.addHandlerToSource(type, source,
- handler);
- registrations.add(rtn);
- return rtn;
+ return wrap(addHandlerToSource((Event.Type<H>) type, source, handler));
+ }
+
+ @Override
+ public <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler) {
+ return real.addHandlerToSource(type, source, handler);
+ }
+
+ @Override
+ public void fireEvent(Event<?> event) {
+ real.fireEvent(event);
}
@Override
public void fireEvent(GwtEvent<?> event) {
- wrapped.fireEvent(event);
+ castFireEvent(event);
+ }
+
+ @Override
+ public void fireEventFromSource(Event<?> event, Object source) {
+ real.fireEventFromSource(event, source);
}
@Override
public void fireEventFromSource(GwtEvent<?> event, Object source) {
- wrapped.fireEventFromSource(event, source);
+ castFireEventFromSource(event, source);
+ }
+
+ public void removeHandlers() {
+ real.removeHandlers();
}
/**
- * Remove all handlers that have been added through this wrapper.
+ * Visible for testing
*/
- public void removeHandlers() {
- for (HandlerRegistration r : registrations) {
- r.removeHandler();
- }
- registrations.clear();
+ int getRegistrationSize() {
+ return real.getRegistrationSize();
}
}
diff --git a/user/src/com/google/gwt/event/shared/SimpleEventBus.java b/user/src/com/google/gwt/event/shared/SimpleEventBus.java
index 673d698..40b042d 100644
--- a/user/src/com/google/gwt/event/shared/SimpleEventBus.java
+++ b/user/src/com/google/gwt/event/shared/SimpleEventBus.java
@@ -15,309 +15,55 @@
*/
package com.google.gwt.event.shared;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.event.shared.GwtEvent.Type;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Set;
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.Event.Type;
+import com.google.web.bindery.event.shared.HandlerRegistration;
/**
- * Basic implementation of {@link EventBus}.
+ * Wraps {com.google.web.bindery.event.shared.SimpleEventBus} for legacy
+ * compatibility.
*/
public class SimpleEventBus extends EventBus {
- private final boolean isReverseOrder;
+ private final com.google.web.bindery.event.shared.SimpleEventBus real =
+ new com.google.web.bindery.event.shared.SimpleEventBus();
- private int firingDepth = 0;
-
- /**
- * Add and remove operations received during dispatch.
- */
- private List<ScheduledCommand> deferredDeltas;
-
- /**
- * Map of event type to map of event source to list of their handlers.
- */
- private final Map<GwtEvent.Type<?>, Map<Object, List<?>>> map = new HashMap<GwtEvent.Type<?>, Map<Object, List<?>>>();
-
- public SimpleEventBus() {
- this(false);
- }
-
- /**
- * Allows creation of an instance that fires its handlers in the reverse of
- * the order in which they were added, although filtered handlers all fire
- * before unfiltered handlers.
- * <p>
- *
- * @deprecated This is a legacy feature, required by HandlerManager. Package
- * protected because it is a bad idea to rely upon the order of
- * event dispatch, and because fully supporting it (that is, not
- * segregating filtered and unfiltered handlers, a distinction not
- * used by HandlerManager) is not worth the effort.
- */
- @Deprecated
- SimpleEventBus(boolean fireInReverseOrder) {
- isReverseOrder = fireInReverseOrder;
+ public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandler(
+ GwtEvent.Type<H> type, H handler) {
+ return wrap(addHandler((Event.Type<H>) type, handler));
}
@Override
- public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type,
- H handler) {
- if (type == null) {
- throw new NullPointerException("Cannot add a handler with a null type");
- }
- if (handler == null) {
- throw new NullPointerException("Cannot add a null handler");
- }
+ public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+ return real.addHandler(type, handler);
+ }
- return doAdd(type, null, handler);
+ public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandlerToSource(
+ GwtEvent.Type<H> type, Object source, H handler) {
+ return wrap(addHandlerToSource((Event.Type<H>) type, source, handler));
}
@Override
- public <H extends EventHandler> HandlerRegistration addHandlerToSource(
- final GwtEvent.Type<H> type, final Object source, final H handler) {
- if (type == null) {
- throw new NullPointerException("Cannot add a handler with a null type");
- }
- if (source == null) {
- throw new NullPointerException("Cannot add a handler with a null source");
- }
- if (handler == null) {
- throw new NullPointerException("Cannot add a null handler");
- }
+ public <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler) {
+ return real.addHandlerToSource(type, source, handler);
+ }
- return doAdd(type, source, handler);
+ @Override
+ public void fireEvent(Event<?> event) {
+ real.fireEvent(event);
}
@Override
public void fireEvent(GwtEvent<?> event) {
- if (event == null) {
- throw new NullPointerException("Cannot fire null event");
- }
- doFire(event, null);
+ castFireEvent(event);
+ }
+
+ @Override
+ public void fireEventFromSource(Event<?> event, Object source) {
+ real.fireEventFromSource(event, source);
}
@Override
public void fireEventFromSource(GwtEvent<?> event, Object source) {
- if (event == null) {
- throw new NullPointerException("Cannot fire null event");
- }
- if (source == null) {
- throw new NullPointerException("Cannot fire from a null source");
- }
- doFire(event, source);
+ castFireEventFromSource(event, source);
}
-
- /**
- * Package protected to support legacy features in HandlerManager.
- */
- <H extends EventHandler> void doRemove(
- com.google.gwt.event.shared.GwtEvent.Type<H> type, Object source,
- H handler) {
- if (firingDepth > 0) {
- enqueueRemove(type, source, handler);
- } else {
- doRemoveNow(type, source, handler);
- }
- }
-
- /**
- * Package protected to support legacy features in HandlerManager.
- */
- @Deprecated
- <H extends EventHandler> H getHandler(GwtEvent.Type<H> type, int index) {
- assert index < getHandlerCount(type) : "handlers for " + type.getClass()
- + " have size: " + getHandlerCount(type)
- + " so do not have a handler at index: " + index;
-
- List<H> l = getHandlerList(type, null);
- return l.get(index);
- }
-
- /**
- * Package protected to support legacy features in HandlerManager.
- */
- @Deprecated
- int getHandlerCount(GwtEvent.Type<?> eventKey) {
- return getHandlerList(eventKey, null).size();
- }
-
- /**
- * Package protected to support legacy features in HandlerManager.
- */
- @Deprecated
- boolean isEventHandled(GwtEvent.Type<?> eventKey) {
- return map.containsKey(eventKey);
- }
-
- private void defer(ScheduledCommand command) {
- if (deferredDeltas == null) {
- deferredDeltas = new ArrayList<ScheduledCommand>();
- }
- deferredDeltas.add(command);
- }
-
- private <H extends EventHandler> HandlerRegistration doAdd(
- final GwtEvent.Type<H> type, final Object source, final H handler) {
- if (firingDepth > 0) {
- enqueueAdd(type, source, handler);
- } else {
- doAddNow(type, source, handler);
- }
-
- return new HandlerRegistration() {
- public void removeHandler() {
- doRemove(type, source, handler);
- }
- };
- }
-
- private <H extends EventHandler> void doAddNow(GwtEvent.Type<H> type,
- Object source, H handler) {
- List<H> l = ensureHandlerList(type, source);
- l.add(handler);
- }
-
- private <H extends EventHandler> void doFire(GwtEvent<H> event, Object source) {
- try {
- firingDepth++;
-
- if (source != null) {
- event.setSource(source);
- }
-
- List<H> handlers = getDispatchList(event.getAssociatedType(), source);
- Set<Throwable> causes = null;
-
- ListIterator<H> it = isReverseOrder
- ? handlers.listIterator(handlers.size()) : handlers.listIterator();
- while (isReverseOrder ? it.hasPrevious() : it.hasNext()) {
- H handler = isReverseOrder ? it.previous() : it.next();
-
- try {
- event.dispatch(handler);
- } catch (Throwable e) {
- if (causes == null) {
- causes = new HashSet<Throwable>();
- }
- causes.add(e);
- }
- }
-
- if (causes != null) {
- throw new UmbrellaException(causes);
- }
- } finally {
- firingDepth--;
- if (firingDepth == 0) {
- handleQueuedAddsAndRemoves();
- }
- }
- }
-
- private <H> void doRemoveNow(GwtEvent.Type<H> type, Object source, H handler) {
- List<H> l = getHandlerList(type, source);
-
- boolean removed = l.remove(handler);
- assert removed : "redundant remove call";
- if (removed && l.isEmpty()) {
- prune(type, source);
- }
- }
-
- private <H extends EventHandler> void enqueueAdd(final GwtEvent.Type<H> type,
- final Object source, final H handler) {
- defer(new ScheduledCommand() {
- public void execute() {
- doAddNow(type, source, handler);
- }
- });
- }
-
- private <H extends EventHandler> void enqueueRemove(
- final GwtEvent.Type<H> type, final Object source, final H handler) {
- defer(new ScheduledCommand() {
- public void execute() {
- doRemoveNow(type, source, handler);
- }
- });
- }
-
- private <H> List<H> ensureHandlerList(GwtEvent.Type<H> type, Object source) {
- Map<Object, List<?>> sourceMap = map.get(type);
- if (sourceMap == null) {
- sourceMap = new HashMap<Object, List<?>>();
- map.put(type, sourceMap);
- }
-
- // safe, we control the puts.
- @SuppressWarnings("unchecked")
- List<H> handlers = (List<H>) sourceMap.get(source);
- if (handlers == null) {
- handlers = new ArrayList<H>();
- sourceMap.put(source, handlers);
- }
-
- return handlers;
- }
-
- private <H> List<H> getDispatchList(GwtEvent.Type<H> type, Object source) {
- List<H> directHandlers = getHandlerList(type, source);
- if (source == null) {
- return directHandlers;
- }
-
- List<H> globalHandlers = getHandlerList(type, null);
-
- List<H> rtn = new ArrayList<H>(directHandlers);
- rtn.addAll(globalHandlers);
- return rtn;
- }
-
- private <H> List<H> getHandlerList(GwtEvent.Type<H> type, Object source) {
- Map<Object, List<?>> sourceMap = map.get(type);
- if (sourceMap == null) {
- return Collections.emptyList();
- }
-
- // safe, we control the puts.
- @SuppressWarnings("unchecked")
- List<H> handlers = (List<H>) sourceMap.get(source);
- if (handlers == null) {
- return Collections.emptyList();
- }
-
- return handlers;
- }
-
- private void handleQueuedAddsAndRemoves() {
- if (deferredDeltas != null) {
- try {
- for (ScheduledCommand c : deferredDeltas) {
- c.execute();
- }
- } finally {
- deferredDeltas = null;
- }
- }
- }
-
- private void prune(GwtEvent.Type<?> type, Object source) {
- Map<Object, List<?>> sourceMap = map.get(type);
-
- List<?> pruned = sourceMap.remove(source);
-
- assert pruned != null : "Can't prune what wasn't there";
- assert pruned.isEmpty() : "Pruned unempty list!";
-
- if (sourceMap.isEmpty()) {
- map.remove(type);
- }
- }
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/event/shared/UmbrellaException.java b/user/src/com/google/gwt/event/shared/UmbrellaException.java
index 134af51..dd34695 100644
--- a/user/src/com/google/gwt/event/shared/UmbrellaException.java
+++ b/user/src/com/google/gwt/event/shared/UmbrellaException.java
@@ -15,43 +15,21 @@
*/
package com.google.gwt.event.shared;
-import java.util.HashSet;
import java.util.Set;
/**
- * A {@link RuntimeException} that collects a {@link Set} of child
- * {@link Throwable}s together. Typically thrown after loop, with all of the
- * exceptions thrown during that loop, but delayed so that the loop finishes
- * executing.
+ * Wraps {com.google.web.bindery.event.shared.UmbrellaException} for legacy
+ * compatibility.
*/
-public class UmbrellaException extends RuntimeException {
-
- /**
- * The causes of the exception.
- */
- private Set<Throwable> causes;
-
- /*
- * The default constructor enables RPC support.
- */
- public UmbrellaException() {
- this(new HashSet<Throwable>());
- }
-
+public class UmbrellaException extends com.google.web.bindery.event.shared.UmbrellaException {
public UmbrellaException(Set<Throwable> causes) {
- super(
- "One or more exceptions caught, see full set in UmbrellaException#getCauses",
- causes.size() == 0 ? null : causes.toArray(new Throwable[0])[0]);
- this.causes = causes;
+ super(causes);
}
/**
- * Get the set of exceptions that caused the failure.
- *
- * @return the set of causes
+ * Required for GWT RPC serialization.
*/
- public Set<Throwable> getCauses() {
- return causes;
+ protected UmbrellaException() {
+ super();
}
-
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/event/shared/testing/CountingEventBus.java b/user/src/com/google/gwt/event/shared/testing/CountingEventBus.java
index 3c1a121..b0aa76b 100644
--- a/user/src/com/google/gwt/event/shared/testing/CountingEventBus.java
+++ b/user/src/com/google/gwt/event/shared/testing/CountingEventBus.java
@@ -15,87 +15,68 @@
*/
package com.google.gwt.event.shared.testing;
-import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.event.shared.GwtEvent.Type;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.event.shared.SimpleEventBus;
-
-import java.util.HashMap;
-import java.util.Map;
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.Event.Type;
+import com.google.web.bindery.event.shared.HandlerRegistration;
/**
- * Wraps an {@link EventBus} to keep a count of registered handlers. Handy for
- * tests.
+ * Legacy compatibility wrapper for
+ * {@link com.google.web.bindery.event.shared.testing.CountingEventBus}.
*/
-public class CountingEventBus extends EventBus {
- private final Map<Type<?>, Integer> counts = new HashMap<GwtEvent.Type<?>, Integer>();
- private final EventBus wrapped;
-
+public class CountingEventBus extends com.google.gwt.event.shared.EventBus {
+ private final com.google.web.bindery.event.shared.testing.CountingEventBus real;
+
public CountingEventBus() {
- this(new SimpleEventBus());
+ real = new com.google.web.bindery.event.shared.testing.CountingEventBus();
+ }
+
+ public CountingEventBus(com.google.gwt.event.shared.EventBus wrapped) {
+ real = new com.google.web.bindery.event.shared.testing.CountingEventBus(wrapped);
+ }
+
+ public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandler(
+ GwtEvent.Type<H> type, H handler) {
+ return wrap(addHandler((Event.Type<H>) type, handler));
+ }
+
+ @Override
+ public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+ return real.addHandler(type, handler);
+ }
+
+ public <H extends EventHandler> com.google.gwt.event.shared.HandlerRegistration addHandlerToSource(
+ GwtEvent.Type<H> type, Object source, H handler) {
+ return wrap(addHandlerToSource((Event.Type<H>) type, source, handler));
}
- public CountingEventBus(EventBus wrapped) {
- this.wrapped = wrapped;
+ @Override
+ public <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler) {
+ return real.addHandlerToSource(type, source, handler);
}
@Override
- public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type,
- H handler) {
- increment(type);
- final HandlerRegistration superReg = wrapped.addHandler(type, handler);
- return makeReg(type, superReg);
- }
-
- @Override
- public <H extends EventHandler> HandlerRegistration addHandlerToSource(
- final Type<H> type, Object source, H handler) {
- increment(type);
- final HandlerRegistration superReg = wrapped.addHandlerToSource(type,
- source, handler);
- return makeReg(type, superReg);
+ public void fireEvent(Event<?> event) {
+ real.fireEvent(event);
}
@Override
public void fireEvent(GwtEvent<?> event) {
- wrapped.fireEvent(event);
+ castFireEvent(event);
+ }
+
+ @Override
+ public void fireEventFromSource(Event<?> event, Object source) {
+ real.fireEventFromSource(event, source);
}
@Override
public void fireEventFromSource(GwtEvent<?> event, Object source) {
- wrapped.fireEventFromSource(event, source);
+ castFireEventFromSource(event, source);
}
- public int getCount(Type<?> type) {
- Integer count = counts.get(type);
- return count == null ? 0 : count;
- }
-
- private void decrement(Type<?> type) {
- Integer count = counts.get(type);
- if (count == null) {
- count = 0;
- }
- counts.put(type, count - 1);
- }
-
- private <H> void increment(final Type<H> type) {
- Integer count = counts.get(type);
- if (count == null) {
- count = 0;
- }
- counts.put(type, count + 1);
- }
-
- private <H> HandlerRegistration makeReg(final Type<H> type,
- final HandlerRegistration superReg) {
- return new HandlerRegistration() {
- public void removeHandler() {
- decrement(type);
- superReg.removeHandler();
- }
- };
+ public int getCount(GwtEvent.Type<?> type) {
+ return real.getCount(type);
}
}
diff --git a/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java b/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
index 6cc5306..26060d6 100644
--- a/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/client/DefaultRequestTransport.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -34,7 +34,12 @@
/**
* An implementation of {@link RequestTransport} that uses a
* {@link RequestBuilder}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class DefaultRequestTransport implements RequestTransport {
private static final String SERVER_ERROR = "Server Error";
@@ -57,7 +62,7 @@
/**
* Returns the current URL used by this transport.
- *
+ *
* @return the URL as a String
* @see #setRequestUrl(String)
*/
@@ -83,7 +88,7 @@
/**
* Override the default URL used by this transport.
- *
+ *
* @param url a String URL
* @see #getRequestUrl()
*/
@@ -93,7 +98,7 @@
/**
* Override to change the headers sent in the HTTP request.
- *
+ *
* @param builder a {@link RequestBuilder} instance
*/
protected void configureRequestBuilder(RequestBuilder builder) {
@@ -105,7 +110,7 @@
/**
* Constructs a {@link RequestBuilder} using the {@link RequestBuilder#POST}
* method sent to the URL returned from {@link #getRequestUrl()}.
- *
+ *
* @return a {@link RequestBuilder} instance
*/
protected RequestBuilder createRequestBuilder() {
@@ -116,7 +121,7 @@
* Creates a RequestCallback that maps the HTTP response onto the
* {@link com.google.gwt.requestfactory.shared.RequestTransport.TransportReceiver
* TransportReceiver} interface.
- *
+ *
* @param receiver a {@link TransportReceiver}
* @return a {@link RequestCallback} instance
*/
diff --git a/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java b/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java
index 0482875..5f13dd7 100644
--- a/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/client/HasRequestContext.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -22,9 +22,14 @@
* Editors used with {@link RequestFactoryEditorDriver} that implement this
* interface will be provided with the {@link RequestContext} associated with
* the current editing session.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> the type of data being edited
*/
+@Deprecated
public interface HasRequestContext<T> extends Editor<T> {
/**
* Called by {@link RequestFactoryEditorDriver} with the
@@ -32,7 +37,7 @@
* {@link RequestFactoryEditorDriver#edit(Object, RequestContext) edit()} or
* {@code null} if {@link RequestFactoryEditorDriver#display(Object)
* display()} is called.
- *
+ *
* @param the RequestContext associated with the current editing session which
* may be {@code null}
*/
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
index 71cb681..dab47d3 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -26,33 +26,39 @@
* The interface that links RequestFactory and the Editor framework together.
* <p>
* Instances of this interface are created with
- *
+ *
* <pre>
* interface MyRFED extends RequestFactoryEditorDriver<MyObjectProxy, MyObjectEditor> {}
* MyRFED instance = GWT.create(MyRFED.class);
* {
* instance.initialize(.....);
* myRequest.with(instance.getPaths());
- *
+ *
* // Fire the request, in the callback
* instance.edit(retrievedRecord);
* // Control when the request is sent
* instance.flush().fire(new Receiver {...});
* }
* </pre>
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <P> the type of Proxy being edited
* @param <E> the type of Editor that will edit the Record
+ *
* @see HasRequestContext
* @see {@link com.google.gwt.requestfactory.client.testing.MockRequestFactoryEditorDriver
* MockRequestFactoryEditorDriver}
*/
+@Deprecated
public interface RequestFactoryEditorDriver<P, E extends Editor<? super P>>
extends EditorDriver<RequestContext> {
/**
* Start driving the Editor and its sub-editors with data for display-only
* mode.
- *
+ *
* @param proxy a Proxy of type P
*/
void display(P proxy);
@@ -62,7 +68,7 @@
* {@link RequestContext} is required to provide context for the changes to
* the proxy (see {@link RequestContext#edit}. Note that this driver will not
* fire the request.
- *
+ *
* @param proxy the proxy to be edited
* @param request the request context that will accumulate edits and is
* returned form {@link #flush}
@@ -71,7 +77,7 @@
/**
* Update the object being edited with the current state of the Editor.
- *
+ *
* @return the RequestContext passed into
* {@link #edit(Object, RequestContext)}
* @throws IllegalStateException if {@link #edit(Object, RequestContext)} has
@@ -81,7 +87,7 @@
/**
* Returns a new array containing the request paths.
- *
+ *
* @return an array of Strings
*/
String[] getPaths();
@@ -89,11 +95,11 @@
/**
* Overload of {@link #initialize(RequestFactory, Editor)} to allow a modified
* {@link EventBus} to be monitored for subscription services.
- *
+ *
* @param eventBus the {@link EventBus}
* @param requestFactory a {@link RequestFactory} instance
* @param editor an {@link Editor} of type E
- *
+ *
* @see com.google.gwt.editor.client.EditorDelegate#subscribe
* @see com.google.gwt.event.shared.ResettableEventBus
*/
@@ -102,10 +108,10 @@
/**
* Initializes a driver with the editor it will run, and a RequestFactory to
* use for subscription services.
- *
+ *
* @param requestFactory a {@link RequestFactory} instance
* @param editor an {@link Editor} of type E
- *
+ *
* @see com.google.gwt.editor.client.EditorDelegate#subscribe
*/
void initialize(RequestFactory requestFactory, E editor);
@@ -114,7 +120,7 @@
* Initializes a driver that will not be able to support subscriptions. Calls
* to {@link com.google.gwt.editor.client.EditorDelegate#subscribe()} will do
* nothing.
- *
+ *
* @param editor an {@link Editor} of type E
*/
void initialize(E editor);
@@ -125,7 +131,7 @@
* {@link com.google.gwt.editor.client.EditorError EditorError} objects whose
* {@link com.google.gwt.editor.client.EditorError#getUserData()
* getUserData()} method can be used to access the original Violation object.
- *
+ *
* @param violations an Iterable over {@link Violation} instances
* @return <code>true</code> if there were any unconsumed EditorErrors which
* can be retrieved from {@link #getErrors()}
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
index d3f0b4f..9fee95a 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
@@ -1,19 +1,18 @@
/*
* Copyright 2010 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.requestfactory.client;
import com.google.gwt.logging.client.JsonLogRecordClientUtil;
@@ -27,12 +26,22 @@
/**
* A Handler that does remote logging for applications using RequestFactory.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class RequestFactoryLogHandler extends RemoteLogHandlerBase {
-
- /**
+
+ /**
* Provides a logging request.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static interface LoggingRequestProvider {
/**
* Returns the logging request.
@@ -41,9 +50,9 @@
*/
LoggingRequest getLoggingRequest();
}
-
+
private LoggingRequestProvider requestProvider;
-
+
/**
* Since records from this handler go accross the wire, it should only be
* used for important messages, and it's Level will often be higher than the
@@ -52,7 +61,7 @@
* name of the logger(s) which will be used to log acknowledgements of
* activity going accross the wire. If we did not exclude these loggers, an
* infinite loop would occur.
- *
+ *
* @param requestProvider a {@link LoggingRequestProvider} instance
* @param level a logging {@link Level}
* @param ignoredLoggerNames a List of Strings
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
index c38fba9..6db7d3b 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractClientRequestFactory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -21,7 +21,12 @@
/**
* A RequestFactory that uses a {@link DefaultRequestTransport} by default.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public abstract class AbstractClientRequestFactory extends
AbstractRequestFactory {
@Override
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
index d60cf8c..ae3c91f 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -40,16 +40,26 @@
/**
* Contains utility methods for top-level driver implementations.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <R> the type being edited
* @param <E> the type of Editor
*/
+@Deprecated
public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>>
extends BaseEditorDriver<R, E> implements RequestFactoryEditorDriver<R, E> {
/**
* Adapts a RequestFactory Violation object to the SimpleViolation interface.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
static class SimpleViolationAdapter extends SimpleViolation {
private final Violation v;
@@ -79,7 +89,12 @@
/**
* Provides a source of SimpleViolation objects based on RequestFactory's
* simplified Violation interface.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
static class ViolationIterable implements Iterable<SimpleViolation> {
private final Iterable<Violation> violations;
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java b/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java
index 01a5fdf..83d0967 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/PathCollector.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -26,9 +26,13 @@
/**
* Collects all non-value-type paths in an editor hierarchy for use with
- * {@link com.google.gwt.requestfactory.client.RequestFactoryEditorDriver#getPaths()}
- * .
+ * {@link com.google.gwt.requestfactory.client.RequestFactoryEditorDriver#getPaths()}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class PathCollector extends EditorVisitor {
/**
* Use a set in the case of aliased editors, so we don't repeat path entries.
@@ -62,4 +66,4 @@
}
return true;
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
index bc40e1b..4073ca7 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -35,10 +35,15 @@
/**
* Base class for generated EditorDelegates using a RequestFactory as the
* backend.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <P> the type of Proxy
* @param <E> the type of Editor
*/
+@Deprecated
public abstract class RequestFactoryEditorDelegate<P, E extends Editor<P>>
extends AbstractEditorDelegate<P, E> {
private class SubscriptionHandler implements
diff --git a/user/src/com/google/gwt/requestfactory/client/package-info.java b/user/src/com/google/gwt/requestfactory/client/package-info.java
index 4ca199c..2a58d05 100644
--- a/user/src/com/google/gwt/requestfactory/client/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/client/package-info.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -15,9 +15,14 @@
*/
/**
- * A package for manging client-server requests.
+ * A package for managing client-server requests.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*
* @since GWT 2.1
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.requestfactory.client;
diff --git a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
index f0e4d89..58d6652 100644
--- a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -32,10 +32,15 @@
/**
* A no-op implementation of {@link RequestFactoryEditorDriver} that records its
* inputs.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <P> the Proxy type being edited
* @param <E> the Editor type
*/
+@Deprecated
public class MockRequestFactoryEditorDriver<P, E extends Editor<P>> implements
RequestFactoryEditorDriver<P, E> {
private static final String[] EMPTY_STRING = new String[0];
diff --git a/user/src/com/google/gwt/requestfactory/client/testing/package-info.java b/user/src/com/google/gwt/requestfactory/client/testing/package-info.java
index eaa68a7..21aa40a 100644
--- a/user/src/com/google/gwt/requestfactory/client/testing/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/client/testing/package-info.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,7 +17,12 @@
/**
* Classes used for testing the request factory service.
*
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @since GWT 2.1
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.requestfactory.client.testing;
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
index 9926e0d..2598ba4 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -28,7 +28,12 @@
/**
* Generates implementations of RFEDs.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class RequestFactoryEditorDriverGenerator extends
AbstractEditorDriverGenerator {
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index 5c1d109..1384ec7 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -69,7 +69,12 @@
* Generates implementations of
* {@link com.google.gwt.requestfactory.shared.RequestFactory RequestFactory}
* and its nested interfaces.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class RequestFactoryGenerator extends Generator {
/**
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java b/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java
index e3fb75c..4519e45 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/AcceptsModelVisitor.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -17,7 +17,12 @@
/**
* A common interface for model types.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface AcceptsModelVisitor {
void accept(ModelVisitor visitor);
}
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java b/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
index 367f5b7..3eb376b 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/ContextMethod.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -25,12 +25,22 @@
/**
* Represents a service endpoint.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class ContextMethod implements AcceptsModelVisitor {
/**
* Builds a {@link ContextMethod}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class Builder {
private ContextMethod toReturn = new ContextMethod();
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
index ffcae99..5b3d789 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -20,11 +20,21 @@
/**
* Represents an EntityProxy subtype.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class EntityProxyModel implements AcceptsModelVisitor {
/**
* Builds {@link EntityProxyModel}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class Builder {
private EntityProxyModel toReturn = new EntityProxyModel();
@@ -69,7 +79,12 @@
/**
* The kind of proxy. This is an enum in case more proxy types are defined in
* the future.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public enum Type {
ENTITY, VALUE
}
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java b/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java
index a380da8..9c174ef 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/ModelVisitor.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -17,7 +17,12 @@
/**
* Implements traversal across a RequestFactory model.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class ModelVisitor {
public void endVisit(ContextMethod x) {
}
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
index 142fdfa..413bf80 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -52,7 +52,12 @@
/**
* Represents a RequestFactory interface declaration.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class RequestFactoryModel implements AcceptsModelVisitor {
static String badContextReturnType(JMethod method,
JClassType requestInterface, JClassType instanceRequestInterface) {
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java
index 867764c..42f6d29 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestMethod.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -27,12 +27,22 @@
* Represents a method declaration that causes data to be transported. This can
* be a method declared in a RequestContext or a getter or setter on an
* EntityProxy.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class RequestMethod implements AcceptsModelVisitor {
/**
- * Builds a {@link ContextMethod}.
+ * Builds a {@link RequestMethod}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class Builder {
private RequestMethod toReturn = new RequestMethod();
@@ -106,7 +116,12 @@
/**
* Indicates the type of collection that a Request will return.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public enum CollectionType {
LIST, SET, MAP
}
@@ -141,7 +156,7 @@
/**
* If the method returns a collection, this method will return the element
* type.
- *
+ *
* @return
*/
public JClassType getCollectionElementType() {
diff --git a/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java b/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
index 535239e..476c391 100644
--- a/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
+++ b/user/src/com/google/gwt/requestfactory/server/DeadEntityException.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -18,11 +18,16 @@
/**
* Indicates the user attempted to perform an operation on an irretrievable
* entity.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class DeadEntityException extends RuntimeException {
/**
* Contructs a new {@link DeadEntityException} with a given message.
- *
+ *
* @param message a message String
*/
public DeadEntityException(String message) {
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
index 77b901b..87e1883 100644
--- a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -20,10 +20,15 @@
/**
* Default implementation for handling exceptions thrown while processing a
* request. Suppresses stack traces and the exception class name.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class DefaultExceptionHandler implements ExceptionHandler {
public ServerFailure createServerFailure(Throwable throwable) {
return new ServerFailure("Server Error: "
+ (throwable == null ? null : throwable.getMessage()), null, null, true);
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
index 8735633..bcc8454 100644
--- a/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
+++ b/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -19,14 +19,19 @@
/**
* Handles an exception produced while processing a request.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see DefaultExceptionHandler
*/
+@Deprecated
public interface ExceptionHandler {
/**
* Generates a {@link ServerFailure} based on the information contained in the
* received {@code exception}.
- *
+ *
* @param throwable a Throwable instance
* @return a {@link ServerFailure} instance
* @see DefaultExceptionHandler
diff --git a/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
index 221f286..81815ff 100644
--- a/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/LocatorServiceLayer.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -31,7 +31,12 @@
/**
* Adds support to the ServiceLayer chain for using {@link Locator} and
* {@link ServiceLocator} helper objects.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
final class LocatorServiceLayer extends ServiceLayerDecorator {
@Override
diff --git a/user/src/com/google/gwt/requestfactory/server/Logging.java b/user/src/com/google/gwt/requestfactory/server/Logging.java
index cbcb1b0..e13298a 100644
--- a/user/src/com/google/gwt/requestfactory/server/Logging.java
+++ b/user/src/com/google/gwt/requestfactory/server/Logging.java
@@ -1,19 +1,18 @@
/*
* Copyright 2010 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.requestfactory.server;
import com.google.gwt.logging.server.RemoteLoggingServiceUtil;
@@ -26,7 +25,12 @@
/**
* Server side object that handles log messages sent by
* {@link com.google.gwt.requestfactory.client.RequestFactoryLogHandler}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class Logging {
private static StackTraceDeobfuscator deobfuscator = new StackTraceDeobfuscator(
@@ -34,7 +38,7 @@
/**
* Logs a message.
- *
+ *
* @param serializedLogRecordString a json serialized LogRecord, as provided by
* {@link com.google.gwt.logging.client.JsonLogRecordClientUtil.logRecordAsJsonObject(LogRecord)}
* @throws RemoteLoggingException if logging fails
@@ -58,7 +62,7 @@
/**
* This function is only for server side use which is why it's not in the
* LoggingRequest interface.
- *
+ *
* @param dir a directory, specified as a String
*/
public static void setSymbolMapsDirectory(String dir) {
@@ -71,7 +75,7 @@
/**
* Returns the id of this instance.
- *
+ *
* @return a String id
* @see #setId(String)
*/
@@ -81,7 +85,7 @@
/**
* Returns the version of this instance.
- *
+ *
* @return an Integer version number
* @see #setVersion(Integer)
*/
@@ -91,7 +95,7 @@
/**
* Sets the id on this instance.
- *
+ *
* @param id a String id
* @see #getId()
*/
@@ -101,7 +105,7 @@
/**
* Sets the version of this instance.
- *
+ *
* @param version an Integer version number
* @see #getVersion()
*/
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
index 9445f54..25f4c76 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -43,7 +43,12 @@
/**
* Implements all methods that interact with domain objects.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
final class ReflectiveServiceLayer extends ServiceLayerDecorator {
/*
* NB: All calls that ReflectiveServiceLayer makes to public APIs inherited
diff --git a/user/src/com/google/gwt/requestfactory/server/ReportableException.java b/user/src/com/google/gwt/requestfactory/server/ReportableException.java
index 292b452..ba3da8c 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReportableException.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReportableException.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,7 +17,12 @@
/**
* Encapsulates exceptions that should be thrown back to the client.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
@SuppressWarnings("serial")
class ReportableException extends RuntimeException {
public ReportableException(String msg) {
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index f558697..8837c48 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -64,7 +64,7 @@
* JVM.
* <p>
* This class is amenable to being used as a unit test:
- *
+ *
* <pre>
* public void testRequestFactory() {
* Logger logger = Logger.getLogger("");
@@ -76,18 +76,28 @@
* </pre>
* This class also has a {@code main} method and can be used as a build-time
* tool:
- *
+ *
* <pre>
* java -cp gwt-servlet.jar:your-code.jar \
* com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidator \
* com.example.MyRequestFactory
* </pre>
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class RequestFactoryInterfaceValidator {
/**
* An implementation of {@link Loader} that uses a {@link ClassLoader} to
* retrieve the class files.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public static class ClassLoaderLoader implements Loader {
private final ClassLoader loader;
@@ -106,13 +116,18 @@
/**
* Abstracts the mechanism by which class files are loaded.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see ClassLoaderLoader
*/
+ @Deprecated
public interface Loader {
/**
* Returns true if the specified resource can be loaded.
- *
+ *
* @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
*/
boolean exists(String resource);
@@ -120,7 +135,7 @@
/**
* Returns an InputStream to access the specified resource, or
* <code>null</code> if no such resource exists.
- *
+ *
* @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
*/
InputStream getResourceAsStream(String resource);
@@ -130,7 +145,12 @@
* Improves error messages by providing context for the user.
* <p>
* Visible for testing.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
static class ErrorContext {
private final Logger logger;
private final ErrorContext parent;
@@ -225,7 +245,12 @@
/**
* Used internally as a placeholder for types that cannot be mapped to a
* domain object.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
interface MissingDomainType {
}
@@ -721,7 +746,7 @@
* equivalent domain method</li>
* <li>All referenced proxy types are valid</li>
* </ul>
- *
+ *
* @param binaryName the binary name (e.g. {@link Class#getName()}) of the
* EntityProxy subtype
*/
@@ -733,7 +758,7 @@
* Determine if the specified type implements a proxy interface and apply the
* appropriate validations. This can be used as a general-purpose entry method
* when writing unit tests.
- *
+ *
* @param binaryName the binary name (e.g. {@link Class#getName()}) of the
* EntityProxy or ValueProxy subtype
*/
@@ -772,7 +797,7 @@
* equivalent domain method</li>
* <li>All referenced EntityProxy types are valid</li>
* </ul>
- *
+ *
* @param binaryName the binary name (e.g. {@link Class#getName()}) of the
* RequestContext subtype
* @see #validateEntityProxy(String)
@@ -830,7 +855,7 @@
* <li> <code>binaryName</code> implements RequestFactory</li>
* <li>All referenced RequestContext types are valid</li>
* </ul>
- *
+ *
* @param binaryName the binary name (e.g. {@link Class#getName()}) of the
* RequestContext subtype
* @see #validateRequestContext(String)
@@ -880,7 +905,7 @@
* equivalent domain method</li>
* <li>All referenced proxy types are valid</li>
* </ul>
- *
+ *
* @param binaryName the binary name (e.g. {@link Class#getName()}) of the
* EntityProxy subtype
*/
@@ -1521,7 +1546,7 @@
/**
* Load the classfile for the given binary name and apply the provided
* visitor.
- *
+ *
* @return <code>true</code> if the visitor was successfully visited
*/
private boolean visit(ErrorContext logger, String internalName,
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index cfac4b4..68a4e5a 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -29,8 +29,13 @@
import javax.servlet.http.HttpServletResponse;
/**
- * Handles GWT RequestFactory JSON requests.
+ * Handles GWT RequestFactory JSON requests.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
@SuppressWarnings("serial")
public class RequestFactoryServlet extends HttpServlet {
@@ -48,7 +53,7 @@
/**
* Returns the thread-local {@link HttpServletRequest}.
- *
+ *
* @return an {@link HttpServletRequest} instance
*/
public static HttpServletRequest getThreadLocalRequest() {
@@ -57,7 +62,7 @@
/**
* Returns the thread-local {@link HttpServletResponse}.
- *
+ *
* @return an {@link HttpServletResponse} instance
*/
public static HttpServletResponse getThreadLocalResponse() {
@@ -77,7 +82,7 @@
/**
* Use this constructor in subclasses to provide a custom
* {@link ExceptionHandler}.
- *
+ *
* @param exceptionHandler an {@link ExceptionHandler} instance
* @param serviceDecorators an array of ServiceLayerDecorators that change how
* the RequestFactory request processor interact with the domain
@@ -92,7 +97,7 @@
/**
* Processes a POST to the server.
- *
+ *
* @param request an {@link HttpServletRequest} instance
* @param response an {@link HttpServletResponse} instance
* @throws IOException if an internal I/O error occurs
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestState.java b/user/src/com/google/gwt/requestfactory/server/RequestState.java
index 520e6d7..2a29430 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestState.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestState.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -43,7 +43,12 @@
/**
* Encapsulates all state relating to the processing of a single request so that
* the SimpleRequestProcessor can be stateless.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class RequestState implements EntityCodex.EntitySource {
final IdToEntityMap beans = new IdToEntityMap();
private final Map<Object, SimpleProxyId<?>> domainObjectsToId;
@@ -292,4 +297,4 @@
}
return toReturn;
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/Resolver.java b/user/src/com/google/gwt/requestfactory/server/Resolver.java
index 5762f28..1b2ca38 100644
--- a/user/src/com/google/gwt/requestfactory/server/Resolver.java
+++ b/user/src/com/google/gwt/requestfactory/server/Resolver.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -43,9 +43,14 @@
* Responsible for converting between domain and client entities. This class has
* a small amount of temporary state used to handle graph cycles and assignment
* of synthetic ids.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see RequestState#getResolver()
*/
+@Deprecated
class Resolver {
/**
* A parameterized type with a single parameter.
@@ -183,7 +188,7 @@
/**
* Given a domain object, return a value that can be encoded by the client.
- *
+ *
* @param domainValue the domain object to be converted into a client-side
* value
* @param assignableTo the type in the client to which the resolved value
@@ -199,7 +204,7 @@
/**
* Convert a client-side value into a domain value.
- *
+ *
* @param maybeEntityProxy the client object to resolve
* @param detectDeadEntities if <code>true</code> this method will throw a
* ReportableException containing a {@link DeadEntityException} if an
@@ -447,4 +452,4 @@
throw new ReportableException("Unsupported domain type "
+ returnClass.getCanonicalName());
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java
index c0f2f72..e142ead 100644
--- a/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ResolverServiceLayer.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -33,7 +33,12 @@
/**
* Implements all of the resolution methods in ServiceLayer.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
final class ResolverServiceLayer extends ServiceLayerDecorator {
private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
index 753fe1d..300d24e 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -35,7 +35,12 @@
* logic can be decorated by extending an {@link ServiceLayerDecorator}.
* <p>
* This API is subject to change in future releases.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public abstract class ServiceLayer {
/*
* NB: This type cannot be directly extended by the user since it has a
@@ -53,7 +58,7 @@
/**
* Create a RequestFactory ServiceLayer that is optionally modified by the
* given decorators.
- *
+ *
* @param decorators the decorators that will modify the behavior of the core
* service layer implementation
* @return a ServiceLayer instance
@@ -101,7 +106,7 @@
/**
* Create an instance of the requested domain type.
- *
+ *
* @param <T> the requested domain type
* @param clazz the requested domain type
* @return an instance of the requested domain type
@@ -110,7 +115,7 @@
/**
* Create an instance of the requested {@link Locator} type.
- *
+ *
* @param <T> the requested Locator type
* @param clazz the requested Locator type
* @return an instance of the requested Locator type
@@ -120,7 +125,7 @@
/**
* Create an instance of a service object that can be used as the target for
* the given method invocation.
- *
+ *
* @param contextMethod a method defined in a RequestContext
* @param domainMethod the method that the service object must implement
* @return an instance of the requested service object
@@ -138,7 +143,7 @@
/**
* Determine the method to invoke when retrieving the given property.
- *
+ *
* @param domainType a domain entity type
* @param property the name of the property to be retrieved
* @return the Method that should be invoked to retrieve the property or
@@ -154,7 +159,7 @@
* <p>
* The values returned from this method may be passed to
* {@link #loadDomainObject(Class, Object)} in the future.
- *
+ *
* @param domainObject a domain object
* @return the persistent id of the domain object or {@code null} if the
* object is not persistent
@@ -165,7 +170,7 @@
* Returns the type of object the domain type's {@code findFoo()} or
* {@link com.google.gwt.requestfactory.shared.Locator#getId(Object)
* Locator.getId()} expects to receive.
- *
+ *
* @param domainType a domain entity type
* @return the type of the persistent id value used to represent the domain
* type
@@ -174,7 +179,7 @@
/**
* Retrieve the named property from the domain object.
- *
+ *
* @param domainObject the domain object being examined
* @param property the property name
* @return the value of the property
@@ -189,7 +194,7 @@
/**
* Determine the method to invoke when setting the given property.
- *
+ *
* @param domainType a domain entity type
* @param property the name of the property to be set
* @return the Method that should be invoked to set the property or
@@ -202,7 +207,7 @@
* persisted. The value returned from this method must be a simple type (e.g.
* Integer, String) or a domain type for which a mapping to an EntityProxy or
* Value proxy exists.
- *
+ *
* @param domainObject a domain object
* @return the version of the domain object or {@code null} if the object is
* not persistent
@@ -212,7 +217,7 @@
/**
* Invoke a domain service method. The underlying eventually calls
* {@link Method#invoke(Object, Object...)}.
- *
+ *
* @param domainMethod the method to invoke
* @param args the arguments to pass to the method
* @return the value returned from the method invocation
@@ -222,7 +227,7 @@
/**
* Returns {@code true} if the given domain object is still live (i.e. not
* deleted) in the backing store.
- *
+ *
* @param domainObject a domain entity
* @return {@code true} if {@code domainObject} could be retrieved at a later
* point in time
@@ -232,7 +237,7 @@
/**
* Load an object from the backing store. This method may return {@code null}
* to indicate that the requested object is no longer available.
- *
+ *
* @param <T> the type of object to load
* @param clazz the type of object to load
* @param domainId an id previously returned from {@link #getId(Object)}
@@ -247,7 +252,7 @@
* <p>
* The default implementation of this method will delegate to
* {@link #loadDomainObject(Class, Object)}.
- *
+ *
* @param classes type type of each object to load
* @param domainIds the ids previously returned from {@link #getId(Object)}
* @return the requested objects, elements of which may be {@code null} if the
@@ -259,7 +264,7 @@
* Determines if the invocation of a domain method requires a
* {@link ServiceLocator} as the 0th parameter when passed into
* {@link #invoke(Method, Object...)}.
- *
+ *
* @param contextMethod a method defined in a RequestContext
* @param domainMethod a domain method
* @return {@code true} if a ServiceLocator is required
@@ -270,7 +275,7 @@
* Given a type token previously returned from
* {@link #resolveTypeToken(Class)}, return the Class literal associated with
* the token.
- *
+ *
* @param typeToken a string token
* @return the type represented by the token
*/
@@ -280,7 +285,7 @@
* Determine the type used by the client code to represent a given domain
* type. If multiple proxy types have been mapped to the same domain type, the
* {@code clientType} parameter is used to ensure assignability.
- *
+ *
* @param domainClass the server-side type to be transported to the client
* @param clientType the type to which the returned type must be assignable
* @param required if {@code true} and no mapping is available, throw an
@@ -295,7 +300,7 @@
/**
* Determine the domain (server-side) type that the given client type is
* mapped to.
- *
+ *
* @param clientType a client-side type
* @return the domain type that {@code clientType} represents
*/
@@ -305,7 +310,7 @@
* Return the domain service method associated with a RequestContext method
* declaration. The {@code requestContextMethod} will have been previously
* resolved by {@link #resolveRequestContextMethod(String, String)}.
- *
+ *
* @param requestContextMethod a RequestContext method declaration.
* @return the domain service method that should be invoked
*/
@@ -314,7 +319,7 @@
/**
* Return the type of {@link Locator} that should be used to access the given
* domain type.
- *
+ *
* @param domainType a domain (server-side) type
* @return the type of Locator to use, or {@code null} if the type conforms to
* the RequestFactory entity protocol
@@ -323,7 +328,7 @@
/**
* Find a RequestContext method declaration by name.
- *
+ *
* @param requestContextClass the fully-qualified binary name of the
* RequestContext
* @param methodName the name of the service method declared within the
@@ -347,7 +352,7 @@
* {@link ServiceLocator} that should be used when invoking the domain method.
* This method will only be called if {@link #requiresServiceLocator(Method)}
* returned {@code true} for the associated domain method.
- *
+ *
* @param contextMethod a RequestContext method declaration
* @param domainMethod the domain method that will be invoked
* @return the type of ServiceLocator to use
@@ -357,7 +362,7 @@
/**
* Return a string used to represent the given type in the wire protocol.
- *
+ *
* @param proxyType a client-side EntityProxy or ValueProxy type
* @return the type token used to represent the proxy type
*/
@@ -365,7 +370,7 @@
/**
* Sets a property on a domain object.
- *
+ *
* @param domainObject the domain object to operate on
* @param property the name of the property to set
* @param expectedType the type of the property
@@ -377,7 +382,7 @@
/**
* Invoke a JSR 303 validator on the given domain object. If no validator is
* available, this method is a no-op.
- *
+ *
* @param <T> the type of data being validated
* @param domainObject the domain objcet to validate
* @return the violations associated with the domain object
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
index 201fc62..097345a 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerCache.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -31,7 +31,12 @@
* A cache for idempotent methods in {@link ServiceLayer}. The caching is
* separate from {@link ReflectiveServiceLayer} so that the cache can be applied
* to any decorators injected by the user.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class ServiceLayerCache extends ServiceLayerDecorator {
/**
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
index d93f359..9dfe5dd 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -36,7 +36,12 @@
* {@link ServiceLayer#create(ServiceLayerDecorator...)}. The methods defined in
* this type will automatically delegate to the next decorator or the root
* service object after being processed by{@code create()}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class ServiceLayerDecorator extends ServiceLayer {
private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
@@ -187,7 +192,7 @@
* should be used to provide diagnostic information that will help the
* end-developer track down problems when that data would expose
* implementation details of the server to the client.
- *
+ *
* @param e a throwable with more data, may be {@code null}
* @param message a printf-style format string
* @param args arguments for the message
@@ -205,7 +210,7 @@
* should use the instance provided by {@code getTop()} when calling public
* methods on the ServiceLayer API to allow higher-level decorators to
* override behaviors built into lower-level decorators.
- *
+ *
* @return the ServiceLayer returned by
* {@link #create(ServiceLayerDecorator...)}
*/
@@ -216,7 +221,7 @@
/**
* Report an exception thrown by code that is under the control of the
* end-developer.
- *
+ *
* @param an {@link InvocationTargetException} thrown by an invocation of
* user-provided code
* @throws ReportableException this method never returns normally
@@ -229,7 +234,7 @@
/**
* Return a message to the client. This method should not include any data
* that was not sent to the server by the client to avoid leaking data.
- *
+ *
* @param msg a printf-style format string
* @param args arguments for the message
* @throws ReportableException this method never returns normally
diff --git a/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java b/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
index ebd6299..b8951c2 100644
--- a/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
+++ b/user/src/com/google/gwt/requestfactory/server/SignatureAdapter.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -21,7 +21,12 @@
* An empty implementation of SignatureVisitor, used by
* {@link RequestFactoryInterfaceValidator}. This is a copy of the dev package's
* EmptySignatureVisitor.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class SignatureAdapter implements SignatureVisitor {
private static final SignatureAdapter ignore = new SignatureAdapter();
@@ -82,4 +87,4 @@
public void visitTypeVariable(String name) {
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
index 5eeb6b8..21934f1 100644
--- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -64,12 +64,22 @@
/**
* Processes request payloads from a RequestFactory client. This implementation
* is stateless. A single instance may be reused and is thread-safe.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class SimpleRequestProcessor {
/**
* This parameterization is so long, it improves readability to have a
* specific type.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
@SuppressWarnings("serial")
static class IdToEntityMap extends
HashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> {
@@ -113,7 +123,7 @@
/**
* Process a payload sent by a RequestFactory client.
- *
+ *
* @param payload the payload sent by the client
* @return a payload to return to the client
*/
diff --git a/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java b/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
index f6cac79..9b1788b 100644
--- a/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
+++ b/user/src/com/google/gwt/requestfactory/server/UnexpectedException.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -18,7 +18,12 @@
/**
* Encapsulates exceptions that indicate something went wrong in RequestFactory
* code.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class UnexpectedException extends RuntimeException {
public UnexpectedException(String msg, Throwable cause) {
super(msg, cause);
diff --git a/user/src/com/google/gwt/requestfactory/server/impl/FindService.java b/user/src/com/google/gwt/requestfactory/server/impl/FindService.java
index 8f467e1..8e337eb 100644
--- a/user/src/com/google/gwt/requestfactory/server/impl/FindService.java
+++ b/user/src/com/google/gwt/requestfactory/server/impl/FindService.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,12 +17,17 @@
/**
* Server side service to support a generic find method.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class FindService {
/**
* For now, a simple implementation of find will work.
- *
+ *
* @param entityInstance an entity instance
* @return the passed-in entity instance
*/
diff --git a/user/src/com/google/gwt/requestfactory/server/package-info.java b/user/src/com/google/gwt/requestfactory/server/package-info.java
index 6d9ddab..70cf54c 100644
--- a/user/src/com/google/gwt/requestfactory/server/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/server/package-info.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,13 +17,17 @@
/**
* Server side classes for mediating between the client side and the persistent
* datastore.
- *
+ *
* This package contains classes that can receive client side read and write
* requests in the JSON format, perform the necessary operations on the
* persistent datastore, and return the results in JSON format.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @since GWT 2.1
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.requestfactory.server;
-
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
index 2f74d22..01220ee 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestContext.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -39,7 +39,12 @@
/**
* An in-process implementation of RequestContext
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class InProcessRequestContext extends AbstractRequestContext {
class RequestContextHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, final Object[] args)
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
index 836fb1a..ffc64fe 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -41,7 +41,12 @@
/**
* A JRE-compatible implementation of RequestFactory.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class InProcessRequestFactory extends AbstractRequestFactory {
@Category(value = {
EntityProxyCategory.class, ValueProxyCategory.class,
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
index b215b4f..4cd4f39 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestTransport.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,7 +23,7 @@
* A RequesTransports that calls a {@link SimpleRequestProcessor}. This test can
* be used for end-to-end tests of RequestFactory service methods in non-GWT
* test suites.
- *
+ *
* <pre>
* ServiceLayer serviceLayer = ServiceLayer.create();
* SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
@@ -31,13 +31,18 @@
* MyRequestFactory f = RequestFactoryMagic.create(MyRequestFactory.class);
* f.initialize(eventBus, new InProcessRequestTransport(processor));
* </pre>
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see RequestFactoryMagic
* @see com.google.gwt.requestfactory.server.ServiceLayer#create(com.google.gwt.requestfactory.server.ServiceLayerDecorator...)
* ServiceLayer.create()
* @see com.google.gwt.event.shared.SimpleEventBus SimpleEventBus
* @see SimpleRequestProcessor
*/
+@Deprecated
public class InProcessRequestTransport implements RequestTransport {
private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
private final SimpleRequestProcessor processor;
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java b/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
index 3d1286d..706eebf 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/RequestFactoryMagic.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -22,7 +22,14 @@
/**
* Create JRE-compatible instances of a RequestFactory interface.
+ *
+ * @deprecated Replaced by {@link com.google.web.bindery.vm.RequestFactorySource}
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class RequestFactoryMagic {
/**
* Create an instance of a RequestFactory. The returned RequestFactory must be
@@ -31,7 +38,7 @@
* RequestTransport} via the
* {@link RequestFactory#initialize(com.google.gwt.event.shared.EventBus, com.google.gwt.requestfactory.shared.RequestTransport)
* initialize(EventBus, RequestTransport} method.
- *
+ *
* @param <T> the RequestFactory type
* @param requestFactory the RequestFactory type
* @return an instance of the RequestFactory type
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/package-info.java b/user/src/com/google/gwt/requestfactory/server/testing/package-info.java
index 23f9f88..7b3b710 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/package-info.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,7 +17,12 @@
/**
* Classes used for testing the request factory service.
*
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @since GWT 2.1.1
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.requestfactory.server.testing;
diff --git a/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java b/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
index 3764fc0..3944fa4 100644
--- a/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -19,9 +19,14 @@
* The root type from which all client-side proxy objects are derived. Users
* should not extend the BaseProxy type directly, but use either
* {@link EntityProxy} or {@link ValueProxy}.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see EntityProxy
* @see ValueProxy
*/
+@Deprecated
public interface BaseProxy {
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/DefaultProxyStore.java b/user/src/com/google/gwt/requestfactory/shared/DefaultProxyStore.java
index 26d9eef..632d034 100644
--- a/user/src/com/google/gwt/requestfactory/shared/DefaultProxyStore.java
+++ b/user/src/com/google/gwt/requestfactory/shared/DefaultProxyStore.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -27,7 +27,12 @@
/**
* An in-memory ProxyStore store that can encode its state as a JSON object
* literal.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class DefaultProxyStore implements ProxyStore {
/**
* Provide a little bit of future-proofing to at least detect an encoded
@@ -52,7 +57,7 @@
/**
* Construct a DefaultProxyStore using the a value returned from
* {@link #encode()}.
- *
+ *
* @param payload a String previously returned from {@link #encode()}
* @throws IllegalArgumentException if the payload cannot be parsed
*/
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
index 4bb312d..9e73822 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,7 +17,12 @@
/**
* A proxy for a server-side domain object.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface EntityProxy extends BaseProxy {
/**
* Returns the {@link EntityProxyId} that identifies a particular instance of
@@ -30,7 +35,7 @@
* Subtypes should override to declare they return a stable id of their own
* type, to allow type safe use of the request objects returned by
* {@link RequestFactory#find(EntityProxyId)}.
- *
+ *
* @return an {@link EntityProxyId} instance
*/
EntityProxyId<?> stableId();
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxyChange.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxyChange.java
index 1c59370..64e08b9 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxyChange.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxyChange.java
@@ -31,8 +31,13 @@
* <p>
* TODO: use ProxyId rather than an empty proxy
*
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <P> the type of the proxy
*/
+@Deprecated
public class EntityProxyChange<P extends EntityProxy> extends
GwtEvent<EntityProxyChange.Handler<P>> {
@@ -40,7 +45,12 @@
* Implemented by methods that handle EntityProxyChange events.
*
* @param <P> the proxy type
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public interface Handler<P extends EntityProxy> extends EventHandler {
/**
* Called when an {@link EntityProxyChange} event is fired.
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
index a64e390..e56818f 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -21,15 +21,20 @@
* <p>
* In particular, an {@link EntityProxy} foo that is yet to be persisted and a
* copy of foo after being persisted have equal {@link EntityProxyId}.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <P> the entity type
*/
+@Deprecated
@ProxyFor(Object.class)
public interface EntityProxyId<P extends EntityProxy> {
/**
* Returns the class of the proxy identified.
- *
+ *
* @return a Class object of type P
*/
Class<P> getProxyClass();
diff --git a/user/src/com/google/gwt/requestfactory/shared/InstanceRequest.java b/user/src/com/google/gwt/requestfactory/shared/InstanceRequest.java
index 27e5055..a13be4d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/InstanceRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/InstanceRequest.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -19,15 +19,20 @@
* Used to call instance methods. Note that this does not extend {@link Request}
* — rather it vends one. There is no way to get an instance method's
* {@link Request#fire} without assigning an instance by calling {@link #using}.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <P> the instance type of BaseProxy
* @param <T> the type eventually returned by the method invocation
*/
+@Deprecated
public interface InstanceRequest<P extends BaseProxy, T> {
/**
* Provide the instance on which the request will be invoked.
- *
+ *
* @param instanceObject an {@link BaseProxy} instance of type P
* @return an instance of {@link Request}<T>
*/
diff --git a/user/src/com/google/gwt/requestfactory/shared/JsonRpcContent.java b/user/src/com/google/gwt/requestfactory/shared/JsonRpcContent.java
index 7fe081e..0dd5e05 100644
--- a/user/src/com/google/gwt/requestfactory/shared/JsonRpcContent.java
+++ b/user/src/com/google/gwt/requestfactory/shared/JsonRpcContent.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -25,8 +25,13 @@
* declaration to indicate that a particular parameter is used as the
* {@code request} portion of the JSON-RPC request. This is analogous to the
* payload body in a REST-style request.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
+@Deprecated
public @interface JsonRpcContent {
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/JsonRpcProxy.java b/user/src/com/google/gwt/requestfactory/shared/JsonRpcProxy.java
index 5cb7300..27f9222 100644
--- a/user/src/com/google/gwt/requestfactory/shared/JsonRpcProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/JsonRpcProxy.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -18,6 +18,11 @@
/**
* <b>Experimental API, subject to change</b> Used instead of the
* {@link ProxyFor} annotation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public @interface JsonRpcProxy {
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/JsonRpcService.java b/user/src/com/google/gwt/requestfactory/shared/JsonRpcService.java
index e147b72..3b28a23 100644
--- a/user/src/com/google/gwt/requestfactory/shared/JsonRpcService.java
+++ b/user/src/com/google/gwt/requestfactory/shared/JsonRpcService.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -23,8 +23,13 @@
/**
* <b>Experimental API, subject to change</b> Indicates that a RequestContext
* should be encoded as a JSON-RPC request.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
+@Deprecated
public @interface JsonRpcService {
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/JsonRpcWireName.java b/user/src/com/google/gwt/requestfactory/shared/JsonRpcWireName.java
index 36449c4..9f506a6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/JsonRpcWireName.java
+++ b/user/src/com/google/gwt/requestfactory/shared/JsonRpcWireName.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -21,10 +21,15 @@
/**
* <b>Experimental API, subject to change</b> Provides the method name for a
* JSON-RPC invocation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonRpcWireName {
String value();
String version() default "";
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Locator.java b/user/src/com/google/gwt/requestfactory/shared/Locator.java
index 3516984..c031813 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Locator.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Locator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -25,16 +25,22 @@
* Locator subtypes must be default instantiable (i.e. public static types with
* a no-arg constructor). Instances of Locators may be retained and reused by
* the RequestFactory service layer.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> the type of domain object the Locator will operate on
* @param <I> the type of object the Locator expects to use as an id for the
* domain object
+ *
* @see ProxyFor#locator()
*/
+@Deprecated
public abstract class Locator<T, I> {
/**
* Create a new instance of the requested type.
- *
+ *
* @param clazz the type of object to create
* @return the new instance of the domain type
*/
@@ -43,7 +49,7 @@
/**
* Retrieve an object. May return {@code null} to indicate that the requested
* object could not be found.
- *
+ *
* @param clazz the type of object to retrieve
* @param id an id previously returned from {@link #getId(Object)}
* @return the requested object or {@code null} if it could not be found
@@ -59,7 +65,7 @@
* Returns a domain object to be used as the id for the given object. This
* method may return {@code null} if the object has not been persisted or
* should be treated as irretrievable.
- *
+ *
* @param domainObject the object to obtain an id for
* @return the object's id or {@code null}
*/
@@ -74,7 +80,7 @@
* Returns a domain object to be used as the version for the given object.
* This method may return {@code null} if the object has not been persisted or
* should be treated as irretrievable.
- *
+ *
* @param domainObject the object to obtain an id for
* @return the object's version or {@code null}
*/
diff --git a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
index 776d9f4..5ba468a 100644
--- a/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/LoggingRequest.java
@@ -1,19 +1,18 @@
/*
* Copyright 2010 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.requestfactory.shared;
import com.google.gwt.requestfactory.server.Logging;
@@ -21,7 +20,12 @@
/**
* "API Generated" request selector interface implemented by objects that give
* client access to the methods of {@link Logging}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
@Service(Logging.class)
public interface LoggingRequest extends RequestContext {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
index 38f6df6..2c8d7c7 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyFor.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,9 +23,14 @@
/**
* Annotation on EntityProxy and ValueProxy classes specifying the domain
* (server-side) object type.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see ProxyForName
*/
+@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ProxyFor {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
index 5eb5594..843e791 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyForName.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -24,7 +24,12 @@
* Annotation on EntityProxy classes specifying the domain (server-side) object
* type. This annotation can be used in place of {@link ProxyFor} if the domain
* object is not available to the GWT compiler or DevMode runtime.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ProxyForName {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxySerializer.java b/user/src/com/google/gwt/requestfactory/shared/ProxySerializer.java
index 3cfcafd..a55f2f7 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxySerializer.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxySerializer.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -25,11 +25,11 @@
* are not stable.
* <p>
* To create a self-contained message that encapsulates a proxy:
- *
+ *
* <pre>
* RequestFactory myFactory = ...;
* MyFooProxy someProxy = ...;
- *
+ *
* DefaultProxyStore store = new DefaultProxyStore();
* ProxySerializer ser = myFactory.getSerializer(store);
* // More than one proxy could be serialized
@@ -37,26 +37,31 @@
* // Create the flattened representation
* String payload = store.encode();
* </pre>
- *
+ *
* To recreate the object:
- *
+ *
* <pre>
* ProxyStore store = new DefaultProxyStore(payload);
* ProxySerializer ser = myFactory.getSerializer(store);
* MyFooProxy someProxy = ser.deserialize(MyFooProxy.class, key);
* </pre>
- *
+ *
* If two objects refer to different EntityProxy instances that have the same
* stableId(), the last mutable proxy encountered will be preferred, otherwise
* the first immutable proxy will be used.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see DefaultProxyStore
*/
+@Deprecated
public interface ProxySerializer {
/**
* Recreate a proxy instance that was previously passed to
* {@link #serialize(BaseProxy)}.
- *
+ *
* @param <T> the type of proxy object to create
* @param proxyType the type of proxy object to create
* @param key a value previously returned from {@link #serialize(BaseProxy)}
@@ -68,7 +73,7 @@
/**
* Recreate a {@link EntityProxy} instance that was previously passed to
* {@link #serialize(BaseProxy)}.
- *
+ *
* @param <T> the type of proxy object to create
* @param id the {@link EntityProxyId} of the desired entity
* @return a new, immutable instance of the proxy or {@code null} if the data
@@ -78,7 +83,7 @@
/**
* Store a proxy into the backing store.
- *
+ *
* @param proxy the proxy to store
* @return a key value that can be passed to
* {@link #deserialize(Class, String)}
diff --git a/user/src/com/google/gwt/requestfactory/shared/ProxyStore.java b/user/src/com/google/gwt/requestfactory/shared/ProxyStore.java
index 412b070..0ea58ce 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ProxyStore.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ProxyStore.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -22,14 +22,19 @@
* persistence mechanism. The ProxyStore does not need to be able to interpret
* the data sent to it by the ProxySerializer; it is merely a wrapper around a
* persistence mechanism.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see DefaultProxyStore
*/
+@Deprecated
public interface ProxyStore {
/**
* Called by {@link ProxySerializer} to retrieve a value previously provided
* to {@link #put(String, Splittable)}.
- *
+ *
* @param key the key
* @return the associated value or {@code null} if {@code key} is unknown
*/
@@ -44,10 +49,10 @@
/**
* Called by {@link ProxySerializer} to store a value.
- *
+ *
* @param key a key value that will be passed to {@link #get(String)}
* @param value the data to store
* @see Splittable#getPayload()
*/
void put(String key, Splittable value);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Receiver.java b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
index a1a4fc6..fa44d8d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Receiver.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -20,15 +20,20 @@
/**
* Callback object for {@link Request#fire(Receiver)} and
* {@link RequestContext#fire(Receiver)}.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <V> value type
*/
+@Deprecated
public abstract class Receiver<V> {
/**
* Receives general failure notifications. The default implementation looks at
* {@link ServerFailure#isFatal()}, and throws a runtime exception with the
* failure object's error message if it is true.
- *
+ *
* @param error a {@link ServerFailure} instance
*/
public void onFailure(ServerFailure error) {
@@ -39,7 +44,7 @@
/**
* Called when a Request has been successfully executed on the server.
- *
+ *
* @param response a response of type V
*/
public abstract void onSuccess(V response);
@@ -48,7 +53,7 @@
* Called if an object sent to the server could not be validated. The default
* implementation calls {@link #onFailure(ServerFailure)} if <code>errors
* </code> is not empty.
- *
+ *
* @param errors a Set of {@link Violation} instances
*/
public void onViolation(Set<Violation> errors) {
diff --git a/user/src/com/google/gwt/requestfactory/shared/Request.java b/user/src/com/google/gwt/requestfactory/shared/Request.java
index d8190d0..cee9753 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Request.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Request.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,9 +17,14 @@
/**
* Implemented by the request objects created by this factory.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> The return type of objects in the corresponding response.
*/
+@Deprecated
public interface Request<T> {
/**
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestContext.java b/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
index c5ad7af..ceaf7d9 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,13 +17,18 @@
/**
* The base interface for RequestFactory service endpoints.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface RequestContext {
/**
* Returns a new mutable proxy that this request can carry to the server,
* perhaps to be persisted. If the object is succesfully persisted, a PERSIST
* event will be posted including the EntityProxyId of this proxy.
- *
+ *
* @param clazz a Class object of type T
* @return an {@link BaseProxy} instance of type T
*/
@@ -33,7 +38,7 @@
* Returns a mutable version of the proxy, whose mutations will accumulate in
* this context. Proxies reached via getters on this mutable proxy will also
* be mutable.
- *
+ *
* @param object an instance of type T
* @return an {@link EntityProxy} or {@link ValueProxy} instance of type T
*/
@@ -51,7 +56,7 @@
/**
* For receiving errors or validation failures only.
- *
+ *
* @param receiver a {@link Receiver} instance
* @throws IllegalArgumentException if <code>receiver</code> is
* <code>null</code>
@@ -63,7 +68,7 @@
* context. Note that vacuous changes — e.g. foo.setName(foo.getName()
* — will not trip the changed flag. Similarly, "unmaking" a change will
* clear the isChanged flag
- *
+ *
* <pre>
* String name = bar.getName();
* bar.setName("something else");
@@ -71,7 +76,7 @@
* bar.setName(name);
* assertFalse(context.isChanged());
* </pre>
- *
+ *
* @return {@code true} if any changes have been made
*/
boolean isChanged();
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index 16d116c..ea2fca5 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -39,9 +39,14 @@
* effect, treating it as an instance of the supertype. Returning abstract
* supertypes of value types is not supported (e.g. Object, Enum, Number).
* </p>
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see com.google.gwt.requestfactory.server.testing.InProcessRequestTransport
*/
+@Deprecated
public interface RequestFactory {
/**
* The JSON content type String.
@@ -50,7 +55,7 @@
/**
* Return a request to find a fresh instance of the referenced proxy.
- *
+ *
* @param proxyId an {@link EntityProxyId} instance of type P
* @return a {@link Request} object
*/
@@ -59,7 +64,7 @@
/**
* Returns the event bus this factory's events are posted on, which was set
* via {@link #initialize}.
- *
+ *
* @return the {@link EventBus} associated with this instance
*/
EventBus getEventBus();
@@ -68,7 +73,7 @@
* Get a {@link com.google.gwt.user.client.History} compatible token that
* represents the given class. It can be processed by
* {@link #getProxyClass(String)}
- *
+ *
* @param clazz a Class object for an {@link EntityProxy} subclass
* @return a {@link com.google.gwt.user.client.History} compatible token
*/
@@ -87,7 +92,7 @@
* operation. In other words, the "future" history token returned for an
* as-yet-unpersisted EntityProxy is only valid for the duration of the
* RequestFactory's lifespan.
- *
+ *
* @param proxy an {@link EntityProxyId} instance
* @return a {@link com.google.gwt.user.client.History} compatible token
*/
@@ -98,7 +103,7 @@
* type of this token, via {@link RequestContext#create}. The token may
* represent either a proxy instance (see {@link #getHistoryToken}) or a proxy
* class (see {@link #getProxyClass}).
- *
+ *
* @param historyToken a String token
* @return a Class object for an {@link EntityProxy} subclass
*/
@@ -107,7 +112,7 @@
/**
* Return the appropriate {@link EntityProxyId} using a string returned from
* {@link #getHistoryToken(EntityProxyId)}.
- *
+ *
* @param historyToken a String token
* @return an {@link EntityProxyId}
*/
@@ -116,7 +121,7 @@
/**
* Returns a ProxySerializer that can encode and decode the various
* EntityProxy and ValueProxy types reachable from the RequestFactory.
- *
+ *
* @param store a helper object for the ProxySerializer to provide low-level
* storage access
* @return a new ProxySerializer
@@ -127,14 +132,14 @@
/**
* Start this request factory with a
* {@link com.google.gwt.requestfactory.client.DefaultRequestTransport}.
- *
+ *
* @param eventBus an {@link EventBus}
*/
void initialize(EventBus eventBus);
/**
* Start this request factory with a user-provided transport.
- *
+ *
* @param eventBus an {@link EventBus}
* @param transport a {@link RequestTransport} instance
*/
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java b/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
index b3947cb..a12118a 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestTransport.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -18,24 +18,34 @@
/**
* Abstracts the mechanism by which a RequestFactory instance transmits its
* payload to the backend.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see com.google.gwt.requestfactory.client.DefaultRequestTransport
*/
+@Deprecated
public interface RequestTransport {
/**
* A callback interface.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public interface TransportReceiver {
/**
* Called when the transmission succeeds.
- *
+ *
* @param payload the String payload
*/
void onTransportSuccess(String payload);
/**
* Called to report a transmission failure as a ServerFailure.
- *
+ *
* @param message the String error message
*/
void onTransportFailure(ServerFailure failure);
@@ -43,7 +53,7 @@
/**
* Called by the RequestFactory implementation.
- *
+ *
* @param payload the String payload
* @param receiver a {@link TransportReceiver} instance
*/
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
index 91f2fa8..ba8016a 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -21,7 +21,12 @@
* This error reporting mechanism is adequate at best. When RequestFactory is
* extended to handle polymorphic types, this class will likely be replaced with
* something more expressive.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class ServerFailure {
private final String message;
private final String stackTraceString;
@@ -44,7 +49,7 @@
/**
* Constructs a ServerFailure object.
- *
+ *
* @param message a String containing the failure message
* @param exceptionType a String containing the exception type
* @param stackTraceString a String containing the stack trace
@@ -59,7 +64,7 @@
/**
* Return the exception type.
- *
+ *
* @return the exception type as a String
*/
public String getExceptionType() {
@@ -68,7 +73,7 @@
/**
* Return the failure message.
- *
+ *
* @return the message as a String
*/
public String getMessage() {
@@ -77,7 +82,7 @@
/**
* Return the failure stack trace.
- *
+ *
* @return the stack trace as a String
*/
public String getStackTraceString() {
@@ -87,7 +92,7 @@
/**
* Return true if this is a fatal error. The default implementation of
* {@link Receiver#onFailure} throws a runtime exception for fatal failures.
- *
+ *
* @return whether this is a fatal failure
*/
public boolean isFatal() {
diff --git a/user/src/com/google/gwt/requestfactory/shared/Service.java b/user/src/com/google/gwt/requestfactory/shared/Service.java
index de14983..c88701c 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Service.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Service.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,9 +23,14 @@
/**
* Annotation on Request classes specifying the server side implementations that
* back them.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see ServiceName
*/
+@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Service {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java b/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java
index dfa841d..a6449da 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ServiceLocator.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,13 +23,18 @@
* ServiceLocator subtypes must be default instantiable (i.e. public static
* types with a no-arg constructor). Instances of ServiceLocators may be
* retained and reused by the RequestFactory service layer.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @see Service#locator()
*/
+@Deprecated
public interface ServiceLocator {
/**
* Returns an instance of the service object.
- *
+ *
* @param clazz the requested type of service object
* @return an instance of the service object
*/
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServiceName.java b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
index 464c032..1f26404 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ServiceName.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -24,7 +24,12 @@
* Annotation on Request classes specifying the server side implementations that
* back them.This annotation can be used in place of {@link Service} if the
* service type is not available to the GWT compiler or DevMode runtime.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ServiceName {
diff --git a/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java b/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
index 2882b3e..052f672 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -18,6 +18,11 @@
/**
* An analog to EntityProxy for domain types that do not have an identity
* concept.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface ValueProxy extends BaseProxy {
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Violation.java b/user/src/com/google/gwt/requestfactory/shared/Violation.java
index 23466b0..fab366d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Violation.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Violation.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -18,19 +18,24 @@
/**
* A lightweight representation of a
* {@link javax.validation.ConstraintViolation}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface Violation {
/**
* If the ConstraintViolation occurred while validating a object, this method
* will return a BaseProxy that contains the invalid values.
- *
+ *
* @return the BaseProxy that caused the ConstraintViolation
*/
BaseProxy getInvalidProxy();
/**
* Returns the message associated with this {@link Violation}.
- *
+ *
* @return a String message
*/
String getMessage();
@@ -39,7 +44,7 @@
* If the ConstraintViolation occurred while validating a value object that
* originated from the server, this method will return a BaseProxy that
* contains the original values.
- *
+ *
* @return the BaseProxy originally sent by the server or {@code null} if the
* BaseProxy was created on the client.
*/
@@ -47,7 +52,7 @@
/**
* Returns the path associated with this {@link Violation}.
- *
+ *
* @return a String path
*/
String getPath();
@@ -55,7 +60,7 @@
/**
* Returns the proxy id associated with this {@link Violation} if the object
* associated with the violation is an {@link EntityProxy}.
- *
+ *
* @return an {@link EntityProxyId} instance or {@code null} if the object is
* a ValueProxy.
*/
diff --git a/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java b/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
index 89b5d9c..a10fea0 100644
--- a/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
+++ b/user/src/com/google/gwt/requestfactory/shared/WriteOperation.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -25,7 +25,12 @@
* <li>A DELETE event is fired after a proxy that was deleted on the client is
* deleted on the server as well.
* </ul>
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public enum WriteOperation {
PERSIST("PERSIST"), UPDATE("UPDATE"), DELETE("DELETE");
@@ -40,10 +45,10 @@
/**
* Returns the unobfuscated name of the event associated with this
* {@link WriteOperation}.
- *
+ *
* @return one of "PERSIST", "UPDATE", or "DELETE"
*/
public String getUnObfuscatedEnumName() {
return this.unObfuscatedEnumName;
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
index 8524524..c68b081 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequest.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -32,9 +32,14 @@
/**
* Abstract implementation of {@link Request}. Each request stores a
* {@link DeltaValueStoreJsonImpl}.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> return type
*/
+@Deprecated
public abstract class AbstractRequest<T> implements Request<T>,
InstanceRequest<BaseProxy, T> {
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
index 3d53b91..c906ac8 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -66,14 +66,24 @@
/**
* Base implementations for RequestContext services.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public abstract class AbstractRequestContext implements RequestContext,
EntityCodex.EntitySource {
/**
* Allows the payload dialect to be injected into the AbstractRequestContext
* without the caller needing to be concerned with how the implementation
* object is instantiated.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public enum Dialect {
STANDARD {
@Override
@@ -90,6 +100,12 @@
abstract DialectImpl create(AbstractRequestContext context);
}
+ /*
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ */
+ @Deprecated
interface DialectImpl {
void addInvocation(AbstractRequest<?> request);
@@ -515,7 +531,7 @@
public boolean isChanged() {
/*
* NB: Don't use the presence of ephemeral objects for this test.
- *
+ *
* Diff the objects until one is found to be different. It's not just a
* simple flag-check because of the possibility of "unmaking" a change, per
* the JavaDoc.
@@ -730,7 +746,7 @@
/**
* Create a new EntityProxy from a snapshot in the return payload.
- *
+ *
* @param id the EntityProxyId of the object
* @param returnRecord the JSON map containing property/value pairs
* @param operations the WriteOperation eventns to broadcast over the EventBus
@@ -808,7 +824,7 @@
/**
* Get-or-create method for synthetic ids.
- *
+ *
* @see #syntheticIds
*/
private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
index 3c26cf4..b52a4e6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -31,7 +31,12 @@
/**
* Base type for generated RF interfaces.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public abstract class AbstractRequestFactory extends IdFactory implements
RequestFactory {
private static final int MAX_VERSION_ENTRIES = 10000;
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
index 9782088..34b5eb4 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -24,7 +24,12 @@
/**
* Contains behaviors common to all proxy instances.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class BaseProxyCategory {
/**
* Sniff all return values and ensure that if the current bean is a mutable
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
index ec0cba6..64f29eb 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,7 +17,12 @@
/**
* Contains a variety of AutoBean tag constants to prevent typos.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface Constants {
String DOMAIN_OBJECT = "domainObject";
String IN_RESPONSE = "inResponse";
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java b/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
index 4304885..c89dab8 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EntityCodex.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -32,11 +32,21 @@
/**
* Analogous to {@link ValueCodex}, but for object types.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class EntityCodex {
/**
* Abstracts the process by which EntityProxies are created.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public interface EntitySource {
/**
* Expects an encoded
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
index 26ed7f2..6492817 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -24,7 +24,12 @@
/**
* Contains static implementation of EntityProxy-specific methods.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class EntityProxyCategory {
/**
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java b/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
index 7888fb1..94e3b08 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/FindRequest.java
@@ -1,19 +1,18 @@
/*
* Copyright 2010 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.requestfactory.shared.impl;
import com.google.gwt.requestfactory.server.impl.FindService;
@@ -25,11 +24,20 @@
/**
* Request selector interface for implementing a find method.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
@Service(FindService.class)
public interface FindRequest extends RequestContext {
/**
* Use the implicit lookup in passing EntityProxy types to service methods.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
Request<EntityProxy> find(EntityProxyId<?> proxy);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
index aa3254d..fde538b 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -24,7 +24,12 @@
/**
* Handles common code for creating SimpleProxyIds.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public abstract class IdFactory {
/**
* Maps ephemeral history tokens to an id object. This canonicalizing mapping
@@ -246,4 +251,4 @@
return toReturn;
}
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
index fd71302..874b786 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -17,7 +17,12 @@
/**
* Common functions for slicing and dicing EntityProxy ids.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class IdUtil {
private static final String ANY_SEPARATOR_PATTERN = "@[012]@";
private static final String EPHEMERAL_SEPARATOR = "@1@";
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java b/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
index a36a6ae..7775118 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -20,7 +20,12 @@
/**
* This class has a super-source version with a client-only implementation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface MessageFactoryHolder {
MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java b/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
index 0c16b50..2144916 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -18,9 +18,14 @@
/**
* Used to lock down mutable, non-proxy, value objects when their owning proxy
* is frozen.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <T> the type of simple value the Poser is standing in for
*/
+@Deprecated
public interface Poser<T> {
T getPosedValue();
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java b/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java
index 1e53bbb..2562795 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/ProxySerializerImpl.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -38,7 +38,12 @@
/**
* The default implementation of ProxySerializer.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
class ProxySerializerImpl extends AbstractRequestContext implements
ProxySerializer {
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java b/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java
index db66e2c..0da33ff 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/RequestData.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,7 +23,12 @@
/**
* A class that encapsulates the parameters and method name to be invoked on the
* server.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class RequestData {
private final Class<?> elementType;
private final String operation;
@@ -111,7 +116,7 @@
/**
* Represents the {@code request} object in a JSON-RPC request.
- *
+ *
* @see com.google.gwt.requestfactory.shared.JsonRpcContent
*/
public void setRequestContent(Object requestContent) {
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
index 027a1d6..50366b1 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -21,9 +21,14 @@
/**
* Extends SimpleProxyId with the correct parameterization to implement
* EntityProxyId.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <P> the type of EntityProxy object the id describes
*/
+@Deprecated
public class SimpleEntityProxyId<P extends EntityProxy> extends
SimpleProxyId<P> implements EntityProxyId<P> {
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
index dce3a15..fb13712 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,9 +23,14 @@
* EntityProxy as far as metadata maintenance is concerned. There is a specific
* subtype {@link SimpleEntityProxyId} which implements the requisite public
* interface for EntityProxy types.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <P> the type of BaseProxy object the id describes
*/
+@Deprecated
public class SimpleProxyId<P extends BaseProxy> {
/**
* A placeholder value for {@link #clientId} to indicate the id was not
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java b/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
index 7234528..b80bec3 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/TypeLibrary.java
@@ -27,7 +27,12 @@
/**
* Utility methods for querying, encoding, and decoding typed
* payload data.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class TypeLibrary {
public static final Collection<Class<?>> VALUE_TYPES;
@@ -60,5 +65,5 @@
public static boolean isValueType(Class<?> type) {
return VALUE_TYPES.contains(type);
- }
+ }
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
index 5550921..c858f8e 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,7 +23,12 @@
/**
* Contains static implementation of ValueProxy-specific methods.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public class ValueProxyCategory {
/**
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java b/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
index 403ebc3..559de8d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -21,8 +21,12 @@
/**
* A sometimes-mutable implementation of {@link Date}.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
-@SuppressWarnings("deprecation")
+@Deprecated
public class DatePoser extends Date implements Poser<Date> {
private boolean frozen;
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
index 2b8919b..9e4a8de 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -19,11 +19,21 @@
/**
* Used as a base type for messages that are about a particular id.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface IdMessage {
/**
* Describes the longevity of the id.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+ @Deprecated
public enum Strength {
/**
* The id is indefinitely persistent and can be freely reused by the client
@@ -81,4 +91,4 @@
@PropertyName(TYPE_TOKEN)
void setTypeToken(String value);
-}
\ No newline at end of file
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java
index 0bd07e2..62491d3 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/InvocationMessage.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,7 +23,12 @@
/**
* Describes a method invocation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface InvocationMessage {
String OPERATIONS = "O";
String PARAMETERS = "P";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/JsonRpcRequest.java b/user/src/com/google/gwt/requestfactory/shared/messages/JsonRpcRequest.java
index 063f315..711a199 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/JsonRpcRequest.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/JsonRpcRequest.java
@@ -1,12 +1,12 @@
/*
* Copyright 2011 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
@@ -22,7 +22,12 @@
/**
* A JSON-RPC request payload.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface JsonRpcRequest {
String getApiVersion();
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
index c38216e..278d96f 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -20,14 +20,19 @@
/**
* The factory for creating RequestFactory wire messages.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface MessageFactory extends AutoBeanFactory {
AutoBean<ServerFailureMessage> failure();
AutoBean<IdMessage> id();
AutoBean<InvocationMessage> invocation();
-
+
AutoBean<JsonRpcRequest> jsonRpcRequest();
AutoBean<OperationMessage> operation();
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java
index c57c934..870af5f 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/OperationMessage.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -23,7 +23,12 @@
/**
* Represents an operation to be carried out on a single entity on the server.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface OperationMessage extends IdMessage, VersionedMessage {
String OPERATION = "O";
String PROPERTY_MAP = "P";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java
index 660f50c..2079898 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/RequestMessage.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -21,7 +21,12 @@
/**
* The message sent from the client to the server.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface RequestMessage extends VersionedMessage {
String INVOCATION = "I";
String OPERATIONS = "O";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
index 2555ea5..4628400 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -22,7 +22,12 @@
/**
* The result of fulfilling a request on the server.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface ResponseMessage extends VersionedMessage {
String GENERAL_FAILURE = "F";
String INVOCATION_RESULTS = "I";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
index 27a5007..89f780d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ServerFailureMessage.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -19,7 +19,12 @@
/**
* Encapsulates a ServerFailure object.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface ServerFailureMessage {
String EXCEPTION_TYPE = "X";
String MESSAGE = "M";
@@ -34,7 +39,7 @@
@PropertyName(STACK_TRACE)
String getStackTrace();
-
+
@PropertyName(FATAL)
boolean isFatal();
@@ -46,7 +51,7 @@
@PropertyName(MESSAGE)
void setMessage(String message);
-
+
@PropertyName(STACK_TRACE)
void setStackTrace(String stackTrace);
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
index e216e37..6a11990 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -19,7 +19,12 @@
/**
* Describes a message that contains version information.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface VersionedMessage {
String VERSION = "V";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
index b891ac0..a864a6f 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -19,7 +19,12 @@
/**
* Represents a ConstraintViolation.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
public interface ViolationMessage extends IdMessage {
String MESSAGE = "M";
String PATH = "P";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java b/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
index b396dfc..c318718 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/package-info.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -16,9 +16,13 @@
/**
* Contains classes that define the RequestFactory wire format.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @since GWT 2.1.1
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.requestfactory.shared.messages;
-
diff --git a/user/src/com/google/gwt/requestfactory/shared/package-info.java b/user/src/com/google/gwt/requestfactory/shared/package-info.java
index 3d5c70e..977284c 100644
--- a/user/src/com/google/gwt/requestfactory/shared/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/shared/package-info.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -15,9 +15,15 @@
*/
/**
- * Shared classes used on both the client and the server side for transmitting data between the sever and the client in JSON format.
+ * Shared classes used on both the client and the server side for
+ * transmitting data between the sever and the client in JSON format.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*
* @since GWT 2.1
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.requestfactory.shared;
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/EntityProxyKeyProvider.java b/user/src/com/google/gwt/requestfactory/ui/client/EntityProxyKeyProvider.java
index 84d715f..f42a6ab 100644
--- a/user/src/com/google/gwt/requestfactory/ui/client/EntityProxyKeyProvider.java
+++ b/user/src/com/google/gwt/requestfactory/ui/client/EntityProxyKeyProvider.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -22,13 +22,18 @@
* An {@link EntityProxy}-aware key provider, handy for use with
* {@link com.google.gwt.view.client.SelectionModel} and various
* cell widgets.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
+ * @param <P> the proxy type
+ *
* @see com.google.gwt.user.cellview.client.CellBrowser
* @see com.google.gwt.user.cellview.client.CellList
* @see com.google.gwt.user.cellview.client.CellTable
- *
- * @param <P> the proxy type
*/
+@Deprecated
public class EntityProxyKeyProvider<P extends EntityProxy> implements
ProvidesKey<P> {
/**
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/ProxyRenderer.java b/user/src/com/google/gwt/requestfactory/ui/client/ProxyRenderer.java
index 2ddf49c..8390acf 100644
--- a/user/src/com/google/gwt/requestfactory/ui/client/ProxyRenderer.java
+++ b/user/src/com/google/gwt/requestfactory/ui/client/ProxyRenderer.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -20,9 +20,14 @@
/**
* Renders a proxy object, and reports the properties it requires to do that
* rendering.
- *
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
+ *
* @param <R> the type to render
*/
+@Deprecated
public abstract class ProxyRenderer<R> extends
AbstractRenderer<R> {
diff --git a/user/src/com/google/gwt/requestfactory/ui/client/package-info.java b/user/src/com/google/gwt/requestfactory/ui/client/package-info.java
index 30e5f71..6d1c658 100644
--- a/user/src/com/google/gwt/requestfactory/ui/client/package-info.java
+++ b/user/src/com/google/gwt/requestfactory/ui/client/package-info.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 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
@@ -16,6 +16,11 @@
/**
* Classes used by the request factory to manage proxies, user logins, and authentication.
+ *
+ * <p><span style='color:red'>RequestFactory has moved to
+ * <code>com.google.web.bindery.requestfactory</code>. This package will be
+ * removed in a future version of GWT.</span></p>
*/
+@Deprecated
@com.google.gwt.util.PreventSpuriousRebuilds
package com.google.gwt.requestfactory.ui.client;
diff --git a/user/src/com/google/gwt/user/client/ui/Widget.java b/user/src/com/google/gwt/user/client/ui/Widget.java
index 7bc1ba3..932aaee 100644
--- a/user/src/com/google/gwt/user/client/ui/Widget.java
+++ b/user/src/com/google/gwt/user/client/ui/Widget.java
@@ -23,7 +23,6 @@
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
-import com.google.gwt.event.shared.GwtEvent.Type;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
@@ -246,8 +245,8 @@
*
* @return the {@link HandlerManager} you want to use
*/
- protected com.google.gwt.event.shared.HandlerManager createHandlerManager() {
- return new com.google.gwt.event.shared.HandlerManager(this);
+ protected HandlerManager createHandlerManager() {
+ return new HandlerManager(this);
}
/**
@@ -289,7 +288,7 @@
* @param type the event type
* @return the number of registered handlers
*/
- protected int getHandlerCount(Type<?> type) {
+ protected int getHandlerCount(GwtEvent.Type<?> type) {
return handlerManager == null ? 0 : handlerManager.getHandlerCount(type);
}
@@ -412,12 +411,12 @@
*
* @return the handler manager
* */
- com.google.gwt.event.shared.HandlerManager ensureHandlers() {
+ HandlerManager ensureHandlers() {
return handlerManager == null ? handlerManager = createHandlerManager()
: handlerManager;
}
- com.google.gwt.event.shared.HandlerManager getHandlerManager() {
+ HandlerManager getHandlerManager() {
return handlerManager;
}
diff --git a/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml b/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml
new file mode 100644
index 0000000..88b8981
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/AutoBean.gwt.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" ?>
+<!--
+ Copyright 2010 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.
+-->
+
+<!-- AutoBean framework -->
+<module>
+ <inherits name="com.google.gwt.core.Core" />
+ <inherits name="com.google.gwt.user.User" />
+ <source path="gwt/client" />
+ <source path="shared" />
+ <super-source path="super" />
+ <generate-with class="com.google.web.bindery.autobean.gwt.rebind.AutoBeanFactoryGenerator">
+ <when-type-assignable class="com.google.web.bindery.autobean.shared.AutoBeanFactory" />
+ </generate-with>
+</module>
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java
new file mode 100644
index 0000000..fc3a0cd
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/AbstractAutoBeanFactory.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.impl.EnumMap;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides base implementations of AutoBeanFactory methods.
+ */
+public abstract class AbstractAutoBeanFactory implements AutoBeanFactory, EnumMap {
+
+ protected Map<Enum<?>, String> enumToStringMap;
+ // This map is almost always one-to-one
+ protected Map<String, List<Enum<?>>> stringsToEnumsMap;
+ private JsniCreatorMap creatorMap;
+
+ public <T> AutoBean<T> create(Class<T> clazz) {
+ maybeInitializeCreatorMap();
+ return creatorMap.create(clazz, this);
+ }
+
+ public <T, U extends T> AutoBean<T> create(Class<T> clazz, U delegate) {
+ maybeInitializeCreatorMap();
+ return creatorMap.create(clazz, this, delegate);
+ }
+
+ /**
+ * EnumMap support.
+ */
+ public <E extends Enum<?>> E getEnum(Class<E> clazz, String token) {
+ maybeInitializeEnumMap();
+ List<Enum<?>> list = stringsToEnumsMap.get(token);
+ if (list == null) {
+ throw new IllegalArgumentException(token);
+ }
+ for (Enum<?> e : list) {
+ if (e.getDeclaringClass().equals(clazz)) {
+ @SuppressWarnings("unchecked")
+ E toReturn = (E) e;
+ return toReturn;
+ }
+ }
+ throw new IllegalArgumentException(clazz.getName());
+ }
+
+ /**
+ * EnumMap support.
+ */
+ public String getToken(Enum<?> e) {
+ maybeInitializeEnumMap();
+ String toReturn = enumToStringMap.get(e);
+ if (toReturn == null) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ return toReturn;
+ }
+
+ protected abstract void initializeCreatorMap(JsniCreatorMap creatorMap);
+
+ protected abstract void initializeEnumMap();
+
+ private void maybeInitializeCreatorMap() {
+ if (creatorMap == null) {
+ creatorMap = JsniCreatorMap.createMap();
+ initializeCreatorMap(creatorMap);
+ }
+ }
+
+ private void maybeInitializeEnumMap() {
+ if (enumToStringMap == null) {
+ enumToStringMap = new HashMap<Enum<?>, String>();
+ stringsToEnumsMap = new HashMap<String, List<Enum<?>>>();
+ initializeEnumMap();
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java
new file mode 100644
index 0000000..08c753c
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/ClientPropertyContext.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.MapPropertyContext;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.PropertyContext;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides base methods for generated implementations of PropertyContext.
+ */
+public final class ClientPropertyContext implements PropertyContext, CollectionPropertyContext,
+ MapPropertyContext {
+
+ /**
+ * A reference to an instance setter method.
+ */
+ public static final class Setter extends JavaScriptObject {
+ /**
+ * Create a trivial Setter that calls {@link AbstractAutoBean#setProperty()}
+ * .
+ */
+ public static native Setter beanSetter(AbstractAutoBean<?> bean, String key) /*-{
+ return function(value) {
+ bean.@com.google.web.bindery.autobean.shared.impl.AbstractAutoBean::setProperty(*)(key, value);
+ };
+ }-*/;
+
+ protected Setter() {
+ }
+
+ public native void call(Object instance, Object value) /*-{
+ this.call(instance, value);
+ }-*/;
+ }
+
+ private final Object instance;
+ private final int[] paramCounts;
+ private final Class<?>[] paramTypes;
+ private final Setter setter;
+ private final Class<?> simpleType;
+
+ public ClientPropertyContext(Object instance, Setter setter, Class<?> type) {
+ this.instance = instance;
+ this.setter = setter;
+ this.simpleType = type;
+ this.paramTypes = null;
+ this.paramCounts = null;
+ }
+
+ public ClientPropertyContext(Object instance, Setter setter, Class<?>[] types, int[] paramCounts) {
+ this.instance = instance;
+ this.setter = setter;
+ this.simpleType = null;
+ this.paramTypes = types;
+ this.paramCounts = paramCounts;
+
+ /*
+ * Verify input arrays of same length and that the total parameter count,
+ * plus one for the root type, equals the total number of types passed in.
+ */
+ if (ClientPropertyContext.class.desiredAssertionStatus()) {
+ assert types.length == paramCounts.length : "Length mismatch " + types.length + " != "
+ + paramCounts.length;
+ int count = 1;
+ for (int i = 0, j = paramCounts.length; i < j; i++) {
+ count += paramCounts[i];
+ }
+ assert count == types.length : "Mismatch in total parameter count " + count + " != "
+ + types.length;
+ }
+ }
+
+ public void accept(ParameterizationVisitor visitor) {
+ traverse(visitor, 0);
+ }
+
+ public boolean canSet() {
+ return setter != null;
+ }
+
+ public Class<?> getElementType() {
+ if (paramTypes == null || paramTypes.length < 2) {
+ return null;
+ }
+ if (List.class.equals(paramTypes[0]) || Set.class.equals(paramTypes[0])) {
+ return paramTypes[1];
+ }
+ return null;
+ }
+
+ public Class<?> getKeyType() {
+ if (paramTypes == null || paramTypes.length < 3) {
+ return null;
+ }
+ if (Map.class.equals(paramTypes[0])) {
+ return paramTypes[1];
+ }
+ return null;
+ }
+
+ public Class<?> getType() {
+ return simpleType == null ? paramTypes[0] : simpleType;
+ }
+
+ public Class<?> getValueType() {
+ if (paramTypes == null || paramTypes.length < 3) {
+ return null;
+ }
+ if (Map.class.equals(paramTypes[0])) {
+ return paramTypes[2];
+ }
+ return null;
+ }
+
+ public void set(Object value) {
+ setter.call(instance, value);
+ }
+
+ private int traverse(ParameterizationVisitor visitor, int count) {
+ if (simpleType != null) {
+ visitor.visitType(simpleType);
+ visitor.endVisitType(simpleType);
+ return 0;
+ }
+
+ Class<?> type = paramTypes[count];
+ int paramCount = paramCounts[count];
+ ++count;
+ if (visitor.visitType(type)) {
+ for (int i = 0; i < paramCount; i++) {
+ if (visitor.visitParameter()) {
+ count = traverse(visitor, count);
+ }
+ visitor.endVisitParameter();
+ }
+ }
+ visitor.endVisitType(type);
+ return count;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java
new file mode 100644
index 0000000..d6e4468
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsniCreatorMap.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+/**
+ * Used in prod-mode code to create instances of generated AutoBean subtypes via
+ * JSNI references to their constructor methods.
+ */
+public final class JsniCreatorMap extends JavaScriptObject {
+ public static JsniCreatorMap createMap() {
+ return JavaScriptObject.createObject().cast();
+ }
+
+ /*
+ * Structure is a string map of class literal names to the no-arg and one-arg
+ * constructors of a generated AutoBean subtype.
+ */
+ protected JsniCreatorMap() {
+ }
+
+ public void add(Class<?> clazz, JsArray<JavaScriptObject> constructors) {
+ assert constructors.length() == 2 : "Expecting two constructor references";
+ set(clazz.getName(), constructors);
+ }
+
+ public <T> AutoBean<T> create(Class<T> clazz, AbstractAutoBeanFactory factory) {
+ JsArray<JavaScriptObject> arr = get(clazz.getName());
+ if (arr != null && arr.get(0) != null) {
+ return invoke(arr.get(0), factory, null);
+ }
+ return null;
+ }
+
+ public <T> AutoBean<T> create(Class<T> clazz, AbstractAutoBeanFactory factory, Object delegate) {
+ JsArray<JavaScriptObject> arr = get(clazz.getName());
+ if (arr != null) {
+ assert arr.get(1) != null : "No delegate-based constructor";
+ return invoke(arr.get(1), factory, delegate);
+ }
+ return null;
+ }
+
+ private native JsArray<JavaScriptObject> get(String key) /*-{
+ return this[key];
+ }-*/;
+
+ private native <T> AutoBean<T> invoke(JavaScriptObject fn, Object arg1, Object arg2)/*-{
+ return fn(arg1, arg2);
+ }-*/;
+
+ private native void set(String key, JsArray<JavaScriptObject> arr) /*-{
+ this[key] = arr;
+ }-*/;
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java
new file mode 100644
index 0000000..33eba24
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/client/impl/JsoSplittable.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.HasSplittable;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+import com.google.gwt.core.client.GwtScriptOnly;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsonUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implements the EntityCodex.Splittable interface using a raw JavaScriptObject.
+ * <p>
+ * A string value represented by a JsoSplittable can't use the string object
+ * directly, since {@code String.prototype} is overridden, so instead a
+ * temporary wrapper object is used to encapsulate the string data.
+ */
+@GwtScriptOnly
+public final class JsoSplittable extends JavaScriptObject implements Splittable, HasSplittable {
+ private static boolean stringifyFastTested;
+ private static boolean stringifyFastResult;
+
+ public static native JsoSplittable create() /*-{
+ return {};
+ }-*/;
+
+ public static Splittable create(boolean value) {
+ return create0(value);
+ }
+
+ public static Splittable create(double value) {
+ return create0(value);
+ }
+
+ public static Splittable create(String value) {
+ return create0(value);
+ }
+
+ public static native JsoSplittable createIndexed() /*-{
+ return [];
+ }-*/;
+
+ public static native JsoSplittable nullValue() /*-{
+ return null;
+ }-*/;
+
+ private static native Splittable create0(boolean object) /*-{
+ return Boolean(object);
+ }-*/;
+
+ private static native Splittable create0(double object) /*-{
+ return Number(object);
+ }-*/;
+
+ private static native Splittable create0(String object) /*-{
+ return {
+ __s : object
+ };
+ }-*/;
+
+ private static native boolean isUnwrappedString(JavaScriptObject obj) /*-{
+ return Object.prototype.toString.call(obj) == '[object String]';
+ }-*/;
+
+ private static boolean stringifyFastSupported() {
+ if (stringifyFastTested) {
+ return stringifyFastResult;
+ }
+ stringifyFastTested = true;
+ return stringifyFastResult = stringifyFastSupported0();
+ }
+
+ /**
+ * Test that the JSON api is available and that it does not add function
+ * objects to the output. The test for function objects is for old versions of
+ * Safari.
+ */
+ private static native boolean stringifyFastSupported0() /*-{
+ return $wnd.JSON && $wnd.JSON.stringify && $wnd.JSON.stringify({
+ b : function() {
+ }
+ }) == '{}';
+ }-*/;
+
+ protected JsoSplittable() {
+ };
+
+ public native boolean asBoolean() /*-{
+ return this == true;
+ }-*/;
+
+ public native double asNumber() /*-{
+ return Number(this);
+ }-*/;
+
+ public void assign(Splittable parent, int index) {
+ if (isString()) {
+ assign0(parent, index, asString());
+ } else {
+ assign0(parent, index, this);
+ }
+ }
+
+ public void assign(Splittable parent, String index) {
+ if (isString()) {
+ assign0(parent, index, asString());
+ } else {
+ assign0(parent, index, this);
+ }
+ }
+
+ public native String asString() /*-{
+ return this.__s;
+ }-*/;
+
+ public Splittable deepCopy() {
+ return StringQuoter.split(getPayload());
+ }
+
+ public JsoSplittable get(int index) {
+ return getRaw(index);
+ }
+
+ public JsoSplittable get(String key) {
+ return getRaw(key);
+ }
+
+ public String getPayload() {
+ if (isString()) {
+ return JsonUtils.escapeValue(asString());
+ }
+ if (stringifyFastSupported()) {
+ return stringifyFast();
+ }
+ return stringifySlow();
+ }
+
+ public List<String> getPropertyKeys() {
+ List<String> toReturn = new ArrayList<String>();
+ getPropertyKeys0(toReturn);
+ return Collections.unmodifiableList(toReturn);
+ }
+
+ public native Object getReified(String key) /*-{
+ return this.__reified && this.__reified[':' + key];
+ }-*/;
+
+ public Splittable getSplittable() {
+ return this;
+ }
+
+ public native boolean isBoolean() /*-{
+ return Object.prototype.toString.call(this) == '[object Boolean]';
+ }-*/;
+
+ public native boolean isFunction() /*-{
+ return Object.prototype.toString.call(this) == '[object Function]';
+ }-*/;
+
+ public native boolean isIndexed() /*-{
+ return Object.prototype.toString.call(this) == '[object Array]';
+ }-*/;
+
+ public boolean isKeyed() {
+ return this != NULL && !isString() && !isIndexed() && !isFunction();
+ }
+
+ public native boolean isNull(int index) /*-{
+ return this[index] == null;
+ }-*/;
+
+ public native boolean isNull(String key) /*-{
+ return this[key] == null;
+ }-*/;
+
+ public native boolean isNumber() /*-{
+ return Object.prototype.toString.call(this) == '[object Number]';
+ }-*/;
+
+ public native boolean isReified(String key) /*-{
+ return !!(this.__reified && this.__reified.hasOwnProperty(':' + key));
+ }-*/;
+
+ /**
+ * Returns whether or not the current object is a string-carrier.
+ */
+ public native boolean isString() /*-{
+ return this && this.__s != null;
+ }-*/;
+
+ public native boolean isUndefined(String key) /*-{
+ return this[key] === undefined;
+ }-*/;
+
+ public native void setReified(String key, Object object) /*-{
+ // Use a function object so native JSON.stringify will ignore
+ (this.__reified || (this.__reified = function() {
+ }))[':' + key] = object;
+ }-*/;
+
+ public native void setSize(int size) /*-{
+ this.length = size;
+ }-*/;
+
+ public native int size() /*-{
+ return this.length;
+ }-*/;
+
+ private native void assign0(Splittable parent, int index, Splittable value) /*-{
+ parent[index] = value;
+ }-*/;
+
+ private native void assign0(Splittable parent, int index, String value) /*-{
+ parent[index] = value;
+ }-*/;
+
+ private native void assign0(Splittable parent, String index, Splittable value) /*-{
+ parent[index] = value;
+ }-*/;
+
+ private native void assign0(Splittable parent, String index, String value) /*-{
+ parent[index] = value;
+ }-*/;
+
+ private native void getPropertyKeys0(List<String> list) /*-{
+ for (key in this) {
+ if (this.hasOwnProperty(key)) {
+ list.@java.util.List::add(Ljava/lang/Object;)(key);
+ }
+ }
+ }-*/;
+
+ private native JsoSplittable getRaw(int index) /*-{
+ _ = this[index];
+ if (_ == null) {
+ return null;
+ }
+ if (@com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::isUnwrappedString(*)(_)) {
+ return @com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::create(Ljava/lang/String;)(_);
+ }
+ return Object(_);
+ }-*/;
+
+ private native JsoSplittable getRaw(String index) /*-{
+ _ = this[index];
+ if (_ == null) {
+ return null;
+ }
+ if (@com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::isUnwrappedString(*)(_)) {
+ return @com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable::create(Ljava/lang/String;)(_);
+ }
+ return Object(_);
+ }-*/;
+
+ /**
+ * The test for {@code $H} removes the key in the emitted JSON, however making
+ * a similar test for {@code __reified} causes the key to be emitted with an
+ * explicit {@code null} value.
+ */
+ private native String stringifyFast() /*-{
+ return $wnd.JSON.stringify(this, function(key, value) {
+ if (key == "$H") {
+ return;
+ }
+ return value;
+ });
+ }-*/;
+
+ private String stringifySlow() {
+ StringBuilder sb = new StringBuilder();
+ stringifySlow(sb);
+ return sb.toString();
+ }
+
+ private void stringifySlow(StringBuilder sb) {
+ if (this == NULL) {
+ sb.append("null");
+ return;
+ }
+ if (isBoolean()) {
+ sb.append(asBoolean());
+ return;
+ }
+ if (isNumber()) {
+ sb.append(asNumber());
+ return;
+ }
+ if (isString()) {
+ sb.append(JsonUtils.escapeValue(asString()));
+ return;
+ }
+ if (isIndexed()) {
+ sb.append("[");
+ for (int i = 0, j = size(); i < j; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ get(i).stringifySlow(sb);
+ }
+ sb.append("]");
+ return;
+ }
+
+ sb.append("{");
+ boolean needsComma = false;
+ for (String key : getPropertyKeys()) {
+ if (needsComma) {
+ sb.append(",");
+ } else {
+ needsComma = true;
+ }
+ JsoSplittable value = get(key);
+ if (!value.isFunction()) {
+ if ("$H".equals(key)) {
+ // Ignore hashcode
+ continue;
+ }
+ sb.append(JsonUtils.escapeValue(key));
+ sb.append(":");
+ value.stringifySlow(sb);
+ }
+ }
+ sb.append("}");
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java
new file mode 100644
index 0000000..0ec39b4
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/AutoBeanFactoryGenerator.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.gwt.rebind;
+
+import com.google.web.bindery.autobean.gwt.client.impl.AbstractAutoBeanFactory;
+import com.google.web.bindery.autobean.gwt.client.impl.ClientPropertyContext;
+import com.google.web.bindery.autobean.gwt.client.impl.JsniCreatorMap;
+import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanFactoryMethod;
+import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanFactoryModel;
+import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanMethod;
+import com.google.web.bindery.autobean.gwt.rebind.model.AutoBeanType;
+import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean.OneShotContext;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.impl.WeakMapping;
+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.JEnumConstant;
+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.editor.rebind.model.ModelUtils;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Generates implementations of AutoBeanFactory.
+ */
+public class AutoBeanFactoryGenerator extends Generator {
+
+ private GeneratorContext context;
+ private String simpleSourceName;
+ private TreeLogger logger;
+ private AutoBeanFactoryModel model;
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context, String typeName)
+ throws UnableToCompleteException {
+ this.context = context;
+ this.logger = logger;
+
+ TypeOracle oracle = context.getTypeOracle();
+ JClassType toGenerate = oracle.findType(typeName).isInterface();
+ if (toGenerate == null) {
+ logger.log(TreeLogger.ERROR, typeName + " is not an interface type");
+ throw new UnableToCompleteException();
+ }
+
+ String packageName = toGenerate.getPackage().getName();
+ simpleSourceName = toGenerate.getName().replace('.', '_') + "Impl";
+ PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName);
+ if (pw == null) {
+ return packageName + "." + simpleSourceName;
+ }
+
+ model = new AutoBeanFactoryModel(logger, toGenerate);
+
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(packageName, simpleSourceName);
+ factory.setSuperclass(AbstractAutoBeanFactory.class.getCanonicalName());
+ factory.addImplementedInterface(typeName);
+ SourceWriter sw = factory.createSourceWriter(context, pw);
+ for (AutoBeanType type : model.getAllTypes()) {
+ writeAutoBean(type);
+ }
+ writeDynamicMethods(sw);
+ writeEnumSetup(sw);
+ writeMethods(sw);
+ sw.commit(logger);
+
+ return factory.getCreatedClassName();
+ }
+
+ /**
+ * Flattens a parameterized type into a simple list of types.
+ */
+ private void createTypeList(List<JType> accumulator, JType type) {
+ accumulator.add(type);
+ JParameterizedType hasParams = type.isParameterized();
+ if (hasParams != null) {
+ for (JClassType arg : hasParams.getTypeArgs()) {
+ createTypeList(accumulator, arg);
+ }
+ }
+ }
+
+ private String getBaseMethodDeclaration(JMethod jmethod) {
+ // Foo foo, Bar bar, Baz baz
+ StringBuilder parameters = new StringBuilder();
+ for (JParameter param : jmethod.getParameters()) {
+ parameters.append(",").append(ModelUtils.getQualifiedBaseSourceName(param.getType())).append(
+ " ").append(param.getName());
+ }
+ if (parameters.length() > 0) {
+ parameters = parameters.deleteCharAt(0);
+ }
+
+ StringBuilder throwsDeclaration = new StringBuilder();
+ if (jmethod.getThrows().length > 0) {
+ for (JType thrown : jmethod.getThrows()) {
+ throwsDeclaration.append(". ").append(ModelUtils.getQualifiedBaseSourceName(thrown));
+ }
+ throwsDeclaration.deleteCharAt(0);
+ throwsDeclaration.insert(0, "throws ");
+ }
+ String returnName = ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType());
+ assert !returnName.contains("extends");
+ return String.format("%s %s(%s) %s", returnName, jmethod.getName(), parameters,
+ throwsDeclaration);
+ }
+
+ /**
+ * Used by {@link #writeShim} to avoid duplicate declarations of Object
+ * methods.
+ */
+ private boolean isObjectMethodImplementedByShim(JMethod jmethod) {
+ String methodName = jmethod.getName();
+ JParameter[] parameters = jmethod.getParameters();
+ switch (parameters.length) {
+ case 0:
+ return methodName.equals("hashCode") || methodName.equals("toString");
+ case 1:
+ return methodName.equals("equals")
+ && parameters[0].getType().equals(context.getTypeOracle().getJavaLangObject());
+ }
+ return false;
+ }
+
+ private void writeAutoBean(AutoBeanType type) throws UnableToCompleteException {
+ PrintWriter pw = context.tryCreate(logger, type.getPackageNome(), type.getSimpleSourceName());
+ if (pw == null) {
+ // Previously-created
+ return;
+ }
+
+ ClassSourceFileComposerFactory factory =
+ new ClassSourceFileComposerFactory(type.getPackageNome(), type.getSimpleSourceName());
+ factory.setSuperclass(AbstractAutoBean.class.getCanonicalName() + "<"
+ + type.getPeerType().getQualifiedSourceName() + ">");
+ SourceWriter sw = factory.createSourceWriter(context, pw);
+
+ writeShim(sw, type);
+
+ // Instance initializer code to set the shim's association
+ sw.println("{ %s.set(shim, %s.class.getName(), this); }", WeakMapping.class.getCanonicalName(),
+ AutoBean.class.getCanonicalName());
+
+ // Only simple wrappers have a default constructor
+ if (type.isSimpleBean()) {
+ // public FooIntfAutoBean(AutoBeanFactory factory) {}
+ sw.println("public %s(%s factory) {super(factory);}", type.getSimpleSourceName(),
+ AutoBeanFactory.class.getCanonicalName());
+ }
+
+ // Wrapping constructor
+ // public FooIntfAutoBean(AutoBeanFactory factory, FooIntfo wrapped) {
+ sw.println("public %s(%s factory, %s wrapped) {", type.getSimpleSourceName(),
+ AutoBeanFactory.class.getCanonicalName(), type.getPeerType().getQualifiedSourceName());
+ sw.indentln("super(wrapped, factory);");
+ sw.println("}");
+
+ // public FooIntf as() {return shim;}
+ sw.println("public %s as() {return shim;}", type.getPeerType().getQualifiedSourceName());
+
+ // public Class<Intf> getType() {return Intf.class;}
+ sw.println("public Class<%1$s> getType() {return %1$s.class;}", ModelUtils.ensureBaseType(
+ type.getPeerType()).getQualifiedSourceName());
+
+ if (type.isSimpleBean()) {
+ writeCreateSimpleBean(sw, type);
+ }
+ writeTraversal(sw, type);
+ sw.commit(logger);
+ }
+
+ /**
+ * For interfaces that consist of nothing more than getters and setters,
+ * create a map-based implementation that will allow the AutoBean's internal
+ * state to be easily consumed.
+ */
+ private void writeCreateSimpleBean(SourceWriter sw, AutoBeanType type) {
+ sw.println("@Override protected %s createSimplePeer() {", type.getPeerType()
+ .getQualifiedSourceName());
+ sw.indent();
+ // return new FooIntf() {
+ sw.println("return new %s() {", type.getPeerType().getQualifiedSourceName());
+ sw.indent();
+ sw.println("private final %s data = %s.this.data;", Splittable.class.getCanonicalName(), type
+ .getQualifiedSourceName());
+ for (AutoBeanMethod method : type.getMethods()) {
+ JMethod jmethod = method.getMethod();
+ JType returnType = jmethod.getReturnType();
+ sw.println("public %s {", getBaseMethodDeclaration(jmethod));
+ sw.indent();
+ switch (method.getAction()) {
+ case GET: {
+ String castType;
+ if (returnType.isPrimitive() != null) {
+ castType = returnType.isPrimitive().getQualifiedBoxedSourceName();
+ // Boolean toReturn = getOrReify("foo");
+ sw.println("%s toReturn = getOrReify(\"%s\");", castType, method.getPropertyName());
+ // return toReturn == null ? false : toReturn;
+ sw.println("return toReturn == null ? %s : toReturn;", returnType.isPrimitive()
+ .getUninitializedFieldExpression());
+ } else if (returnType.equals(context.getTypeOracle().findType(
+ Splittable.class.getCanonicalName()))) {
+ sw.println("return data.isNull(\"%1$s\") ? null : data.get(\"%1$s\");", method
+ .getPropertyName());
+ } else {
+ // return (ReturnType) values.getOrReify(\"foo\");
+ castType = ModelUtils.getQualifiedBaseSourceName(returnType);
+ sw.println("return (%s) getOrReify(\"%s\");", castType, method.getPropertyName());
+ }
+ }
+ break;
+ case SET:
+ case SET_BUILDER: {
+ JParameter param = jmethod.getParameters()[0];
+ // setProperty("foo", parameter);
+ sw.println("setProperty(\"%s\", %s);", method.getPropertyName(), param.getName());
+ if (JBeanMethod.SET_BUILDER.equals(method.getAction())) {
+ sw.println("return this;");
+ }
+ break;
+ }
+ case CALL:
+ // return com.example.Owner.staticMethod(Outer.this, param,
+ // param);
+ JMethod staticImpl = method.getStaticImpl();
+ if (!returnType.equals(JPrimitiveType.VOID)) {
+ sw.print("return ");
+ }
+ sw.print("%s.%s(%s.this", staticImpl.getEnclosingType().getQualifiedSourceName(),
+ staticImpl.getName(), type.getSimpleSourceName());
+ for (JParameter param : jmethod.getParameters()) {
+ sw.print(", %s", param.getName());
+ }
+ sw.println(");");
+ break;
+ default:
+ throw new RuntimeException();
+ }
+ sw.outdent();
+ sw.println("}");
+ }
+ sw.outdent();
+ sw.println("};");
+ sw.outdent();
+ sw.println("}");
+ }
+
+ /**
+ * Write an instance initializer block to populate the creators map.
+ */
+ private void writeDynamicMethods(SourceWriter sw) {
+ List<JClassType> privatePeers = new ArrayList<JClassType>();
+ sw.println("@Override protected void initializeCreatorMap(%s map) {", JsniCreatorMap.class
+ .getCanonicalName());
+ sw.indent();
+ for (AutoBeanType type : model.getAllTypes()) {
+ if (type.isNoWrap()) {
+ continue;
+ }
+ String classLiteralAccessor;
+ JClassType peer = type.getPeerType();
+ String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName();
+ if (peer.isPublic()) {
+ classLiteralAccessor = peerName + ".class";
+ } else {
+ privatePeers.add(peer);
+ classLiteralAccessor = "classLit_" + peerName.replace('.', '_') + "()";
+ }
+ // map.add(Foo.class, getConstructors_com_foo_Bar());
+ sw.println("map.add(%s, getConstructors_%s());", classLiteralAccessor, peerName.replace('.',
+ '_'));
+ }
+ sw.outdent();
+ sw.println("}");
+
+ /*
+ * Create a native method for each peer type that isn't public since Java
+ * class literal references are scoped.
+ */
+ for (JClassType peer : privatePeers) {
+ String peerName = ModelUtils.ensureBaseType(peer).getQualifiedSourceName();
+ sw.println("private native Class<?> classLit_%s() /*-{return @%s::class;}-*/;", peerName
+ .replace('.', '_'), peerName);
+ }
+
+ /*
+ * Create a method that returns an array containing references to the
+ * constructors.
+ */
+ String factoryJNIName =
+ context.getTypeOracle().findType(AutoBeanFactory.class.getCanonicalName())
+ .getJNISignature();
+ for (AutoBeanType type : model.getAllTypes()) {
+ String peerName = ModelUtils.ensureBaseType(type.getPeerType()).getQualifiedSourceName();
+ String peerJNIName = ModelUtils.ensureBaseType(type.getPeerType()).getJNISignature();
+ /*-
+ * private native JsArray<JSO> getConstructors_com_foo_Bar() {
+ * return [
+ * BarProxyImpl::new(ABFactory),
+ * BarProxyImpl::new(ABFactory, DelegateType)
+ * ];
+ * }
+ */
+ sw.println("private native %s<%s> getConstructors_%s() /*-{", JsArray.class
+ .getCanonicalName(), JavaScriptObject.class.getCanonicalName(), peerName
+ .replace('.', '_'));
+ sw.indent();
+ sw.println("return [");
+ if (type.isSimpleBean()) {
+ sw.indentln("@%s::new(%s),", type.getQualifiedSourceName(), factoryJNIName);
+ } else {
+ sw.indentln(",");
+ }
+ sw.indentln("@%s::new(%s%s)", type.getQualifiedSourceName(), factoryJNIName, peerJNIName);
+ sw.println("];");
+ sw.outdent();
+ sw.println("}-*/;");
+ }
+ }
+
+ private void writeEnumSetup(SourceWriter sw) {
+ // Make the deobfuscation model
+ Map<String, List<JEnumConstant>> map = new HashMap<String, List<JEnumConstant>>();
+ for (Map.Entry<JEnumConstant, String> entry : model.getEnumTokenMap().entrySet()) {
+ List<JEnumConstant> list = map.get(entry.getValue());
+ if (list == null) {
+ list = new ArrayList<JEnumConstant>();
+ map.put(entry.getValue(), list);
+ }
+ list.add(entry.getKey());
+ }
+
+ sw.println("@Override protected void initializeEnumMap() {");
+ sw.indent();
+ for (Map.Entry<JEnumConstant, String> entry : model.getEnumTokenMap().entrySet()) {
+ // enumToStringMap.put(Enum.FOO, "FOO");
+ sw.println("enumToStringMap.put(%s.%s, \"%s\");", entry.getKey().getEnclosingType()
+ .getQualifiedSourceName(), entry.getKey().getName(), entry.getValue());
+ }
+ for (Map.Entry<String, List<JEnumConstant>> entry : map.entrySet()) {
+ String listExpr;
+ if (entry.getValue().size() == 1) {
+ JEnumConstant e = entry.getValue().get(0);
+ // Collections.singletonList(Enum.FOO)
+ listExpr =
+ String.format("%s.<%s<?>> singletonList(%s.%s)", Collections.class.getCanonicalName(),
+ Enum.class.getCanonicalName(), e.getEnclosingType().getQualifiedSourceName(), e
+ .getName());
+ } else {
+ // Arrays.asList(Enum.FOO, OtherEnum.FOO, ThirdEnum,FOO)
+ StringBuilder sb = new StringBuilder();
+ boolean needsComma = false;
+ sb.append(String.format("%s.<%s<?>> asList(", Arrays.class.getCanonicalName(), Enum.class
+ .getCanonicalName()));
+ for (JEnumConstant e : entry.getValue()) {
+ if (needsComma) {
+ sb.append(",");
+ }
+ needsComma = true;
+ sb.append(e.getEnclosingType().getQualifiedSourceName()).append(".").append(e.getName());
+ }
+ sb.append(")");
+ listExpr = sb.toString();
+ }
+ sw.println("stringsToEnumsMap.put(\"%s\", %s);", entry.getKey(), listExpr);
+ }
+ sw.outdent();
+ sw.println("}");
+ }
+
+ private void writeMethods(SourceWriter sw) throws UnableToCompleteException {
+ for (AutoBeanFactoryMethod method : model.getMethods()) {
+ AutoBeanType autoBeanType = method.getAutoBeanType();
+ // public AutoBean<Foo> foo(FooSubtype wrapped) {
+ sw.println("public %s %s(%s) {", method.getReturnType().getQualifiedSourceName(), method
+ .getName(), method.isWrapper()
+ ? (method.getWrappedType().getQualifiedSourceName() + " wrapped") : "");
+ if (method.isWrapper()) {
+ sw.indent();
+ // AutoBean<Foo> toReturn = AutoBeanUtils.getAutoBean(wrapped);
+ sw.println("%s toReturn = %s.getAutoBean(wrapped);", method.getReturnType()
+ .getParameterizedQualifiedSourceName(), AutoBeanUtils.class.getCanonicalName());
+ sw.println("if (toReturn != null) {return toReturn;}");
+ // return new FooAutoBean(Factory.this, wrapped);
+ sw.println("return new %s(%s.this, wrapped);", autoBeanType.getQualifiedSourceName(),
+ simpleSourceName);
+ sw.outdent();
+ } else {
+ // return new FooAutoBean(Factory.this);
+ sw.indentln("return new %s(%s.this);", autoBeanType.getQualifiedSourceName(),
+ simpleSourceName);
+ }
+ sw.println("}");
+ }
+ }
+
+ private void writeReturnWrapper(SourceWriter sw, AutoBeanType type, AutoBeanMethod method)
+ throws UnableToCompleteException {
+ if (!method.isValueType() && !method.isNoWrap()) {
+ JMethod jmethod = method.getMethod();
+ JClassType returnClass = jmethod.getReturnType().isClassOrInterface();
+ AutoBeanType peer = model.getPeer(returnClass);
+
+ sw.println("if (toReturn != null) {");
+ sw.indent();
+ sw.println("if (%s.this.isWrapped(toReturn)) {", type.getSimpleSourceName());
+ sw.indentln("toReturn = %s.this.getFromWrapper(toReturn);", type.getSimpleSourceName());
+ sw.println("} else {");
+ sw.indent();
+ if (peer != null) {
+ // toReturn = new FooAutoBean(getFactory(), toReturn).as();
+ sw.println("toReturn = new %s(getFactory(), toReturn).as();", peer.getQualifiedSourceName());
+ }
+ sw.outdent();
+ sw.println("}");
+
+ sw.outdent();
+ sw.println("}");
+ }
+ // Allow return values to be intercepted
+ JMethod interceptor = type.getInterceptor();
+ if (interceptor != null) {
+ // toReturn = FooCategory.__intercept(FooAutoBean.this, toReturn);
+ sw.println("toReturn = %s.%s(%s.this, toReturn);", interceptor.getEnclosingType()
+ .getQualifiedSourceName(), interceptor.getName(), type.getSimpleSourceName());
+ }
+ }
+
+ /**
+ * Create the shim instance of the AutoBean's peer type that lets us hijack
+ * the method calls. Using a shim type, as opposed to making the AutoBean
+ * implement the peer type directly, means that there can't be any conflicts
+ * between methods in the peer type and methods declared in the AutoBean
+ * implementation.
+ */
+ private void writeShim(SourceWriter sw, AutoBeanType type) throws UnableToCompleteException {
+ // private final FooImpl shim = new FooImpl() {
+ sw.println("private final %1$s shim = new %1$s() {", type.getPeerType()
+ .getQualifiedSourceName());
+ sw.indent();
+ for (AutoBeanMethod method : type.getMethods()) {
+ JMethod jmethod = method.getMethod();
+ String methodName = jmethod.getName();
+ JParameter[] parameters = jmethod.getParameters();
+ if (isObjectMethodImplementedByShim(jmethod)) {
+ // Skip any methods declared on Object, since we have special handling
+ continue;
+ }
+
+ // foo, bar, baz
+ StringBuilder arguments = new StringBuilder();
+ {
+ for (JParameter param : parameters) {
+ arguments.append(",").append(param.getName());
+ }
+ if (arguments.length() > 0) {
+ arguments = arguments.deleteCharAt(0);
+ }
+ }
+
+ sw.println("public %s {", getBaseMethodDeclaration(jmethod));
+ sw.indent();
+
+ switch (method.getAction()) {
+ case GET:
+ /*
+ * The getter call will ensure that any non-value return type is
+ * definitely wrapped by an AutoBean instance.
+ */
+ sw.println("%s toReturn = %s.this.getWrapped().%s();", ModelUtils
+ .getQualifiedBaseSourceName(jmethod.getReturnType()), type.getSimpleSourceName(),
+ methodName);
+
+ // Non-value types might need to be wrapped
+ writeReturnWrapper(sw, type, method);
+ sw.println("return toReturn;");
+ break;
+ case SET:
+ case SET_BUILDER:
+ // getWrapped().setFoo(foo);
+ sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName,
+ parameters[0].getName());
+ // FooAutoBean.this.set("setFoo", foo);
+ sw.println("%s.this.set(\"%s\", %s);", type.getSimpleSourceName(), methodName,
+ parameters[0].getName());
+ if (JBeanMethod.SET_BUILDER.equals(method.getAction())) {
+ sw.println("return this;");
+ }
+ break;
+ case CALL:
+ // XXX How should freezing and calls work together?
+ // sw.println("checkFrozen();");
+ if (JPrimitiveType.VOID.equals(jmethod.getReturnType())) {
+ // getWrapped().doFoo(params);
+ sw.println("%s.this.getWrapped().%s(%s);", type.getSimpleSourceName(), methodName,
+ arguments);
+ // call("doFoo", null, params);
+ sw.println("%s.this.call(\"%s\", null%s %s);", type.getSimpleSourceName(), methodName,
+ arguments.length() > 0 ? "," : "", arguments);
+ } else {
+ // Type toReturn = getWrapped().doFoo(params);
+ sw.println("%s toReturn = %s.this.getWrapped().%s(%s);", ModelUtils.ensureBaseType(
+ jmethod.getReturnType()).getQualifiedSourceName(), type.getSimpleSourceName(),
+ methodName, arguments);
+ // Non-value types might need to be wrapped
+ writeReturnWrapper(sw, type, method);
+ // call("doFoo", toReturn, params);
+ sw.println("%s.this.call(\"%s\", toReturn%s %s);", type.getSimpleSourceName(),
+ methodName, arguments.length() > 0 ? "," : "", arguments);
+ sw.println("return toReturn;");
+ }
+ break;
+ default:
+ throw new RuntimeException();
+ }
+ sw.outdent();
+ sw.println("}");
+ }
+
+ // Delegate equals(), hashCode(), and toString() to wrapped object
+ sw.println("@Override public boolean equals(Object o) {");
+ sw.indentln("return this == o || getWrapped().equals(o);");
+ sw.println("}");
+ sw.println("@Override public int hashCode() {");
+ sw.indentln("return getWrapped().hashCode();");
+ sw.println("}");
+ sw.println("@Override public String toString() {");
+ sw.indentln("return getWrapped().toString();");
+ sw.println("}");
+
+ // End of shim field declaration and assignment
+ sw.outdent();
+ sw.println("};");
+ }
+
+ /**
+ * Generate traversal logic.
+ */
+ private void writeTraversal(SourceWriter sw, AutoBeanType type) {
+ List<AutoBeanMethod> referencedSetters = new ArrayList<AutoBeanMethod>();
+ sw.println("@Override protected void traverseProperties(%s visitor, %s ctx) {",
+ AutoBeanVisitor.class.getCanonicalName(), OneShotContext.class.getCanonicalName());
+ sw.indent();
+ sw.println("%s bean;", AbstractAutoBean.class.getCanonicalName());
+ sw.println("Object value;");
+ sw.println("%s propertyContext;", ClientPropertyContext.class.getCanonicalName());
+ // Local variable ref cleans up emitted js
+ sw.println("%1$s as = as();", type.getPeerType().getQualifiedSourceName());
+
+ for (AutoBeanMethod method : type.getMethods()) {
+ if (!method.getAction().equals(JBeanMethod.GET)) {
+ continue;
+ }
+
+ AutoBeanMethod setter = null;
+ // If it's not a simple bean type, try to find a real setter method
+ if (!type.isSimpleBean()) {
+ for (AutoBeanMethod maybeSetter : type.getMethods()) {
+ boolean isASetter =
+ maybeSetter.getAction().equals(JBeanMethod.SET)
+ || maybeSetter.getAction().equals(JBeanMethod.SET_BUILDER);
+ if (isASetter && maybeSetter.getPropertyName().equals(method.getPropertyName())) {
+ setter = maybeSetter;
+ break;
+ }
+ }
+ }
+
+ // The type of property influences the visitation
+ String valueExpression =
+ String.format("bean = (%1$s) %2$s.getAutoBean(as.%3$s());", AbstractAutoBean.class
+ .getCanonicalName(), AutoBeanUtils.class.getCanonicalName(), method.getMethod()
+ .getName());
+ String visitMethod;
+ String visitVariable = "bean";
+ if (method.isCollection()) {
+ visitMethod = "Collection";
+ } else if (method.isMap()) {
+ visitMethod = "Map";
+ } else if (method.isValueType()) {
+ valueExpression = String.format("value = as.%s();", method.getMethod().getName());
+ visitMethod = "Value";
+ visitVariable = "value";
+ } else {
+ visitMethod = "Reference";
+ }
+ sw.println(valueExpression);
+
+ // Map<List<Foo>, Bar> --> Map, List, Foo, Bar
+ List<JType> typeList = new ArrayList<JType>();
+ createTypeList(typeList, method.getMethod().getReturnType());
+ assert typeList.size() > 0;
+
+ /*
+ * Make the PropertyContext that lets us call the setter. We allow
+ * multiple methods to be bound to the same property (e.g. to allow JSON
+ * payloads to be interpreted as different types). The leading underscore
+ * allows purely numeric property names, which are valid JSON map keys.
+ */
+ // propertyContext = new CPContext(.....);
+ sw.println("propertyContext = new %s(", ClientPropertyContext.class.getCanonicalName());
+ sw.indent();
+ // The instance on which the context is nominally operating
+ sw.println("as,");
+ // Produce a JSNI reference to a setter function to call
+ {
+ if (setter != null) {
+ // Call a method that returns a JSNI reference to the method to call
+ // setFooMethodReference(),
+ sw.println("%sMethodReference(as),", setter.getMethod().getName());
+ referencedSetters.add(setter);
+ } else {
+ // Create a function that will update the values map
+ // CPContext.beanSetter(FooBeanImpl.this, "foo");
+ sw.println("%s.beanSetter(%s.this, \"%s\"),", ClientPropertyContext.Setter.class
+ .getCanonicalName(), type.getSimpleSourceName(), method.getPropertyName());
+ }
+ }
+ if (typeList.size() == 1) {
+ sw.println("%s.class", ModelUtils.ensureBaseType(typeList.get(0)).getQualifiedSourceName());
+ } else {
+ // Produce the array of parameter types
+ sw.print("new Class<?>[] {");
+ boolean first = true;
+ for (JType lit : typeList) {
+ if (first) {
+ first = false;
+ } else {
+ sw.print(", ");
+ }
+ sw.print("%s.class", ModelUtils.ensureBaseType(lit).getQualifiedSourceName());
+ }
+ sw.println("},");
+
+ // Produce the array of parameter counts
+ sw.print("new int[] {");
+ first = true;
+ for (JType lit : typeList) {
+ if (first) {
+ first = false;
+ } else {
+ sw.print(", ");
+ }
+ JParameterizedType hasParam = lit.isParameterized();
+ if (hasParam == null) {
+ sw.print("0");
+ } else {
+ sw.print(String.valueOf(hasParam.getTypeArgs().length));
+ }
+ }
+ sw.println("}");
+ }
+ sw.outdent();
+ sw.println(");");
+
+ // if (visitor.visitReferenceProperty("foo", value, ctx))
+ sw.println("if (visitor.visit%sProperty(\"%s\", %s, propertyContext)) {", visitMethod, method
+ .getPropertyName(), visitVariable);
+ if (!method.isValueType()) {
+ // Cycle-detection in AbstractAutoBean.traverse
+ sw.indentln("if (bean != null) { bean.traverse(visitor, ctx); }");
+ }
+ sw.println("}");
+ // visitor.endVisitorReferenceProperty("foo", value, ctx);
+ sw.println("visitor.endVisit%sProperty(\"%s\", %s, propertyContext);", visitMethod, method
+ .getPropertyName(), visitVariable);
+ }
+ sw.outdent();
+ sw.println("}");
+
+ for (AutoBeanMethod method : referencedSetters) {
+ JMethod jmethod = method.getMethod();
+ assert jmethod.getParameters().length == 1;
+
+ /*-
+ * Setter setFooMethodReference(Object instance) {
+ * return instance.@com.example.Blah::setFoo(Lcom/example/Foo;);
+ * }
+ */
+ sw.println("public static native %s %sMethodReference(Object instance) /*-{",
+ ClientPropertyContext.Setter.class.getCanonicalName(), jmethod.getName());
+ sw.indentln("return instance.@%s::%s(%s);", jmethod.getEnclosingType()
+ .getQualifiedSourceName(), jmethod.getName(), jmethod.getParameters()[0].getType()
+ .getJNISignature());
+ sw.println("}-*/;");
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java
new file mode 100644
index 0000000..4069a57
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryMethod.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.gwt.rebind.model;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+
+/**
+ * Represents a single method in an AutoBeanFactory interface.
+ */
+public class AutoBeanFactoryMethod {
+ /**
+ * Builds AutoBeanFactoryMethods.
+ */
+ public static class Builder {
+ private AutoBeanFactoryMethod toReturn = new AutoBeanFactoryMethod();
+
+ public AutoBeanFactoryMethod build() {
+ try {
+ return toReturn;
+ } finally {
+ toReturn = null;
+ }
+ }
+
+ public void setAutoBeanType(AutoBeanType type) {
+ toReturn.autoBeanType = type;
+ }
+
+ public void setMethod(JMethod method) {
+ setName(method.getName());
+ setReturnType(method.getReturnType().isClassOrInterface());
+ if (method.getParameters().length == 1) {
+ setWrappedType(method.getParameters()[0].getType().isClassOrInterface());
+ }
+ }
+
+ public void setName(String name) {
+ toReturn.name = name;
+ }
+
+ public void setReturnType(JClassType returnType) {
+ toReturn.returnType = returnType;
+ }
+
+ public void setWrappedType(JClassType wrapped) {
+ toReturn.wrappedType = wrapped;
+ }
+ }
+
+ private AutoBeanType autoBeanType;
+ private JClassType wrappedType;
+ private String name;
+ private JClassType returnType;
+
+ private AutoBeanFactoryMethod() {
+ }
+
+ public AutoBeanType getAutoBeanType() {
+ return autoBeanType;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public JClassType getReturnType() {
+ return returnType;
+ }
+
+ public JClassType getWrappedType() {
+ return wrappedType;
+ }
+
+ public boolean isWrapper() {
+ return wrappedType != null;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java
new file mode 100644
index 0000000..6122dfc
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanFactoryModel.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.gwt.rebind.model;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.shared.impl.EnumMap.ExtraEnums;
+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.JEnumConstant;
+import com.google.gwt.core.ext.typeinfo.JEnumType;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
+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.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.editor.rebind.model.ModelUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ */
+public class AutoBeanFactoryModel {
+ private static final JType[] EMPTY_JTYPE = new JType[0];
+
+ private final JGenericType autoBeanInterface;
+ private final JClassType autoBeanFactoryInterface;
+ private final Map<JEnumConstant, String> allEnumConstants = new LinkedHashMap<JEnumConstant, String>();
+ private final List<JClassType> categoryTypes;
+ private final List<JClassType> noWrapTypes;
+ private final TreeLogger logger;
+ private final List<AutoBeanFactoryMethod> methods = new ArrayList<AutoBeanFactoryMethod>();
+ private final List<JMethod> objectMethods;
+ private final TypeOracle oracle;
+ private final Map<JClassType, AutoBeanType> peers = new LinkedHashMap<JClassType, AutoBeanType>();
+ private boolean poisoned;
+
+ /**
+ * Accumulates bean types that are reachable through the type graph.
+ */
+ private Set<JClassType> toCalculate = new LinkedHashSet<JClassType>();
+
+ public AutoBeanFactoryModel(TreeLogger logger, JClassType factoryType)
+ throws UnableToCompleteException {
+ this.logger = logger;
+ oracle = factoryType.getOracle();
+ autoBeanInterface = oracle.findType(AutoBean.class.getCanonicalName()).isGenericType();
+ autoBeanFactoryInterface = oracle.findType(
+ AutoBeanFactory.class.getCanonicalName()).isInterface();
+
+ /*
+ * We want to allow the user to override some of the useful Object methods,
+ * so we'll extract them here.
+ */
+ JClassType objectType = oracle.getJavaLangObject();
+ objectMethods = Arrays.asList(
+ objectType.findMethod("equals", new JType[] {objectType}),
+ objectType.findMethod("hashCode", EMPTY_JTYPE),
+ objectType.findMethod("toString", EMPTY_JTYPE));
+
+ // Process annotations
+ {
+ Category categoryAnnotation = factoryType.getAnnotation(Category.class);
+ if (categoryAnnotation != null) {
+ categoryTypes = new ArrayList<JClassType>(
+ categoryAnnotation.value().length);
+ processClassArrayAnnotation(categoryAnnotation.value(), categoryTypes);
+ } else {
+ categoryTypes = null;
+ }
+
+ noWrapTypes = new ArrayList<JClassType>();
+ noWrapTypes.add(oracle.findType(AutoBean.class.getCanonicalName()));
+ NoWrap noWrapAnnotation = factoryType.getAnnotation(NoWrap.class);
+ if (noWrapAnnotation != null) {
+ processClassArrayAnnotation(noWrapAnnotation.value(), noWrapTypes);
+ }
+
+ ExtraEnums extraEnumsAnnotation = factoryType.getAnnotation(ExtraEnums.class);
+ if (extraEnumsAnnotation != null) {
+ for (Class<?> clazz : extraEnumsAnnotation.value()) {
+ JEnumType asEnum = oracle.findType(clazz.getCanonicalName()).isEnum();
+ assert asEnum != null;
+ for (JEnumConstant value : asEnum.getEnumConstants()) {
+ allEnumConstants.put(value, AutoBeanMethod.getEnumName(value));
+ }
+ }
+ }
+ }
+
+ for (JMethod method : factoryType.getOverridableMethods()) {
+ if (method.getEnclosingType().equals(autoBeanFactoryInterface)) {
+ // Ignore methods in AutoBeanFactory
+ continue;
+ }
+
+ JClassType returnType = method.getReturnType().isInterface();
+ if (returnType == null) {
+ poison("The return type of method %s is a primitive type",
+ method.getName());
+ continue;
+ }
+
+ // AutoBean<FooIntf> blah() --> beanType = FooIntf
+ JClassType beanType = ModelUtils.findParameterizationOf(
+ autoBeanInterface, returnType)[0];
+ if (beanType.isInterface() == null) {
+ poison("The %s parameterization is not an interface",
+ beanType.getQualifiedSourceName());
+ continue;
+ }
+
+ // AutoBean<FooIntf> blah(FooIntfSub foo) --> toWrap = FooIntfSub
+ JClassType toWrap;
+ if (method.getParameters().length == 0) {
+ toWrap = null;
+ } else if (method.getParameters().length == 1) {
+ toWrap = method.getParameters()[0].getType().isClassOrInterface();
+ if (!beanType.isAssignableFrom(toWrap)) {
+ poison(
+ "The %s parameterization %s is not assignable from the delegate"
+ + " type %s", autoBeanInterface.getSimpleSourceName(),
+ toWrap.getQualifiedSourceName());
+ continue;
+ }
+ } else {
+ poison("Unexpecetd parameters in method %s", method.getName());
+ continue;
+ }
+
+ AutoBeanType autoBeanType = getAutoBeanType(beanType);
+
+ // Must wrap things that aren't simple interfaces
+ if (!autoBeanType.isSimpleBean() && toWrap == null) {
+ if (categoryTypes != null) {
+ poison("The %s parameterization is not simple and the following"
+ + " methods did not have static implementations:",
+ beanType.getQualifiedSourceName());
+ for (AutoBeanMethod missing : autoBeanType.getMethods()) {
+ if (missing.getAction().equals(JBeanMethod.CALL)
+ && missing.getStaticImpl() == null) {
+ poison(missing.getMethod().getReadableDeclaration());
+ }
+ }
+ } else {
+ poison("The %s parameterization is not simple, but the %s method"
+ + " does not provide a delegate",
+ beanType.getQualifiedSourceName(), method.getName());
+ }
+ continue;
+ }
+
+ AutoBeanFactoryMethod.Builder builder = new AutoBeanFactoryMethod.Builder();
+ builder.setAutoBeanType(autoBeanType);
+ builder.setMethod(method);
+ methods.add(builder.build());
+ }
+
+ while (!toCalculate.isEmpty()) {
+ Set<JClassType> examine = toCalculate;
+ toCalculate = new LinkedHashSet<JClassType>();
+ for (JClassType beanType : examine) {
+ getAutoBeanType(beanType);
+ }
+ }
+
+ if (poisoned) {
+ die("Unable to complete due to previous errors");
+ }
+ }
+
+ public Collection<AutoBeanType> getAllTypes() {
+ return Collections.unmodifiableCollection(peers.values());
+ }
+
+ public List<JClassType> getCategoryTypes() {
+ return categoryTypes;
+ }
+
+ public Map<JEnumConstant, String> getEnumTokenMap() {
+ return Collections.unmodifiableMap(allEnumConstants);
+ }
+
+ public List<AutoBeanFactoryMethod> getMethods() {
+ return Collections.unmodifiableList(methods);
+ }
+
+ public AutoBeanType getPeer(JClassType beanType) {
+ beanType = ModelUtils.ensureBaseType(beanType);
+ return peers.get(beanType);
+ }
+
+ private List<AutoBeanMethod> computeMethods(JClassType beanType) {
+ List<JMethod> toExamine = new ArrayList<JMethod>();
+ toExamine.addAll(Arrays.asList(beanType.getInheritableMethods()));
+ toExamine.addAll(objectMethods);
+ List<AutoBeanMethod> toReturn = new ArrayList<AutoBeanMethod>(
+ toExamine.size());
+ for (JMethod method : toExamine) {
+ if (method.isPrivate()) {
+ // Ignore private methods
+ continue;
+ }
+ AutoBeanMethod.Builder builder = new AutoBeanMethod.Builder();
+ builder.setMethod(method);
+
+ // See if this method shouldn't have its return type wrapped
+ // TODO: Allow class return types?
+ JClassType classReturn = method.getReturnType().isInterface();
+ if (classReturn != null) {
+ maybeCalculate(classReturn);
+ if (noWrapTypes != null) {
+ for (JClassType noWrap : noWrapTypes) {
+ if (noWrap.isAssignableFrom(classReturn)) {
+ builder.setNoWrap(true);
+ break;
+ }
+ }
+ }
+ }
+
+ // GET, SET, or CALL
+ JBeanMethod action = JBeanMethod.which(method);
+ builder.setAction(action);
+ if (JBeanMethod.CALL.equals(action)) {
+ JMethod staticImpl = findStaticImpl(beanType, method);
+ if (staticImpl == null && objectMethods.contains(method)) {
+ // Don't complain about lack of implementation for Object methods
+ continue;
+ }
+ builder.setStaticImp(staticImpl);
+ }
+
+ AutoBeanMethod toAdd = builder.build();
+
+ // Collect referenced enums
+ if (toAdd.hasEnumMap()) {
+ allEnumConstants.putAll(toAdd.getEnumMap());
+ }
+
+ // See if parameterizations will pull in more types
+ if (toAdd.isCollection()) {
+ maybeCalculate(toAdd.getElementType());
+ } else if (toAdd.isMap()) {
+ maybeCalculate(toAdd.getKeyType());
+ maybeCalculate(toAdd.getValueType());
+ }
+
+ toReturn.add(toAdd);
+ }
+ return toReturn;
+ }
+
+ private void die(String message) throws UnableToCompleteException {
+ poison(message);
+ throw new UnableToCompleteException();
+ }
+
+ /**
+ * Find <code>Object __intercept(AutoBean<?> bean, Object value);</code> in
+ * the category types.
+ */
+ private JMethod findInterceptor(JClassType beanType) {
+ if (categoryTypes == null) {
+ return null;
+ }
+ for (JClassType category : categoryTypes) {
+ for (JMethod method : category.getOverloads("__intercept")) {
+ // Ignore non-static, non-public methods
+ // TODO: Implement visibleFrom() to allow package-protected categories
+ if (!method.isStatic() || !method.isPublic()) {
+ continue;
+ }
+
+ JParameter[] params = method.getParameters();
+ if (params.length != 2) {
+ continue;
+ }
+ if (!methodAcceptsAutoBeanAsFirstParam(beanType, method)) {
+ continue;
+ }
+ JClassType value = params[1].getType().isClassOrInterface();
+ if (value == null) {
+ continue;
+ }
+ if (!oracle.getJavaLangObject().isAssignableTo(value)) {
+ continue;
+ }
+ return method;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Search the category types for a static implementation of an interface
+ * method. Given the interface method declaration:
+ *
+ * <pre>
+ * Foo bar(Baz baz);
+ * </pre>
+ *
+ * this will search the types in {@link #categoryTypes} for the following
+ * method:
+ *
+ * <pre>
+ * public static Foo bar(AutoBean<Intf> bean, Baz baz) {}
+ * </pre>
+ */
+ private JMethod findStaticImpl(JClassType beanType, JMethod method) {
+ if (categoryTypes == null) {
+ return null;
+ }
+
+ for (JClassType category : categoryTypes) {
+ // One extra argument for the AutoBean
+ JParameter[] methodParams = method.getParameters();
+ int requiredArgs = methodParams.length + 1;
+ overload : for (JMethod overload : category.getOverloads(method.getName())) {
+ if (!overload.isStatic() || !overload.isPublic()) {
+ // Ignore non-static, non-public methods
+ continue;
+ }
+
+ JParameter[] overloadParams = overload.getParameters();
+ if (overloadParams.length != requiredArgs) {
+ continue;
+ }
+
+ if (!methodAcceptsAutoBeanAsFirstParam(beanType, overload)) {
+ // Ignore if the first parameter is a primitive or not assignable
+ continue;
+ }
+
+ // Match the rest of the parameters
+ for (int i = 1; i < requiredArgs; i++) {
+ JType methodType = methodParams[i - 1].getType();
+ JType overloadType = overloadParams[i].getType();
+ if (methodType.equals(overloadType)) {
+ // Match; exact, the usual case
+ } else if (methodType.isClassOrInterface() != null
+ && overloadType.isClassOrInterface() != null
+ && methodType.isClassOrInterface().isAssignableTo(
+ overloadType.isClassOrInterface())) {
+ // Match; assignment-compatible
+ } else {
+ // No match, keep looking
+ continue overload;
+ }
+ }
+ return overload;
+ }
+ }
+ return null;
+ }
+
+ private AutoBeanType getAutoBeanType(JClassType beanType) {
+ beanType = ModelUtils.ensureBaseType(beanType);
+ AutoBeanType toReturn = peers.get(beanType);
+ if (toReturn == null) {
+ AutoBeanType.Builder builder = new AutoBeanType.Builder();
+ builder.setOwnerFactory(this);
+ builder.setPeerType(beanType);
+ builder.setMethods(computeMethods(beanType));
+ builder.setInterceptor(findInterceptor(beanType));
+ if (noWrapTypes != null) {
+ for (JClassType noWrap : noWrapTypes) {
+ if (noWrap.isAssignableFrom(beanType)) {
+ builder.setNoWrap(true);
+ break;
+ }
+ }
+ }
+ toReturn = builder.build();
+ peers.put(beanType, toReturn);
+ }
+ return toReturn;
+ }
+
+ /**
+ * Enqueue a type in {@link #toCalculate} if {@link #peers} does not already
+ * contain an entry.
+ */
+ private void maybeCalculate(JClassType type) {
+ if (type.isInterface() == null || ModelUtils.isValueType(oracle, type)) {
+ return;
+ }
+ if (!peers.containsKey(type)) {
+ toCalculate.add(type);
+ }
+ }
+
+ private boolean methodAcceptsAutoBeanAsFirstParam(JClassType beanType,
+ JMethod method) {
+ JParameter[] params = method.getParameters();
+ if (params.length == 0) {
+ return false;
+ }
+ JClassType paramAsClass = params[0].getType().isClassOrInterface();
+
+ // First parameter is a primitive
+ if (paramAsClass == null) {
+ return false;
+ }
+
+ // Check using base types to account for erasure semantics
+ JParameterizedType expectedFirst = oracle.getParameterizedType(
+ autoBeanInterface,
+ new JClassType[] {ModelUtils.ensureBaseType(beanType)});
+ return expectedFirst.isAssignableTo(paramAsClass);
+ }
+
+ private void poison(String message, Object... args) {
+ logger.log(TreeLogger.ERROR, String.format(message, args));
+ poisoned = true;
+ }
+
+ private void processClassArrayAnnotation(Class<?>[] classes,
+ Collection<JClassType> accumulator) {
+ for (Class<?> clazz : classes) {
+ JClassType category = oracle.findType(clazz.getCanonicalName());
+ if (category == null) {
+ poison("Could not find @%s type %s in the TypeOracle",
+ Category.class.getSimpleName(), clazz.getCanonicalName());
+ continue;
+ } else if (!category.isPublic()) {
+ poison("Category type %s is not public",
+ category.getQualifiedSourceName());
+ continue;
+ } else if (!category.isStatic() && category.isMemberType()) {
+ poison("Category type %s must be static",
+ category.getQualifiedSourceName());
+ continue;
+ }
+ accumulator.add(category);
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java
new file mode 100644
index 0000000..43763df
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanMethod.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.gwt.rebind.model;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JEnumConstant;
+import com.google.gwt.core.ext.typeinfo.JEnumType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.editor.rebind.model.ModelUtils;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Describes a method implemented by an AutoBean.
+ */
+public class AutoBeanMethod {
+ /**
+ * Creates AutoBeanMethods.
+ */
+ public static class Builder {
+ private AutoBeanMethod toReturn = new AutoBeanMethod();
+
+ public AutoBeanMethod build() {
+ if (toReturn.action.equals(JBeanMethod.GET)
+ || toReturn.action.equals(JBeanMethod.SET)
+ || toReturn.action.equals(JBeanMethod.SET_BUILDER)) {
+ PropertyName annotation = toReturn.method.getAnnotation(PropertyName.class);
+ if (annotation != null) {
+ toReturn.propertyName = annotation.value();
+ } else {
+ toReturn.propertyName = toReturn.action.inferName(toReturn.method);
+ }
+ }
+
+ try {
+ return toReturn;
+ } finally {
+ toReturn = null;
+ }
+ }
+
+ public void setAction(JBeanMethod action) {
+ toReturn.action = action;
+ }
+
+ public void setMethod(JMethod method) {
+ toReturn.method = method;
+ TypeOracle oracle = method.getEnclosingType().getOracle();
+
+ JType returnType = method.getReturnType();
+ toReturn.isValueType = ModelUtils.isValueType(oracle, returnType);
+
+ if (!toReturn.isValueType) {
+ // See if it's a collection or a map
+ JClassType returnClass = returnType.isClassOrInterface();
+ JClassType collectionInterface = oracle.findType(Collection.class.getCanonicalName());
+ JClassType mapInterface = oracle.findType(Map.class.getCanonicalName());
+ if (collectionInterface.isAssignableFrom(returnClass)) {
+ JClassType[] parameterizations = ModelUtils.findParameterizationOf(
+ collectionInterface, returnClass);
+ toReturn.elementType = parameterizations[0];
+ maybeProcessEnumType(toReturn.elementType);
+ } else if (mapInterface.isAssignableFrom(returnClass)) {
+ JClassType[] parameterizations = ModelUtils.findParameterizationOf(
+ mapInterface, returnClass);
+ toReturn.keyType = parameterizations[0];
+ toReturn.valueType = parameterizations[1];
+ maybeProcessEnumType(toReturn.keyType);
+ maybeProcessEnumType(toReturn.valueType);
+ }
+ } else {
+ maybeProcessEnumType(returnType);
+ }
+ }
+
+ public void setNoWrap(boolean noWrap) {
+ toReturn.isNoWrap = noWrap;
+ }
+
+ public void setStaticImp(JMethod staticImpl) {
+ toReturn.staticImpl = staticImpl;
+ }
+
+ /**
+ * Call {@link #processEnumType(JEnumType)} if {@code type} is a
+ * {@link JEnumType}.
+ */
+ private void maybeProcessEnumType(JType type) {
+ assert type != null : "type == null";
+ JEnumType enumType = type.isEnum();
+ if (enumType != null) {
+ processEnumType(enumType);
+ }
+ }
+
+ /**
+ * Adds a JEnumType to the AutoBeanMethod's enumMap so that the
+ * AutoBeanFactoryGenerator can embed extra metadata about the enum values.
+ */
+ private void processEnumType(JEnumType enumType) {
+ Map<JEnumConstant, String> map = toReturn.enumMap;
+ if (map == null) {
+ map = toReturn.enumMap = new LinkedHashMap<JEnumConstant, String>();
+ }
+ for (JEnumConstant e : enumType.getEnumConstants()) {
+ String name = getEnumName(e);
+ map.put(e, name);
+ }
+ }
+ }
+
+ static String getEnumName(JEnumConstant e) {
+ String name;
+ PropertyName annotation = e.getAnnotation(PropertyName.class);
+ if (annotation == null) {
+ name = e.getName();
+ } else {
+ name = annotation.value();
+ }
+ return name;
+ }
+
+ private JBeanMethod action;
+ private JClassType elementType;
+ private Map<JEnumConstant, String> enumMap;
+ private JClassType keyType;
+ private JMethod method;
+ private boolean isNoWrap;
+ private boolean isValueType;
+ private String propertyName;
+ private JMethod staticImpl;
+ private JClassType valueType;
+
+ private AutoBeanMethod() {
+ }
+
+ public JBeanMethod getAction() {
+ return action;
+ }
+
+ public JClassType getElementType() {
+ return elementType;
+ }
+
+ public Map<JEnumConstant, String> getEnumMap() {
+ return enumMap;
+ }
+
+ public JClassType getKeyType() {
+ return keyType;
+ }
+
+ public JMethod getMethod() {
+ return method;
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ /**
+ * If the AutoBean method was declared in a type containing a
+ * {@link com.google.gwt.editor.client.AutoBean.Category Category} annotation,
+ * this method will return the static implementation.
+ */
+ public JMethod getStaticImpl() {
+ return staticImpl;
+ }
+
+ public JClassType getValueType() {
+ return valueType;
+ }
+
+ public boolean hasEnumMap() {
+ return enumMap != null;
+ }
+
+ public boolean isCollection() {
+ return elementType != null;
+ }
+
+ public boolean isMap() {
+ return keyType != null;
+ }
+
+ public boolean isNoWrap() {
+ return isNoWrap;
+ }
+
+ public boolean isValueType() {
+ return isValueType;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return method.toString();
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java
new file mode 100644
index 0000000..da40afe
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/AutoBeanType.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.gwt.rebind.model;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Describes an AutoBean.
+ */
+public class AutoBeanType {
+
+ /**
+ * Builder.
+ */
+ public static class Builder {
+ private boolean affectedByCategories;
+ private String beanSimpleSourceName;
+ private String categorySuffix;
+ private AutoBeanType toReturn = new AutoBeanType();
+
+ public AutoBeanType build() {
+ // Different implementations necessary for category-affected impls
+ toReturn.simpleSourceName = beanSimpleSourceName
+ + (affectedByCategories ? categorySuffix : "");
+ try {
+ return toReturn;
+ } finally {
+ toReturn = null;
+ }
+ }
+
+ public void setInterceptor(JMethod interceptor) {
+ affectedByCategories = interceptor != null;
+ toReturn.interceptor = interceptor;
+ }
+
+ public void setMethods(List<AutoBeanMethod> methods) {
+ toReturn.methods = new ArrayList<AutoBeanMethod>(methods);
+ Collections.sort(toReturn.methods, new Comparator<AutoBeanMethod>() {
+ public int compare(AutoBeanMethod o1, AutoBeanMethod o2) {
+ int c = o1.getAction().compareTo(o2.getAction());
+ if (c != 0) {
+ return c;
+ }
+ // Name alone would cause overload conflicts
+ return o1.getMethod().getReadableDeclaration().compareTo(
+ o2.getMethod().getReadableDeclaration());
+ }
+ });
+ toReturn.methods = Collections.unmodifiableList(toReturn.methods);
+
+ toReturn.simpleBean = true;
+ for (AutoBeanMethod method : methods) {
+ if (method.getAction().equals(JBeanMethod.CALL)) {
+ if (method.getStaticImpl() == null) {
+ toReturn.simpleBean = false;
+ } else {
+ affectedByCategories = true;
+ }
+ }
+ }
+ }
+
+ public void setNoWrap(boolean noWrap) {
+ toReturn.noWrap = noWrap;
+ }
+
+ public void setOwnerFactory(AutoBeanFactoryModel autoBeanFactoryModel) {
+ if (autoBeanFactoryModel.getCategoryTypes() == null) {
+ return;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (JClassType category : autoBeanFactoryModel.getCategoryTypes()) {
+ sb.append("_").append(
+ category.getQualifiedSourceName().replace('.', '_'));
+ }
+ categorySuffix = sb.toString();
+ }
+
+ public void setPeerType(JClassType type) {
+ assert type.isParameterized() == null && type.isRawType() == null;
+ toReturn.peerType = type;
+ String packageName = type.getPackage().getName();
+ if (packageName.startsWith("java")) {
+ packageName = "emul." + packageName;
+ }
+ toReturn.packageName = packageName;
+ beanSimpleSourceName = type.getName().replace('.', '_') + "AutoBean";
+ }
+ }
+
+ private JMethod interceptor;
+ private List<AutoBeanMethod> methods;
+ private boolean noWrap;
+ private String packageName;
+ private JClassType peerType;
+ private boolean simpleBean;
+ private String simpleSourceName;
+
+ private AutoBeanType() {
+ }
+
+ /**
+ * A method that is allowed to intercept and modify return values from
+ * getters.
+ */
+ public JMethod getInterceptor() {
+ return interceptor;
+ }
+
+ public List<AutoBeanMethod> getMethods() {
+ return methods;
+ }
+
+ public String getPackageNome() {
+ return packageName;
+ }
+
+ public JClassType getPeerType() {
+ return peerType;
+ }
+
+ public String getQualifiedSourceName() {
+ return getPackageNome() + "." + getSimpleSourceName();
+ }
+
+ public String getSimpleSourceName() {
+ return simpleSourceName;
+ }
+
+ public boolean isNoWrap() {
+ return noWrap;
+ }
+
+ /**
+ * A simple bean has only getters and setters.
+ */
+ public boolean isSimpleBean() {
+ return simpleBean;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return peerType.toString();
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java
new file mode 100644
index 0000000..d600c19
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/gwt/rebind/model/JBeanMethod.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.gwt.rebind.model;
+
+import static com.google.web.bindery.autobean.vm.impl.BeanMethod.GET_PREFIX;
+import static com.google.web.bindery.autobean.vm.impl.BeanMethod.HAS_PREFIX;
+import static com.google.web.bindery.autobean.vm.impl.BeanMethod.IS_PREFIX;
+import static com.google.web.bindery.autobean.vm.impl.BeanMethod.SET_PREFIX;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+
+import java.beans.Introspector;
+
+/**
+ * Common utility code for matching {@link JMethod} and against bean-style
+ * accessor semantics.
+ *
+ * @see com.google.web.bindery.autobean.vm.impl.BeanMethod
+ */
+public enum JBeanMethod {
+ GET {
+ @Override
+ public String inferName(JMethod method) {
+ if (isBooleanProperty(method) && method.getName().startsWith(IS_PREFIX)) {
+ return Introspector.decapitalize(method.getName().substring(2));
+ }
+ return super.inferName(method);
+ }
+
+ @Override
+ public boolean matches(JMethod method) {
+ if (method.getParameters().length > 0) {
+ return false;
+ }
+
+ if (isBooleanProperty(method)) {
+ return true;
+ }
+
+ String name = method.getName();
+ if (name.startsWith(GET_PREFIX) && name.length() > 3) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if the method matches {@code boolean isFoo()} or
+ * {@code boolean hasFoo()} property accessors.
+ */
+ private boolean isBooleanProperty(JMethod method) {
+ JType returnType = method.getReturnType();
+ if (JPrimitiveType.BOOLEAN.equals(returnType)
+ || method.getEnclosingType().getOracle().findType(
+ Boolean.class.getCanonicalName()).equals(returnType)) {
+ String name = method.getName();
+ if (name.startsWith(IS_PREFIX) && name.length() > 2) {
+ return true;
+ }
+ if (name.startsWith(HAS_PREFIX) && name.length() > 3) {
+ return true;
+ }
+ }
+ return false;
+ }
+ },
+ SET {
+ @Override
+ public boolean matches(JMethod method) {
+ if (!JPrimitiveType.VOID.equals(method.getReturnType())) {
+ return false;
+ }
+ if (method.getParameters().length != 1) {
+ return false;
+ }
+ String name = method.getName();
+ if (name.startsWith(SET_PREFIX) && name.length() > 3) {
+ return true;
+ }
+ return false;
+ }
+ },
+ SET_BUILDER {
+ @Override
+ public boolean matches(JMethod method) {
+ JClassType returnClass = method.getReturnType().isClassOrInterface();
+ if (returnClass == null
+ || !returnClass.isAssignableFrom(method.getEnclosingType())) {
+ return false;
+ }
+ if (method.getParameters().length != 1) {
+ return false;
+ }
+ String name = method.getName();
+ if (name.startsWith(SET_PREFIX) && name.length() > 3) {
+ return true;
+ }
+ return false;
+ }
+ },
+ CALL {
+ /**
+ * Matches all leftover methods.
+ */
+ @Override
+ public boolean matches(JMethod method) {
+ return true;
+ }
+ };
+
+ /**
+ * Determine which Action a method maps to.
+ */
+ public static JBeanMethod which(JMethod method) {
+ for (JBeanMethod action : JBeanMethod.values()) {
+ if (action.matches(method)) {
+ return action;
+ }
+ }
+ throw new RuntimeException("CALL should have matched");
+ }
+
+ /**
+ * Infer the name of a property from the method.
+ */
+ public String inferName(JMethod method) {
+ if (this == CALL) {
+ throw new UnsupportedOperationException(
+ "Cannot infer a property name for a CALL-type method");
+ }
+ return Introspector.decapitalize(method.getName().substring(3));
+ }
+
+ /**
+ * Returns {@code true} if the BeanLikeMethod matches the method.
+ */
+ public abstract boolean matches(JMethod method);
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBean.java b/user/src/com/google/web/bindery/autobean/shared/AutoBean.java
new file mode 100644
index 0000000..9aa94fc
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBean.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A controller for an implementation of a bean interface. Instances of
+ * AutoBeans are obtained from an {@link AutoBeanFactory}.
+ *
+ * @param <T> the type of interface that will be wrapped.
+ */
+public interface AutoBean<T> {
+ /**
+ * An annotation that allows inferred property names to be overridden.
+ * <p>
+ * This annotation is asymmetric, applying it to a getter will not affect the
+ * setter. The asymmetry allows existing users of an interface to read old
+ * {@link AutoBeanCodex} messages, but write new ones.
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
+ public @interface PropertyName {
+ String value();
+ }
+
+ /**
+ * Accept an AutoBeanVisitor.
+ *
+ * @param visitor an {@link AutoBeanVisitor}
+ */
+ void accept(AutoBeanVisitor visitor);
+
+ /**
+ * Returns a proxy implementation of the <code>T</code> interface which will
+ * delegate to the underlying wrapped object, if any.
+ *
+ * @return a proxy that delegates to the wrapped object
+ */
+ T as();
+
+ /**
+ * This method always throws an {@link UnsupportedOperationException}. The
+ * implementation of this method in previous releases was not sufficiently
+ * robust and there are no further uses of this method within the GWT code
+ * base. Furthermore, there are many different semantics that can be applied
+ * to a cloning process that cannot be adequately addressed with a single
+ * implementation.
+ * <p>
+ * A simple clone of an acyclic datastructure can be created by using
+ * {@link AutoBeanCodex} to encode and decode the root object. Other cloning
+ * algorithms are best implemented by using an {@link AutoBeanVisitor}.
+ *
+ * @throws UnsupportedOperationException
+ * @deprecated with no replacement
+ */
+ @Deprecated
+ AutoBean<T> clone(boolean deep);
+
+ /**
+ * Returns the AutoBeanFactory that created the AutoBean.
+ *
+ * @return an AutoBeanFactory
+ */
+ AutoBeanFactory getFactory();
+
+ /**
+ * Retrieve a tag value that was previously provided to
+ * {@link #setTag(String, Object)}.
+ *
+ * @param tagName the tag name
+ * @return the tag value
+ * @see #setTag(String, Object)
+ */
+ <Q> Q getTag(String tagName);
+
+ /**
+ * Returns the wrapped interface type.
+ */
+ Class<T> getType();
+
+ /**
+ * Returns the value most recently passed to {@link #setFrozen}, or
+ * {@code false} if it has never been called.
+ *
+ * @return {@code true} if this instance is frozen
+ */
+ boolean isFrozen();
+
+ /**
+ * Returns {@code true} if the AutoBean was provided with an external object.
+ *
+ * @return {@code true} if this instance is a wrapper
+ */
+ boolean isWrapper();
+
+ /**
+ * Disallows any method calls other than getters. All setter and call
+ * operations will throw an {@link IllegalStateException}.
+ *
+ * @param frozen if {@code true}, freeze this instance
+ */
+ void setFrozen(boolean frozen);
+
+ /**
+ * A tag is an arbitrary piece of external metadata to be associated with the
+ * wrapped value.
+ *
+ * @param tagName the tag name
+ * @param value the wrapped value
+ * @see #getTag(String)
+ */
+ void setTag(String tagName, Object value);
+
+ /**
+ * If the AutoBean wraps an object, return the underlying object. The AutoBean
+ * will no longer function once unwrapped.
+ *
+ * @return the previously-wrapped object
+ * @throws IllegalStateException if the AutoBean is not a wrapper
+ */
+ T unwrap();
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java
new file mode 100644
index 0000000..2f07b1a
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanCodex.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+
+/**
+ * Utility methods for encoding an AutoBean graph into a JSON-compatible string.
+ * This codex intentionally does not preserve object identity, nor does it
+ * encode cycles, but it will detect them.
+ */
+public class AutoBeanCodex {
+
+ /**
+ * Decode an AutoBeanCodex payload.
+ *
+ * @param <T> the expected return type
+ * @param factory an AutoBeanFactory capable of producing {@code AutoBean<T>}
+ * @param clazz the expected return type
+ * @param data a payload previously generated by {@link #encode(AutoBean)}
+ * @return an AutoBean containing the payload contents
+ */
+ public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, Splittable data) {
+ return AutoBeanCodexImpl.doDecode(EncodeState.forDecode(factory), clazz, data);
+ }
+
+ /**
+ * Decode an AutoBeanCodex payload.
+ *
+ * @param <T> the expected return type
+ * @param factory an AutoBeanFactory capable of producing {@code AutoBean<T>}
+ * @param clazz the expected return type
+ * @param payload a payload string previously generated by
+ * {@link #encode(AutoBean)}{@link Splittable#getPayload()
+ * .getPayload()}.
+ * @return an AutoBean containing the payload contents
+ */
+ public static <T> AutoBean<T> decode(AutoBeanFactory factory, Class<T> clazz, String payload) {
+ Splittable data = StringQuoter.split(payload);
+ return decode(factory, clazz, data);
+ }
+
+ /**
+ * Copy data from a {@link Splittable} into an AutoBean. Unset values in the
+ * Splittable will not nullify data that already exists in the AutoBean.
+ *
+ * @param data the source data to copy
+ * @param bean the target AutoBean
+ */
+ public static void decodeInto(Splittable data, AutoBean<?> bean) {
+ AutoBeanCodexImpl.doDecodeInto(EncodeState.forDecode(bean.getFactory()), data, bean);
+ }
+
+ /**
+ * Encodes an AutoBean. The actual payload contents can be retrieved through
+ * {@link Splittable#getPayload()}.
+ *
+ * @param bean the bean to encode
+ * @return a Splittable that encodes the state of the AutoBean
+ */
+ public static Splittable encode(AutoBean<?> bean) {
+ if (bean == null) {
+ return Splittable.NULL;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ EncodeState state = EncodeState.forEncode(bean.getFactory(), sb);
+ AutoBeanCodexImpl.doEncode(state, bean);
+ return StringQuoter.split(sb.toString());
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java
new file mode 100644
index 0000000..44496a5
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanFactory.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A tag interface for the AutoBean generator. Instances of AutoBeans are
+ * created by declaring factory methods on a subtype of this interface.
+ * <p>
+ * Simple interfaces, consisting of only getters and setters, can be constructed
+ * with a no-arg method. Non-simple interfaces must provide a delegate object to
+ * implement a non-simple interface or use a {@link Category}.
+ *
+ * <pre>
+ * interface MyFactory extends AutoBeanFactory {
+ * // A factory method for a simple bean
+ * AutoBean<BeanInterface> beanInterface();
+ * // A factory method for a wrapper bean
+ * AutoBean<ArbitraryInterface> wrapper(ArbitraryInterface delegate);
+ * }
+ * </pre>
+ *
+ * @see <a
+ * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ * wiki page</a>
+ */
+public interface AutoBeanFactory {
+ /**
+ * Allows non-property methods on simple bean implementations when applied.
+ * For any given method, the specified classes will be searched for a public,
+ * static method whose method signature is exactly equal to the declared
+ * method's signature, save for the addition of a new initial paramater that
+ * must accept <code>AutoBean<T></code>.
+ *
+ * <pre>
+ * interface HasMethod {
+ * void doSomething(int a, double b);
+ * }
+ * </pre>
+ *
+ * would be paired with a category implemenation such as
+ *
+ * <pre>
+ * class HasMethodCategory {
+ * public static void doSomething(AutoBean<HasMethod> bean, int a, double b) {
+ * }
+ * }
+ * </pre>
+ *
+ * and registered with
+ *
+ * <pre>
+ * {@literal @}Category(HasMethodCategory.class)
+ * interface MyBeanFactory extends AutoBeanFactory {
+ * AutoBean<HasMethod> hasMethod();
+ * }
+ * </pre>
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface Category {
+ Class<?>[] value();
+ }
+
+ /**
+ * The types specified by this annotation will not be wrapped by an AutoBean
+ * when returned from an AutoBean-controlled method.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface NoWrap {
+ /**
+ * The interface types that should not be wrapped.
+ */
+ Class<?>[] value();
+ }
+
+ /**
+ * Allows dynamic creation of AutoBean instances based on declared
+ * parameterizations.
+ *
+ * @param <T> the parameterization of the created {@link AutoBean}
+ * @param clazz the Class of type T of the new instance
+ * @return an {@link AutoBean} of type T or {@code null} if the interface type
+ * is unknown to the factory
+ */
+ <T> AutoBean<T> create(Class<T> clazz);
+
+ /**
+ * Allows dynamic creation of wrapped AutoBean instances based on declared
+ * parameterizations.
+ *
+ * @param <T> the parameterization of the created {@link AutoBean}
+ * @param <U> the delegate's type, a subtype of T
+ * @param clazz the Class of type T of the new instance
+ * @param delegate a delegate that extends type T
+ * @return an {@link AutoBean} of type T or {@code null} if the interface type
+ * is unknown to the factory
+ */
+ <T, U extends T> AutoBean<T> create(Class<T> clazz, U delegate);
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java
new file mode 100644
index 0000000..e97c438
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanUtils.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import com.google.gwt.core.client.impl.WeakMapping;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility methods for working with AutoBeans.
+ */
+public final class AutoBeanUtils {
+ /*
+ * TODO(bobv): Make Comparison a real type that holds a map contain the diff
+ * between the two objects. Then export a Map of PendingComparison to
+ * Comparisons as a public API to make it easy for developers to perform deep
+ * diffs across a graph structure.
+ *
+ * Three-way merge...
+ */
+
+ private enum Comparison {
+ TRUE, FALSE, PENDING;
+ }
+
+ /**
+ * A Pair where order does not matter and the objects are compared by
+ * identity.
+ */
+ private static class PendingComparison {
+ private final AutoBean<?> a;
+ private final AutoBean<?> b;
+ private final int hashCode;
+
+ public PendingComparison(AutoBean<?> a, AutoBean<?> b) {
+ this.a = a;
+ this.b = b;
+ // Don't make relatively prime since order does not matter
+ hashCode = System.identityHashCode(a) + System.identityHashCode(b);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PendingComparison)) {
+ return false;
+ }
+ PendingComparison other = (PendingComparison) o;
+ return a == other.a && b == other.b || // Direct match
+ a == other.b && b == other.a; // Swapped
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+
+ /**
+ * Compare two graphs of AutoBeans based on values.
+ * <p>
+ * <ul>
+ * <li>AutoBeans are compared based on type and property values</li>
+ * <li>Lists are compared with element-order equality</li>
+ * <li>Sets and all other Collection types are compare with bag equality</li>
+ * <li>Maps are compared as a lists of keys-value pairs</li>
+ * <li>{@link Splittable Splittables} are compared by value</li>
+ * </ul>
+ * <p>
+ * This will work for both simple and wrapper AutoBeans.
+ * <p>
+ * This method may crawl the entire object graph reachable from the input
+ * parameters and may be arbitrarily expensive to compute.
+ *
+ * @param a an {@link AutoBean}
+ * @param b an {@link AutoBean}
+ * @return {@code false} if any values in the graph reachable through
+ * <code>a</code> are different from those reachable from
+ * <code>b</code>
+ */
+ public static boolean deepEquals(AutoBean<?> a, AutoBean<?> b) {
+ return sameOrEquals(a, b, new HashMap<PendingComparison, Comparison>());
+ }
+
+ /**
+ * Returns a map of properties that differ (via {@link Object#equals(Object)})
+ * between two AutoBeans. The keys are property names and the values are the
+ * value of the property in <code>b</code>. Properties present in
+ * <code>a</code> but missing in <code>b</code> will be represented by
+ * <code>null</code> values. This implementation will compare AutoBeans of
+ * different parameterizations, although the diff produced is likely
+ * meaningless.
+ * <p>
+ * This will work for both simple and wrapper AutoBeans.
+ *
+ * @param a an {@link AutoBean}
+ * @param b an {@link AutoBean}
+ * @return a {@link Map} of differing properties
+ */
+ public static Map<String, Object> diff(AutoBean<?> a, AutoBean<?> b) {
+ // Fast check for comparing an object to itself
+ if (a.equals(b)) {
+ return Collections.emptyMap();
+ }
+ final Map<String, Object> toReturn = getAllProperties(b);
+
+ // Remove the entries that are equal, adding nulls for missing properties
+ a.accept(new AutoBeanVisitor() {
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> previousValue,
+ PropertyContext ctx) {
+ if (toReturn.containsKey(propertyName)) {
+ if (equal(propertyName, previousValue)) {
+ // No change
+ toReturn.remove(propertyName);
+ }
+ } else {
+ // The predecessor has a value that this object doesn't.
+ toReturn.put(propertyName, null);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object previousValue,
+ PropertyContext ctx) {
+ if (toReturn.containsKey(propertyName)) {
+ if (equal(propertyName, previousValue)) {
+ // No change
+ toReturn.remove(propertyName);
+ }
+ } else {
+ // The predecessor has a value that this object doesn't.
+ toReturn.put(propertyName, null);
+ }
+ return false;
+ }
+
+ private boolean equal(String propertyName, AutoBean<?> previousValue) {
+ return previousValue == null && toReturn.get(propertyName) == null || previousValue != null
+ && equal(propertyName, previousValue.as());
+ }
+
+ private boolean equal(String propertyName, Object previousValue) {
+ Object currentValue = toReturn.get(propertyName);
+ return previousValue == null && currentValue == null || previousValue != null
+ && previousValue.equals(currentValue);
+ }
+ });
+ return toReturn;
+ }
+
+ /**
+ * Returns a map that is a copy of the properties contained in an AutoBean.
+ * The returned map is mutable, but editing it will not have any effect on the
+ * bean that produced it.
+ *
+ * @param bean an {@link AutoBean}
+ * @return a {@link Map} of the bean's properties
+ */
+ public static Map<String, Object> getAllProperties(AutoBean<?> bean) {
+ final Map<String, Object> toReturn = new LinkedHashMap<String, Object>();
+
+ // Look at the previous value of all properties
+ bean.accept(new AutoBeanVisitor() {
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ toReturn.put(propertyName, value == null ? null : value.as());
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ toReturn.put(propertyName, value);
+ return false;
+ }
+ });
+ return toReturn;
+ }
+
+ /**
+ * Return the single AutoBean wrapper that is observing the delegate object or
+ * {@code null} if the parameter is {@code null}or not wrapped by an AutoBean.
+ *
+ * @param delegate a delegate object, or {@code null}
+ * @return the {@link AutoBean} wrapper for the delegate, or {@code null}
+ */
+ @SuppressWarnings("unchecked")
+ public static <T, U extends T> AutoBean<T> getAutoBean(U delegate) {
+ return delegate == null ? null : (AutoBean<T>) WeakMapping.get(delegate, AutoBean.class
+ .getName());
+ }
+
+ /**
+ * Compare two AutoBeans, this method has the type fan-out.
+ */
+ static boolean sameOrEquals(Object value, Object otherValue,
+ Map<PendingComparison, Comparison> pending) {
+ if (value == otherValue) {
+ // Fast exit
+ return true;
+ }
+
+ if (value instanceof Collection<?> && otherValue instanceof Collection<?>) {
+ // Check collections
+ return sameOrEquals((Collection<?>) value, (Collection<?>) otherValue, pending, null);
+ }
+
+ if (value instanceof Map<?, ?> && otherValue instanceof Map<?, ?>) {
+ // Check maps
+ return sameOrEquals((Map<?, ?>) value, (Map<?, ?>) otherValue, pending);
+ }
+
+ if (value instanceof Splittable && otherValue instanceof Splittable) {
+ return sameOrEquals((Splittable) value, (Splittable) otherValue, pending);
+ }
+
+ // Possibly substitute the AutoBean for its shim
+ {
+ AutoBean<?> maybeValue = AutoBeanUtils.getAutoBean(value);
+ AutoBean<?> maybeOther = AutoBeanUtils.getAutoBean(otherValue);
+ if (maybeValue != null && maybeOther != null) {
+ value = maybeValue;
+ otherValue = maybeOther;
+ }
+ }
+
+ if (value instanceof AutoBean<?> && otherValue instanceof AutoBean<?>) {
+ // Check ValueProxies
+ return sameOrEquals((AutoBean<?>) value, (AutoBean<?>) otherValue, pending);
+ }
+
+ if (value == null ^ otherValue == null) {
+ // One is null, the other isn't
+ return false;
+ }
+
+ if (value != null && !value.equals(otherValue)) {
+ // Regular object equality
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * If a comparison between two AutoBeans is currently pending, this method
+ * will skip their comparison.
+ */
+ private static boolean sameOrEquals(AutoBean<?> value, AutoBean<?> otherValue,
+ Map<PendingComparison, Comparison> pending) {
+ if (value == otherValue) {
+ // Simple case
+ return true;
+ } else if (!value.getType().equals(otherValue.getType())) {
+ // Beans of different types
+ return false;
+ }
+
+ /*
+ * The PendingComparison key allows us to break reference cycles when
+ * crawling the graph. Since the entire operation is essentially a
+ * concatenated && operation, it's ok to speculatively return true for
+ * repeated a.equals(b) tests.
+ */
+ PendingComparison key = new PendingComparison(value, otherValue);
+ Comparison previous = pending.get(key);
+ if (previous == null) {
+ // Prevent the same comparison from being made
+ pending.put(key, Comparison.PENDING);
+
+ // Compare each property
+ Map<String, Object> beanProperties = AutoBeanUtils.getAllProperties(value);
+ Map<String, Object> otherProperties = AutoBeanUtils.getAllProperties(otherValue);
+ for (Map.Entry<String, Object> entry : beanProperties.entrySet()) {
+ Object property = entry.getValue();
+ Object otherProperty = otherProperties.get(entry.getKey());
+ if (!sameOrEquals(property, otherProperty, pending)) {
+ pending.put(key, Comparison.FALSE);
+ return false;
+ }
+ }
+ pending.put(key, Comparison.TRUE);
+ return true;
+ } else {
+ // Return true for TRUE or PENDING
+ return !Comparison.FALSE.equals(previous);
+ }
+ }
+
+ /**
+ * Compare two collections by size, then by contents. List comparisons will
+ * preserve order. All other collections will be treated with bag semantics.
+ */
+ private static boolean sameOrEquals(Collection<?> collection, Collection<?> otherCollection,
+ Map<PendingComparison, Comparison> pending, Map<Object, Object> pairs) {
+ if (collection.size() != otherCollection.size()) {
+ return false;
+ }
+
+ if (collection instanceof List<?>) {
+ // Lists we can simply iterate over
+ Iterator<?> it = collection.iterator();
+ Iterator<?> otherIt = otherCollection.iterator();
+ while (it.hasNext()) {
+ assert otherIt.hasNext();
+ Object element = it.next();
+ Object otherElement = otherIt.next();
+ if (!sameOrEquals(element, otherElement, pending)) {
+ return false;
+ }
+ if (pairs != null) {
+ pairs.put(element, otherElement);
+ }
+ }
+ } else {
+ // Do an n*m comparison on any other collection type
+ List<Object> values = new ArrayList<Object>(collection);
+ List<Object> otherValues = new ArrayList<Object>(otherCollection);
+ it : for (Iterator<Object> it = values.iterator(); it.hasNext();) {
+ Object value = it.next();
+ for (Iterator<Object> otherIt = otherValues.iterator(); otherIt.hasNext();) {
+ Object otherValue = otherIt.next();
+ if (sameOrEquals(value, otherValue, pending)) {
+ if (pairs != null) {
+ pairs.put(value, otherValue);
+ }
+ // If a match is found, remove both values from their lists
+ it.remove();
+ otherIt.remove();
+ continue it;
+ }
+ }
+ // A match for the value wasn't found
+ return false;
+ }
+ assert values.isEmpty() && otherValues.isEmpty();
+ }
+ return true;
+ }
+
+ /**
+ * Compare two Maps by size, and key-value pairs.
+ */
+ private static boolean sameOrEquals(Map<?, ?> map, Map<?, ?> otherMap,
+ Map<PendingComparison, Comparison> pending) {
+ if (map.size() != otherMap.size()) {
+ return false;
+ }
+ Map<Object, Object> pairs = new IdentityHashMap<Object, Object>();
+ if (!sameOrEquals(map.keySet(), otherMap.keySet(), pending, pairs)) {
+ return false;
+ }
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ Object otherValue = otherMap.get(pairs.get(entry.getKey()));
+ if (!sameOrEquals(entry.getValue(), otherValue, pending)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compare Splittables by kind and values.
+ */
+ private static boolean sameOrEquals(Splittable value, Splittable otherValue,
+ Map<PendingComparison, Comparison> pending) {
+ if (value == otherValue) {
+ return true;
+ }
+
+ // Strings
+ if (value.isString()) {
+ if (!otherValue.isString()) {
+ return false;
+ }
+ return value.asString().equals(otherValue.asString());
+ }
+
+ // Arrays
+ if (value.isIndexed()) {
+ if (!otherValue.isIndexed()) {
+ return false;
+ }
+
+ if (value.size() != otherValue.size()) {
+ return false;
+ }
+
+ for (int i = 0, j = value.size(); i < j; i++) {
+ if (!sameOrEquals(value.get(i), otherValue.get(i), pending)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Objects
+ if (value.isKeyed()) {
+ if (!otherValue.isKeyed()) {
+ return false;
+ }
+ /*
+ * We want to treat a missing property key as a null value, so we can't
+ * just compare the key lists.
+ */
+ List<String> keys = value.getPropertyKeys();
+ for (String key : keys) {
+ if (value.isNull(key)) {
+ // If value['foo'] is null, other['foo'] must also be null
+ if (!otherValue.isNull(key)) {
+ return false;
+ }
+ } else if (otherValue.isNull(key)
+ || !sameOrEquals(value.get(key), otherValue.get(key), pending)) {
+ return false;
+ }
+ }
+
+ // Look at keys only in otherValue, and ensure nullness
+ List<String> otherKeys = new ArrayList<String>(otherValue.getPropertyKeys());
+ otherKeys.removeAll(keys);
+ for (String key : otherKeys) {
+ if (!value.isNull(key)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Unexpected
+ throw new UnsupportedOperationException("Splittable of unknown type");
+ }
+
+ /**
+ * Utility class.
+ */
+ private AutoBeanUtils() {
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java b/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java
new file mode 100644
index 0000000..bff9d4a
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/AutoBeanVisitor.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Allows traversal of an AutoBean object graph.
+ */
+public class AutoBeanVisitor {
+ /**
+ * A PropertyContext that describes the parameterization of the Collection
+ * being visited.
+ */
+ public interface CollectionPropertyContext extends PropertyContext {
+ /**
+ * Returns the collection's element type.
+ *
+ * @return a Class object representing the element type
+ */
+ Class<?> getElementType();
+ }
+
+ /**
+ * Reserved for future expansion to avoid API breaks.
+ */
+ public interface Context {
+ }
+
+ /**
+ * A PropertyContext that describes the parameterization of the Map being
+ * visited.
+ */
+ public interface MapPropertyContext extends PropertyContext {
+ /**
+ * Returns the map's key type.
+ *
+ * @return a Class object representing the key type
+ */
+ Class<?> getKeyType();
+
+ /**
+ * Returns the map's value type.
+ *
+ * @return a Class object representing the value type
+ */
+ Class<?> getValueType();
+ }
+
+ /**
+ * The ParameterizationVisitor provides access to more complete type
+ * information than a simple class literal can provide.
+ * <p>
+ * The order of traversal reflects the declared parameterization of the
+ * property. For example, a {@code Map<String, List<Foo>>} would be traversed
+ * via the following sequence:
+ *
+ * <pre>
+ * visitType(Map.class);
+ * visitParameter();
+ * visitType(String.class);
+ * endVisitType(String.class);
+ * endVisitParameter();
+ * visitParameter();
+ * visitType(List.class);
+ * visitParameter();
+ * visitType(Foo.class);
+ * endVisitType(Foo.class);
+ * endParameter();
+ * endVisitType(List.class);
+ * endVisitParameter();
+ * endVisitType(Map.class);
+ * </pre>
+ */
+ public static class ParameterizationVisitor {
+ /**
+ * Called when finished with a type parameter.
+ */
+ public void endVisitParameter() {
+ }
+
+ /**
+ * Called when finished with a type.
+ *
+ * @param type a Class object
+ */
+ public void endVisitType(Class<?> type) {
+ }
+
+ /**
+ * Called when visiting a type parameter.
+ *
+ * @return {@code true} if the type parameter should be visited
+ */
+ public boolean visitParameter() {
+ return true;
+ }
+
+ /**
+ * Called when visiting a possibly parameterized type.
+ *
+ * @param type a Class object
+ * @return {@code true} if the type should be visited
+ */
+ public boolean visitType(Class<?> type) {
+ return true;
+ }
+ }
+
+ /**
+ * Allows properties to be reset.
+ */
+ public interface PropertyContext {
+ /**
+ * Allows deeper inspection of the declared parameterization of the
+ * property.
+ */
+ void accept(ParameterizationVisitor visitor);
+
+ /**
+ * Indicates if the {@link #set} method will succeed.
+ *
+ * @return {@code true} if the property can be set
+ */
+ boolean canSet();
+
+ /**
+ * Returns the expected type of the property.
+ *
+ * @return a Class object representing the property type
+ */
+ Class<?> getType();
+
+ /**
+ * Sets a property value.
+ *
+ * @param value the new value
+ */
+ void set(Object value);
+ }
+
+ /**
+ * Called after visiting an {@link AutoBean}.
+ *
+ * @param bean an {@link AutoBean}
+ * @param ctx a Context
+ */
+ public void endVisit(AutoBean<?> bean, Context ctx) {
+ }
+
+ /**
+ * Called after visiting a reference property.
+ *
+ * @param propertyName the property name, as a String
+ * @param value the property value
+ * @param ctx a PropertyContext
+ */
+ public void endVisitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+ CollectionPropertyContext ctx) {
+ endVisitReferenceProperty(propertyName, value, ctx);
+ }
+
+ /**
+ * Called after visiting a reference property.
+ *
+ * @param propertyName the property name, as a String
+ * @param value the property value
+ * @param ctx a PropertyContext
+ */
+ public void endVisitMapProperty(String propertyName, AutoBean<Map<?, ?>> value,
+ MapPropertyContext ctx) {
+ endVisitReferenceProperty(propertyName, value, ctx);
+ }
+
+ /**
+ * Called after visiting a reference property.
+ *
+ * @param propertyName the property name, as a String
+ * @param value the property value
+ * @param ctx a PropertyContext
+ */
+ public void endVisitReferenceProperty(String propertyName, AutoBean<?> value, PropertyContext ctx) {
+ }
+
+ /**
+ * Called after visiting a value property.
+ *
+ * @param propertyName the property name, as a String
+ * @param value the property value
+ * @param ctx a PropertyContext
+ */
+ public void endVisitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ }
+
+ /**
+ * Called when visiting an {@link AutoBean}.
+ *
+ * @param bean an {@link AutoBean}
+ * @param ctx a Context
+ */
+ public boolean visit(AutoBean<?> bean, Context ctx) {
+ return true;
+ }
+
+ /**
+ * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+ * the value only the first time it is encountered.
+ *
+ * @param propertyName the property name, as a String
+ * @param value the property value
+ * @param ctx a PropertyContext
+ */
+ public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+ CollectionPropertyContext ctx) {
+ return visitReferenceProperty(propertyName, value, ctx);
+ }
+
+ /**
+ * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+ * the value only the first time it is encountered.
+ *
+ * @param propertyName the property name, as a String
+ * @param value the property value
+ * @param ctx a PropertyContext
+ */
+ public boolean visitMapProperty(String propertyName, AutoBean<Map<?, ?>> value,
+ MapPropertyContext ctx) {
+ return visitReferenceProperty(propertyName, value, ctx);
+ }
+
+ /**
+ * Called every time, but {@link #visit(AutoBean, Context)} will be called for
+ * the value only the first time it is encountered.
+ *
+ * @param propertyName the property name, as a String
+ * @param value the property value
+ * @param ctx a PropertyContext
+ */
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value, PropertyContext ctx) {
+ return true;
+ }
+
+ /**
+ * TODO: document.
+ *
+ * @param propertyName the property name, as a String
+ * @param value the property value
+ * @param ctx a PropertyContext
+ */
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ return true;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/Splittable.java b/user/src/com/google/web/bindery/autobean/shared/Splittable.java
new file mode 100644
index 0000000..2698345
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/Splittable.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+
+import java.util.List;
+
+/**
+ * This interface provides an abstraction around the underlying data model
+ * (JavaScriptObject, {@code org.json}, or XML) used to encode an AutoBeanCodex
+ * payload.
+ */
+public interface Splittable {
+ /**
+ * A value that represents {@code null}.
+ */
+ Splittable NULL = StringQuoter.nullValue();
+
+ /**
+ * Returns a boolean representation of the data;
+ */
+ boolean asBoolean();
+
+ /**
+ * Returns a numeric representation of the data.
+ */
+ double asNumber();
+
+ /**
+ * Assign the splittable to the specified index of the {@code parent} object.
+ */
+ void assign(Splittable parent, int index);
+
+ /**
+ * Assign the splittable to the named property of the {@code parent} object.
+ */
+ void assign(Splittable parent, String propertyName);
+
+ /**
+ * Returns a string representation of the data.
+ */
+ String asString();
+
+ /**
+ * Clones the Splittable, ignoring cycles and tags.
+ */
+ Splittable deepCopy();
+
+ /**
+ * Returns the nth element of a list.
+ */
+ Splittable get(int index);
+
+ /**
+ * Returns the named property.
+ */
+ Splittable get(String key);
+
+ /**
+ * Returns a wire-format representation of the data.
+ */
+ String getPayload();
+
+ /**
+ * Returns all keys available in the Splittable. This method may be expensive
+ * to compute.
+ */
+ List<String> getPropertyKeys();
+
+ /**
+ * Returns a value previously set with {@link #setReified(String, Object)}.
+ */
+ Object getReified(String key);
+
+ /**
+ * Returns {@code true} if the value of the Splittable is a boolean.
+ */
+ boolean isBoolean();
+
+ /**
+ * Returns {@code} true if {@link #size()} and {@link #get(int)} can be
+ * expected to return meaningful values.
+ */
+ boolean isIndexed();
+
+ /**
+ * Returns {@code} true if {@link #getPropertyKeys()} and {@link #get(String)}
+ * can be expected to return meaningful values.
+ */
+ boolean isKeyed();
+
+ /**
+ * Indicates if the nth element of a list is null or undefined.
+ */
+ boolean isNull(int index);
+
+ /**
+ * Indicates if the named property is null or undefined.
+ */
+ boolean isNull(String key);
+
+ /**
+ * Returns {@code true} if the value of the Splittable is numeric.
+ */
+ boolean isNumber();
+
+ /**
+ * Returns {@code true} if {@link #setReified(String, Object)} has been called
+ * with the given key.
+ */
+ boolean isReified(String key);
+
+ /**
+ * Returns {@code} true if {@link #asString()} can be expected to return a
+ * meaningful value.
+ */
+ boolean isString();
+
+ /**
+ * Returns {@code true} if the value of the key is undefined.
+ */
+ boolean isUndefined(String key);
+
+ /**
+ * Associates a tag value with the Splittable.
+ */
+ void setReified(String key, Object object);
+
+ /**
+ * Resets the length of an indexed Splittable.
+ */
+ void setSize(int i);
+
+ /**
+ * Returns the size of an indexed Splittable.
+ */
+ int size();
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java b/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java
new file mode 100644
index 0000000..4bcb304
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/ValueCodex.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides unified encoding and decoding of value objects.
+ */
+public class ValueCodex {
+ enum Type {
+ BIG_DECIMAL(BigDecimal.class) {
+ @Override
+ public boolean canUpcast(Object value) {
+ return value instanceof BigDecimal;
+ }
+
+ @Override
+ public BigDecimal decode(Class<?> clazz, Splittable value) {
+ return new BigDecimal(value.asString());
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create(((BigDecimal) value).toString());
+ }
+ },
+ BIG_INTEGER(BigInteger.class) {
+ @Override
+ public boolean canUpcast(Object value) {
+ return value instanceof BigInteger;
+ }
+
+ @Override
+ public BigInteger decode(Class<?> clazz, Splittable value) {
+ return new BigInteger(value.asString());
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create(((BigInteger) value).toString());
+ }
+ },
+ BOOLEAN(Boolean.class, boolean.class, false) {
+ @Override
+ public Boolean decode(Class<?> clazz, Splittable value) {
+ return value.asBoolean();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Boolean) value);
+ }
+ },
+ BYTE(Byte.class, byte.class, (byte) 0) {
+ @Override
+ public Byte decode(Class<?> clazz, Splittable value) {
+ return (byte) value.asNumber();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Byte) value);
+ }
+ },
+ CHARACTER(Character.class, char.class, (char) 0) {
+ @Override
+ public Character decode(Class<?> clazz, Splittable value) {
+ return value.asString().charAt(0);
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create(String.valueOf((Character) value));
+ }
+ },
+ DATE(Date.class) {
+ @Override
+ public boolean canUpcast(Object value) {
+ return value instanceof Date;
+ }
+
+ @Override
+ public Date decode(Class<?> clazz, Splittable value) {
+ return StringQuoter.tryParseDate(value.asString());
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create(String.valueOf(((Date) value).getTime()));
+ }
+ },
+ DOUBLE(Double.class, double.class, 0d) {
+ @Override
+ public Double decode(Class<?> clazz, Splittable value) {
+ return value.asNumber();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Double) value);
+ }
+ },
+ ENUM(Enum.class) {
+ @Override
+ public Enum<?> decode(Class<?> clazz, Splittable value) {
+ return (Enum<?>) clazz.getEnumConstants()[(int) value.asNumber()];
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create(((Enum<?>) value).ordinal());
+ }
+ },
+ FLOAT(Float.class, float.class, 0f) {
+ @Override
+ public Float decode(Class<?> clazz, Splittable value) {
+ return (float) value.asNumber();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Float) value);
+ }
+ },
+ INTEGER(Integer.class, int.class, 0) {
+ @Override
+ public Integer decode(Class<?> clazz, Splittable value) {
+ return Integer.valueOf((int) value.asNumber());
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Integer) value);
+ }
+ },
+ LONG(Long.class, long.class, 0L) {
+ @Override
+ public Long decode(Class<?> clazz, Splittable value) {
+ return Long.parseLong(value.asString());
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create(String.valueOf((Long) value));
+ }
+ },
+ SHORT(Short.class, short.class, (short) 0) {
+ @Override
+ public Short decode(Class<?> clazz, Splittable value) {
+ return (short) value.asNumber();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((Short) value);
+ }
+ },
+ STRING(String.class) {
+ @Override
+ public String decode(Class<?> clazz, Splittable value) {
+ return value.asString();
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return StringQuoter.create((String) value);
+ }
+ },
+ SPLITTABLE(Splittable.class) {
+ @Override
+ public Splittable decode(Class<?> clazz, Splittable value) {
+ return value;
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return (Splittable) value;
+ }
+ },
+ VOID(Void.class, void.class, null) {
+ @Override
+ public Void decode(Class<?> clazz, Splittable value) {
+ return null;
+ }
+
+ @Override
+ public Splittable encode(Object value) {
+ return null;
+ }
+ };
+ private final Object defaultValue;
+ private final Class<?> type;
+ private final Class<?> primitiveType;
+
+ Type(Class<?> objectType) {
+ this(objectType, null, null);
+ }
+
+ Type(Class<?> objectType, Class<?> primitiveType, Object defaultValue) {
+ this.type = objectType;
+ this.primitiveType = primitiveType;
+ this.defaultValue = defaultValue;
+ }
+
+ /**
+ * Determines whether or not the Type can handle the given value via
+ * upcasting semantics.
+ *
+ * @param value a value Object
+ */
+ public boolean canUpcast(Object value) {
+ // Most value types are final, so this method is meaningless
+ return false;
+ }
+
+ public abstract Object decode(Class<?> clazz, Splittable value);
+
+ public abstract Splittable encode(Object value);
+
+ public Object getDefaultValue() {
+ return defaultValue;
+ }
+
+ public Class<?> getPrimitiveType() {
+ return primitiveType;
+ }
+
+ public Class<?> getType() {
+ return type;
+ }
+ }
+
+ private static final Set<Class<?>> ALL_VALUE_TYPES;
+ private static final Map<Class<?>, Type> TYPES_BY_CLASS;
+ static {
+ Map<Class<?>, Type> temp = new HashMap<Class<?>, Type>();
+ for (Type t : Type.values()) {
+ temp.put(t.getType(), t);
+ if (t.getPrimitiveType() != null) {
+ temp.put(t.getPrimitiveType(), t);
+ }
+ }
+ ALL_VALUE_TYPES = Collections.unmodifiableSet(temp.keySet());
+ TYPES_BY_CLASS = Collections.unmodifiableMap(temp);
+ }
+
+ /**
+ * Returns true if ValueCodex can operate on values of the given type.
+ *
+ * @param clazz a Class object
+ * @return {@code true} if the given object type can be decoded
+ */
+ public static boolean canDecode(Class<?> clazz) {
+ if (findType(clazz) != null) {
+ return true;
+ }
+ // Use other platform-specific tests
+ return ValueCodexHelper.canDecode(clazz);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> T decode(Class<T> clazz, Splittable split) {
+ if (split == null || split == Splittable.NULL) {
+ return null;
+ }
+ return (T) getTypeOrDie(clazz).decode(clazz, split);
+ }
+
+ /**
+ * No callers in GWT codebase.
+ *
+ * @deprecated use {@link #decode(Class, Splittable)} instead.
+ * @throws UnsupportedOperationException
+ */
+ @Deprecated
+ public static <T> T decode(Class<T> clazz, String string) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Encode a value object when the wire format type is known. This method
+ * should be preferred over {@link #encode(Object)} when possible.
+ */
+ public static Splittable encode(Class<?> clazz, Object obj) {
+ if (obj == null) {
+ return Splittable.NULL;
+ }
+ return getTypeOrDie(clazz).encode(obj);
+ }
+
+ public static Splittable encode(Object obj) {
+ if (obj == null) {
+ return Splittable.NULL;
+ }
+ Type t = findType(obj.getClass());
+ // Try upcasting
+ if (t == null) {
+ for (Type maybe : Type.values()) {
+ if (maybe.canUpcast(obj)) {
+ t = maybe;
+ break;
+ }
+ }
+ }
+ if (t == null) {
+ throw new UnsupportedOperationException(obj.getClass().getName());
+ }
+ return t.encode(obj);
+ }
+
+ /**
+ * Return all Value types that can be processed by the ValueCodex.
+ */
+ public static Set<Class<?>> getAllValueTypes() {
+ return ALL_VALUE_TYPES;
+ }
+
+ /**
+ * Returns the uninitialized field value for the given primitive type.
+ */
+ public static Object getUninitializedFieldValue(Class<?> clazz) {
+ Type type = getTypeOrDie(clazz);
+ if (clazz.equals(type.getPrimitiveType())) {
+ return type.getDefaultValue();
+ }
+ return null;
+ }
+
+ /**
+ * May return <code>null</code>.
+ */
+ private static <T> Type findType(Class<T> clazz) {
+ if (clazz.isEnum()) {
+ return Type.ENUM;
+ }
+ return TYPES_BY_CLASS.get(clazz);
+ }
+
+ private static <T> Type getTypeOrDie(Class<T> clazz) {
+ Type toReturn = findType(clazz);
+ if (toReturn == null) {
+ throw new UnsupportedOperationException(clazz.getName());
+ }
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java b/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java
new file mode 100644
index 0000000..fb9fcc9
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * Provides reflection-based operation for server (JVM) implementation. There is
+ * a no-op super-source version for client (dev- and web-mode) code.
+ */
+class ValueCodexHelper {
+ /**
+ * Returns {@code true} if {@code clazz} is assignable to any of the value
+ * types.
+ */
+ static boolean canDecode(Class<?> clazz) {
+ assert !GWT.isClient();
+ for (Class<?> valueType : ValueCodex.getAllValueTypes()) {
+ if (valueType.isAssignableFrom(clazz)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java b/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java
new file mode 100644
index 0000000..4e801d9
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.Context;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+import com.google.gwt.core.client.impl.WeakMapping;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Basic implementation.
+ *
+ * @param <T> the wrapper type
+ */
+public abstract class AbstractAutoBean<T> implements AutoBean<T>, HasSplittable {
+ /**
+ * Used to avoid cycles when visiting.
+ */
+ public static class OneShotContext implements Context {
+ private final Set<AbstractAutoBean<?>> seen = new HashSet<AbstractAutoBean<?>>();
+
+ public boolean hasSeen(AbstractAutoBean<?> bean) {
+ return !seen.add(bean);
+ }
+ }
+
+ public static final String UNSPLITTABLE_VALUES_KEY = "__unsplittableValues";
+ protected static final Object[] EMPTY_OBJECT = new Object[0];
+
+ /**
+ * Used by {@link #createSimplePeer()}.
+ */
+ protected Splittable data;
+ protected T wrapped;
+ private final AutoBeanFactory factory;
+ private boolean frozen;
+ /**
+ * Lazily initialized by {@link #setTag(String, Object)} because not all
+ * instances will make use of tags.
+ */
+ private Map<String, Object> tags;
+ private final boolean usingSimplePeer;
+
+ /**
+ * Constructor that will use a generated simple peer.
+ */
+ protected AbstractAutoBean(AutoBeanFactory factory) {
+ this(factory, StringQuoter.createSplittable());
+ }
+
+ /**
+ * Constructor that will use a generated simple peer, backed with existing
+ * data.
+ */
+ protected AbstractAutoBean(AutoBeanFactory factory, Splittable data) {
+ this.data = data;
+ this.factory = factory;
+ usingSimplePeer = true;
+ wrapped = createSimplePeer();
+ }
+
+ /**
+ * Constructor that wraps an existing object. The parameters on this method
+ * are reversed to avoid conflicting with the other two-arg constructor for
+ * {@code AutoBean<Splittable>} instances.
+ */
+ protected AbstractAutoBean(T wrapped, AutoBeanFactory factory) {
+ this.factory = factory;
+ usingSimplePeer = false;
+ data = null;
+ this.wrapped = wrapped;
+
+ // Used by AutoBeanUtils
+ WeakMapping.set(wrapped, AutoBean.class.getName(), this);
+ }
+
+ public void accept(AutoBeanVisitor visitor) {
+ traverse(visitor, new OneShotContext());
+ }
+
+ public abstract T as();
+
+ public AutoBean<T> clone(boolean deep) {
+ throw new UnsupportedOperationException();
+ }
+
+ public AutoBeanFactory getFactory() {
+ return factory;
+ }
+
+ public Splittable getSplittable() {
+ return data;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <Q> Q getTag(String tagName) {
+ return tags == null ? null : (Q) tags.get(tagName);
+ }
+
+ /**
+ * Indicates that the value returned from {@link #getSplittable()} may not
+ * contain all of the data encapsulated by the AutoBean.
+ */
+ public boolean hasUnsplittableValues() {
+ return data.isReified(UNSPLITTABLE_VALUES_KEY);
+ }
+
+ public boolean isFrozen() {
+ return frozen;
+ }
+
+ public boolean isWrapper() {
+ return !usingSimplePeer;
+ }
+
+ public void setData(Splittable data) {
+ assert data != null : "null data";
+ this.data = data;
+ /*
+ * The simple peer aliases the data object from the enclosing bean to avoid
+ * needing to call up the this.this$0 chain.
+ */
+ wrapped = createSimplePeer();
+ }
+
+ public void setFrozen(boolean frozen) {
+ this.frozen = frozen;
+ }
+
+ public void setTag(String tagName, Object value) {
+ if (tags == null) {
+ tags = new HashMap<String, Object>();
+ }
+ tags.put(tagName, value);
+ }
+
+ public void traverse(AutoBeanVisitor visitor, OneShotContext ctx) {
+ // Avoid cycles
+ if (ctx.hasSeen(this)) {
+ return;
+ }
+ if (visitor.visit(this, ctx)) {
+ traverseProperties(visitor, ctx);
+ }
+ visitor.endVisit(this, ctx);
+ }
+
+ public T unwrap() {
+ if (usingSimplePeer) {
+ throw new IllegalStateException();
+ }
+ try {
+ WeakMapping.set(wrapped, AutoBean.class.getName(), null);
+ return wrapped;
+ } finally {
+ wrapped = null;
+ }
+ }
+
+ /**
+ * No-op. Used as a debugger hook point for generated code.
+ *
+ * @param method the method name
+ * @param returned the returned object
+ * @param parameters the parameter list
+ */
+ protected void call(String method, Object returned, Object... parameters) {
+ }
+
+ protected void checkFrozen() {
+ if (frozen) {
+ throw new IllegalStateException("The AutoBean has been frozen");
+ }
+ }
+
+ protected void checkWrapped() {
+ if (wrapped == null && !usingSimplePeer) {
+ throw new IllegalStateException("The AutoBean has been unwrapped");
+ }
+ }
+
+ protected T createSimplePeer() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * No-op. Used as a debugger hook point for generated code.
+ *
+ * @param method the method name
+ * @param toReturn the value to return
+ */
+ protected <V> V get(String method, V toReturn) {
+ return toReturn;
+ }
+
+ protected <W> W getFromWrapper(W obj) {
+ // Some versions of javac have problem inferring the generics here
+ return AutoBeanUtils.<W, W> getAutoBean(obj).as();
+ }
+
+ /**
+ * Native getters and setters for primitive properties are generated for each
+ * type to ensure inlining.
+ */
+ protected <Q> Q getOrReify(String propertyName) {
+ checkWrapped();
+ if (data.isReified(propertyName)) {
+ @SuppressWarnings("unchecked")
+ Q temp = (Q) data.getReified(propertyName);
+ return temp;
+ }
+ if (data.isNull(propertyName)) {
+ return null;
+ }
+ data.setReified(propertyName, null);
+ Coder coder = AutoBeanCodexImpl.doCoderFor(this, propertyName);
+ @SuppressWarnings("unchecked")
+ Q toReturn = (Q) coder.decode(EncodeState.forDecode(factory), data.get(propertyName));
+ data.setReified(propertyName, toReturn);
+ return toReturn;
+ }
+
+ protected T getWrapped() {
+ checkWrapped();
+ return wrapped;
+ }
+
+ protected boolean isUsingSimplePeer() {
+ return usingSimplePeer;
+ }
+
+ protected boolean isWrapped(Object obj) {
+ return AutoBeanUtils.getAutoBean(obj) != null;
+ }
+
+ /**
+ * No-op. Used as a debugger hook point for generated code.
+ *
+ * @param method the method name
+ * @param value the Object value to be set
+ */
+ protected void set(String method, Object value) {
+ }
+
+ protected void setProperty(String propertyName, Object value) {
+ checkWrapped();
+ checkFrozen();
+ data.setReified(propertyName, value);
+ if (value == null) {
+ Splittable.NULL.assign(data, propertyName);
+ return;
+ }
+ Coder coder = AutoBeanCodexImpl.doCoderFor(this, propertyName);
+ Splittable backing = coder.extractSplittable(EncodeState.forDecode(factory), value);
+ if (backing == null) {
+ /*
+ * External data type, such as an ArrayList or a concrete implementation
+ * of a setter's interface type. This means that a slow serialization pass
+ * is necessary.
+ */
+ data.setReified(UNSPLITTABLE_VALUES_KEY, true);
+ } else {
+ backing.assign(data, propertyName);
+ }
+ }
+
+ protected abstract void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx);
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java b/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java
new file mode 100644
index 0000000..78b4f2e
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/AutoBeanCodexImpl.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * Contains the implementation details of AutoBeanCodex. This type was factored
+ * out of AutoBeanCodex so that various implementation details can be accessed
+ * without polluting a public API.
+ */
+public class AutoBeanCodexImpl {
+
+ /**
+ * Describes a means of encoding or decoding a particular type of data to or
+ * from a wire format representation. Any given instance of a Coder should be
+ * stateless; any state required for operation must be maintained in an
+ * {@link EncodeState}.
+ */
+ public interface Coder {
+ Object decode(EncodeState state, Splittable data);
+
+ void encode(EncodeState state, Object value);
+
+ Splittable extractSplittable(EncodeState state, Object value);
+ }
+
+ /**
+ * Contains transient state for Coder operation.
+ */
+ public static class EncodeState {
+ /**
+ * Constructs a state object used for decoding payloads.
+ */
+ public static EncodeState forDecode(AutoBeanFactory factory) {
+ return new EncodeState(factory, null);
+ }
+
+ /**
+ * Constructs a state object used for encoding payloads.
+ */
+ public static EncodeState forEncode(AutoBeanFactory factory, StringBuilder sb) {
+ return new EncodeState(factory, sb);
+ }
+
+ /**
+ * Constructs a "stateless" state for testing Coders that do not require
+ * AutoBean implementation details.
+ */
+ public static EncodeState forTesting() {
+ return new EncodeState(null, null);
+ }
+
+ final EnumMap enumMap;
+ final AutoBeanFactory factory;
+ final StringBuilder sb;
+ final Stack<AutoBean<?>> seen;
+
+ private EncodeState(AutoBeanFactory factory, StringBuilder sb) {
+ this.factory = factory;
+ enumMap = factory instanceof EnumMap ? (EnumMap) factory : null;
+ this.sb = sb;
+ this.seen = sb == null ? null : new Stack<AutoBean<?>>();
+ }
+ }
+
+ /**
+ * Dynamically creates a Coder that is capable of operating on a particular
+ * parameterization of a datastructure (e.g. {@code Map<String, List<String>>}
+ * ).
+ */
+ static class CoderCreator extends ParameterizationVisitor {
+ private Stack<Coder> stack = new Stack<Coder>();
+
+ @Override
+ public void endVisitType(Class<?> type) {
+ if (List.class.equals(type) || Set.class.equals(type)) {
+ stack.push(collectionCoder(type, stack.pop()));
+ } else if (Map.class.equals(type)) {
+ // Note that the parameters are passed in reverse order
+ stack.push(mapCoder(stack.pop(), stack.pop()));
+ } else if (Splittable.class.equals(type)) {
+ stack.push(splittableCoder());
+ } else if (type.getEnumConstants() != null) {
+ @SuppressWarnings(value = {"unchecked"})
+ Class<Enum<?>> enumType = (Class<Enum<?>>) type;
+ stack.push(enumCoder(enumType));
+ } else if (ValueCodex.canDecode(type)) {
+ stack.push(valueCoder(type));
+ } else {
+ stack.push(objectCoder(type));
+ }
+ }
+
+ public Coder getCoder() {
+ assert stack.size() == 1 : "Incorrect size: " + stack.size();
+ return stack.pop();
+ }
+ }
+
+ /**
+ * Constructs one of the lightweight collection types.
+ */
+ static class CollectionCoder implements Coder {
+ private final Coder elementDecoder;
+ private final Class<?> type;
+
+ public CollectionCoder(Class<?> type, Coder elementDecoder) {
+ this.elementDecoder = elementDecoder;
+ this.type = type;
+ }
+
+ public Object decode(EncodeState state, Splittable data) {
+ Collection<Object> collection;
+ if (List.class.equals(type)) {
+ collection = new SplittableList<Object>(data, elementDecoder, state);
+ } else if (Set.class.equals(type)) {
+ collection = new SplittableSet<Object>(data, elementDecoder, state);
+ } else {
+ // Should not reach here
+ throw new RuntimeException(type.getName());
+ }
+ return collection;
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+
+ Iterator<?> it = ((Collection<?>) value).iterator();
+ state.sb.append("[");
+ if (it.hasNext()) {
+ elementDecoder.encode(state, it.next());
+ while (it.hasNext()) {
+ state.sb.append(",");
+ elementDecoder.encode(state, it.next());
+ }
+ }
+ state.sb.append("]");
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return tryExtractSplittable(value);
+ }
+ }
+
+ /**
+ * Produces enums.
+ *
+ * @param <E>
+ */
+ static class EnumCoder<E extends Enum<?>> implements Coder {
+ private final Class<E> type;
+
+ public EnumCoder(Class<E> type) {
+ this.type = type;
+ }
+
+ public Object decode(EncodeState state, Splittable data) {
+ return state.enumMap.getEnum(type, data.asString());
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+ state.sb.append(StringQuoter.quote(state.enumMap.getToken((Enum<?>) value)));
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return StringQuoter.split(StringQuoter.quote(state.enumMap.getToken((Enum<?>) value)));
+ }
+ }
+
+ /**
+ * Used to stop processing.
+ */
+ static class HaltException extends RuntimeException {
+ public HaltException(RuntimeException cause) {
+ super(cause);
+ }
+
+ @Override
+ public RuntimeException getCause() {
+ return (RuntimeException) super.getCause();
+ }
+ }
+
+ /**
+ * Constructs one of the lightweight Map types, depending on the key type.
+ */
+ static class MapCoder implements Coder {
+ private final Coder keyDecoder;
+ private final Coder valueDecoder;
+
+ /**
+ * Parameters in reversed order to accommodate stack-based setup.
+ */
+ public MapCoder(Coder valueDecoder, Coder keyDecoder) {
+ this.keyDecoder = keyDecoder;
+ this.valueDecoder = valueDecoder;
+ }
+
+ public Object decode(EncodeState state, Splittable data) {
+ Map<Object, Object> toReturn;
+ if (data.isIndexed()) {
+ assert data.size() == 2 : "Wrong data size: " + data.size();
+ toReturn = new SplittableComplexMap<Object, Object>(data, keyDecoder, valueDecoder, state);
+ } else {
+ toReturn = new SplittableSimpleMap<Object, Object>(data, keyDecoder, valueDecoder, state);
+ }
+ return toReturn;
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+
+ Map<?, ?> map = (Map<?, ?>) value;
+ boolean isSimpleMap = keyDecoder instanceof ValueCoder;
+ if (isSimpleMap) {
+ boolean first = true;
+ state.sb.append("{");
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ Object mapKey = entry.getKey();
+ if (mapKey == null) {
+ // A null key in a simple map is meaningless
+ continue;
+ }
+ Object mapValue = entry.getValue();
+
+ if (first) {
+ first = false;
+ } else {
+ state.sb.append(",");
+ }
+
+ keyDecoder.encode(state, mapKey);
+ state.sb.append(":");
+ if (mapValue == null) {
+ // Null values must be preserved
+ state.sb.append("null");
+ } else {
+ valueDecoder.encode(state, mapValue);
+ }
+ }
+ state.sb.append("}");
+ } else {
+ List<Object> keys = new ArrayList<Object>(map.size());
+ List<Object> values = new ArrayList<Object>(map.size());
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ keys.add(entry.getKey());
+ values.add(entry.getValue());
+ }
+ state.sb.append("[");
+ collectionCoder(List.class, keyDecoder).encode(state, keys);
+ state.sb.append(",");
+ collectionCoder(List.class, valueDecoder).encode(state, values);
+ state.sb.append("]");
+ }
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return tryExtractSplittable(value);
+ }
+ }
+
+ /**
+ * Recurses into {@link AutoBeanCodexImpl}.
+ */
+ static class ObjectCoder implements Coder {
+ private final Class<?> type;
+
+ public ObjectCoder(Class<?> type) {
+ this.type = type;
+ }
+
+ public Object decode(EncodeState state, Splittable data) {
+ AutoBean<?> bean = doDecode(state, type, data);
+ return bean == null ? null : bean.as();
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+ doEncode(state, AutoBeanUtils.getAutoBean(value));
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return tryExtractSplittable(value);
+ }
+ }
+
+ static class PropertyCoderCreator extends AutoBeanVisitor {
+ private AutoBean<?> bean;
+
+ @Override
+ public boolean visit(AutoBean<?> bean, Context ctx) {
+ this.bean = bean;
+ return true;
+ }
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ maybeCreateCoder(propertyName, ctx);
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ maybeCreateCoder(propertyName, ctx);
+ return false;
+ }
+
+ private void maybeCreateCoder(String propertyName, PropertyContext ctx) {
+ CoderCreator creator = new CoderCreator();
+ ctx.accept(creator);
+ coderFor.put(key(bean, propertyName), creator.getCoder());
+ }
+ }
+
+ /**
+ * Extracts properties from a bean and turns them into JSON text.
+ */
+ static class PropertyGetter extends AutoBeanVisitor {
+ private boolean first = true;
+ private final EncodeState state;
+
+ public PropertyGetter(EncodeState state) {
+ this.state = state;
+ }
+
+ @Override
+ public void endVisit(AutoBean<?> bean, Context ctx) {
+ state.sb.append("}");
+ state.seen.pop();
+ }
+
+ @Override
+ public boolean visit(AutoBean<?> bean, Context ctx) {
+ if (state.seen.contains(bean)) {
+ throw new HaltException(new UnsupportedOperationException("Cycles not supported"));
+ }
+ state.seen.push(bean);
+ state.sb.append("{");
+ return true;
+ }
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ if (value != null) {
+ encodeProperty(propertyName, value.as(), ctx);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ if (value != null && !value.equals(ValueCodex.getUninitializedFieldValue(ctx.getType()))) {
+ encodeProperty(propertyName, value, ctx);
+ }
+ return false;
+ }
+
+ private void encodeProperty(String propertyName, Object value, PropertyContext ctx) {
+ CoderCreator pd = new CoderCreator();
+ ctx.accept(pd);
+ Coder decoder = pd.getCoder();
+ if (first) {
+ first = false;
+ } else {
+ state.sb.append(",");
+ }
+ state.sb.append(StringQuoter.quote(propertyName));
+ state.sb.append(":");
+ decoder.encode(state, value);
+ }
+ }
+
+ /**
+ * Populates beans with data extracted from an evaluated JSON payload.
+ */
+ static class PropertySetter extends AutoBeanVisitor {
+ private Splittable data;
+ private EncodeState state;
+
+ public void decodeInto(EncodeState state, Splittable data, AutoBean<?> bean) {
+ this.data = data;
+ this.state = state;
+ bean.accept(this);
+ }
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ decodeProperty(propertyName, ctx);
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ decodeProperty(propertyName, ctx);
+ return false;
+ }
+
+ protected void decodeProperty(String propertyName, PropertyContext ctx) {
+ if (!data.isNull(propertyName)) {
+ CoderCreator pd = new CoderCreator();
+ ctx.accept(pd);
+ Coder decoder = pd.getCoder();
+ Object propertyValue = decoder.decode(state, data.get(propertyName));
+ ctx.set(propertyValue);
+ }
+ }
+ }
+
+ /**
+ * A passthrough Coder.
+ */
+ static class SplittableCoder implements Coder {
+ static final Coder INSTANCE = new SplittableCoder();
+
+ public Object decode(EncodeState state, Splittable data) {
+ return data;
+ }
+
+ public void encode(EncodeState state, Object value) {
+ if (value == null) {
+ state.sb.append("null");
+ return;
+ }
+ state.sb.append(((Splittable) value).getPayload());
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return (Splittable) value;
+ }
+ }
+
+ /**
+ * Delegates to ValueCodex.
+ */
+ static class ValueCoder implements Coder {
+ private final Class<?> type;
+
+ public ValueCoder(Class<?> type) {
+ assert type.getEnumConstants() == null : "Should use EnumTypeCodex";
+ this.type = type;
+ }
+
+ public Object decode(EncodeState state, Splittable propertyValue) {
+ if (propertyValue == null || propertyValue == Splittable.NULL) {
+ return ValueCodex.getUninitializedFieldValue(type);
+ }
+ return ValueCodex.decode(type, propertyValue);
+ }
+
+ public void encode(EncodeState state, Object value) {
+ state.sb.append(ValueCodex.encode(type, value).getPayload());
+ }
+
+ public Splittable extractSplittable(EncodeState state, Object value) {
+ return ValueCodex.encode(type, value);
+ }
+ }
+
+ /**
+ * A map of AutoBean interface+property names to the Coder for that property.
+ */
+ private static final Map<String, Coder> coderFor = new HashMap<String, Coder>();
+ /**
+ * A map of types to a Coder that handles the type.
+ */
+ private static final Map<Class<?>, Coder> coders = new HashMap<Class<?>, Coder>();
+
+ public static Coder collectionCoder(Class<?> type, Coder elementCoder) {
+ return new CollectionCoder(type, elementCoder);
+ }
+
+ public static Coder doCoderFor(AutoBean<?> bean, String propertyName) {
+ String key = key(bean, propertyName);
+ Coder toReturn = coderFor.get(key);
+ if (toReturn == null) {
+ bean.accept(new PropertyCoderCreator());
+ toReturn = coderFor.get(key);
+ if (toReturn == null) {
+ throw new IllegalArgumentException(propertyName);
+ }
+ }
+ return toReturn;
+ }
+
+ public static <T> AutoBean<T> doDecode(EncodeState state, Class<T> clazz, Splittable data) {
+ /*
+ * If we decode the same Splittable twice, re-use the ProxyAutoBean to
+ * maintain referential integrity. If we didn't do this, either facade would
+ * update the same backing data, yet not be the same object via ==
+ * comparison.
+ */
+ @SuppressWarnings("unchecked")
+ AutoBean<T> toReturn = (AutoBean<T>) data.getReified(AutoBeanCodexImpl.class.getName());
+ if (toReturn != null) {
+ return toReturn;
+ }
+ toReturn = state.factory.create(clazz);
+ data.setReified(AutoBeanCodexImpl.class.getName(), toReturn);
+ if (toReturn == null) {
+ throw new IllegalArgumentException(clazz.getName());
+ }
+ ((AbstractAutoBean<T>) toReturn).setData(data);
+ return toReturn;
+ }
+
+ public static void doDecodeInto(EncodeState state, Splittable data, AutoBean<?> bean) {
+ new PropertySetter().decodeInto(state, data, bean);
+ }
+
+ public static void doEncode(EncodeState state, AutoBean<?> bean) {
+ PropertyGetter e = new PropertyGetter(state);
+ try {
+ bean.accept(e);
+ } catch (HaltException ex) {
+ throw ex.getCause();
+ }
+ }
+
+ public static <E extends Enum<?>> Coder enumCoder(Class<E> type) {
+ Coder toReturn = coders.get(type);
+ if (toReturn == null) {
+ toReturn = new EnumCoder<E>(type);
+ coders.put(type, toReturn);
+ }
+ return toReturn;
+ }
+
+ public static Coder mapCoder(Coder valueCoder, Coder keyCoder) {
+ return new MapCoder(valueCoder, keyCoder);
+ }
+
+ public static Coder objectCoder(Class<?> type) {
+ Coder toReturn = coders.get(type);
+ if (toReturn == null) {
+ toReturn = new ObjectCoder(type);
+ coders.put(type, toReturn);
+ }
+ return toReturn;
+ }
+
+ public static Coder splittableCoder() {
+ return SplittableCoder.INSTANCE;
+ }
+
+ public static Coder valueCoder(Class<?> type) {
+ Coder toReturn = coders.get(type);
+ if (toReturn == null) {
+ toReturn = new ValueCoder(type);
+ coders.put(type, toReturn);
+ }
+ return toReturn;
+ }
+
+ static Splittable tryExtractSplittable(Object value) {
+ AutoBean<?> bean = AutoBeanUtils.getAutoBean(value);
+ if (bean != null) {
+ value = bean;
+ }
+ if (bean instanceof HasSplittable) {
+ return ((HasSplittable) bean).getSplittable();
+ }
+ return null;
+ }
+
+ private static String key(AutoBean<?> bean, String propertyName) {
+ return bean.getType().getName() + ":" + propertyName;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java
new file mode 100644
index 0000000..8356831
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/EnumMap.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared.impl;
+
+/**
+ * This interface is implemented by our generated AutoBeanFactory types to
+ * convert enum values to strings.
+ */
+public interface EnumMap {
+ /**
+ * Extra enums that should be included in the AutoBeanFactory.
+ */
+ public @interface ExtraEnums {
+ Class<? extends Enum<?>>[] value();
+ }
+
+ <E extends Enum<?>> E getEnum(Class<E> clazz, String token);
+
+ String getToken(Enum<?> e);
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java b/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java
new file mode 100644
index 0000000..41b39d2
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/HasSplittable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+
+/**
+ * Allows reified type facades to return their underlying Splittable
+ * datastructure.
+ */
+public interface HasSplittable {
+ Splittable getSplittable();
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/SplittableComplexMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableComplexMap.java
new file mode 100644
index 0000000..c118d29
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableComplexMap.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Map implementation for complex keys.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ */
+public class SplittableComplexMap<K, V> implements Map<K, V>, HasSplittable {
+ private final Splittable data;
+ private final List<K> keys;
+ private final List<V> values;
+
+ public SplittableComplexMap(Splittable data, Coder keyCoder, Coder valueCoder, EncodeState state) {
+ this.data = data;
+ this.keys = new SplittableList<K>(data.get(0), keyCoder, state);
+ this.values = new SplittableList<V>(data.get(1), valueCoder, state);
+ assert this.keys.size() == this.values.size();
+ }
+
+ public void clear() {
+ // Trigger ConcurrentModificationExceptions for any outstanding Iterators
+ keys.clear();
+ values.clear();
+ }
+
+ public boolean containsKey(Object key) {
+ return keys.contains(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return values.contains(value);
+ }
+
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ return new AbstractSet<Map.Entry<K, V>>() {
+
+ @Override
+ public Iterator<java.util.Map.Entry<K, V>> iterator() {
+ return new Iterator<Map.Entry<K, V>>() {
+ Iterator<K> keyIt = keys.iterator();
+ ListIterator<V> valueIt = values.listIterator();
+
+ public boolean hasNext() {
+ assert keyIt.hasNext() == valueIt.hasNext();
+ return keyIt.hasNext();
+ }
+
+ public java.util.Map.Entry<K, V> next() {
+ return new Map.Entry<K, V>() {
+ final K key = keyIt.next();
+ final V value = valueIt.next();
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V value) {
+ valueIt.set(value);
+ return value;
+ }
+ };
+ }
+
+ public void remove() {
+ keyIt.remove();
+ valueIt.remove();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ public V get(Object key) {
+ int idx = keys.indexOf(key);
+ if (idx == -1) {
+ return null;
+ }
+ return values.get(idx);
+ }
+
+ public Splittable getSplittable() {
+ return data;
+ }
+
+ public boolean isEmpty() {
+ return keys.isEmpty();
+ }
+
+ public Set<K> keySet() {
+ return new AbstractSet<K>() {
+ @Override
+ public Iterator<K> iterator() {
+ return keys.iterator();
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ public V put(K key, V value) {
+ int idx = keys.indexOf(key);
+ if (idx == -1) {
+ keys.add(key);
+ values.add(value);
+ return null;
+ }
+ return values.set(idx, value);
+ }
+
+ public void putAll(Map<? extends K, ? extends V> m) {
+ for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public V remove(Object key) {
+ int idx = keys.indexOf(key);
+ if (idx == -1) {
+ return null;
+ }
+ keys.remove(idx);
+ return values.remove(idx);
+ }
+
+ public int size() {
+ return keys.size();
+ }
+
+ public Collection<V> values() {
+ return new AbstractCollection<V>() {
+ @Override
+ public Iterator<V> iterator() {
+ return new Iterator<V>() {
+ final Iterator<K> keyIt = keys.iterator();
+ final Iterator<V> valueIt = values.iterator();
+
+ public boolean hasNext() {
+ return keyIt.hasNext();
+ }
+
+ public V next() {
+ keyIt.next();
+ return valueIt.next();
+ }
+
+ public void remove() {
+ keyIt.remove();
+ valueIt.remove();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/SplittableList.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableList.java
new file mode 100644
index 0000000..48fe0f6
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableList.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+
+import java.util.AbstractList;
+
+/**
+ * A list implementation that lazily reifies its constituent elements.
+ *
+ * @param <E> the element type
+ */
+public class SplittableList<E> extends AbstractList<E> implements HasSplittable {
+ static <Q> Q reify(EncodeState state, Splittable data, int index, Coder coder) {
+ if (data.isNull(index)) {
+ return null;
+ }
+ @SuppressWarnings("unchecked")
+ Q toReturn = (Q) coder.decode(state, data.get(index));
+ data.setReified(String.valueOf(index), toReturn);
+ return toReturn;
+ }
+
+ static void set(EncodeState state, Splittable data, int index, Coder coder, Object value) {
+ data.setReified(String.valueOf(index), value);
+ if (value == null) {
+ Splittable.NULL.assign(data, index);
+ return;
+ }
+ Splittable backing = coder.extractSplittable(state, value);
+ if (backing == null) {
+ /*
+ * External data type, such as an ArrayList or a concrete implementation
+ * of a setter's interface type. This means that a slow serialization pass
+ * is necessary.
+ */
+ data.setReified(AbstractAutoBean.UNSPLITTABLE_VALUES_KEY, true);
+ } else {
+ backing.assign(data, index);
+ }
+ }
+
+ private Splittable data;
+ private final Coder elementCoder;
+ private final EncodeState state;
+
+ public SplittableList(Splittable data, Coder elementCoder, EncodeState state) {
+ assert data.isIndexed() : "Expecting indexed data";
+ this.data = data;
+ this.elementCoder = elementCoder;
+ this.state = state;
+ }
+
+ @Override
+ public void add(int index, E element) {
+ set(state, data, index, elementCoder, element);
+ }
+
+ @Override
+ public E get(int index) {
+ if (data.isReified(String.valueOf(index))) {
+ @SuppressWarnings("unchecked")
+ E toReturn = (E) data.getReified(String.valueOf(index));
+ return toReturn;
+ }
+ // javac generics bug
+ return SplittableList.<E> reify(state, data, index, elementCoder);
+ }
+
+ public Splittable getSplittable() {
+ return data;
+ }
+
+ @Override
+ public E remove(int index) {
+ E toReturn = get(index);
+ // XXX This is terrible, use Array.splice
+ int newSize = data.size() - 1;
+ for (int i = index; i < newSize; i++) {
+ data.get(i + 1).assign(data, i);
+ data.setReified(String.valueOf(i), data.getReified(String.valueOf(i + 1)));
+ }
+ data.setSize(newSize);
+ return toReturn;
+ }
+
+ @Override
+ public E set(int index, E element) {
+ E previous = get(index);
+ set(state, data, index, elementCoder, element);
+ return previous;
+ }
+
+ @Override
+ public int size() {
+ return data.size();
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSet.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSet.java
new file mode 100644
index 0000000..9b6d8ab
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSet.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+
+import java.util.AbstractSet;
+import java.util.Iterator;
+
+/**
+ * This type is optimized for the read-only case and has {@code O(n)} insertion
+ * / lookup performance since computing hashcodes for the elements would require
+ * up-front reification.
+ *
+ * @param <E> the element type
+ */
+public class SplittableSet<E> extends AbstractSet<E> implements HasSplittable {
+ private SplittableList<E> data;
+
+ public SplittableSet(Splittable data, Coder elementCoder, EncodeState state) {
+ this.data = new SplittableList<E>(data, elementCoder, state);
+ }
+
+ @Override
+ public boolean add(E e) {
+ if (!data.contains(e)) {
+ data.add(e);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void clear() {
+ data.clear();
+ }
+
+ public Splittable getSplittable() {
+ return data.getSplittable();
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return data.iterator();
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return data.remove(o);
+ }
+
+ @Override
+ public int size() {
+ return data.size();
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSimpleMap.java b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSimpleMap.java
new file mode 100644
index 0000000..fc4dc0f
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/SplittableSimpleMap.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Map implementation for regular JSON maps with value-type keys.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ */
+public class SplittableSimpleMap<K, V> implements Map<K, V>, HasSplittable {
+ private final Splittable data;
+ private final Coder keyCoder;
+ private final EncodeState state;
+ private final Coder valueCoder;
+ /**
+ * Don't hang the reified data from {@link #data} since we can't tell the
+ * __reified field from the actual data.
+ */
+ private Splittable reified = StringQuoter.createSplittable();
+
+ public SplittableSimpleMap(Splittable data, Coder keyCoder, Coder valueCoder, EncodeState state) {
+ this.data = data;
+ this.keyCoder = keyCoder;
+ this.state = state;
+ this.valueCoder = valueCoder;
+ }
+
+ public void clear() {
+ for (String key : data.getPropertyKeys()) {
+ Splittable.NULL.assign(data, key);
+ reified.setReified(key, null);
+ }
+ }
+
+ public boolean containsKey(Object key) {
+ String encodedKey = encodedKey(key);
+ return !data.isUndefined(encodedKey) || reified.isReified(encodedKey);
+ }
+
+ public boolean containsValue(Object value) {
+ return values().contains(value);
+ }
+
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ return new AbstractSet<Map.Entry<K, V>>() {
+ final List<String> keys = data.getPropertyKeys();
+
+ @Override
+ public Iterator<java.util.Map.Entry<K, V>> iterator() {
+ return new Iterator<Map.Entry<K, V>>() {
+ Iterator<String> keyIterator = keys.iterator();
+ String encodedKey;
+
+ public boolean hasNext() {
+ return keyIterator.hasNext();
+ }
+
+ public java.util.Map.Entry<K, V> next() {
+ encodedKey = keyIterator.next();
+ return new Map.Entry<K, V>() {
+ @SuppressWarnings("unchecked")
+ final K key = (K) keyCoder.decode(state, StringQuoter.split(StringQuoter
+ .quote(encodedKey)));
+ @SuppressWarnings("unchecked")
+ final V value = (V) valueCoder.decode(state, data.get(encodedKey));
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V newValue) {
+ return put(key, newValue);
+ }
+ };
+ }
+
+ public void remove() {
+ Splittable.NULL.assign(data, encodedKey);
+ reified.setReified(encodedKey, null);
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ public V get(Object key) {
+ String encodedKey = encodedKey(key);
+ return getRaw(encodedKey);
+ }
+
+ public Splittable getSplittable() {
+ return data;
+ }
+
+ public boolean isEmpty() {
+ return data.getPropertyKeys().isEmpty();
+ }
+
+ public Set<K> keySet() {
+ return new AbstractSet<K>() {
+ final List<String> keys = data.getPropertyKeys();
+
+ @Override
+ public Iterator<K> iterator() {
+ return new Iterator<K>() {
+ final Iterator<String> it = keys.iterator();
+ String lastEncodedKey;
+
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ public K next() {
+ lastEncodedKey = it.next();
+ @SuppressWarnings("unchecked")
+ K toReturn =
+ (K) keyCoder.decode(state, StringQuoter.split(StringQuoter.quote(lastEncodedKey)));
+ return toReturn;
+ }
+
+ public void remove() {
+ Splittable.NULL.assign(data, lastEncodedKey);
+ reified.setReified(lastEncodedKey, null);
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ public V put(K key, V value) {
+ V toReturn = get(key);
+ String encodedKey = encodedKey(key);
+ reified.setReified(encodedKey, value);
+ Splittable encodedValue = valueCoder.extractSplittable(state, value);
+ if (encodedValue == null) {
+ // External datastructure
+ reified.setReified(AbstractAutoBean.UNSPLITTABLE_VALUES_KEY, true);
+ } else {
+ encodedValue.assign(data, encodedKey);
+ }
+ return toReturn;
+ }
+
+ public void putAll(Map<? extends K, ? extends V> m) {
+ for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public V remove(Object key) {
+ V toReturn = get(key);
+ String encodedKey = encodedKey(key);
+ reified.setReified(encodedKey, null);
+ Splittable.NULL.assign(data, encodedKey);
+ return toReturn;
+ }
+
+ public int size() {
+ return data.getPropertyKeys().size();
+ }
+
+ public Collection<V> values() {
+ return new AbstractCollection<V>() {
+ final List<String> keys = data.getPropertyKeys();
+
+ @Override
+ public Iterator<V> iterator() {
+ return new Iterator<V>() {
+ final Iterator<String> it = keys.iterator();
+ String lastEncodedKey;
+
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ public V next() {
+ lastEncodedKey = it.next();
+ return getRaw(lastEncodedKey);
+ }
+
+ public void remove() {
+ Splittable.NULL.assign(data, lastEncodedKey);
+ reified.setReified(lastEncodedKey, null);
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+ };
+ }
+
+ private String encodedKey(Object key) {
+ return keyCoder.extractSplittable(state, key).asString();
+ }
+
+ private V getRaw(String encodedKey) {
+ if (reified.isReified(encodedKey)) {
+ @SuppressWarnings("unchecked")
+ V toReturn = (V) reified.getReified(encodedKey);
+ return toReturn;
+ }
+ // Both undefined or an explicit null should return null here
+ if (data.isNull(encodedKey)) {
+ return null;
+ }
+ Splittable value = data.get(encodedKey);
+ @SuppressWarnings("unchecked")
+ V toReturn = (V) valueCoder.decode(state, value);
+ reified.setReified(encodedKey, toReturn);
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java b/user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
new file mode 100644
index 0000000..d30fb3e
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.vm.impl.JsonSplittable;
+
+import org.json.JSONObject;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * This class has a super-source version with a client-only implementation.
+ */
+public class StringQuoter {
+ private static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSz";
+ private static final DateFormat ISO8601 = new SimpleDateFormat(ISO8601_PATTERN, Locale
+ .getDefault());
+
+ private static final String RFC2822_PATTERN = "EEE, d MMM yyyy HH:mm:ss Z";
+ private static final DateFormat RFC2822 = new SimpleDateFormat(RFC2822_PATTERN, Locale
+ .getDefault());
+
+ public static Splittable create(boolean value) {
+ return JsonSplittable.create(String.valueOf(value));
+ }
+
+ public static Splittable create(double value) {
+ return JsonSplittable.create(String.valueOf(value));
+ }
+
+ public static Splittable create(String value) {
+ return JsonSplittable.create(quote(value));
+ }
+
+ public static Splittable createIndexed() {
+ return JsonSplittable.createIndexed();
+ }
+
+ public static Splittable createSplittable() {
+ return JsonSplittable.create();
+ }
+
+ public static Splittable nullValue() {
+ return JsonSplittable.createNull();
+ }
+
+ /**
+ * Create a quoted JSON string.
+ */
+ public static String quote(String raw) {
+ return JSONObject.quote(raw);
+ }
+
+ public static Splittable split(String payload) {
+ return JsonSplittable.create(payload);
+ }
+
+ /**
+ * Attempt to parse an ISO-8601 date format. May return {@code null} if the
+ * input cannot be parsed.
+ */
+ public static Date tryParseDate(String date) {
+ try {
+ return new Date(Long.parseLong(date));
+ } catch (NumberFormatException ignored) {
+ }
+ if (date.endsWith("Z")) {
+ date = date.substring(0, date.length() - 1) + "+0000";
+ }
+ try {
+ return ISO8601.parse(date);
+ } catch (ParseException ignored) {
+ }
+ try {
+ return RFC2822.parse(date);
+ } catch (ParseException ignored) {
+ }
+ return null;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/shared/package-info.java b/user/src/com/google/web/bindery/autobean/shared/package-info.java
new file mode 100644
index 0000000..4dec6fa
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/shared/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/**
+ * The AutoBean framework provides automatically-generated implementations of
+ * bean-like interfaces and a low-level serialization mechanism for those
+ * interfaces. AutoBeans can be used in both client and server code to improve
+ * code re-use.
+ *
+ * @see <a
+ * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ * wiki page</a>
+ * @see com.google.web.bindery.autobean.shared.AutoBeanFactory
+ * @see com.google.web.bindery.autobean.vm.AutoBeanFactorySource
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.autobean.shared;
+
diff --git a/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java b/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java
new file mode 100644
index 0000000..eaba1d0
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/AutoBeanFactorySource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.shared.impl.EnumMap;
+import com.google.web.bindery.autobean.vm.impl.FactoryHandler;
+import com.google.web.bindery.autobean.vm.impl.ProxyAutoBean;
+
+/**
+ * Generates JVM-compatible implementations of AutoBeanFactory and AutoBean
+ * types.
+ * <p>
+ * This implementation is written assuming that the AutoBeanFactory and
+ * associated declarations will validate if compiled and used with the
+ * AutoBeanFactoyModel.
+ * <p>
+ * <span style='color: red'>This is experimental, unsupported code.</span>
+ */
+public class AutoBeanFactorySource {
+ /*
+ * NB: This implementation is excessively dynamic, however the inability to
+ * create a TypeOracle fram a ClassLoader prevents re-using the existing model
+ * code. If the model code could be reused, it would be straightforward to
+ * simply generate implementations of the various interfaces.
+ */
+ private static final AutoBeanFactory EMPTY = create(AutoBeanFactory.class);
+
+ /**
+ * Create an instance of an AutoBeanFactory.
+ *
+ * @param <F> the factory type
+ * @param clazz the Class representing the factory interface
+ * @return an instance of the AutoBeanFactory
+ */
+ public static <F extends AutoBeanFactory> F create(Class<F> clazz) {
+ Configuration.Builder builder = new Configuration.Builder();
+ Category cat = clazz.getAnnotation(Category.class);
+ if (cat != null) {
+ builder.setCategories(cat.value());
+ }
+ NoWrap noWrap = clazz.getAnnotation(NoWrap.class);
+ if (noWrap != null) {
+ builder.setNoWrap(noWrap.value());
+ }
+
+ return ProxyAutoBean.makeProxy(clazz, new FactoryHandler(builder.build()), EnumMap.class);
+ }
+
+ /**
+ * Create an instance of an AutoBean directly.
+ *
+ * @param <T> the interface type implemented by the AutoBean
+ * @param clazz the interface type implemented by the AutoBean
+ * @return an instance of an AutoBean
+ */
+ public static <T> AutoBean<T> createBean(Class<T> clazz, Configuration configuration) {
+ return new ProxyAutoBean<T>(EMPTY, clazz, configuration);
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/Configuration.java b/user/src/com/google/web/bindery/autobean/vm/Configuration.java
new file mode 100644
index 0000000..593ad52
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/Configuration.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Used by {@link AutoBeanFactorySource#createBean(Class, Configuration)}. This
+ * type replicates the annotations that may be applied to an AutoBeanFactory
+ * declaration.
+ * <p>
+ * <span style='color: red'>This is experimental, unsupported code.</span>
+ */
+public class Configuration {
+ /**
+ * Builds {@link Configuration} objects.
+ */
+ public static class Builder {
+ private Configuration toReturn = new Configuration();
+
+ public Configuration build() {
+ toReturn.noWrap.add(AutoBean.class);
+ toReturn.noWrap = Collections.unmodifiableSet(toReturn.noWrap);
+ try {
+ return toReturn;
+ } finally {
+ toReturn = null;
+ }
+ }
+
+ /**
+ * Equivalent to applying a
+ * {@link com.google.web.bindery.autobean.shared.AutoBeanFactory.Category
+ * Category} annotation to an AutoBeanFactory declaration.
+ *
+ * @param categories the category types that should be searched for static
+ * implementations of non-property methods
+ * @return the Builder
+ */
+ public Builder setCategories(Class<?>... categories) {
+ toReturn.categories =
+ Collections.unmodifiableList(new ArrayList<Class<?>>(Arrays.asList(categories)));
+ return this;
+ }
+
+ /**
+ * Equivalent to applying a
+ * {@link com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap
+ * NoWrap} annotation to an AutoBeanFactory declaration.
+ *
+ * @param noWrap the types that should be excluded from wrapping
+ * @return the Builder
+ */
+ public Builder setNoWrap(Class<?>... noWrap) {
+ toReturn.noWrap.addAll(Arrays.asList(noWrap));
+ return this;
+ }
+ }
+
+ private List<Class<?>> categories = Collections.emptyList();
+
+ private Set<Class<?>> noWrap = new HashSet<Class<?>>();
+
+ private Configuration() {
+ }
+
+ public List<Class<?>> getCategories() {
+ return categories;
+ }
+
+ public Set<Class<?>> getNoWrap() {
+ return noWrap;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java b/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java
new file mode 100644
index 0000000..f165239
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/BeanMethod.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Breakout of method types that an AutoBean shim interface can implement. The
+ * order of the values of the enum is important.
+ *
+ * @see com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod
+ */
+public enum BeanMethod {
+ /**
+ * Methods defined in Object.
+ */
+ OBJECT {
+
+ @Override
+ public String inferName(Method method) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable {
+ if (CALL.matches(handler, method)) {
+ return CALL.invoke(handler, method, args);
+ }
+ return method.invoke(handler, args);
+ }
+
+ @Override
+ boolean matches(SimpleBeanHandler<?> handler, Method method) {
+ return method.getDeclaringClass().equals(Object.class);
+ }
+ },
+ /**
+ * Getters.
+ */
+ GET {
+ @Override
+ public String inferName(Method method) {
+ String name = method.getName();
+ if (name.startsWith(IS_PREFIX) && !method.isAnnotationPresent(PropertyName.class)) {
+ Class<?> returnType = method.getReturnType();
+ if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) {
+ return decapitalize(name.substring(2));
+ }
+ }
+ return super.inferName(method);
+ }
+
+ @Override
+ Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+ String propertyName = inferName(method);
+ Object toReturn = handler.getBean().getOrReify(propertyName);
+ if (toReturn == null && method.getReturnType().isPrimitive()) {
+ toReturn = TypeUtils.getDefaultPrimitiveValue(method.getReturnType());
+ }
+ return toReturn;
+ }
+
+ @Override
+ boolean matches(SimpleBeanHandler<?> handler, Method method) {
+ Class<?> returnType = method.getReturnType();
+ if (method.getParameterTypes().length != 0 || Void.TYPE.equals(returnType)) {
+ return false;
+ }
+
+ String name = method.getName();
+ if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) {
+ if (name.startsWith(IS_PREFIX) && name.length() > 2 || name.startsWith(HAS_PREFIX)
+ && name.length() > 3) {
+ return true;
+ }
+ }
+ return name.startsWith(GET_PREFIX) && name.length() > 3;
+ }
+ },
+ /**
+ * Setters.
+ */
+ SET {
+ @Override
+ Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+ handler.getBean().setProperty(inferName(method), args[0]);
+ return null;
+ }
+
+ @Override
+ boolean matches(SimpleBeanHandler<?> handler, Method method) {
+ String name = method.getName();
+ return name.startsWith(SET_PREFIX) && name.length() > 3
+ && method.getParameterTypes().length == 1 && method.getReturnType().equals(Void.TYPE);
+ }
+ },
+ /**
+ * A setter that returns a type assignable from the interface in which the
+ * method is declared to support chained, builder-pattern setters. For
+ * example, {@code foo.setBar(1).setBaz(42)}.
+ */
+ SET_BUILDER {
+ @Override
+ Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) {
+ ProxyAutoBean<?> bean = handler.getBean();
+ bean.setProperty(inferName(method), args[0]);
+ return bean.as();
+ }
+
+ @Override
+ boolean matches(SimpleBeanHandler<?> handler, Method method) {
+ String name = method.getName();
+ return name.startsWith(SET_PREFIX) && name.length() > 3
+ && method.getParameterTypes().length == 1
+ && method.getReturnType().isAssignableFrom(method.getDeclaringClass());
+ }
+ },
+ /**
+ * Domain methods.
+ */
+ CALL {
+ @Override
+ public String inferName(Method method) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable {
+ if (args == null) {
+ args = EMPTY_OBJECT;
+ }
+
+ Method found = findMethod(handler, method);
+ if (found != null) {
+ Object[] realArgs = new Object[args.length + 1];
+ realArgs[0] = handler.getBean();
+ System.arraycopy(args, 0, realArgs, 1, args.length);
+ return found.invoke(null, realArgs);
+ }
+ throw new RuntimeException("Could not find category implementation of "
+ + method.toGenericString());
+ }
+
+ @Override
+ boolean matches(SimpleBeanHandler<?> handler, Method method) {
+ return handler.getBean().isWrapper()
+ || !handler.getBean().getConfiguration().getCategories().isEmpty()
+ && findMethod(handler, method) != null;
+ }
+ };
+
+ public static final String GET_PREFIX = "get";
+ public static final String HAS_PREFIX = "has";
+ public static final String IS_PREFIX = "is";
+ public static final String SET_PREFIX = "set";
+
+ private static final Object[] EMPTY_OBJECT = new Object[0];
+
+ static Method findMethod(SimpleBeanHandler<?> handler, Method method) {
+ Class<?>[] declaredParams = method.getParameterTypes();
+ Class<?>[] searchParams = new Class<?>[declaredParams.length + 1];
+ searchParams[0] = AutoBean.class;
+ System.arraycopy(declaredParams, 0, searchParams, 1, declaredParams.length);
+ Class<?> autoBeanType = handler.getBean().getType();
+
+ for (Class<?> clazz : handler.getBean().getConfiguration().getCategories()) {
+ try {
+ Method found = clazz.getMethod(method.getName(), searchParams);
+ if (!Modifier.isStatic(found.getModifiers())) {
+ continue;
+ }
+ // Check the AutoBean parameterization of the 0th argument
+ Class<?> foundAutoBean =
+ TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(AutoBean.class, found
+ .getGenericParameterTypes()[0]));
+ if (!foundAutoBean.isAssignableFrom(autoBeanType)) {
+ continue;
+ }
+ return found;
+ } catch (NoSuchMethodException expected) {
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Private equivalent of Introspector.decapitalize(String) since
+ * java.beans.Introspector is not available in Android 2.2.
+ */
+ private static String decapitalize(String name) {
+ if (name == null) {
+ return null;
+ }
+ int length = name.length();
+ if (length == 0 || (length > 1 && Character.isUpperCase(name.charAt(1)))) {
+ return name;
+ }
+ StringBuilder sb = new StringBuilder(length);
+ sb.append(Character.toLowerCase(name.charAt(0)));
+ sb.append(name.substring(1));
+ return sb.toString();
+ }
+
+ public String inferName(Method method) {
+ PropertyName prop = method.getAnnotation(PropertyName.class);
+ if (prop != null) {
+ return prop.value();
+ }
+ return decapitalize(method.getName().substring(3));
+ }
+
+ /**
+ * Convenience method, not valid for {@link BeanMethod#CALL}.
+ */
+ public boolean matches(Method method) {
+ return matches(null, method);
+ }
+
+ /**
+ * Invoke the method.
+ */
+ abstract Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args)
+ throws Throwable;
+
+ /**
+ * Determine if the method maches the given type.
+ */
+ abstract boolean matches(SimpleBeanHandler<?> handler, Method method);
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java
new file mode 100644
index 0000000..a4fe31f
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/BeanPropertyContext.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import java.lang.reflect.Method;
+
+/**
+ * A property context that allows setters to be called on a simple peer,
+ * regardless of whether or not the interface actually has a setter.
+ */
+class BeanPropertyContext extends MethodPropertyContext {
+ private final ProxyAutoBean<?> bean;
+ private final String propertyName;
+
+ public BeanPropertyContext(ProxyAutoBean<?> bean, Method getter) {
+ super(getter);
+ this.bean = bean;
+ propertyName = BeanMethod.GET.inferName(getter);
+ }
+
+ @Override
+ public boolean canSet() {
+ return true;
+ }
+
+ @Override
+ public void set(Object value) {
+ Class<?> maybeAutobox = TypeUtils.maybeAutobox(getType());
+ assert value == null || maybeAutobox.isInstance(value) : value.getClass().getCanonicalName()
+ + " is not assignable to " + maybeAutobox.getCanonicalName();
+ bean.setProperty(propertyName, maybeAutobox.cast(value));
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java
new file mode 100644
index 0000000..8532f41
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/FactoryHandler.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.vm.Configuration;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+
+/**
+ * Handles dispatches on AutoBeanFactory interfaces.
+ */
+public class FactoryHandler implements InvocationHandler {
+ private final Configuration configuration;
+
+ /**
+ * Constructor.
+ *
+ * @param categories the classes specified by a Category annotation
+ */
+ public FactoryHandler(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ /**
+ * Handles both declared factory methods as well as the dynamic create
+ * methods.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+
+ Class<?> beanType;
+ Object toWrap = null;
+ String name = method.getName();
+ if (name.equals("create")) {
+ // Dynamic create. Guaranteed to have at least one argument
+ // create(clazz); or create(clazz, toWrap);
+ beanType = (Class<?>) args[0];
+ if (args.length == 2) {
+ toWrap = args[1];
+ }
+ } else if (name.equals("getEnum")) {
+ Class<?> clazz = (Class<?>) args[0];
+ String token = (String) args[1];
+ return getEnum(clazz, token);
+ } else if (name.equals("getToken")) {
+ Enum<?> e = (Enum<?>) args[0];
+ return getToken(e);
+ } else {
+ // Declared factory method, use the parameterization
+ // AutoBean<Foo> foo(); or Autobean<foo> foo(Foo toWrap);
+ ParameterizedType returnType = (ParameterizedType) method.getGenericReturnType();
+ beanType = (Class<?>) returnType.getActualTypeArguments()[0];
+
+ if (args != null && args.length == 1) {
+ toWrap = args[0];
+ }
+ }
+
+ // Return any existing wrapper
+ ProxyAutoBean<Object> toReturn = (ProxyAutoBean<Object>) AutoBeanUtils.getAutoBean(toWrap);
+ if (toReturn == null) {
+ // Create the implementation bean
+ if (toWrap == null) {
+ toReturn = new ProxyAutoBean<Object>((AutoBeanFactory) proxy, beanType,
+ configuration);
+ } else {
+ toReturn = new ProxyAutoBean<Object>((AutoBeanFactory) proxy, beanType,
+ configuration, toWrap);
+ }
+ }
+
+ return toReturn;
+ }
+
+ /**
+ * EnumMap support.
+ */
+ private Object getEnum(Class<?> clazz, String token)
+ throws IllegalAccessException {
+ for (Field f : clazz.getFields()) {
+ String fieldName;
+ PropertyName annotation = f.getAnnotation(PropertyName.class);
+ if (annotation != null) {
+ fieldName = annotation.value();
+ } else {
+ fieldName = f.getName();
+ }
+ if (token.equals(fieldName)) {
+ f.setAccessible(true);
+ return f.get(null);
+ }
+ }
+ throw new IllegalArgumentException("Cannot find enum " + token
+ + " in type " + clazz.getCanonicalName());
+ }
+
+ /**
+ * EnumMap support.
+ */
+ private Object getToken(Enum<?> e) throws NoSuchFieldException {
+ // Remember enum constants are fields
+ PropertyName annotation = e.getDeclaringClass().getField(e.name()).getAnnotation(
+ PropertyName.class);
+ if (annotation != null) {
+ return annotation.value();
+ } else {
+ return e.name();
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java
new file mode 100644
index 0000000..d00b8ed
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/GetterPropertyContext.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Used by {@link ProxyAutoBean#traverseProperties()}.
+ */
+class GetterPropertyContext extends MethodPropertyContext {
+ private final Method setter;
+ private final Object shim;
+
+ GetterPropertyContext(ProxyAutoBean<?> bean, Method getter) {
+ super(getter);
+ this.shim = bean.as();
+
+ // Look for the setter method.
+ Method found = null;
+ String name = BeanMethod.GET.inferName(getter);
+ for (Method m : getter.getDeclaringClass().getMethods()) {
+ if (BeanMethod.SET.matches(m) || BeanMethod.SET_BUILDER.matches(m)) {
+ if (BeanMethod.SET.inferName(m).equals(name)
+ && getter.getReturnType().isAssignableFrom(m.getParameterTypes()[0])) {
+ found = m;
+ break;
+ }
+ }
+ }
+ setter = found;
+ }
+
+ @Override
+ public boolean canSet() {
+ return setter != null;
+ }
+
+ @Override
+ public void set(Object value) {
+ if (!canSet()) {
+ throw new UnsupportedOperationException("No setter");
+ }
+ try {
+ setter.setAccessible(true);
+ setter.invoke(shim, value);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java b/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java
new file mode 100644
index 0000000..8fdad79
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.impl.HasSplittable;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Uses the org.json packages to slice and dice request payloads.
+ */
+public class JsonSplittable implements Splittable, HasSplittable {
+
+ /**
+ * Ensures that the same JsonSplittable will be returned for a given backing
+ * JSONObject.
+ */
+ private static final Map<Object, Reference<JsonSplittable>> canonical =
+ new WeakHashMap<Object, Reference<JsonSplittable>>();
+
+ public static JsonSplittable create() {
+ return new JsonSplittable(new JSONObject());
+ }
+
+ public static Splittable create(String payload) {
+ try {
+ switch (payload.charAt(0)) {
+ case '{':
+ return new JsonSplittable(new JSONObject(payload));
+ case '[':
+ return new JsonSplittable(new JSONArray(payload));
+ case '"':
+ return new JsonSplittable(new JSONArray("[" + payload + "]").getString(0));
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return new JsonSplittable(Double.parseDouble(payload));
+ case 't':
+ case 'f':
+ return new JsonSplittable(Boolean.parseBoolean(payload));
+ case 'n':
+ return null;
+ default:
+ throw new RuntimeException("Could not parse payload: payload[0] = " + payload.charAt(0));
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException("Could not parse payload", e);
+ }
+ }
+
+ public static Splittable createIndexed() {
+ return new JsonSplittable(new JSONArray());
+ }
+
+ public static Splittable createNull() {
+ return new JsonSplittable();
+ }
+
+ /**
+ * Private equivalent of org.json.JSONObject.getNames(JSONObject) since that
+ * method is not available in Android 2.2. Used to represent a null value.
+ */
+ private static String[] getNames(JSONObject json) {
+ int length = json.length();
+ if (length == 0) {
+ return null;
+ }
+ String[] names = new String[length];
+ Iterator<?> i = json.keys();
+ int j = 0;
+ while (i.hasNext()) {
+ names[j++] = (String) i.next();
+ }
+ return names;
+ }
+
+ private JSONArray array;
+ private Boolean bool;
+ /**
+ * Used to represent a null value.
+ */
+ private boolean isNull;
+ private Double number;
+ private JSONObject obj;
+ private String string;
+ private final Map<String, Object> reified = new HashMap<String, Object>();
+
+ /**
+ * Constructor for a null value.
+ */
+ private JsonSplittable() {
+ isNull = true;
+ }
+
+ private JsonSplittable(boolean value) {
+ this.bool = value;
+ }
+
+ private JsonSplittable(double value) {
+ this.number = value;
+ }
+
+ private JsonSplittable(JSONArray array) {
+ this.array = array;
+ }
+
+ private JsonSplittable(JSONObject obj) {
+ this.obj = obj;
+ }
+
+ private JsonSplittable(String string) {
+ this.array = null;
+ this.obj = null;
+ this.string = string;
+ }
+
+ public boolean asBoolean() {
+ return bool;
+ }
+
+ public double asNumber() {
+ return number;
+ }
+
+ public void assign(Splittable parent, int index) {
+ try {
+ ((JsonSplittable) parent).array.put(index, value());
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void assign(Splittable parent, String propertyName) {
+ try {
+ ((JsonSplittable) parent).obj.put(propertyName, value());
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String asString() {
+ return string;
+ }
+
+ public Splittable deepCopy() {
+ return create(getPayload());
+ }
+
+ public Splittable get(int index) {
+ try {
+ return makeSplittable(array.get(index));
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Splittable get(String key) {
+ try {
+ return makeSplittable(obj.get(key));
+ } catch (JSONException e) {
+ throw new RuntimeException(key, e);
+ }
+ }
+
+ public String getPayload() {
+ if (isNull) {
+ return "null";
+ }
+ if (obj != null) {
+ return obj.toString();
+ }
+ if (array != null) {
+ return array.toString();
+ }
+ if (string != null) {
+ return StringQuoter.quote(string);
+ }
+ if (number != null) {
+ return String.valueOf(number);
+ }
+ if (bool != null) {
+ return String.valueOf(bool);
+ }
+ throw new RuntimeException("No data in this JsonSplittable");
+ }
+
+ public List<String> getPropertyKeys() {
+ String[] names = getNames(obj);
+ if (names == null) {
+ return Collections.emptyList();
+ } else {
+ return Collections.unmodifiableList(Arrays.asList(names));
+ }
+ }
+
+ public Object getReified(String key) {
+ return reified.get(key);
+ }
+
+ public Splittable getSplittable() {
+ return this;
+ }
+
+ public boolean isBoolean() {
+ return bool != null;
+ }
+
+ public boolean isIndexed() {
+ return array != null;
+ }
+
+ public boolean isKeyed() {
+ return obj != null;
+ }
+
+ public boolean isNull(int index) {
+ return array.isNull(index);
+ }
+
+ public boolean isNull(String key) {
+ // Treat undefined and null as the same
+ return !obj.has(key) || obj.isNull(key);
+ }
+
+ public boolean isNumber() {
+ return number != null;
+ }
+
+ public boolean isReified(String key) {
+ return reified.containsKey(key);
+ }
+
+ public boolean isString() {
+ return string != null;
+ }
+
+ public boolean isUndefined(String key) {
+ return !obj.has(key);
+ }
+
+ public void setReified(String key, Object object) {
+ reified.put(key, object);
+ }
+
+ public void setSize(int size) {
+ // This is terrible, but there's no API support for resizing or splicing
+ JSONArray newArray = new JSONArray();
+ for (int i = 0; i < size; i++) {
+ try {
+ newArray.put(i, array.get(i));
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ array = newArray;
+ }
+
+ public int size() {
+ return array.length();
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return getPayload();
+ }
+
+ private synchronized JsonSplittable makeSplittable(Object object) {
+ if (JSONObject.NULL.equals(object)) {
+ return null;
+ }
+ Reference<JsonSplittable> ref = canonical.get(object);
+ JsonSplittable seen = ref == null ? null : ref.get();
+ if (seen == null) {
+ if (object instanceof JSONObject) {
+ seen = new JsonSplittable((JSONObject) object);
+ } else if (object instanceof JSONArray) {
+ seen = new JsonSplittable((JSONArray) object);
+ } else if (object instanceof String) {
+ seen = new JsonSplittable(object.toString());
+ } else if (object instanceof Number) {
+ seen = new JsonSplittable(((Number) object).doubleValue());
+ } else if (object instanceof Boolean) {
+ seen = new JsonSplittable((Boolean) object);
+ } else {
+ throw new RuntimeException("Unhandled type " + object.getClass());
+ }
+ canonical.put(object, new WeakReference<JsonSplittable>(seen));
+ }
+ return seen;
+ }
+
+ private Object value() {
+ if (isNull) {
+ return null;
+ }
+ if (obj != null) {
+ return obj;
+ }
+ if (array != null) {
+ return array;
+ }
+ if (string != null) {
+ return string;
+ }
+ if (number != null) {
+ return number;
+ }
+ if (bool != null) {
+ return bool;
+ }
+ throw new RuntimeException("No data");
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java b/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java
new file mode 100644
index 0000000..10ce579
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/MethodPropertyContext.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.CollectionPropertyContext;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.MapPropertyContext;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * A base type to handle analyzing the return value of a getter method. The
+ * accessor methods are implemented in subtypes.
+ */
+abstract class MethodPropertyContext implements CollectionPropertyContext,
+ MapPropertyContext {
+ private static class Data {
+ Class<?> elementType;
+ Type genericType;
+ Class<?> keyType;
+ Class<?> valueType;
+ Class<?> type;
+ }
+
+ /**
+ * Save prior instances in order to decrease the amount of data computed.
+ */
+ private static final Map<Method, Data> cache = new WeakHashMap<Method, Data>();
+ private final Data data;
+
+ public MethodPropertyContext(Method getter) {
+ synchronized (cache) {
+ Data previous = cache.get(getter);
+ if (previous != null) {
+ this.data = previous;
+ return;
+ }
+
+ this.data = new Data();
+ data.genericType = getter.getGenericReturnType();
+ data.type = getter.getReturnType();
+ // Compute collection element type
+ if (Collection.class.isAssignableFrom(getType())) {
+ data.elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+ Collection.class, getter.getGenericReturnType(),
+ getter.getReturnType()));
+ } else if (Map.class.isAssignableFrom(getType())) {
+ Type[] types = TypeUtils.getParameterization(Map.class,
+ getter.getGenericReturnType());
+ data.keyType = TypeUtils.ensureBaseType(types[0]);
+ data.valueType = TypeUtils.ensureBaseType(types[1]);
+ }
+ cache.put(getter, data);
+ }
+ }
+
+ public void accept(ParameterizationVisitor visitor) {
+ traverse(visitor, data.genericType);
+ }
+
+ public abstract boolean canSet();
+
+ public Class<?> getElementType() {
+ return data.elementType;
+ }
+
+ public Class<?> getKeyType() {
+ return data.keyType;
+ }
+
+ public Class<?> getType() {
+ return data.type;
+ }
+
+ public Class<?> getValueType() {
+ return data.valueType;
+ }
+
+ public abstract void set(Object value);
+
+ private void traverse(ParameterizationVisitor visitor, Type type) {
+ Class<?> base = TypeUtils.ensureBaseType(type);
+ if (visitor.visitType(base)) {
+ Type[] params = TypeUtils.getParameterization(base, type);
+ for (Type t : params) {
+ if (visitor.visitParameter()) {
+ traverse(visitor, t);
+ }
+ visitor.endVisitParameter();
+ }
+ }
+ visitor.endVisitType(base);
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java b/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java
new file mode 100644
index 0000000..cdf61dc
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
+import com.google.web.bindery.autobean.vm.Configuration;
+import com.google.gwt.core.client.impl.WeakMapping;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * An implementation of an AutoBean that uses reflection.
+ *
+ * @param <T> the type of interface being wrapped
+ */
+public class ProxyAutoBean<T> extends AbstractAutoBean<T> {
+ private static class Data {
+ final List<Method> getters = new ArrayList<Method>();
+ final List<String> getterNames = new ArrayList<String>();
+ final List<PropertyType> propertyType = new ArrayList<PropertyType>();
+ }
+
+ private enum PropertyType {
+ VALUE, REFERENCE, COLLECTION, MAP;
+ }
+
+ private static final Map<Class<?>, Data> cache = new WeakHashMap<Class<?>, Data>();
+
+ /**
+ * Utility method to crete a new {@link Proxy} instance.
+ *
+ * @param <T> the interface type to be implemented by the Proxy
+ * @param intf the Class representing the interface type
+ * @param handler the implementation of the interface
+ * @param extraInterfaces additional interface types the Proxy should
+ * implement
+ * @return a Proxy instance
+ */
+ public static <T> T makeProxy(Class<T> intf, InvocationHandler handler,
+ Class<?>... extraInterfaces) {
+ Class<?>[] intfs;
+ if (extraInterfaces == null) {
+ intfs = new Class<?>[]{intf};
+ } else {
+ intfs = new Class<?>[extraInterfaces.length + 1];
+ intfs[0] = intf;
+ System.arraycopy(extraInterfaces, 0, intfs, 1, extraInterfaces.length);
+ }
+
+ return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(), intfs, handler));
+ }
+
+ private static Data calculateData(Class<?> beanType) {
+ Data toReturn;
+ synchronized (cache) {
+ toReturn = cache.get(beanType);
+ if (toReturn == null) {
+ toReturn = new Data();
+ for (Method method : beanType.getMethods()) {
+ if (BeanMethod.GET.matches(method)) {
+ toReturn.getters.add(method);
+
+ String name;
+ PropertyName annotation = method.getAnnotation(PropertyName.class);
+ if (annotation != null) {
+ name = annotation.value();
+ } else {
+ name = BeanMethod.GET.inferName(method);
+ }
+ toReturn.getterNames.add(name);
+
+ Class<?> returnType = method.getReturnType();
+ if (TypeUtils.isValueType(returnType)) {
+ toReturn.propertyType.add(PropertyType.VALUE);
+ } else if (Collection.class.isAssignableFrom(returnType)) {
+ toReturn.propertyType.add(PropertyType.COLLECTION);
+ } else if (Map.class.isAssignableFrom(returnType)) {
+ toReturn.propertyType.add(PropertyType.MAP);
+ } else {
+ toReturn.propertyType.add(PropertyType.REFERENCE);
+ }
+ }
+ }
+ cache.put(beanType, toReturn);
+ }
+ }
+ return toReturn;
+ }
+
+ private final Class<T> beanType;
+ private final Configuration configuration;
+ private final Data data;
+ private final T shim;
+
+ // These constructors mirror the generated constructors.
+ @SuppressWarnings("unchecked")
+ public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration) {
+ super(factory);
+ this.beanType = (Class<T>) beanType;
+ this.configuration = configuration;
+ this.data = calculateData(beanType);
+ this.shim = createShim();
+ }
+
+ @SuppressWarnings("unchecked")
+ public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration,
+ T toWrap) {
+ super(toWrap, factory);
+ this.beanType = (Class<T>) beanType;
+ this.configuration = configuration;
+ this.data = calculateData(beanType);
+ this.shim = createShim();
+ }
+
+ @Override
+ public T as() {
+ return shim;
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ public Class<T> getType() {
+ return beanType;
+ }
+
+ /**
+ * Allow access by {@link ShimHandler}.
+ */
+ @Override
+ protected void call(String method, Object returned, Object... parameters) {
+ super.call(method, returned, parameters);
+ }
+
+ /**
+ * Allow access by {@link ShimHandler}.
+ */
+ @Override
+ protected void checkFrozen() {
+ super.checkFrozen();
+ }
+
+ /**
+ * Allow access by {@link ShimHandler}.
+ */
+ @Override
+ protected void checkWrapped() {
+ super.checkWrapped();
+ }
+
+ /**
+ * Not used in this implementation. Instead, the simple implementation is
+ * created lazily in {@link #getWrapped()}.
+ */
+ @Override
+ protected T createSimplePeer() {
+ return null;
+ }
+
+ /**
+ * Allow access by {@link ShimHandler}.
+ */
+ @Override
+ protected <V> V get(String method, V toReturn) {
+ return super.get(method, toReturn);
+ }
+
+ /**
+ * Allow access by BeanMethod.
+ */
+ @Override
+ protected <V> V getOrReify(String propertyName) {
+ return super.<V> getOrReify(propertyName);
+ }
+
+ /**
+ * Allow access by {@link ShimHandler}.
+ */
+ @Override
+ protected T getWrapped() {
+ if (wrapped == null && isUsingSimplePeer()) {
+ wrapped = (T) ProxyAutoBean.makeProxy(beanType, new SimpleBeanHandler<T>(this));
+ }
+ return super.getWrapped();
+ }
+
+ /**
+ * Allow access by {@link ShimHandler}.
+ */
+ @Override
+ protected void set(String method, Object value) {
+ super.set(method, value);
+ }
+
+ @Override
+ protected void setProperty(String propertyName, Object value) {
+ super.setProperty(propertyName, value);
+ }
+
+ // TODO: Port to model-based when class-based TypeOracle is available.
+ @Override
+ protected void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx) {
+ assert data.getters.size() == data.getterNames.size()
+ && data.getters.size() == data.propertyType.size();
+ Iterator<Method> getterIt = data.getters.iterator();
+ Iterator<String> nameIt = data.getterNames.iterator();
+ Iterator<PropertyType> typeIt = data.propertyType.iterator();
+ while (getterIt.hasNext()) {
+ Method getter = getterIt.next();
+ String name = nameIt.next();
+ PropertyType propertyType = typeIt.next();
+
+ // Use the shim to handle automatic wrapping
+ Object value;
+ try {
+ getter.setAccessible(true);
+ value = getter.invoke(shim);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getCause());
+ }
+
+ // Create the context used for the property visitation
+ MethodPropertyContext x =
+ isUsingSimplePeer() ? new BeanPropertyContext(this, getter) : new GetterPropertyContext(
+ this, getter);
+
+ switch (propertyType) {
+ case VALUE: {
+ if (visitor.visitValueProperty(name, value, x)) {
+ }
+ visitor.endVisitValueProperty(name, value, x);
+ break;
+ }
+ case COLLECTION: {
+ // Workaround for generics bug in mac javac 1.6.0_22
+ @SuppressWarnings("rawtypes")
+ AutoBean temp = AutoBeanUtils.getAutoBean((Collection) value);
+ @SuppressWarnings("unchecked")
+ AutoBean<Collection<?>> bean = (AutoBean<Collection<?>>) temp;
+ if (visitor.visitCollectionProperty(name, bean, x)) {
+ if (value != null) {
+ ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+ }
+ }
+ visitor.endVisitCollectionProperty(name, bean, x);
+ break;
+ }
+ case MAP: {
+ // Workaround for generics bug in mac javac 1.6.0_22
+ @SuppressWarnings("rawtypes")
+ AutoBean temp = AutoBeanUtils.getAutoBean((Map) value);
+ @SuppressWarnings("unchecked")
+ AutoBean<Map<?, ?>> bean = (AutoBean<Map<?, ?>>) temp;
+ if (visitor.visitMapProperty(name, bean, x)) {
+ if (value != null) {
+ ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+ }
+ }
+ visitor.endVisitMapProperty(name, bean, x);
+ break;
+ }
+ case REFERENCE: {
+ ProxyAutoBean<?> bean = (ProxyAutoBean<?>) AutoBeanUtils.getAutoBean(value);
+ if (visitor.visitReferenceProperty(name, bean, x)) {
+ if (value != null) {
+ bean.traverse(visitor, ctx);
+ }
+ }
+ visitor.endVisitReferenceProperty(name, bean, x);
+ break;
+ }
+ }
+ }
+ }
+
+ Class<?> getBeanType() {
+ return beanType;
+ }
+
+ private T createShim() {
+ T toReturn = ProxyAutoBean.makeProxy(beanType, new ShimHandler<T>(this, getWrapped()));
+ WeakMapping.set(toReturn, AutoBean.class.getName(), this);
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/ShimHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/ShimHandler.java
new file mode 100644
index 0000000..95dabe8
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/ShimHandler.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Implements an AutoBean's shim interface that intercepts calls to the backing
+ * object.
+ *
+ * @param <T> the interface type of the AutoBean
+ */
+class ShimHandler<T> implements InvocationHandler {
+ private final ProxyAutoBean<T> bean;
+ private final Method interceptor;
+
+ public ShimHandler(ProxyAutoBean<T> bean, T toWrap) {
+ this.bean = bean;
+
+ Method maybe = null;
+ for (Class<?> clazz : bean.getConfiguration().getCategories()) {
+ try {
+ maybe = clazz.getMethod("__intercept", AutoBean.class, Object.class);
+ break;
+ } catch (SecurityException expected) {
+ } catch (NoSuchMethodException expected) {
+ }
+ }
+ interceptor = maybe;
+ }
+
+ @Override
+ public boolean equals(Object couldBeShim) {
+ if (couldBeShim == null) {
+ return false;
+ }
+ // Handles the foo.equals(foo) case
+ if (Proxy.isProxyClass(couldBeShim.getClass())
+ && this == Proxy.getInvocationHandler(couldBeShim)) {
+ return true;
+ }
+ return bean.getWrapped().equals(couldBeShim);
+ }
+
+ @Override
+ public int hashCode() {
+ return bean.getWrapped().hashCode();
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ method.setAccessible(true);
+ Object toReturn;
+ String name = method.getName();
+ method.setAccessible(true);
+ try {
+ if (BeanMethod.OBJECT.matches(method)) {
+ return method.invoke(this, args);
+ } else if (BeanMethod.GET.matches(method)) {
+ toReturn = method.invoke(bean.getWrapped(), args);
+ toReturn = bean.get(name, toReturn);
+ } else if (BeanMethod.SET.matches(method) || BeanMethod.SET_BUILDER.matches(method)) {
+ toReturn = method.invoke(bean.getWrapped(), args);
+ bean.set(name, args[0]);
+ } else {
+ // XXX How should freezing and calls work together?
+ toReturn = method.invoke(bean.getWrapped(), args);
+ bean.call(name, toReturn, args);
+ }
+ Class<?> intf = method.getReturnType();
+ if (!Object.class.equals(intf)) {
+ // XXX Need to deal with resolving generic T return types
+ toReturn = maybeWrap(intf, toReturn);
+ }
+ if (interceptor != null) {
+ toReturn = interceptor.invoke(null, bean, toReturn);
+ }
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ return toReturn;
+ }
+
+ @Override
+ public String toString() {
+ return bean.getWrapped().toString();
+ }
+
+ private Object maybeWrap(Class<?> intf, Object toReturn) {
+ if (toReturn == null) {
+ return null;
+ }
+ AutoBean<?> returnBean = AutoBeanUtils.getAutoBean(toReturn);
+ if (returnBean != null) {
+ return returnBean.as();
+ }
+ if (TypeUtils.isValueType(intf) || TypeUtils.isValueType(toReturn.getClass())
+ || bean.getConfiguration().getNoWrap().contains(intf)) {
+ return toReturn;
+ }
+ if (toReturn.getClass().isArray()) {
+ /*
+ * We can't reliably wrap arrays, but the only time we typically see an
+ * array is with toArray() call on a collection, since arrays aren't
+ * supported property types.
+ */
+ return toReturn;
+ }
+ ProxyAutoBean<Object> newBean =
+ new ProxyAutoBean<Object>(bean.getFactory(), intf, bean.getConfiguration(), toReturn);
+ return newBean.as();
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java b/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java
new file mode 100644
index 0000000..a6624a6
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/SimpleBeanHandler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+/**
+ * Dynamic implementation of an AutoBean's simple peer object.
+ *
+ * @param <T> the type of interface the shim allows access to
+ */
+class SimpleBeanHandler<T> implements InvocationHandler {
+ private final ProxyAutoBean<T> bean;
+
+ public SimpleBeanHandler(ProxyAutoBean<T> bean) {
+ this.bean = bean;
+ }
+
+ public ProxyAutoBean<T> getBean() {
+ return bean;
+ }
+
+ /**
+ * Delegates most work to {@link BeanMethod}.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ for (BeanMethod type : BeanMethod.values()) {
+ if (type.matches(this, method)) {
+ Object toReturn = type.invoke(this, method, args);
+ return toReturn;
+ }
+ }
+ throw new RuntimeException("Unhandled invocation " + method.getName());
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return bean.getSplittable().getPayload();
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java b/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java
new file mode 100644
index 0000000..73b977e
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/TypeUtils.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm.impl;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Shared code for answering question about Class objects. This is a
+ * server-compatible analog to ModelUtils.
+ */
+public class TypeUtils {
+ static final Map<Class<?>, Class<?>> AUTOBOX_MAP;
+ static final Map<Class<?>, Object> DEFAULT_PRIMITIVE_VALUES;
+ @SuppressWarnings("unchecked")
+ static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
+ Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
+ Enum.class, Number.class, String.class, Void.class)));
+
+ static {
+ Map<Class<?>, Object> temp = new HashMap<Class<?>, Object>();
+ temp.put(boolean.class, false);
+ temp.put(byte.class, (byte) 0);
+ temp.put(char.class, (char) 0);
+ temp.put(double.class, (double) 0);
+ temp.put(float.class, (float) 0);
+ temp.put(int.class, 0);
+ temp.put(long.class, (long) 0);
+ temp.put(short.class, (short) 0);
+ temp.put(void.class, null);
+
+ DEFAULT_PRIMITIVE_VALUES = Collections.unmodifiableMap(temp);
+ }
+
+ static {
+ Map<Class<?>, Class<?>> autoBoxMap = new HashMap<Class<?>, Class<?>>();
+ autoBoxMap.put(boolean.class, Boolean.class);
+ autoBoxMap.put(byte.class, Byte.class);
+ autoBoxMap.put(char.class, Character.class);
+ autoBoxMap.put(double.class, Double.class);
+ autoBoxMap.put(float.class, Float.class);
+ autoBoxMap.put(int.class, Integer.class);
+ autoBoxMap.put(long.class, Long.class);
+ autoBoxMap.put(short.class, Short.class);
+ autoBoxMap.put(void.class, Void.class);
+ AUTOBOX_MAP = Collections.unmodifiableMap(autoBoxMap);
+ }
+
+ /**
+ * Similar to ModelUtils#ensureBaseType(JType) but for the reflection API.
+ */
+ public static Class<?> ensureBaseType(Type type) {
+ if (type instanceof Class<?>) {
+ return (Class<?>) type;
+ }
+ if (type instanceof GenericArrayType) {
+ return Array.newInstance(
+ ensureBaseType(((GenericArrayType) type).getGenericComponentType()),
+ 0).getClass();
+ }
+ if (type instanceof ParameterizedType) {
+ return ensureBaseType(((ParameterizedType) type).getRawType());
+ }
+ if (type instanceof TypeVariable<?>) {
+ return ensureBaseType(((TypeVariable<?>) type).getBounds()[0]);
+ }
+ if (type instanceof WildcardType) {
+ WildcardType wild = (WildcardType) type;
+ return ensureBaseType(wild.getUpperBounds()[0]);
+ }
+ throw new RuntimeException("Cannot handle " + type.getClass().getName());
+ }
+
+ /**
+ * Given a primitive Class type, return a default value.
+ */
+ public static Object getDefaultPrimitiveValue(Class<?> clazz) {
+ assert clazz.isPrimitive() : "Expecting primitive type";
+ return DEFAULT_PRIMITIVE_VALUES.get(clazz);
+ }
+
+ public static Type[] getParameterization(Class<?> intf, Type... types) {
+ for (Type type : types) {
+ if (type == null) {
+ continue;
+ } else if (type instanceof ParameterizedType) {
+ ParameterizedType param = (ParameterizedType) type;
+ Type[] actualTypeArguments = param.getActualTypeArguments();
+ Class<?> base = ensureBaseType(param.getRawType());
+ Type[] typeParameters = base.getTypeParameters();
+
+ Map<Type, Type> map = new HashMap<Type, Type>();
+ for (int i = 0, j = typeParameters.length; i < j; i++) {
+ map.put(typeParameters[i], actualTypeArguments[i]);
+ }
+ Type[] lookFor = intf.equals(base) ? intf.getTypeParameters()
+ : getParameterization(intf, base.getGenericInterfaces());
+ List<Type> toReturn = new ArrayList<Type>();
+ for (int i = 0, j = lookFor.length; i < j; i++) {
+ Type found = map.get(lookFor[i]);
+ if (found != null) {
+ toReturn.add(found);
+ }
+ }
+ return toReturn.toArray(new Type[toReturn.size()]);
+ } else if (type instanceof Class<?>) {
+ Class<?> clazz = (Class<?>) type;
+ if (intf.equals(clazz)) {
+ return intf.getTypeParameters();
+ }
+ Type[] found = getParameterization(intf, clazz.getGenericSuperclass());
+ if (found != null) {
+ return found;
+ }
+ found = getParameterization(intf, clazz.getGenericInterfaces());
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ public static Type getSingleParameterization(Class<?> intf, Type... types) {
+ Type[] found = getParameterization(intf, types);
+ return found == null ? null : found[0];
+ }
+
+ public static boolean isValueType(Class<?> clazz) {
+ if (clazz.isPrimitive() || VALUE_TYPES.contains(clazz)) {
+ return true;
+ }
+ for (Class<?> c : VALUE_TYPES) {
+ if (c.isAssignableFrom(clazz)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static <V> Class<V> maybeAutobox(Class<V> domainType) {
+ @SuppressWarnings("unchecked")
+ Class<V> autoBoxType = (Class<V>) AUTOBOX_MAP.get(domainType);
+ return autoBoxType == null ? domainType : autoBoxType;
+ }
+
+ private TypeUtils() {
+ }
+}
diff --git a/user/src/com/google/web/bindery/autobean/vm/package-info.java b/user/src/com/google/web/bindery/autobean/vm/package-info.java
new file mode 100644
index 0000000..ef1a6ee
--- /dev/null
+++ b/user/src/com/google/web/bindery/autobean/vm/package-info.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/**
+ * Contains JVM-compatible implementations of the AutoBean framework.
+ * <p>
+ * <span style='color: red'>This is experimental, unsupported code.</span>
+ *
+ * @see <a
+ * href="http://code.google.com/p/google-web-toolkit/wiki/AutoBean">AutoBean
+ * wiki page</a>
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.autobean.vm;
+
diff --git a/user/src/com/google/web/bindery/event/Event.gwt.xml b/user/src/com/google/web/bindery/event/Event.gwt.xml
new file mode 100644
index 0000000..8bafd18
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/Event.gwt.xml
@@ -0,0 +1,18 @@
+<!--
+ Copyright 2011 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>
+ <source path="shared" />
+</module>
diff --git a/user/src/com/google/web/bindery/event/shared/Event.java b/user/src/com/google/web/bindery/event/shared/Event.java
new file mode 100644
index 0000000..487771f
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/Event.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 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.web.bindery.event.shared;
+
+/**
+ * Base Event object.
+ *
+ * @param <H> interface implemented by handlers of this kind of event
+ */
+public abstract class Event<H> {
+ /**
+ * Type class used to register events with an {@link EventBus}.
+ *
+ * @param <H> handler type
+ */
+ public static class Type<H> {
+ private static int nextHashCode;
+ private final int index;
+
+ /**
+ * Constructor.
+ */
+ public Type() {
+ index = ++nextHashCode;
+ }
+
+ @Override
+ public final int hashCode() {
+ return index;
+ }
+
+ @Override
+ public String toString() {
+ return "Event type";
+ }
+ }
+
+ private Object source;
+
+ /**
+ * Constructor.
+ */
+ protected Event() {
+ }
+
+ /**
+ * Returns the {@link Type} used to register this event, allowing an
+ * {@link EventBus} to find handlers of the appropriate class.
+ *
+ * @return the type
+ */
+ public abstract Type<H> getAssociatedType();
+
+ /**
+ * Returns the source for this event. The type and meaning of the source is
+ * arbitrary, and is most useful as a secondary key for handler registration.
+ * (See {@link EventBus#addHandlerToSource}, which allows a handler to
+ * register for events of a particular type, tied to a particular source.)
+ * <p>
+ * Note that the source is actually set at dispatch time, e.g. via
+ * {@link EventBus#fireEventFromSource(Event, Object)}.
+ *
+ * @return object representing the source of this event
+ */
+ public Object getSource() {
+ return source;
+ }
+
+ /**
+ * This is a method used primarily for debugging. It gives a string
+ * representation of the event details. This does not override the toString
+ * method because the compiler cannot always optimize toString out correctly.
+ * Event types should override as desired.
+ *
+ * @return a string representing the event's specifics.
+ */
+ public String toDebugString() {
+ String name = this.getClass().getName();
+ name = name.substring(name.lastIndexOf(".") + 1);
+ return "event: " + name + ":";
+ }
+
+ /**
+ * The toString() for abstract event is overridden to avoid accidently
+ * including class literals in the the compiled output. Use {@link Event}
+ * #toDebugString to get more information about the event.
+ */
+ @Override
+ public String toString() {
+ return "An event type";
+ }
+
+ /**
+ * Implemented by subclasses to to invoke their handlers in a type safe
+ * manner. Intended to be called by {@link EventBus#fireEvent(Event)} or
+ * {@link EventBus#fireEventFromSource(Event, Object)}.
+ *
+ * @param handler handler
+ */
+ protected abstract void dispatch(H handler);
+
+ /**
+ * Set the source that triggered this event. Intended to be called by the
+ * {@link EventBus} during dispatch.
+ *
+ * @param source the source of this event.
+ * @see EventBus#fireEventFromSource(Event, Object)
+ */
+ protected void setSource(Object source) {
+ this.source = source;
+ }
+}
diff --git a/user/src/com/google/web/bindery/event/shared/EventBus.java b/user/src/com/google/web/bindery/event/shared/EventBus.java
new file mode 100644
index 0000000..91ed3dc
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/EventBus.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2011 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.web.bindery.event.shared;
+
+import com.google.web.bindery.event.shared.Event.Type;
+
+/**
+ * Dispatches {@link Event}s to interested parties. Eases decoupling by allowing
+ * objects to interact without having direct dependencies upon one another, and
+ * without requiring event sources to deal with maintaining handler lists. There
+ * will typically be one EventBus per application, broadcasting events that may
+ * be of general interest.
+ *
+ * @see SimpleEventBus
+ * @see ResettableEventBus
+ * @see com.google.web.bindery.event.shared.testing.CountingEventBus
+ */
+public abstract class EventBus {
+
+ /**
+ * Adds an unfiltered handler to receive events of this type from all sources.
+ * <p>
+ * It is rare to call this method directly. More typically an {@link Event}
+ * subclass will provide a static <code>register</code> method, or a widget
+ * will accept handlers directly.
+ *
+ * @param <H> The type of handler
+ * @param type the event type associated with this handler
+ * @param handler the handler
+ * @return the handler registration, can be stored in order to remove the
+ * handler later
+ */
+ public abstract <H> HandlerRegistration addHandler(Type<H> type, H handler);
+
+ /**
+ * Adds a handler to receive events of this type from the given source.
+ * <p>
+ * It is rare to call this method directly. More typically a {@link Event}
+ * subclass will provide a static <code>register</code> method, or a widget
+ * will accept handlers directly.
+ *
+ * @param <H> The type of handler
+ * @param type the event type associated with this handler
+ * @param source the source associated with this handler
+ * @param handler the handler
+ * @return the handler registration, can be stored in order to remove the
+ * handler later
+ */
+ public abstract <H> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler);
+
+ /**
+ * Fires the event from no source. Only unfiltered handlers will receive it.
+ * <p>
+ * Any exceptions thrown by handlers will be bundled into a
+ * {@link UmbrellaException} and then re-thrown after all handlers have
+ * completed. An exception thrown by a handler will not prevent other handlers
+ * from executing.
+ *
+ * @throws UmbrellaException wrapping exceptions thrown by handlers
+ *
+ * @param event the event to fire
+ */
+ public abstract void fireEvent(Event<?> event);
+
+ /**
+ * Fires the given event to the handlers listening to the event's type.
+ * <p>
+ * Any exceptions thrown by handlers will be bundled into a
+ * {@link UmbrellaException} and then re-thrown after all handlers have
+ * completed. An exception thrown by a handler will not prevent other handlers
+ * from executing.
+ *
+ * @throws UmbrellaException wrapping exceptions thrown by handlers
+ *
+ * @param event the event to fire
+ */
+ public abstract void fireEventFromSource(Event<?> event, Object source);
+}
diff --git a/user/src/com/google/web/bindery/event/shared/HandlerRegistration.java b/user/src/com/google/web/bindery/event/shared/HandlerRegistration.java
new file mode 100644
index 0000000..ce34e39
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/HandlerRegistration.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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.web.bindery.event.shared;
+
+/**
+ * Registration objects returned when an event handler is bound (e.g. via
+ * {@link EventBus#addHandler}), used to deregister.
+ * <p>
+ * A tip: to make a handler deregister itself try something like the following:
+ * <code><pre>new MyHandler() {
+ * HandlerRegistration reg = MyEvent.register(eventBus, this);
+ *
+ * public void onMyThing(MyEvent event) {
+ * {@literal /}* do your thing *{@literal /}
+ * reg.removeHandler();
+ * }
+ * };
+ * </pre></code>
+ */
+public interface HandlerRegistration {
+
+ /**
+ * Deregisters the handler associated with this registration object if the
+ * handler is still attached to the event source. If the handler is no longer
+ * attached to the event source, this is a no-op.
+ */
+ void removeHandler();
+}
diff --git a/user/src/com/google/web/bindery/event/shared/ResettableEventBus.java b/user/src/com/google/web/bindery/event/shared/ResettableEventBus.java
new file mode 100644
index 0000000..a1b56ce
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/ResettableEventBus.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 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.web.bindery.event.shared;
+
+import com.google.web.bindery.event.shared.Event.Type;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * Wraps an EventBus to hold on to any HandlerRegistrations, so that they can
+ * easily all be cleared at once.
+ */
+public class ResettableEventBus extends EventBus {
+ private final EventBus wrapped;
+ private final Set<HandlerRegistration> registrations = new HashSet<HandlerRegistration>();
+
+ public ResettableEventBus(EventBus wrappedBus) {
+ this.wrapped = wrappedBus;
+ }
+
+ @Override
+ public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+ HandlerRegistration rtn = wrapped.addHandler(type, handler);
+ return doRegisterHandler(rtn);
+ }
+
+ @Override
+ public <H> HandlerRegistration addHandlerToSource(Event.Type<H> type, Object source, H handler) {
+ HandlerRegistration rtn = wrapped.addHandlerToSource(type, source, handler);
+ return doRegisterHandler(rtn);
+ }
+
+ @Override
+ public void fireEvent(Event<?> event) {
+ wrapped.fireEvent(event);
+ }
+
+ @Override
+ public void fireEventFromSource(Event<?> event, Object source) {
+ wrapped.fireEventFromSource(event, source);
+ }
+
+ /**
+ * Remove all handlers that have been added through this wrapper.
+ */
+ public void removeHandlers() {
+ Iterator<HandlerRegistration> it = registrations.iterator();
+ while (it.hasNext()) {
+ HandlerRegistration r = it.next();
+
+ /*
+ * must remove before we call removeHandler. Might have come from nested
+ * ResettableEventBus
+ */
+ it.remove();
+
+ r.removeHandler();
+ }
+ }
+
+ /**
+ * Visible for testing
+ */
+ protected int getRegistrationSize() {
+ return registrations.size();
+ }
+
+ private HandlerRegistration doRegisterHandler(final HandlerRegistration registration) {
+ registrations.add(registration);
+ return new HandlerRegistration() {
+ public void removeHandler() {
+ doUnregisterHandler(registration);
+ }
+ };
+ }
+
+ private void doUnregisterHandler(HandlerRegistration registration) {
+ if (registrations.contains(registration)) {
+ registration.removeHandler();
+ registrations.remove(registration);
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/event/shared/SimpleEventBus.java b/user/src/com/google/web/bindery/event/shared/SimpleEventBus.java
new file mode 100644
index 0000000..14a1f3c
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/SimpleEventBus.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2011 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.web.bindery.event.shared;
+
+import com.google.web.bindery.event.shared.Event.Type;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Basic implementation of {@link EventBus}.
+ */
+public class SimpleEventBus extends EventBus {
+ private interface Command {
+ void execute();
+ }
+
+ private final boolean isReverseOrder;
+
+ private int firingDepth = 0;
+
+ /**
+ * Add and remove operations received during dispatch.
+ */
+ private List<Command> deferredDeltas;
+
+ /**
+ * Map of event type to map of event source to list of their handlers.
+ */
+ private final Map<Event.Type<?>, Map<Object, List<?>>> map =
+ new HashMap<Event.Type<?>, Map<Object, List<?>>>();
+
+ public SimpleEventBus() {
+ this(false);
+ }
+
+ /**
+ * Allows creation of an instance that fires its handlers in the reverse of
+ * the order in which they were added, although filtered handlers all fire
+ * before unfiltered handlers.
+ * <p>
+ *
+ * @deprecated This is a legacy feature, required by GWT's old HandlerManager.
+ * Reverse order is not honored for handlers tied to a specific
+ * event source (via {@link #addHandlerToSource}.
+ */
+ @Deprecated
+ protected SimpleEventBus(boolean fireInReverseOrder) {
+ isReverseOrder = fireInReverseOrder;
+ }
+
+ @Override
+ public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+ return doAdd(type, null, handler);
+ }
+
+ @Override
+ public <H> HandlerRegistration addHandlerToSource(final Event.Type<H> type, final Object source,
+ final H handler) {
+ if (source == null) {
+ throw new NullPointerException("Cannot add a handler with a null source");
+ }
+
+ return doAdd(type, source, handler);
+ }
+
+ @Override
+ public void fireEvent(Event<?> event) {
+ doFire(event, null);
+ }
+
+ @Override
+ public void fireEventFromSource(Event<?> event, Object source) {
+ if (source == null) {
+ throw new NullPointerException("Cannot fire from a null source");
+ }
+ doFire(event, source);
+ }
+
+ /**
+ * @deprecated required by legacy features in GWT's old HandlerManager
+ */
+ @Deprecated
+ protected <H> void doRemove(Event.Type<H> type, Object source, H handler) {
+ if (firingDepth > 0) {
+ enqueueRemove(type, source, handler);
+ } else {
+ doRemoveNow(type, source, handler);
+ }
+ }
+
+ /**
+ * @deprecated required by legacy features in GWT's old HandlerManager
+ */
+ @Deprecated
+ protected <H> H getHandler(Event.Type<H> type, int index) {
+ assert index < getHandlerCount(type) : "handlers for " + type.getClass() + " have size: "
+ + getHandlerCount(type) + " so do not have a handler at index: " + index;
+
+ List<H> l = getHandlerList(type, null);
+ return l.get(index);
+ }
+
+ /**
+ * @deprecated required by legacy features in GWT's old HandlerManager
+ */
+ @Deprecated
+ protected int getHandlerCount(Event.Type<?> eventKey) {
+ return getHandlerList(eventKey, null).size();
+ }
+
+ /**
+ * @deprecated required by legacy features in GWT's old HandlerManager
+ */
+ @Deprecated
+ protected boolean isEventHandled(Event.Type<?> eventKey) {
+ return map.containsKey(eventKey);
+ }
+
+ private void defer(Command command) {
+ if (deferredDeltas == null) {
+ deferredDeltas = new ArrayList<Command>();
+ }
+ deferredDeltas.add(command);
+ }
+
+ private <H> HandlerRegistration doAdd(final Event.Type<H> type, final Object source,
+ final H handler) {
+ if (type == null) {
+ throw new NullPointerException("Cannot add a handler with a null type");
+ }
+ if (handler == null) {
+ throw new NullPointerException("Cannot add a null handler");
+ }
+
+ if (firingDepth > 0) {
+ enqueueAdd(type, source, handler);
+ } else {
+ doAddNow(type, source, handler);
+ }
+
+ return new HandlerRegistration() {
+ public void removeHandler() {
+ doRemove(type, source, handler);
+ }
+ };
+ }
+
+ private <H> void doAddNow(Event.Type<H> type, Object source, H handler) {
+ List<H> l = ensureHandlerList(type, source);
+ l.add(handler);
+ }
+
+ private <H> void doFire(Event<H> event, Object source) {
+ if (event == null) {
+ throw new NullPointerException("Cannot fire null event");
+ }
+ try {
+ firingDepth++;
+
+ if (source != null) {
+ event.setSource(source);
+ }
+
+ List<H> handlers = getDispatchList(event.getAssociatedType(), source);
+ Set<Throwable> causes = null;
+
+ ListIterator<H> it =
+ isReverseOrder ? handlers.listIterator(handlers.size()) : handlers.listIterator();
+ while (isReverseOrder ? it.hasPrevious() : it.hasNext()) {
+ H handler = isReverseOrder ? it.previous() : it.next();
+
+ try {
+ event.dispatch(handler);
+ } catch (Throwable e) {
+ if (causes == null) {
+ causes = new HashSet<Throwable>();
+ }
+ causes.add(e);
+ }
+ }
+
+ if (causes != null) {
+ throw new UmbrellaException(causes);
+ }
+ } finally {
+ firingDepth--;
+ if (firingDepth == 0) {
+ handleQueuedAddsAndRemoves();
+ }
+ }
+ }
+
+ private <H> void doRemoveNow(Event.Type<H> type, Object source, H handler) {
+ List<H> l = getHandlerList(type, source);
+
+ boolean removed = l.remove(handler);
+ assert removed : "redundant remove call";
+ if (removed && l.isEmpty()) {
+ prune(type, source);
+ }
+ }
+
+ private <H> void enqueueAdd(final Event.Type<H> type, final Object source, final H handler) {
+ defer(new Command() {
+ public void execute() {
+ doAddNow(type, source, handler);
+ }
+ });
+ }
+
+ private <H> void enqueueRemove(final Event.Type<H> type, final Object source, final H handler) {
+ defer(new Command() {
+ public void execute() {
+ doRemoveNow(type, source, handler);
+ }
+ });
+ }
+
+ private <H> List<H> ensureHandlerList(Event.Type<H> type, Object source) {
+ Map<Object, List<?>> sourceMap = map.get(type);
+ if (sourceMap == null) {
+ sourceMap = new HashMap<Object, List<?>>();
+ map.put(type, sourceMap);
+ }
+
+ // safe, we control the puts.
+ @SuppressWarnings("unchecked")
+ List<H> handlers = (List<H>) sourceMap.get(source);
+ if (handlers == null) {
+ handlers = new ArrayList<H>();
+ sourceMap.put(source, handlers);
+ }
+
+ return handlers;
+ }
+
+ private <H> List<H> getDispatchList(Event.Type<H> type, Object source) {
+ List<H> directHandlers = getHandlerList(type, source);
+ if (source == null) {
+ return directHandlers;
+ }
+
+ List<H> globalHandlers = getHandlerList(type, null);
+
+ List<H> rtn = new ArrayList<H>(directHandlers);
+ rtn.addAll(globalHandlers);
+ return rtn;
+ }
+
+ private <H> List<H> getHandlerList(Event.Type<H> type, Object source) {
+ Map<Object, List<?>> sourceMap = map.get(type);
+ if (sourceMap == null) {
+ return Collections.emptyList();
+ }
+
+ // safe, we control the puts.
+ @SuppressWarnings("unchecked")
+ List<H> handlers = (List<H>) sourceMap.get(source);
+ if (handlers == null) {
+ return Collections.emptyList();
+ }
+
+ return handlers;
+ }
+
+ private void handleQueuedAddsAndRemoves() {
+ if (deferredDeltas != null) {
+ try {
+ for (Command c : deferredDeltas) {
+ c.execute();
+ }
+ } finally {
+ deferredDeltas = null;
+ }
+ }
+ }
+
+ private void prune(Event.Type<?> type, Object source) {
+ Map<Object, List<?>> sourceMap = map.get(type);
+
+ List<?> pruned = sourceMap.remove(source);
+
+ assert pruned != null : "Can't prune what wasn't there";
+ assert pruned.isEmpty() : "Pruned unempty list!";
+
+ if (sourceMap.isEmpty()) {
+ map.remove(type);
+ }
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/event/shared/UmbrellaException.java b/user/src/com/google/web/bindery/event/shared/UmbrellaException.java
new file mode 100644
index 0000000..3ed254b
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/UmbrellaException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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.web.bindery.event.shared;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * A {@link RuntimeException} that collects a {@link Set} of child
+ * {@link Throwable}s together. Typically thrown after a loop, with all of the
+ * exceptions thrown during that loop, but delayed so that the loop finishes
+ * executing.
+ */
+public class UmbrellaException extends RuntimeException {
+
+ private static final String MSG =
+ "One or more exceptions caught, see full set in UmbrellaException#getCauses";
+ /**
+ * The causes of the exception.
+ */
+ private Set<Throwable> causes;
+
+ public UmbrellaException(Set<Throwable> causes) {
+ super(MSG, causes.size() == 0 ? null : causes.toArray(new Throwable[0])[0]);
+ this.causes = causes;
+ }
+
+ /**
+ * Required for GWT RPC serialization.
+ */
+ protected UmbrellaException() {
+ // Can't delegate to the other constructor or GWT RPC gets cranky
+ super(MSG);
+ this.causes = Collections.<Throwable> emptySet();
+ }
+
+ /**
+ * Get the set of exceptions that caused the failure.
+ *
+ * @return the set of causes
+ */
+ public Set<Throwable> getCauses() {
+ return causes;
+ }
+}
diff --git a/user/src/com/google/web/bindery/event/shared/testing/CountingEventBus.java b/user/src/com/google/web/bindery/event/shared/testing/CountingEventBus.java
new file mode 100644
index 0000000..4b657a4
--- /dev/null
+++ b/user/src/com/google/web/bindery/event/shared/testing/CountingEventBus.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 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.web.bindery.event.shared.testing;
+
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.Event.Type;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wraps an {@link EventBus} to keep a count of registered handlers. Handy for
+ * tests.
+ */
+public class CountingEventBus extends EventBus {
+ private final Map<Type<?>, Integer> counts = new HashMap<Event.Type<?>, Integer>();
+ private final EventBus wrapped;
+
+ public CountingEventBus() {
+ this(new SimpleEventBus());
+ }
+
+ public CountingEventBus(EventBus wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public <H> HandlerRegistration addHandler(Type<H> type, H handler) {
+ increment(type);
+ final HandlerRegistration superReg = wrapped.addHandler(type, handler);
+ return makeReg(type, superReg);
+ }
+
+ @Override
+ public <H> HandlerRegistration addHandlerToSource(final Type<H> type, Object source, H handler) {
+ increment(type);
+ final HandlerRegistration superReg = wrapped.addHandlerToSource(type, source, handler);
+ return makeReg(type, superReg);
+ }
+
+ @Override
+ public void fireEvent(Event<?> event) {
+ wrapped.fireEvent(event);
+ }
+
+ @Override
+ public void fireEventFromSource(Event<?> event, Object source) {
+ wrapped.fireEventFromSource(event, source);
+ }
+
+ public int getCount(Type<?> type) {
+ Integer count = counts.get(type);
+ return count == null ? 0 : count;
+ }
+
+ private void decrement(Type<?> type) {
+ counts.put(type, getCount(type) - 1);
+ }
+
+ private <H> void increment(final Type<H> type) {
+ counts.put(type, getCount(type) + 1);
+ }
+
+ private <H> HandlerRegistration makeReg(final Type<H> type, final HandlerRegistration superReg) {
+ return new HandlerRegistration() {
+ public void removeHandler() {
+ decrement(type);
+ superReg.removeHandler();
+ }
+ };
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml b/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml
new file mode 100644
index 0000000..f55cc8d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/RequestFactory.gwt.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.1/distro-source/core/src/gwt-module.dtd">
+<!--
+ Copyright 2010 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>
+ <inherits name='com.google.gwt.core.Core'/>
+ <inherits name='com.google.web.bindery.autobean.AutoBean'/>
+ <inherits name='com.google.gwt.editor.Editor'/>
+ <inherits name='com.google.gwt.http.HTTP'/>
+ <inherits name='com.google.gwt.logging.LoggingDisabled'/>
+
+ <source path="gwt/client"/>
+ <source path="gwt/ui/client"/>
+ <source path="shared"/>
+ <super-source path="super" />
+ <generate-with
+ class="com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryEditorDriverGenerator">
+ <when-type-assignable
+ class="com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver" />
+ </generate-with>
+ <generate-with class="com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator">
+ <when-type-assignable class='com.google.web.bindery.requestfactory.shared.RequestFactory'/>
+ </generate-with>
+</module>
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/DefaultRequestTransport.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/DefaultRequestTransport.java
new file mode 100644
index 0000000..12203a2
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/DefaultRequestTransport.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import static com.google.gwt.user.client.rpc.RpcRequestBuilder.STRONG_NAME_HEADER;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.RequestException;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.user.client.Window.Location;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.RequestTransport;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An implementation of {@link RequestTransport} that uses a
+ * {@link RequestBuilder}.
+ */
+public class DefaultRequestTransport implements RequestTransport {
+ private static final String SERVER_ERROR = "Server Error";
+
+ /**
+ * The default URL for a DefaultRequestTransport is
+ * <code>{@link GWT#getHostPageBaseURL()} + {@value #URL}</code> which may be
+ * overridden by calling {@link #setRequestUrl(String)}.
+ */
+ public static final String URL = "gwtRequest";
+
+ /*
+ * A separate logger for wire activity, which does not get logged by the
+ * remote log handler, so we avoid infinite loops. All log messages that could
+ * happen every time a request is made from the server should be logged to
+ * this logger.
+ */
+ private static final Logger wireLogger = Logger.getLogger("WireActivityLogger");
+
+ private String requestUrl = GWT.getHostPageBaseURL() + URL;
+
+ /**
+ * Returns the current URL used by this transport.
+ *
+ * @return the URL as a String
+ * @see #setRequestUrl(String)
+ */
+ public String getRequestUrl() {
+ return requestUrl;
+ }
+
+ public void send(String payload, TransportReceiver receiver) {
+ RequestBuilder builder = createRequestBuilder();
+ configureRequestBuilder(builder);
+
+ builder.setRequestData(payload);
+ builder.setCallback(createRequestCallback(receiver));
+
+ try {
+ wireLogger.finest("Sending fire request");
+ builder.send();
+ } catch (RequestException e) {
+ wireLogger.log(Level.SEVERE, SERVER_ERROR + " (" + e.getMessage() + ")",
+ e);
+ }
+ }
+
+ /**
+ * Override the default URL used by this transport.
+ *
+ * @param url a String URL
+ * @see #getRequestUrl()
+ */
+ public void setRequestUrl(String url) {
+ this.requestUrl = url;
+ }
+
+ /**
+ * Override to change the headers sent in the HTTP request.
+ *
+ * @param builder a {@link RequestBuilder} instance
+ */
+ protected void configureRequestBuilder(RequestBuilder builder) {
+ builder.setHeader("Content-Type", RequestFactory.JSON_CONTENT_TYPE_UTF8);
+ builder.setHeader("pageurl", Location.getHref());
+ builder.setHeader(STRONG_NAME_HEADER, GWT.getPermutationStrongName());
+ }
+
+ /**
+ * Constructs a {@link RequestBuilder} using the {@link RequestBuilder#POST}
+ * method sent to the URL returned from {@link #getRequestUrl()}.
+ *
+ * @return a {@link RequestBuilder} instance
+ */
+ protected RequestBuilder createRequestBuilder() {
+ return new RequestBuilder(RequestBuilder.POST, getRequestUrl());
+ }
+
+ /**
+ * Creates a RequestCallback that maps the HTTP response onto the
+ * {@link com.google.web.bindery.requestfactory.shared.RequestTransport.TransportReceiver
+ * TransportReceiver} interface.
+ *
+ * @param receiver a {@link com.google.web.bindery.requestfactory.shared.RequestTransport.TransportReceiver TransportReceiver}
+ * @return a {@link RequestCallback} instance
+ */
+ protected RequestCallback createRequestCallback(
+ final TransportReceiver receiver) {
+ return new RequestCallback() {
+
+ public void onError(Request request, Throwable exception) {
+ wireLogger.log(Level.SEVERE, SERVER_ERROR, exception);
+ receiver.onTransportFailure(new ServerFailure(exception.getMessage()));
+ }
+
+ public void onResponseReceived(Request request, Response response) {
+ wireLogger.finest("Response received");
+ if (Response.SC_OK == response.getStatusCode()) {
+ String text = response.getText();
+ receiver.onTransportSuccess(text);
+ } else {
+ String message = SERVER_ERROR + " " + response.getStatusCode() + " "
+ + response.getText();
+ wireLogger.severe(message);
+ receiver.onTransportFailure(new ServerFailure(message));
+ }
+ }
+ };
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/HasRequestContext.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/HasRequestContext.java
new file mode 100644
index 0000000..6ce2413
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/HasRequestContext.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+
+/**
+ * Editors used with {@link RequestFactoryEditorDriver} that implement this
+ * interface will be provided with the {@link RequestContext} associated with
+ * the current editing session.
+ *
+ * @param <T> the type of data being edited
+ */
+public interface HasRequestContext<T> extends Editor<T> {
+ /**
+ * Called by {@link RequestFactoryEditorDriver} with the
+ * {@link RequestContext} passed into
+ * {@link RequestFactoryEditorDriver#edit(Object, RequestContext) edit()} or
+ * {@code null} if {@link RequestFactoryEditorDriver#display(Object)
+ * display()} is called.
+ *
+ * @param ctx the RequestContext associated with the current editing session
+ * which may be {@code null}
+ */
+ void setRequestContext(RequestContext ctx);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryEditorDriver.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryEditorDriver.java
new file mode 100644
index 0000000..e5535e2
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryEditorDriver.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDriver;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+/**
+ * The interface that links RequestFactory and the Editor framework together.
+ * <p>
+ * Instances of this interface are created with
+ *
+ * <pre>
+ * interface MyRFED extends RequestFactoryEditorDriver<MyObjectProxy, MyObjectEditor> {}
+ * MyRFED instance = GWT.create(MyRFED.class);
+ * {
+ * instance.initialize(.....);
+ * myRequest.with(instance.getPaths());
+ *
+ * // Fire the request, in the callback
+ * instance.edit(retrievedRecord);
+ * // Control when the request is sent
+ * instance.flush().fire(new Receiver {...});
+ * }
+ * </pre>
+ *
+ * @param <P> the type of Proxy being edited
+ * @param <E> the type of Editor that will edit the Record
+ * @see HasRequestContext
+ * @see com.google.web.bindery.requestfactory.gwt.client.testing.MockRequestFactoryEditorDriver
+ * MockRequestFactoryEditorDriver
+ */
+public interface RequestFactoryEditorDriver<P, E extends Editor<? super P>>
+ extends EditorDriver<RequestContext> {
+ /**
+ * Start driving the Editor and its sub-editors with data for display-only
+ * mode.
+ *
+ * @param proxy a Proxy of type P
+ */
+ void display(P proxy);
+
+ /**
+ * Start driving the Editor and its sub-editors with data. A
+ * {@link RequestContext} is required to provide context for the changes to
+ * the proxy (see {@link RequestContext#edit}. Note that this driver will not
+ * fire the request.
+ *
+ * @param proxy the proxy to be edited
+ * @param request the request context that will accumulate edits and is
+ * returned form {@link #flush}
+ */
+ void edit(P proxy, RequestContext request);
+
+ /**
+ * Update the object being edited with the current state of the Editor.
+ *
+ * @return the RequestContext passed into
+ * {@link #edit(Object, RequestContext)}
+ * @throws IllegalStateException if {@link #edit(Object, RequestContext)} has
+ * not been called
+ */
+ RequestContext flush();
+
+ /**
+ * Returns a new array containing the request paths.
+ *
+ * @return an array of Strings
+ */
+ String[] getPaths();
+
+ /**
+ * Initializes a driver that will not be able to support subscriptions. Calls
+ * to {@link com.google.gwt.editor.client.EditorDelegate#subscribe()} will do
+ * nothing.
+ *
+ * @param editor an {@link Editor} of type E
+ */
+ void initialize(E editor);
+
+ /**
+ * Overload of {@link #initialize(RequestFactory, Editor)} to allow a modified
+ * {@link EventBus} to be monitored for subscription services.
+ *
+ * @param eventBus the {@link EventBus}
+ * @param requestFactory a {@link RequestFactory} instance
+ * @param editor an {@link Editor} of type E
+ *
+ * @see com.google.gwt.editor.client.EditorDelegate#subscribe
+ * @see com.google.gwt.event.shared.ResettableEventBus
+ */
+ void initialize(EventBus eventBus, RequestFactory requestFactory, E editor);
+
+ /**
+ * Initializes a driver with the editor it will run, and a RequestFactory to
+ * use for subscription services.
+ *
+ * @param requestFactory a {@link RequestFactory} instance
+ * @param editor an {@link Editor} of type E
+ *
+ * @see com.google.gwt.editor.client.EditorDelegate#subscribe
+ */
+ void initialize(RequestFactory requestFactory, E editor);
+
+ /**
+ * Show Violations returned from an attempt to submit a request. The
+ * violations will be converted into
+ * {@link com.google.gwt.editor.client.EditorError EditorError} objects whose
+ * {@link com.google.gwt.editor.client.EditorError#getUserData()
+ * getUserData()} method can be used to access the original Violation object.
+ *
+ * @param violations an Iterable over {@link Violation} instances
+ * @return <code>true</code> if there were any unconsumed EditorErrors which
+ * can be retrieved from {@link #getErrors()}
+ */
+ boolean setViolations(Iterable<Violation> violations);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryLogHandler.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryLogHandler.java
new file mode 100644
index 0000000..47d67a8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryLogHandler.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.gwt.logging.client.JsonLogRecordClientUtil;
+import com.google.gwt.logging.client.RemoteLogHandlerBase;
+import com.google.web.bindery.requestfactory.shared.LoggingRequest;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ * A Handler that does remote logging for applications using RequestFactory.
+ */
+public class RequestFactoryLogHandler extends RemoteLogHandlerBase {
+
+ /**
+ * Provides a logging request.
+ */
+ public static interface LoggingRequestProvider {
+ /**
+ * Returns the logging request.
+ *
+ * @return a {@link LoggingRequest} instance
+ */
+ LoggingRequest getLoggingRequest();
+ }
+
+ private LoggingRequestProvider requestProvider;
+
+ /**
+ * Since records from this handler go accross the wire, it should only be
+ * used for important messages, and it's Level will often be higher than the
+ * Level being used app-wide. This handler also takes string which it will
+ * use to exclude the messages from some loggers. This usually includes the
+ * name of the logger(s) which will be used to log acknowledgements of
+ * activity going accross the wire. If we did not exclude these loggers, an
+ * infinite loop would occur.
+ *
+ * @param requestProvider a {@link LoggingRequestProvider} instance
+ * @param level a logging {@link Level}
+ * @param ignoredLoggerNames a List of Strings
+ */
+ public RequestFactoryLogHandler(LoggingRequestProvider requestProvider,
+ Level level, List<String> ignoredLoggerNames) {
+ super(ignoredLoggerNames);
+ this.requestProvider = requestProvider;
+ setLevel(level);
+ }
+
+ @Override
+ public void publish(LogRecord record) {
+ if (!isLoggable(record)) {
+ return;
+ }
+ String json = JsonLogRecordClientUtil.logRecordAsJson(record);
+ requestProvider.getLoggingRequest().logMessage(json).fire(
+ new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ // Do nothing
+ }
+ });
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractClientRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractClientRequestFactory.java
new file mode 100644
index 0000000..b122674
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractClientRequestFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client.impl;
+
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.gwt.client.DefaultRequestTransport;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestFactory;
+
+/**
+ * A RequestFactory that uses a {@link DefaultRequestTransport} by default.
+ */
+public abstract class AbstractClientRequestFactory extends
+ AbstractRequestFactory {
+ @Override
+ public void initialize(EventBus eventBus) {
+ initialize(eventBus, new DefaultRequestTransport());
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java
new file mode 100644
index 0000000..94f71ba
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
+import com.google.gwt.editor.client.impl.BaseEditorDriver;
+import com.google.gwt.editor.client.impl.DelegateMap;
+import com.google.gwt.editor.client.impl.DelegateMap.KeyMethod;
+import com.google.gwt.editor.client.impl.SimpleViolation;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.gwt.client.HasRequestContext;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.Violation;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Contains utility methods for top-level driver implementations.
+ *
+ * @param <R> the type being edited
+ * @param <E> the type of Editor
+ */
+public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>>
+ extends BaseEditorDriver<R, E> implements RequestFactoryEditorDriver<R, E> {
+
+ /**
+ * Adapts a RequestFactory Violation object to the SimpleViolation interface.
+ */
+ static class SimpleViolationAdapter extends SimpleViolation {
+ private final Violation v;
+
+ /**
+ * @param v
+ */
+ private SimpleViolationAdapter(Violation v) {
+ this.v = v;
+ }
+
+ @Override
+ public Object getKey() {
+ return v.getOriginalProxy();
+ }
+
+ @Override
+ public String getMessage() {
+ return v.getMessage();
+ }
+
+ @Override
+ public String getPath() {
+ return v.getPath();
+ }
+
+ @Override
+ public Object getUserDataObject() {
+ return v;
+ }
+ }
+ /**
+ * Provides a source of SimpleViolation objects based on RequestFactory's
+ * simplified Violation interface.
+ */
+ static class ViolationIterable implements Iterable<SimpleViolation> {
+
+ private final Iterable<Violation> violations;
+
+ public ViolationIterable(Iterable<Violation> violations) {
+ this.violations = violations;
+ }
+
+ public Iterator<SimpleViolation> iterator() {
+ final Iterator<Violation> source = violations.iterator();
+ return new Iterator<SimpleViolation>() {
+ public boolean hasNext() {
+ return source.hasNext();
+ }
+
+ public SimpleViolation next() {
+ return new SimpleViolationAdapter(source.next());
+ }
+
+ public void remove() {
+ source.remove();
+ }
+ };
+ }
+ }
+
+ /**
+ * Since the ValueProxy is being mutated in-place, we need a way to stabilize
+ * its hashcode for future equality checks.
+ */
+ private static class ValueProxyHolder {
+ private final ValueProxy proxy;
+
+ public ValueProxyHolder(ValueProxy proxy) {
+ this.proxy = proxy;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return proxy.equals(((ValueProxyHolder) o).proxy);
+ }
+
+ @Override
+ public int hashCode() {
+ return proxy.getClass().hashCode();
+ }
+ }
+
+ private static final DelegateMap.KeyMethod PROXY_ID_KEY = new DelegateMap.KeyMethod() {
+ public Object key(Object object) {
+ if (object instanceof EntityProxy) {
+ return ((EntityProxy) object).stableId();
+ } else if (object instanceof ValueProxy) {
+ AutoBean<?> bean = AutoBeanUtils.getAutoBean(object);
+ // Possibly replace an editable ValueProxy with its immutable base
+ AutoBean<?> parent = bean.getTag(Constants.PARENT_OBJECT);
+ if (parent != null) {
+ object = parent.as();
+ }
+ return new ValueProxyHolder((ValueProxy) object);
+ }
+ return null;
+ }
+ };
+
+ private EventBus eventBus;
+ private List<String> paths;
+ private RequestFactory factory;
+ private RequestContext saveRequest;
+
+ public void display(R object) {
+ edit(object, null);
+ }
+
+ public void edit(R object, final RequestContext saveRequest) {
+ this.saveRequest = saveRequest;
+ // Provide the delegate and maybe the editor with the RequestContext
+ accept(new EditorVisitor() {
+ @Override
+ public <T> void endVisit(EditorContext<T> ctx) {
+ RequestFactoryEditorDelegate<?, ?> delegate = (RequestFactoryEditorDelegate<?, ?>) ctx.getEditorDelegate();
+ if (delegate != null) {
+ delegate.setRequestContext(saveRequest);
+ }
+ Editor<T> editor = ctx.getEditor();
+ if (editor instanceof HasRequestContext<?>) {
+ ((HasRequestContext<T>) editor).setRequestContext(saveRequest);
+ }
+ }
+ });
+ doEdit(object);
+ }
+
+ public RequestContext flush() {
+ checkSaveRequest();
+ doFlush();
+ return saveRequest;
+ }
+
+ public String[] getPaths() {
+ return paths.toArray(new String[paths.size()]);
+ }
+
+ public void initialize(E editor) {
+ doInitialize(null, null, editor);
+ }
+
+ public void initialize(EventBus eventBus, RequestFactory requestFactory,
+ E editor) {
+ assert eventBus != null : "eventBus must not be null";
+ assert requestFactory != null : "requestFactory must not be null";
+ doInitialize(eventBus, requestFactory, editor);
+ }
+
+ public void initialize(RequestFactory requestFactory, E editor) {
+ initialize(requestFactory.getEventBus(), requestFactory, editor);
+ }
+
+ public boolean setViolations(Iterable<Violation> violations) {
+ return doSetViolations(new ViolationIterable(violations));
+ }
+
+ protected void checkSaveRequest() {
+ if (saveRequest == null) {
+ throw new IllegalStateException("edit() was called with a null Request");
+ }
+ }
+
+ @Override
+ protected void configureDelegate(AbstractEditorDelegate<R, E> rootDelegate) {
+ ((RequestFactoryEditorDelegate<R, E>) rootDelegate).initialize(eventBus,
+ factory, "", getEditor());
+ }
+
+ protected void doInitialize(EventBus eventBus, RequestFactory requestFactory,
+ E editor) {
+ this.eventBus = eventBus;
+ this.factory = requestFactory;
+ super.doInitialize(editor);
+ PathCollector c = new PathCollector();
+ accept(c);
+ this.paths = c.getPaths();
+ }
+
+ @Override
+ protected KeyMethod getViolationKeyMethod() {
+ return PROXY_ID_KEY;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java
new file mode 100644
index 0000000..ab4f8aa
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/PathCollector.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.gwt.client.impl;
+
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.gwt.editor.client.EditorContext;
+import com.google.gwt.editor.client.EditorVisitor;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Collects all non-value-type paths in an editor hierarchy for use with
+ * {@link com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver#getPaths()}.
+ */
+class PathCollector extends EditorVisitor {
+ /**
+ * Use a set in the case of aliased editors, so we don't repeat path entries.
+ */
+ private final Set<String> paths = new LinkedHashSet<String>();
+
+ public List<String> getPaths() {
+ return new ArrayList<String>(paths);
+ }
+
+ @Override
+ public <T> boolean visit(EditorContext<T> ctx) {
+ String path = ctx.getAbsolutePath();
+ if (path.length() > 0) {
+ if (ValueCodex.canDecode(ctx.getEditedType())) {
+ /*
+ * If there's an @Path("foo.bar.valueField") annotation, we want to
+ * collect the containing "foo.bar" path.
+ */
+ int dotPosition = path.lastIndexOf('.');
+ if (dotPosition > 0) {
+ String parentPath = path.substring(0, dotPosition);
+ paths.add(parentPath);
+ }
+ } else {
+ // Always collect @Path("foo.bar.baz") field, when baz isn't a value
+ paths.add(path);
+ }
+ }
+ if (ctx.asCompositeEditor() != null) {
+ ctx.traverseSyntheticCompositeEditor(this);
+ }
+ return true;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/RequestFactoryEditorDelegate.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/RequestFactoryEditorDelegate.java
new file mode 100644
index 0000000..0cf247a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/impl/RequestFactoryEditorDelegate.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client.impl;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
+import com.google.gwt.editor.client.impl.Refresher;
+// This import is not an accident, details in subscribe() implementation
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
+
+import java.util.List;
+
+/**
+ * Base class for generated EditorDelegates using a RequestFactory as the
+ * backend.
+ *
+ * @param <P> the type of Proxy
+ * @param <E> the type of Editor
+ */
+public abstract class RequestFactoryEditorDelegate<P, E extends Editor<P>>
+ extends AbstractEditorDelegate<P, E> {
+ private class SubscriptionHandler implements
+ EntityProxyChange.Handler<EntityProxy> {
+
+ public void onProxyChange(EntityProxyChange<EntityProxy> event) {
+ if (event.getWriteOperation().equals(WriteOperation.UPDATE)
+ && event.getProxyId().equals(((EntityProxy) getObject()).stableId())) {
+ PathCollector collector = new PathCollector();
+ accept(collector);
+ EntityProxyId<?> id = event.getProxyId();
+ doFind(collector.getPaths(), id);
+ }
+ }
+
+ @SuppressWarnings(value = {"rawtypes", "unchecked"})
+ private void doFind(List<String> paths, EntityProxyId id) {
+ factory.find(id).with(paths.toArray(new String[paths.size()])).fire(
+ new SubscriptionReceiver());
+ }
+ }
+
+ private class SubscriptionReceiver extends Receiver<EntityProxy> {
+ @Override
+ public void onSuccess(EntityProxy response) {
+ @SuppressWarnings("unchecked")
+ P cast = (P) response;
+ setObject(cast);
+ accept(new Refresher());
+ }
+ }
+
+ protected EventBus eventBus;
+ protected RequestFactory factory;
+ protected RequestContext request;
+
+ public void setRequestContext(RequestContext request) {
+ this.request = request;
+ }
+
+ @Override
+ public HandlerRegistration subscribe() {
+ if (factory == null) {
+ /*
+ * They called the no-subscriptions version of
+ * RequestFactoryEditorDriver#initialize
+ */
+ return null;
+ }
+
+ if (!(getObject() instanceof EntityProxy)) {
+ /*
+ * This is kind of weird. The user is asking for notifications on a
+ * String, which means there's a HasEditorDelegate<String> in play and not
+ * the usual LeafValueEditor<String>.
+ */
+ return null;
+ }
+
+ // Can't just use getObject().getClass() because it's not the proxy type
+ EntityProxyId<?> stableId = ((EntityProxy) getObject()).stableId();
+ @SuppressWarnings("unchecked")
+ Class<EntityProxy> clazz = (Class<EntityProxy>) stableId.getProxyClass();
+
+ /*
+ * Convert to the old gwt HandlerRegistration type required by the
+ * EditorDelegate interface. This can get cleaned up when Editor moves to
+ * com.google.web.bindery.
+ */
+ final com.google.web.bindery.event.shared.HandlerRegistration toReturn =
+ EntityProxyChange.<EntityProxy> registerForProxyType(eventBus, clazz,
+ new SubscriptionHandler());
+ return new HandlerRegistration() {
+ public void removeHandler() {
+ toReturn.removeHandler();
+ }
+ };
+ }
+
+ @Override
+ protected <R, S extends Editor<R>> void addSubDelegate(
+ AbstractEditorDelegate<R, S> subDelegate, String path, S subEditor) {
+ RequestFactoryEditorDelegate<R, S> d = (RequestFactoryEditorDelegate<R, S>) subDelegate;
+ d.initialize(eventBus, factory, path, subEditor);
+ }
+
+ @Override
+ protected <T> T ensureMutable(T object) {
+ if (request == null) {
+ // Read-only mode
+ return object;
+ }
+ if (object instanceof BaseProxy) {
+ @SuppressWarnings("unchecked")
+ T toReturn = (T) request.edit((BaseProxy) object);
+ return toReturn;
+ }
+ return object;
+ }
+
+ protected void initialize(EventBus eventBus, RequestFactory factory,
+ String pathSoFar, E editor) {
+ this.eventBus = eventBus;
+ this.factory = factory;
+ super.initialize(pathSoFar, editor);
+ }
+
+ /**
+ * Must call four-arg version instead.
+ */
+ @Override
+ protected void initialize(String pathSoFar, E editor) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected boolean shouldFlush() {
+ if (request == null) {
+ return false;
+ }
+ if (request instanceof AbstractRequestContext) {
+ return !((AbstractRequestContext) request).isLocked();
+ }
+ return true;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/package-info.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/package-info.java
new file mode 100644
index 0000000..6413a73
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/**
+ * A package for managing client-server requests.
+ *
+ * @since GWT 2.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.gwt.client;
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/MockRequestFactoryEditorDriver.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/MockRequestFactoryEditorDriver.java
new file mode 100644
index 0000000..5cc2223
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/MockRequestFactoryEditorDriver.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client.testing;
+
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.EditorVisitor;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * A no-op implementation of {@link RequestFactoryEditorDriver} that records its
+ * inputs.
+ *
+ * @param <P> the Proxy type being edited
+ * @param <E> the Editor type
+ */
+public class MockRequestFactoryEditorDriver<P, E extends Editor<P>> implements
+ RequestFactoryEditorDriver<P, E> {
+ private static final String[] EMPTY_STRING = new String[0];
+
+ private EventBus eventBus;
+ private E editor;
+ private P proxy;
+ private RequestContext saveRequest;
+ private RequestFactory requestFactory;
+
+ /**
+ * A no-op method.
+ */
+ public void accept(EditorVisitor visitor) {
+ }
+
+ /**
+ * Records its arguments.
+ */
+ public void display(P proxy) {
+ this.proxy = proxy;
+ }
+
+ /**
+ * Records its arguments.
+ */
+ public void edit(P proxy, RequestContext saveRequest) {
+ this.proxy = proxy;
+ this.saveRequest = saveRequest;
+ }
+
+ /**
+ * Returns <code>null</code> or the last value recorded.
+ */
+ public RequestContext flush() {
+ return saveRequest;
+ }
+
+ /**
+ * Returns <code>null</code> or the last value recorded.
+ */
+ public E getEditor() {
+ return editor;
+ }
+
+ /**
+ * Returns an empty list.
+ */
+ public List<EditorError> getErrors() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * Returns <code>null</code> or the last value recorded.
+ */
+ public EventBus getEventBus() {
+ return eventBus;
+ }
+
+ /**
+ * Returns a zero-length array.
+ */
+ public String[] getPaths() {
+ return EMPTY_STRING;
+ }
+
+ /**
+ * Returns <code>null</code> or the last value recorded.
+ */
+ public P getProxy() {
+ return proxy;
+ }
+
+ /**
+ * Returns <code>null</code> or the last value recorded.
+ */
+ public RequestFactory getRequestFactory() {
+ return requestFactory;
+ }
+
+ /**
+ * Returns <code>null</code> or the last value recorded.
+ */
+ public RequestContext getSaveRequest() {
+ return saveRequest;
+ }
+
+ /**
+ * Returns <code>false</code>.
+ */
+ public boolean hasErrors() {
+ return false;
+ }
+
+ public void initialize(E editor) {
+ initialize(null, editor);
+ }
+
+ /**
+ * Records its arguments.
+ */
+ public void initialize(EventBus eventBus, RequestFactory requestFactory,
+ E editor) {
+ this.eventBus = eventBus;
+ this.requestFactory = requestFactory;
+ this.editor = editor;
+ }
+
+ public void initialize(RequestFactory requestFactory, E editor) {
+ this.initialize(requestFactory.getEventBus(), requestFactory, editor);
+ }
+
+ /**
+ * Returns {@code false}.
+ */
+ public boolean isDirty() {
+ return false;
+ }
+
+ /**
+ * A no-op method that always returns false.
+ */
+ public boolean setConstraintViolations(
+ Iterable<ConstraintViolation<?>> violations) {
+ return false;
+ }
+
+ /**
+ * A no-op method that always returns false.
+ */
+ public boolean setViolations(Iterable<Violation> errors) {
+ return false;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/package-info.java b/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/package-info.java
new file mode 100644
index 0000000..75070ee
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/client/testing/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/**
+ * Classes used for testing the request factory service.
+ *
+ * @since GWT 2.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.gwt.client.testing;
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryEditorDriverGenerator.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryEditorDriverGenerator.java
new file mode 100644
index 0000000..347c576
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryEditorDriverGenerator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.rebind;
+
+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.editor.rebind.AbstractEditorDriverGenerator;
+import com.google.gwt.editor.rebind.model.EditorData;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.gwt.client.impl.AbstractRequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.gwt.client.impl.RequestFactoryEditorDelegate;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+
+/**
+ * Generates implementations of RFEDs.
+ */
+public class RequestFactoryEditorDriverGenerator extends
+ AbstractEditorDriverGenerator {
+
+ private JClassType baseProxyType;
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+ baseProxyType = context.getTypeOracle().findType(
+ BaseProxy.class.getCanonicalName());
+ return super.generate(logger, context, typeName);
+ }
+
+ @Override
+ protected Class<?> getDriverInterfaceType() {
+ return RequestFactoryEditorDriver.class;
+ }
+
+ @Override
+ protected Class<?> getDriverSuperclassType() {
+ return AbstractRequestFactoryEditorDriver.class;
+ }
+
+ @Override
+ protected Class<?> getEditorDelegateType() {
+ return RequestFactoryEditorDelegate.class;
+ }
+
+ @Override
+ protected String mutableObjectExpression(EditorData data,
+ String sourceObjectExpression) {
+ if (baseProxyType.isAssignableFrom(data.getPropertyOwnerType())) {
+ return String.format("((%s) request.edit((%s)))",
+ data.getPropertyOwnerType().getQualifiedSourceName(),
+ sourceObjectExpression);
+ } else {
+ return sourceObjectExpression;
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
new file mode 100644
index 0000000..e6d7f5c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.rebind;
+
+import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.shared.impl.EnumMap.ExtraEnums;
+import com.google.gwt.core.client.GWT;
+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.JEnumType;
+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.JType;
+import com.google.gwt.core.ext.typeinfo.JTypeParameter;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.editor.rebind.model.ModelUtils;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.google.web.bindery.requestfactory.gwt.client.impl.AbstractClientRequestFactory;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.AcceptsModelVisitor;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.ContextMethod;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.EntityProxyModel;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.ModelVisitor;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModel;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestMethod;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.EntityProxyModel.Type;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.JsonRpcContent;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequest;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.RequestData;
+import com.google.web.bindery.requestfactory.shared.impl.ValueProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext.Dialect;
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Generates implementations of
+ * {@link com.google.web.bindery.requestfactory.shared.RequestFactory RequestFactory}
+ * and its nested interfaces.
+ */
+public class RequestFactoryGenerator extends Generator {
+
+ /**
+ * Visits all types reachable from a RequestContext.
+ */
+ private static class AllReachableTypesVisitor extends
+ RequestMethodTypesVisitor {
+ private final RequestFactoryModel model;
+
+ public AllReachableTypesVisitor(RequestFactoryModel model) {
+ this.model = model;
+ }
+
+ @Override
+ void examineTypeOnce(JClassType type) {
+ // Need this to handle List<Foo>, Map<Foo>
+ JParameterizedType parameterized = type.isParameterized();
+ if (parameterized != null) {
+ for (JClassType arg : parameterized.getTypeArgs()) {
+ maybeVisit(arg);
+ }
+ }
+ JClassType base = ModelUtils.ensureBaseType(type);
+ EntityProxyModel peer = model.getPeer(base);
+ if (peer == null) {
+ return;
+ }
+ peer.accept(this);
+ }
+ }
+
+ /**
+ * Visits all types immediately referenced by methods defined in a
+ * RequestContext.
+ */
+ private abstract static class RequestMethodTypesVisitor extends ModelVisitor {
+ private final Set<JClassType> seen = new HashSet<JClassType>();
+
+ @Override
+ public void endVisit(RequestMethod x) {
+ // Request<Foo> -> Foo
+ maybeVisit(x.getDataType());
+ // InstanceRequest<Proxy, Foo> -> Proxy
+ if (x.getInstanceType() != null) {
+ x.getInstanceType().accept(this);
+ }
+ // Request<Void> doSomething(Foo foo, Bar bar) -> Foo, Bar
+ for (JType param : x.getDeclarationMethod().getParameterTypes()) {
+ maybeVisit(param.isClassOrInterface());
+ }
+ // setFoo(Foo foo) -> Foo
+ for (JMethod method : x.getExtraSetters()) {
+ maybeVisit(method.getParameterTypes()[0].isClassOrInterface());
+ }
+ }
+
+ abstract void examineTypeOnce(JClassType type);
+
+ void maybeVisit(JClassType type) {
+ if (type == null) {
+ return;
+ } else if (!seen.add(type)) {
+ // Short-circuit to prevent type-loops
+ return;
+ }
+ examineTypeOnce(type);
+ }
+ }
+
+ private GeneratorContext context;
+ private TreeLogger logger;
+ private RequestFactoryModel model;
+
+ @Override
+ public String generate(TreeLogger logger, GeneratorContext context,
+ String typeName) throws UnableToCompleteException {
+ this.context = context;
+ this.logger = logger;
+
+ TypeOracle oracle = context.getTypeOracle();
+ JClassType toGenerate = oracle.findType(typeName).isInterface();
+ if (toGenerate == null) {
+ logger.log(TreeLogger.ERROR, typeName + " is not an interface type");
+ throw new UnableToCompleteException();
+ }
+
+ String packageName = toGenerate.getPackage().getName();
+ String simpleSourceName = toGenerate.getName().replace('.', '_') + "Impl";
+ PrintWriter pw = context.tryCreate(logger, packageName, simpleSourceName);
+ if (pw == null) {
+ return packageName + "." + simpleSourceName;
+ }
+
+ model = new RequestFactoryModel(logger, toGenerate);
+
+ ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
+ packageName, simpleSourceName);
+ factory.setSuperclass(AbstractClientRequestFactory.class.getCanonicalName());
+ factory.addImplementedInterface(typeName);
+ SourceWriter sw = factory.createSourceWriter(context, pw);
+ writeAutoBeanFactory(sw, model.getAllProxyModels(), findExtraEnums(model));
+ writeContextMethods(sw);
+ writeContextImplementations();
+ writeTypeMap(sw);
+ sw.commit(logger);
+
+ return factory.getCreatedClassName();
+ }
+
+ /**
+ * Find enums that needed to be added to the EnumMap that are not referenced
+ * by any of the proxies. This is necessary because the RequestFactory depends
+ * on the AutoBeanCodex to serialize enum values, which in turn depends on the
+ * AutoBeanFactory's enum map. That enum map only contains enum types
+ * reachable from the AutoBean interfaces, which could lead to method
+ * parameters being un-encodable.
+ */
+ private Set<JEnumType> findExtraEnums(AcceptsModelVisitor method) {
+ final Set<JEnumType> toReturn = new LinkedHashSet<JEnumType>();
+ final Set<JEnumType> referenced = new HashSet<JEnumType>();
+
+ // Called from the adder visitor below on each EntityProxy seen
+ final ModelVisitor remover = new AllReachableTypesVisitor(model) {
+ @Override
+ void examineTypeOnce(JClassType type) {
+ JEnumType asEnum = type.isEnum();
+ if (asEnum != null) {
+ referenced.add(asEnum);
+ }
+ super.examineTypeOnce(type);
+ }
+ };
+
+ // Add enums used by RequestMethods
+ method.accept(new RequestMethodTypesVisitor() {
+ @Override
+ public boolean visit(EntityProxyModel x) {
+ x.accept(remover);
+ return false;
+ }
+
+ @Override
+ void examineTypeOnce(JClassType type) {
+ JEnumType asEnum = type.isEnum();
+ if (asEnum != null) {
+ toReturn.add(asEnum);
+ }
+ }
+ });
+ toReturn.removeAll(referenced);
+ if (toReturn.isEmpty()) {
+ return Collections.emptySet();
+ }
+ return Collections.unmodifiableSet(toReturn);
+ }
+
+ /**
+ * Find all EntityProxyModels reachable from a given ContextMethod.
+ */
+ private Set<EntityProxyModel> findReferencedEntities(ContextMethod method) {
+ final Set<EntityProxyModel> models = new LinkedHashSet<EntityProxyModel>();
+ method.accept(new AllReachableTypesVisitor(model) {
+ @Override
+ public void endVisit(EntityProxyModel x) {
+ models.add(x);
+ }
+ });
+ return models;
+ }
+
+ private void writeAutoBeanFactory(SourceWriter sw,
+ Collection<EntityProxyModel> models, Collection<JEnumType> extraEnums) {
+ if (!extraEnums.isEmpty()) {
+ StringBuilder extraClasses = new StringBuilder();
+ for (JEnumType enumType : extraEnums) {
+ if (extraClasses.length() > 0) {
+ extraClasses.append(",");
+ }
+ extraClasses.append(enumType.getQualifiedSourceName()).append(".class");
+ }
+ sw.println("@%s({%s})", ExtraEnums.class.getCanonicalName(), extraClasses);
+ }
+ // Map in static implementations of EntityProxy methods
+ sw.println("@%s({%s.class, %s.class, %s.class})",
+ Category.class.getCanonicalName(),
+ EntityProxyCategory.class.getCanonicalName(),
+ ValueProxyCategory.class.getCanonicalName(),
+ BaseProxyCategory.class.getCanonicalName());
+ // Don't wrap our id type, because it makes code grungy
+ sw.println("@%s(%s.class)", NoWrap.class.getCanonicalName(),
+ EntityProxyId.class.getCanonicalName());
+ sw.println("interface Factory extends %s {",
+ AutoBeanFactory.class.getCanonicalName());
+ sw.indent();
+
+ for (EntityProxyModel proxy : models) {
+ // AutoBean<FooProxy> com_google_FooProxy();
+ sw.println("%s<%s> %s();", AutoBean.class.getCanonicalName(),
+ proxy.getQualifiedSourceName(),
+ proxy.getQualifiedSourceName().replace('.', '_'));
+ }
+ sw.outdent();
+ sw.println("}");
+
+ // public static final Factory FACTORY = GWT.create(Factory.class);
+ sw.println("public static Factory FACTORY;", GWT.class.getCanonicalName());
+
+ // Write public accessor
+ sw.println("@Override public Factory getAutoBeanFactory() {");
+ sw.indent();
+ sw.println("if (FACTORY == null) {");
+ sw.indentln("FACTORY = %s.create(Factory.class);",
+ GWT.class.getCanonicalName());
+ sw.println("}");
+ sw.println("return FACTORY;");
+ sw.outdent();
+ sw.println("}");
+ }
+
+ private void writeContextImplementations() {
+ for (ContextMethod method : model.getMethods()) {
+ PrintWriter pw = context.tryCreate(logger, method.getPackageName(),
+ method.getSimpleSourceName());
+ if (pw == null) {
+ // Already generated
+ continue;
+ }
+
+ ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
+ method.getPackageName(), method.getSimpleSourceName());
+ factory.setSuperclass(AbstractRequestContext.class.getCanonicalName());
+ factory.addImplementedInterface(method.getImplementedInterfaceQualifiedSourceName());
+ SourceWriter sw = factory.createSourceWriter(context, pw);
+
+ // Constructor that accepts the parent RequestFactory
+ sw.println(
+ "public %s(%s requestFactory) {super(requestFactory, %s.%s);}",
+ method.getSimpleSourceName(),
+ AbstractRequestFactory.class.getCanonicalName(),
+ Dialect.class.getCanonicalName(), method.getDialect().name());
+
+ Set<EntityProxyModel> models = findReferencedEntities(method);
+ Set<JEnumType> extraEnumTypes = findExtraEnums(method);
+ writeAutoBeanFactory(sw, models, extraEnumTypes);
+
+ // Write each Request method
+ for (RequestMethod request : method.getRequestMethods()) {
+ JMethod jmethod = request.getDeclarationMethod();
+ String operation = request.getOperation();
+
+ // foo, bar, baz
+ StringBuilder parameterArray = new StringBuilder();
+ // final Foo foo, final Bar bar, final Baz baz
+ StringBuilder parameterDeclaration = new StringBuilder();
+ // <P extends Blah>
+ StringBuilder typeParameterDeclaration = new StringBuilder();
+
+ if (request.isInstance()) {
+ // Leave a spot for the using() method to fill in later
+ parameterArray.append(",null");
+ }
+ for (JTypeParameter param : jmethod.getTypeParameters()) {
+ typeParameterDeclaration.append(",").append(
+ param.getQualifiedSourceName());
+ }
+ for (JParameter param : jmethod.getParameters()) {
+ parameterArray.append(",").append(param.getName());
+ parameterDeclaration.append(",final ").append(
+ param.getType().getParameterizedQualifiedSourceName()).append(" ").append(
+ param.getName());
+ }
+ if (parameterArray.length() > 0) {
+ parameterArray.deleteCharAt(0);
+ }
+ if (parameterDeclaration.length() > 0) {
+ parameterDeclaration.deleteCharAt(0);
+ }
+ if (typeParameterDeclaration.length() > 0) {
+ typeParameterDeclaration.deleteCharAt(0).insert(0, "<").append(">");
+ }
+
+ // public Request<Foo> doFoo(final Foo foo) {
+ sw.println("public %s %s %s(%s) {", typeParameterDeclaration,
+ jmethod.getReturnType().getParameterizedQualifiedSourceName(),
+ jmethod.getName(), parameterDeclaration);
+ sw.indent();
+ // The implements clause covers InstanceRequest
+ // class X extends AbstractRequest<Return> implements Request<Return> {
+ sw.println("class X extends %s<%s> implements %s {",
+ AbstractRequest.class.getCanonicalName(),
+ request.getDataType().getParameterizedQualifiedSourceName(),
+ jmethod.getReturnType().getParameterizedQualifiedSourceName());
+ sw.indent();
+
+ // public X() { super(FooRequestContext.this); }
+ sw.println("public X() { super(%s.this);}",
+ method.getSimpleSourceName());
+
+ // This could also be gotten rid of by having only Request /
+ // InstanceRequest
+ sw.println("@Override public X with(String... paths) {super.with(paths); return this;}");
+
+ // makeRequestData()
+ sw.println("@Override protected %s makeRequestData() {",
+ RequestData.class.getCanonicalName());
+ // return new RequestData("Foo::bar", {parameters}, propertyRefs,
+ // List.class, FooProxy.class);
+ String elementType = request.isCollectionType()
+ ? request.getCollectionElementType().getQualifiedSourceName()
+ + ".class" : "null";
+ String returnTypeBaseQualifiedName = ModelUtils.ensureBaseType(
+ request.getDataType()).getQualifiedSourceName();
+ sw.indentln(
+ "return new %s(\"%s\", new Object[] {%s}, propertyRefs, %s.class, %s);",
+ RequestData.class.getCanonicalName(), operation, parameterArray,
+ returnTypeBaseQualifiedName, elementType);
+ sw.println("}");
+
+ /*
+ * Only support extra properties in JSON-RPC payloads. Could add this to
+ * standard requests to provide out-of-band data.
+ */
+ if (method.getDialect().equals(Dialect.JSON_RPC)) {
+ for (JMethod setter : request.getExtraSetters()) {
+ PropertyName propertyNameAnnotation = setter.getAnnotation(PropertyName.class);
+ String propertyName = propertyNameAnnotation == null
+ ? JBeanMethod.SET.inferName(setter)
+ : propertyNameAnnotation.value();
+ String maybeReturn = JBeanMethod.SET_BUILDER.matches(setter)
+ ? "return this;" : "";
+ sw.println(
+ "%s { getRequestData().setNamedParameter(\"%s\", %s); %s}",
+ setter.getReadableDeclaration(false, false, false, false, true),
+ propertyName, setter.getParameters()[0].getName(), maybeReturn);
+ }
+ }
+
+ // end class X{}
+ sw.outdent();
+ sw.println("}");
+
+ // Instantiate, enqueue, and return
+ sw.println("X x = new X();");
+
+ if (request.getApiVersion() != null) {
+ sw.println("x.getRequestData().setApiVersion(\"%s\");",
+ Generator.escape(request.getApiVersion()));
+ }
+
+ // JSON-RPC payloads send their parameters in a by-name fashion
+ if (method.getDialect().equals(Dialect.JSON_RPC)) {
+ for (JParameter param : jmethod.getParameters()) {
+ PropertyName annotation = param.getAnnotation(PropertyName.class);
+ String propertyName = annotation == null ? param.getName()
+ : annotation.value();
+ boolean isContent = param.isAnnotationPresent(JsonRpcContent.class);
+ if (isContent) {
+ sw.println("x.getRequestData().setRequestContent(%s);",
+ param.getName());
+ } else {
+ sw.println("x.getRequestData().setNamedParameter(\"%s\", %s);",
+ propertyName, param.getName());
+ }
+ }
+ }
+
+ // See comment in AbstractRequest.using(EntityProxy)
+ if (!request.isInstance()) {
+ sw.println("addInvocation(x);");
+ }
+ sw.println("return x;");
+ sw.outdent();
+ sw.println("}");
+ }
+
+ sw.commit(logger);
+ }
+ }
+
+ private void writeContextMethods(SourceWriter sw) {
+ for (ContextMethod method : model.getMethods()) {
+ // public FooService foo() {
+ sw.println("public %s %s() {", method.getQualifiedSourceName(),
+ method.getMethodName());
+ // return new FooServiceImpl(this);
+ sw.indentln("return new %s(this);", method.getQualifiedSourceName());
+ sw.println("}");
+ }
+ }
+
+ private void writeTypeMap(SourceWriter sw) {
+ sw.println("private static final %1$s<String, Class<?>> tokensToTypes"
+ + " = new %1$s<String, Class<?>>();", HashMap.class.getCanonicalName());
+ sw.println("private static final %1$s<Class<?>, String> typesToTokens"
+ + " = new %1$s<Class<?>, String>();", HashMap.class.getCanonicalName());
+ sw.println(
+ "private static final %1$s<Class<?>> entityProxyTypes = new %1$s<Class<?>>();",
+ HashSet.class.getCanonicalName());
+ sw.println(
+ "private static final %1$s<Class<?>> valueProxyTypes = new %1$s<Class<?>>();",
+ HashSet.class.getCanonicalName());
+ sw.println("static {");
+ sw.indent();
+ for (EntityProxyModel type : model.getAllProxyModels()) {
+ // tokensToTypes.put("Foo", Foo.class);
+ sw.println("tokensToTypes.put(\"%s\", %s.class);",
+ type.getQualifiedBinaryName(), type.getQualifiedSourceName());
+ // typesToTokens.put(Foo.class, Foo);
+ sw.println("typesToTokens.put(%s.class, \"%s\");",
+ type.getQualifiedSourceName(), type.getQualifiedBinaryName());
+ // fooProxyTypes.add(MyFooProxy.class);
+ sw.println("%s.add(%s.class);", type.getType().equals(Type.ENTITY)
+ ? "entityProxyTypes" : "valueProxyTypes",
+ type.getQualifiedSourceName());
+ }
+ sw.outdent();
+ sw.println("}");
+
+ // Write instance methods
+ sw.println("@Override protected Class getTypeFromToken(String typeToken) {");
+ sw.indentln("return tokensToTypes.get(typeToken);");
+ sw.println("}");
+ sw.println("@Override protected String getTypeToken(Class type) {");
+ sw.indentln("return typesToTokens.get(type);");
+ sw.println("}");
+ sw.println("@Override public boolean isEntityType(Class<?> type) {");
+ sw.indentln("return entityProxyTypes.contains(type);");
+ sw.println("}");
+ sw.println("@Override public boolean isValueType(Class<?> type) {");
+ sw.indentln("return valueProxyTypes.contains(type);");
+ sw.println("}");
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/AcceptsModelVisitor.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/AcceptsModelVisitor.java
new file mode 100644
index 0000000..b6e11f4
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/AcceptsModelVisitor.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.gwt.rebind.model;
+
+/**
+ * A common interface for model types.
+ */
+public interface AcceptsModelVisitor {
+ void accept(ModelVisitor visitor);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ContextMethod.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ContextMethod.java
new file mode 100644
index 0000000..4de5093
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ContextMethod.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.rebind.model;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext.Dialect;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a service endpoint.
+ */
+public class ContextMethod implements AcceptsModelVisitor {
+
+ /**
+ * Builds a {@link ContextMethod}.
+ */
+ public static class Builder {
+ private ContextMethod toReturn = new ContextMethod();
+
+ public ContextMethod build() {
+ try {
+ return toReturn;
+ } finally {
+ toReturn = null;
+ }
+ }
+
+ public void setDeclaredMethod(JMethod method) {
+ toReturn.methodName = method.getName();
+ JClassType returnClass = method.getReturnType().isClassOrInterface();
+ toReturn.interfaceName = returnClass.getQualifiedSourceName();
+ toReturn.packageName = returnClass.getPackage().getName();
+ toReturn.simpleSourceName = returnClass.getName().replace('.', '_')
+ + "Impl";
+ toReturn.dialect = returnClass.isAnnotationPresent(JsonRpcService.class)
+ ? Dialect.JSON_RPC : Dialect.STANDARD;
+ }
+
+ public void setRequestMethods(List<RequestMethod> requestMethods) {
+ toReturn.requestMethods = requestMethods;
+ }
+ }
+
+ private Dialect dialect;
+ private String interfaceName;
+ private String methodName;
+ private String packageName;
+ private List<RequestMethod> requestMethods;
+ private String simpleSourceName;
+
+ private ContextMethod() {
+ }
+
+ public void accept(ModelVisitor visitor) {
+ if (visitor.visit(this)) {
+ for (RequestMethod method : getRequestMethods()) {
+ method.accept(visitor);
+ }
+ }
+ visitor.endVisit(this);
+ }
+
+ public Dialect getDialect() {
+ return dialect;
+ }
+
+ /**
+ * The qualified source name of the RequestContext sub-interface (i.e., the
+ * return type of the method declaration).
+ */
+ public String getImplementedInterfaceQualifiedSourceName() {
+ return interfaceName;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /**
+ * The qualified source name of the implementation.
+ */
+ public String getQualifiedSourceName() {
+ return getPackageName() + "." + getSimpleSourceName();
+ }
+
+ public List<RequestMethod> getRequestMethods() {
+ return Collections.unmodifiableList(requestMethods);
+ }
+
+ public String getSimpleSourceName() {
+ return simpleSourceName;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return getQualifiedSourceName() + " " + getMethodName() + "()";
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java
new file mode 100644
index 0000000..87c2eac
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/EntityProxyModel.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.rebind.model;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents an EntityProxy subtype.
+ */
+public class EntityProxyModel implements AcceptsModelVisitor {
+ /**
+ * Builds {@link EntityProxyModel}.
+ */
+ public static class Builder {
+ private EntityProxyModel toReturn = new EntityProxyModel();
+
+ public EntityProxyModel build() {
+ try {
+ return toReturn;
+ } finally {
+ toReturn = null;
+ }
+ }
+
+ /**
+ * Allow access to the unfinished EntityProxyModel to allow for circular
+ * type dependencies.
+ */
+ public EntityProxyModel peek() {
+ return toReturn;
+ }
+
+ public void setQualifiedBinaryName(String qualifiedBinaryName) {
+ toReturn.qualifiedBinaryName = qualifiedBinaryName;
+ }
+
+ public void setQualifiedSourceName(String name) {
+ assert !name.contains(" ");
+ toReturn.qualifiedSourceName = name;
+ }
+
+ public void setRequestMethods(List<RequestMethod> requestMethods) {
+ toReturn.requestMethods = requestMethods;
+ }
+
+ public void setType(Type type) {
+ toReturn.type = type;
+ }
+ }
+
+ /**
+ * The kind of proxy. This is an enum in case more proxy types are defined in
+ * the future.
+ */
+ public enum Type {
+ ENTITY, VALUE
+ }
+
+ private String qualifiedBinaryName;
+ private String qualifiedSourceName;
+ private List<RequestMethod> requestMethods;
+ private Type type;
+
+ private EntityProxyModel() {
+ }
+
+ public void accept(ModelVisitor visitor) {
+ if (visitor.visit(this)) {
+ for (RequestMethod method : requestMethods) {
+ method.accept(visitor);
+ }
+ }
+ visitor.endVisit(this);
+ }
+
+ public String getQualifiedBinaryName() {
+ return qualifiedBinaryName;
+ }
+
+ public String getQualifiedSourceName() {
+ return qualifiedSourceName;
+ }
+
+ public List<RequestMethod> getRequestMethods() {
+ return Collections.unmodifiableList(requestMethods);
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return qualifiedSourceName;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ModelVisitor.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ModelVisitor.java
new file mode 100644
index 0000000..09c799a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/ModelVisitor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.gwt.rebind.model;
+
+/**
+ * Implements traversal across a RequestFactory model.
+ */
+public class ModelVisitor {
+ public void endVisit(ContextMethod x) {
+ }
+
+ public void endVisit(EntityProxyModel x) {
+ }
+
+ public void endVisit(RequestFactoryModel x) {
+ }
+
+ public void endVisit(RequestMethod x) {
+ }
+
+ public boolean visit(ContextMethod x) {
+ return true;
+ }
+
+ public boolean visit(EntityProxyModel x) {
+ return true;
+ }
+
+ public boolean visit(RequestFactoryModel x) {
+ return true;
+ }
+
+ public boolean visit(RequestMethod x) {
+ return true;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java
new file mode 100644
index 0000000..6af1dbb
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModel.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.rebind.model;
+
+import com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod;
+import com.google.web.bindery.autobean.shared.Splittable;
+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.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.editor.rebind.model.ModelUtils;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.EntityProxyModel.Type;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestMethod.CollectionType;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a RequestFactory interface declaration.
+ */
+public class RequestFactoryModel implements AcceptsModelVisitor {
+ public static String poisonedMessage() {
+ return "Unable to create RequestFactoryModel model due to previous errors";
+ }
+
+ static String badContextReturnType(JMethod method,
+ JClassType requestInterface, JClassType instanceRequestInterface) {
+ return String.format(
+ "Return type %s in method %s must be an interface assignable"
+ + " to %s or %s", method.getReturnType(), method.getName(),
+ requestInterface.getSimpleSourceName(),
+ instanceRequestInterface.getSimpleSourceName());
+ }
+
+ static String noSettersAllowed(JMethod found) {
+ return String.format("Optional setters not allowed here: ", found.getName());
+ }
+
+ private final JClassType collectionInterface;
+ private final List<ContextMethod> contextMethods = new ArrayList<ContextMethod>();
+ private final JClassType entityProxyInterface;
+ private final JClassType factoryType;
+ private final JClassType instanceRequestInterface;
+ private final JClassType listInterface;
+ private final TreeLogger logger;
+ private final JClassType mapInterface;
+ private final TypeOracle oracle;
+ /**
+ * This map prevents cyclic type dependencies from overflowing the stack.
+ */
+ private final Map<JClassType, EntityProxyModel.Builder> peerBuilders = new HashMap<JClassType, EntityProxyModel.Builder>();
+ /**
+ * Iterated by {@link #getAllProxyModels()}.
+ */
+ private final Map<JClassType, EntityProxyModel> peers = new LinkedHashMap<JClassType, EntityProxyModel>();
+ private boolean poisoned;
+ private final JClassType requestContextInterface;
+ private final JClassType requestFactoryInterface;
+ private final JClassType requestInterface;
+ private final JClassType setInterface;
+ private final JClassType splittableType;
+
+ private final JClassType valueProxyInterface;
+
+ public RequestFactoryModel(TreeLogger logger, JClassType factoryType)
+ throws UnableToCompleteException {
+ this.logger = logger;
+ this.factoryType = factoryType;
+ this.oracle = factoryType.getOracle();
+ collectionInterface = oracle.findType(Collection.class.getCanonicalName());
+ entityProxyInterface = oracle.findType(EntityProxy.class.getCanonicalName());
+ instanceRequestInterface = oracle.findType(InstanceRequest.class.getCanonicalName());
+ listInterface = oracle.findType(List.class.getCanonicalName());
+ mapInterface = oracle.findType(Map.class.getCanonicalName());
+ requestContextInterface = oracle.findType(RequestContext.class.getCanonicalName());
+ requestFactoryInterface = oracle.findType(RequestFactory.class.getCanonicalName());
+ requestInterface = oracle.findType(Request.class.getCanonicalName());
+ setInterface = oracle.findType(Set.class.getCanonicalName());
+ splittableType = oracle.findType(Splittable.class.getCanonicalName());
+ valueProxyInterface = oracle.findType(ValueProxy.class.getCanonicalName());
+
+ for (JMethod method : factoryType.getOverridableMethods()) {
+ if (method.getEnclosingType().equals(requestFactoryInterface)) {
+ // Ignore methods defined an RequestFactory itself
+ continue;
+ }
+
+ if (method.getParameters().length > 0) {
+ poison("Unexpected parameter on method %s", method.getName());
+ continue;
+ }
+
+ JClassType contextType = method.getReturnType().isInterface();
+ if (contextType == null
+ || !requestContextInterface.isAssignableFrom(contextType)) {
+ poison("Unexpected return type %s on method %s is not"
+ + " an interface assignable to %s",
+ method.getReturnType().getQualifiedSourceName(), method.getName(),
+ requestContextInterface.getSimpleSourceName());
+ continue;
+ }
+
+ ContextMethod.Builder builder = new ContextMethod.Builder();
+ builder.setDeclaredMethod(method);
+ buildContextMethod(builder, contextType);
+ contextMethods.add(builder.build());
+ }
+
+ if (poisoned) {
+ die(poisonedMessage());
+ }
+ }
+
+ public void accept(ModelVisitor visitor) {
+ if (visitor.visit(this)) {
+ for (EntityProxyModel model : getAllProxyModels()) {
+ model.accept(visitor);
+ }
+ for (ContextMethod method : getMethods()) {
+ method.accept(visitor);
+ }
+ }
+ visitor.endVisit(this);
+ }
+
+ public Collection<EntityProxyModel> getAllProxyModels() {
+ return Collections.unmodifiableCollection(peers.values());
+ }
+
+ public JClassType getFactoryType() {
+ return factoryType;
+ }
+
+ public List<ContextMethod> getMethods() {
+ return Collections.unmodifiableList(contextMethods);
+ }
+
+ public EntityProxyModel getPeer(JClassType entityProxyType) {
+ return peers.get(entityProxyType);
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return getFactoryType().getQualifiedSourceName();
+ }
+
+ /**
+ * Examine a RequestContext subtype to populate a ContextMethod.
+ */
+ private void buildContextMethod(ContextMethod.Builder contextBuilder,
+ JClassType contextType) throws UnableToCompleteException {
+ Service serviceAnnotation = contextType.getAnnotation(Service.class);
+ ServiceName serviceNameAnnotation = contextType.getAnnotation(ServiceName.class);
+ JsonRpcService jsonRpcAnnotation = contextType.getAnnotation(JsonRpcService.class);
+ if (serviceAnnotation == null && serviceNameAnnotation == null
+ && jsonRpcAnnotation == null) {
+ poison("RequestContext subtype %s is missing a @%s or @%s annotation",
+ contextType.getQualifiedSourceName(), Service.class.getSimpleName(),
+ JsonRpcService.class.getSimpleName());
+ return;
+ }
+
+ List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
+ for (JMethod method : contextType.getInheritableMethods()) {
+ if (method.getEnclosingType().equals(requestContextInterface)) {
+ // Ignore methods declared in RequestContext
+ continue;
+ }
+
+ RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
+ methodBuilder.setDeclarationMethod(method);
+
+ if (!validateContextMethodAndSetDataType(methodBuilder, method,
+ jsonRpcAnnotation != null)) {
+ continue;
+ }
+
+ requestMethods.add(methodBuilder.build());
+ }
+
+ contextBuilder.setRequestMethods(requestMethods);
+ }
+
+ private void die(String message) throws UnableToCompleteException {
+ poison(message);
+ throw new UnableToCompleteException();
+ }
+
+ private EntityProxyModel getEntityProxyType(JClassType entityProxyType)
+ throws UnableToCompleteException {
+ entityProxyType = ModelUtils.ensureBaseType(entityProxyType);
+ EntityProxyModel toReturn = peers.get(entityProxyType);
+ if (toReturn == null) {
+ EntityProxyModel.Builder inProgress = peerBuilders.get(entityProxyType);
+ if (inProgress != null) {
+ toReturn = inProgress.peek();
+ }
+ }
+ if (toReturn == null) {
+ EntityProxyModel.Builder builder = new EntityProxyModel.Builder();
+ peerBuilders.put(entityProxyType, builder);
+
+ builder.setQualifiedBinaryName(ModelUtils.getQualifiedBaseBinaryName(entityProxyType));
+ builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseSourceName(entityProxyType));
+ if (entityProxyInterface.isAssignableFrom(entityProxyType)) {
+ builder.setType(Type.ENTITY);
+ } else if (valueProxyInterface.isAssignableFrom(entityProxyType)) {
+ builder.setType(Type.VALUE);
+ } else {
+ poison("The type %s is not assignable to either %s or %s",
+ entityProxyInterface.getQualifiedSourceName(),
+ valueProxyInterface.getQualifiedSourceName());
+ // Cannot continue, since knowing the behavior is crucial
+ die(poisonedMessage());
+ }
+
+ // Get the server domain object type
+ ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class);
+ ProxyForName proxyForName = entityProxyType.getAnnotation(ProxyForName.class);
+ JsonRpcProxy jsonRpcProxy = entityProxyType.getAnnotation(JsonRpcProxy.class);
+ if (proxyFor == null && proxyForName == null && jsonRpcProxy == null) {
+ poison("The %s type does not have a @%s, @%s, or @%s annotation",
+ entityProxyType.getQualifiedSourceName(),
+ ProxyFor.class.getSimpleName(), ProxyForName.class.getSimpleName(),
+ JsonRpcProxy.class.getSimpleName());
+ }
+
+ // Look at the methods declared on the EntityProxy
+ List<RequestMethod> requestMethods = new ArrayList<RequestMethod>();
+ Map<String, JMethod> duplicatePropertyGetters = new HashMap<String, JMethod>();
+ for (JMethod method : entityProxyType.getInheritableMethods()) {
+ if (method.getEnclosingType().equals(entityProxyInterface)) {
+ // Ignore methods on EntityProxy
+ continue;
+ }
+ RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
+ methodBuilder.setDeclarationMethod(method);
+
+ JType transportedType;
+ String name = method.getName();
+ if (JBeanMethod.GET.matches(method)) {
+ transportedType = method.getReturnType();
+ String propertyName = JBeanMethod.GET.inferName(method);
+ JMethod previouslySeen = duplicatePropertyGetters.get(propertyName);
+ if (previouslySeen == null) {
+ duplicatePropertyGetters.put(propertyName, method);
+ } else {
+ poison("Duplicate accessors for property %s: %s() and %s()",
+ propertyName, previouslySeen.getName(), method.getName());
+ }
+
+ } else if (JBeanMethod.SET.matches(method)
+ || JBeanMethod.SET_BUILDER.matches(method)) {
+ transportedType = method.getParameters()[0].getType();
+
+ } else if (name.equals("stableId")
+ && method.getParameters().length == 0) {
+ // Ignore any overload of stableId
+ continue;
+ } else {
+ poison("The method %s is neither a getter nor a setter",
+ method.getReadableDeclaration());
+ continue;
+ }
+ validateTransportableType(methodBuilder, transportedType, false);
+ RequestMethod requestMethod = methodBuilder.build();
+ requestMethods.add(requestMethod);
+ }
+ builder.setRequestMethods(requestMethods);
+
+ toReturn = builder.build();
+ peers.put(entityProxyType, toReturn);
+ peerBuilders.remove(entityProxyType);
+ }
+ return toReturn;
+ }
+
+ private void poison(String message, Object... args) {
+ logger.log(TreeLogger.ERROR, String.format(message, args));
+ poisoned = true;
+ }
+
+ /**
+ * Examine a RequestContext method to see if it returns a transportable type.
+ */
+ private boolean validateContextMethodAndSetDataType(
+ RequestMethod.Builder methodBuilder, JMethod method, boolean allowSetters)
+ throws UnableToCompleteException {
+ JClassType requestReturnType = method.getReturnType().isInterface();
+ JClassType invocationReturnType;
+ if (requestReturnType == null) {
+ // Primitive return type
+ poison(badContextReturnType(method, requestInterface,
+ instanceRequestInterface));
+ return false;
+ }
+
+ if (instanceRequestInterface.isAssignableFrom(requestReturnType)) {
+ // Instance method invocation
+ JClassType[] params = ModelUtils.findParameterizationOf(
+ instanceRequestInterface, requestReturnType);
+ methodBuilder.setInstanceType(getEntityProxyType(params[0]));
+ invocationReturnType = params[1];
+ } else if (requestInterface.isAssignableFrom(requestReturnType)) {
+ // Static method invocation
+ JClassType[] params = ModelUtils.findParameterizationOf(requestInterface,
+ requestReturnType);
+ invocationReturnType = params[0];
+
+ } else {
+ // Unhandled return type, must be something random
+ poison(badContextReturnType(method, requestInterface,
+ instanceRequestInterface));
+ return false;
+ }
+
+ // Validate the parameters
+ boolean paramsOk = true;
+ JParameter[] params = method.getParameters();
+ for (int i = 0; i < params.length; ++i) {
+ JParameter param = params[i];
+ paramsOk = validateTransportableType(new RequestMethod.Builder(),
+ param.getType(), false)
+ && paramsOk;
+ }
+
+ // Validate any extra properties on the request type
+ for (JMethod maybeSetter : requestReturnType.getInheritableMethods()) {
+ if (JBeanMethod.SET.matches(maybeSetter)
+ || JBeanMethod.SET_BUILDER.matches(maybeSetter)) {
+ if (allowSetters) {
+ methodBuilder.addExtraSetter(maybeSetter);
+ } else {
+ poison(noSettersAllowed(maybeSetter));
+ }
+ }
+ }
+ return validateTransportableType(methodBuilder, invocationReturnType, true);
+ }
+
+ /**
+ * Examines a type to see if it can be transported.
+ */
+ private boolean validateTransportableType(
+ RequestMethod.Builder methodBuilder, JType type, boolean requireObject)
+ throws UnableToCompleteException {
+ JClassType transportedClass = type.isClassOrInterface();
+ if (transportedClass == null) {
+ if (requireObject) {
+ poison("The type %s cannot be transported by RequestFactory as"
+ + " a return type", type.getQualifiedSourceName());
+ return false;
+ } else {
+ // Primitives always ok
+ return true;
+ }
+ }
+
+ if (ModelUtils.isValueType(oracle, transportedClass)
+ || splittableType.equals(transportedClass)) {
+ // Simple values, like Integer and String
+ methodBuilder.setValueType(true);
+ } else if (entityProxyInterface.isAssignableFrom(transportedClass)
+ || valueProxyInterface.isAssignableFrom(transportedClass)) {
+ // EntityProxy and ValueProxy return types
+ methodBuilder.setEntityType(getEntityProxyType(transportedClass));
+ } else if (collectionInterface.isAssignableFrom(transportedClass)) {
+ // Only allow certain collections for now
+ JParameterizedType parameterized = transportedClass.isParameterized();
+ if (parameterized == null) {
+ poison("Requests that return collections of List or Set must be parameterized");
+ return false;
+ }
+ if (listInterface.equals(parameterized.getBaseType())) {
+ methodBuilder.setCollectionType(CollectionType.LIST);
+ } else if (setInterface.equals(parameterized.getBaseType())) {
+ methodBuilder.setCollectionType(CollectionType.SET);
+ } else {
+ poison("Requests that return collections may be declared with"
+ + " %s or %s only", listInterface.getQualifiedSourceName(),
+ setInterface.getQualifiedSourceName());
+ return false;
+ }
+ // Also record the element type in the method builder
+ JClassType elementType = ModelUtils.findParameterizationOf(
+ collectionInterface, transportedClass)[0];
+ methodBuilder.setCollectionElementType(elementType);
+ validateTransportableType(methodBuilder, elementType, requireObject);
+ } else if (mapInterface.isAssignableFrom(transportedClass)) {
+ JParameterizedType parameterized = transportedClass.isParameterized();
+ if (parameterized == null) {
+ poison("Requests that return Maps must be parameterized");
+ return false;
+ }
+ if (mapInterface.equals(parameterized.getBaseType())) {
+ methodBuilder.setCollectionType(CollectionType.MAP);
+ } else {
+ poison("Requests that return maps may be declared with" + " %s only",
+ mapInterface.getQualifiedSourceName());
+ return false;
+ }
+ // Also record the element type in the method builder
+ JClassType[] params = ModelUtils.findParameterizationOf(mapInterface,
+ transportedClass);
+ JClassType keyType = params[0];
+ JClassType valueType = params[1];
+ methodBuilder.setMapKeyType(keyType);
+ methodBuilder.setMapValueType(valueType);
+ validateTransportableType(methodBuilder, keyType, requireObject);
+ validateTransportableType(methodBuilder, valueType, requireObject);
+ } else {
+ // Unknown type, fail
+ poison("Invalid Request parameterization %s",
+ transportedClass.getQualifiedSourceName());
+ return false;
+ }
+ methodBuilder.setDataType(transportedClass);
+ return true;
+ }
+
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestMethod.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestMethod.java
new file mode 100644
index 0000000..ec5ef6d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestMethod.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.rebind.model;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.web.bindery.requestfactory.shared.JsonRpcWireName;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a method declaration that causes data to be transported. This can
+ * be a method declared in a RequestContext or a getter or setter on an
+ * EntityProxy.
+ */
+public class RequestMethod implements AcceptsModelVisitor {
+
+ /**
+ * Builds a {@link ContextMethod}.
+ */
+ public static class Builder {
+ private RequestMethod toReturn = new RequestMethod();
+
+ public void addExtraSetter(JMethod method) {
+ if (toReturn.extraSetters == null) {
+ toReturn.extraSetters = new ArrayList<JMethod>();
+ }
+ toReturn.extraSetters.add(method);
+ }
+
+ public RequestMethod build() {
+ if (toReturn.extraSetters == null) {
+ toReturn.extraSetters = Collections.emptyList();
+ } else {
+ toReturn.extraSetters = Collections.unmodifiableList(toReturn.extraSetters);
+ }
+ try {
+ return toReturn;
+ } finally {
+ toReturn = null;
+ }
+ }
+
+ public void setCollectionElementType(JClassType elementType) {
+ toReturn.collectionElementType = elementType;
+ }
+
+ public void setCollectionType(CollectionType collectionType) {
+ toReturn.collectionType = collectionType;
+ }
+
+ public void setDataType(JClassType dataType) {
+ toReturn.dataType = dataType;
+ }
+
+ public void setDeclarationMethod(JMethod declarationMethod) {
+ toReturn.declarationMethod = declarationMethod;
+
+ JClassType returnClass = declarationMethod.getReturnType().isClassOrInterface();
+ JsonRpcWireName annotation = returnClass == null ? null
+ : returnClass.getAnnotation(JsonRpcWireName.class);
+ if (annotation == null) {
+ toReturn.operation = declarationMethod.getEnclosingType().getQualifiedBinaryName()
+ + "::" + declarationMethod.getName();
+ } else {
+ toReturn.operation = annotation.value();
+ toReturn.apiVersion = annotation.version();
+ }
+ }
+
+ public void setEntityType(EntityProxyModel entityType) {
+ toReturn.entityType = entityType;
+ }
+
+ public void setInstanceType(EntityProxyModel instanceType) {
+ toReturn.instanceType = instanceType;
+ }
+
+ public void setMapKeyType(JClassType elementType) {
+ toReturn.mapKeyType = elementType;
+ }
+
+ public void setMapValueType(JClassType elementType) {
+ toReturn.mapValueType = elementType;
+ }
+
+ public void setValueType(boolean valueType) {
+ toReturn.valueType = valueType;
+ }
+ }
+
+ /**
+ * Indicates the type of collection that a Request will return.
+ */
+ public enum CollectionType {
+ LIST, SET, MAP
+ }
+
+ private String apiVersion;
+ private JClassType collectionElementType;
+ private CollectionType collectionType;
+ private JClassType dataType;
+ private JMethod declarationMethod;
+ private EntityProxyModel entityType;
+ private List<JMethod> extraSetters = new ArrayList<JMethod>();
+ private EntityProxyModel instanceType;
+ private String operation;
+ private JClassType mapValueType;
+ private JClassType mapKeyType;
+ private boolean valueType;
+
+ private RequestMethod() {
+ }
+
+ public void accept(ModelVisitor visitor) {
+ if (visitor.visit(this)) {
+ // Empty
+ }
+ visitor.endVisit(this);
+ }
+
+ public String getApiVersion() {
+ return apiVersion;
+ }
+
+ /**
+ * If the method returns a collection, this method will return the element
+ * type.
+ *
+ * @return
+ */
+ public JClassType getCollectionElementType() {
+ return collectionElementType;
+ }
+
+ public CollectionType getCollectionType() {
+ return collectionType;
+ }
+
+ public JClassType getDataType() {
+ return dataType;
+ }
+
+ public JMethod getDeclarationMethod() {
+ return declarationMethod;
+ }
+
+ /**
+ * If the type returned from {@link #getDataType()} refers to an EntityProxy
+ * subtype, or a collection of EntityProxy subtypes, returns the
+ * EntityProxyModel describing the entity.
+ */
+ public EntityProxyModel getEntityType() {
+ return entityType;
+ }
+
+ public List<JMethod> getExtraSetters() {
+ return extraSetters;
+ }
+
+ /**
+ * If the method is intended to be invoked on an instance of an EntityProxy,
+ * returns the EntityProxyModel describing that type.
+ */
+ public EntityProxyModel getInstanceType() {
+ return instanceType;
+ }
+
+ public JClassType getMapKeyType() {
+ return mapKeyType;
+ }
+
+ public JClassType getMapValueType() {
+ return mapValueType;
+ }
+
+ public String getOperation() {
+ return operation;
+ }
+
+ public boolean isCollectionType() {
+ return collectionType != null;
+ }
+
+ public boolean isEntityType() {
+ return entityType != null;
+ }
+
+ public boolean isInstance() {
+ return instanceType != null;
+ }
+
+ public boolean isValueType() {
+ return valueType;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return getDeclarationMethod().toString();
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/EntityProxyKeyProvider.java b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/EntityProxyKeyProvider.java
new file mode 100644
index 0000000..9aa6523
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/EntityProxyKeyProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.ui.client;
+
+import com.google.gwt.view.client.ProvidesKey;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+
+/**
+ * An {@link EntityProxy}-aware key provider, handy for use with
+ * {@link com.google.gwt.view.client.SelectionModel} and various
+ * cell widgets.
+ *
+ * @see com.google.gwt.user.cellview.client.CellBrowser
+ * @see com.google.gwt.user.cellview.client.CellList
+ * @see com.google.gwt.user.cellview.client.CellTable
+ *
+ * @param <P> the proxy type
+ */
+public class EntityProxyKeyProvider<P extends EntityProxy> implements
+ ProvidesKey<P> {
+ /**
+ * Returns the key Object for the given item.
+ *
+ * @param item an item of type P
+ */
+ public Object getKey(P item) {
+ return item == null ? null : item.stableId();
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/ProxyRenderer.java b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/ProxyRenderer.java
new file mode 100644
index 0000000..dd5f733
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/ProxyRenderer.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.ui.client;
+
+import com.google.gwt.text.shared.AbstractRenderer;
+
+/**
+ * Renders a proxy object, and reports the properties it requires to do that
+ * rendering.
+ *
+ * @param <R> the type to render
+ */
+public abstract class ProxyRenderer<R> extends
+ AbstractRenderer<R> {
+
+ private final String[] paths;
+
+ /**
+ * Constructs a {@link ProxyRenderer} with a given set of paths.
+ *
+ * @param paths an Array of Strings
+ */
+ public ProxyRenderer(String[] paths) {
+ this.paths = paths;
+ }
+
+ /**
+ * The properties required by this renderer.
+ *
+ * @return an Array of String paths
+ */
+ public String[] getPaths() {
+ return paths;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/package-info.java b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/package-info.java
new file mode 100644
index 0000000..f9d62e8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/ui/client/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/**
+ * Classes used by the request factory to manage proxies, user logins, and authentication.
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.gwt.ui.client;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/DeadEntityException.java b/user/src/com/google/web/bindery/requestfactory/server/DeadEntityException.java
new file mode 100644
index 0000000..5244543
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/DeadEntityException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * Indicates the user attempted to perform an operation on an irretrievable
+ * entity.
+ */
+class DeadEntityException extends RuntimeException {
+ /**
+ * Contructs a new {@link DeadEntityException} with a given message.
+ *
+ * @param message a message String
+ */
+ public DeadEntityException(String message) {
+ super(message);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/DefaultExceptionHandler.java b/user/src/com/google/web/bindery/requestfactory/server/DefaultExceptionHandler.java
new file mode 100644
index 0000000..ab86e5c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/DefaultExceptionHandler.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+/**
+ * Default implementation for handling exceptions thrown while processing a
+ * request. Suppresses stack traces and the exception class name.
+ */
+public class DefaultExceptionHandler implements ExceptionHandler {
+ public ServerFailure createServerFailure(Throwable throwable) {
+ return new ServerFailure("Server Error: "
+ + (throwable == null ? null : throwable.getMessage()), null, null, true);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ExceptionHandler.java b/user/src/com/google/web/bindery/requestfactory/server/ExceptionHandler.java
new file mode 100644
index 0000000..7481ed0
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ExceptionHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+/**
+ * Handles an exception produced while processing a request.
+ *
+ * @see DefaultExceptionHandler
+ */
+public interface ExceptionHandler {
+ /**
+ * Generates a {@link ServerFailure} based on the information contained in the
+ * received {@code exception}.
+ *
+ * @param throwable a Throwable instance
+ * @return a {@link ServerFailure} instance
+ * @see DefaultExceptionHandler
+ */
+ ServerFailure createServerFailure(Throwable throwable);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/LocatorServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/LocatorServiceLayer.java
new file mode 100644
index 0000000..aa1c5d6
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/LocatorServiceLayer.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Adds support to the ServiceLayer chain for using {@link Locator} and
+ * {@link ServiceLocator} helper objects.
+ */
+final class LocatorServiceLayer extends ServiceLayerDecorator {
+
+ @Override
+ public <T> T createDomainObject(Class<T> clazz) {
+ Locator<T, ?> l = getLocator(clazz);
+ if (l == null) {
+ return super.createDomainObject(clazz);
+ }
+ return l.create(clazz);
+ }
+
+ @Override
+ public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+ return newInstance(clazz, Locator.class);
+ }
+
+ @Override
+ public Object createServiceInstance(Method contextMethod, Method domainMethod) {
+ Class<? extends ServiceLocator> locatorType = getTop().resolveServiceLocator(
+ contextMethod, domainMethod);
+ ServiceLocator locator = getTop().createServiceLocator(locatorType);
+ // Enclosing class may be a parent class, so invoke on service class
+ Class<?> declaringClass = contextMethod.getDeclaringClass();
+ Class<?> serviceClass = getTop().resolveServiceClass(
+ declaringClass.asSubclass(RequestContext.class));
+ return locator.getInstance(serviceClass);
+ }
+
+ @Override
+ public <T extends ServiceLocator> T createServiceLocator(
+ Class<T> serviceLocatorType) {
+ return newInstance(serviceLocatorType, ServiceLocator.class);
+ }
+
+ @Override
+ public Object getId(Object domainObject) {
+ return doGetId(domainObject);
+ }
+
+ @Override
+ public Class<?> getIdType(Class<?> domainType) {
+ Locator<?, ?> l = getLocator(domainType);
+ if (l == null) {
+ return super.getIdType(domainType);
+ }
+ return l.getIdType();
+ }
+
+ @Override
+ public Object getVersion(Object domainObject) {
+ return doGetVersion(domainObject);
+ }
+
+ @Override
+ public boolean isLive(Object domainObject) {
+ return doIsLive(domainObject);
+ }
+
+ @Override
+ public <T> T loadDomainObject(Class<T> clazz, Object domainId) {
+ return doLoadDomainObject(clazz, domainId);
+ }
+
+ /**
+ * Returns true if the context method returns a {@link Request} and the domain
+ * method is non-static.
+ */
+ @Override
+ public boolean requiresServiceLocator(Method contextMethod,
+ Method domainMethod) {
+ return Request.class.isAssignableFrom(contextMethod.getReturnType())
+ && !Modifier.isStatic(domainMethod.getModifiers());
+ }
+
+ @Override
+ public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+ // Find the matching BaseProxy
+ Class<?> proxyType = getTop().resolveClientType(domainType,
+ BaseProxy.class, false);
+ if (proxyType == null) {
+ return null;
+ }
+
+ // Check it for annotations
+ Class<? extends Locator<?, ?>> locatorType;
+ ProxyFor l = proxyType.getAnnotation(ProxyFor.class);
+ ProxyForName ln = proxyType.getAnnotation(ProxyForName.class);
+ if (l != null && !Locator.class.equals(l.locator())) {
+ @SuppressWarnings("unchecked")
+ Class<? extends Locator<?, ?>> found = (Class<? extends Locator<?, ?>>) l.locator();
+ locatorType = found;
+ } else if (ln != null && ln.locator().length() > 0) {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<? extends Locator<?, ?>> found = (Class<? extends Locator<?, ?>>) Class.forName(
+ ln.locator(), false, getTop().getDomainClassLoader()).asSubclass(
+ Locator.class);
+ locatorType = found;
+ } catch (ClassNotFoundException e) {
+ return die(
+ e,
+ "Could not find the locator type specified in the @%s annotation %s",
+ ProxyForName.class.getCanonicalName(), ln.value());
+ }
+ } else {
+ // No locator annotation
+ locatorType = null;
+ }
+ return locatorType;
+ }
+
+ @Override
+ public Class<? extends ServiceLocator> resolveServiceLocator(
+ Method contextMethod, Method domainMethod) {
+ Class<? extends ServiceLocator> locatorType;
+
+ // Look at the RequestContext
+ Class<?> requestContextClass = contextMethod.getDeclaringClass();
+ Service l = requestContextClass.getAnnotation(Service.class);
+ ServiceName ln = requestContextClass.getAnnotation(ServiceName.class);
+ if (l != null && !ServiceLocator.class.equals(l.locator())) {
+ locatorType = l.locator();
+ } else if (ln != null && ln.locator().length() > 0) {
+ try {
+ locatorType = Class.forName(ln.locator(), false,
+ getTop().getDomainClassLoader()).asSubclass(ServiceLocator.class);
+ } catch (ClassNotFoundException e) {
+ return die(
+ e,
+ "Could not find the locator type specified in the @%s annotation %s",
+ ServiceName.class.getCanonicalName(), ln.value());
+ }
+ } else {
+ locatorType = null;
+ }
+ return locatorType;
+ }
+
+ private <T> Object doGetId(T domainObject) {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) domainObject.getClass();
+ Locator<T, ?> l = getLocator(clazz);
+ if (l == null) {
+ return super.getId(domainObject);
+ }
+ return l.getId(domainObject);
+ }
+
+ private <T> Object doGetVersion(T domainObject) {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) domainObject.getClass();
+ Locator<T, ?> l = getLocator(clazz);
+ if (l == null) {
+ return super.getVersion(domainObject);
+ }
+ return l.getVersion(domainObject);
+ }
+
+ private <T> boolean doIsLive(T domainObject) {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) domainObject.getClass();
+ Locator<T, ?> l = getLocator(clazz);
+ if (l == null) {
+ return super.isLive(domainObject);
+ }
+ return l.isLive(domainObject);
+ }
+
+ private <T, I> T doLoadDomainObject(Class<T> clazz, Object domainId) {
+ @SuppressWarnings("unchecked")
+ Locator<T, I> l = (Locator<T, I>) getLocator(clazz);
+ if (l == null) {
+ return super.loadDomainObject(clazz, domainId);
+ }
+ I id = l.getIdType().cast(domainId);
+ return l.find(clazz, id);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T, I> Locator<T, I> getLocator(Class<T> domainType) {
+ Class<? extends Locator<?, ?>> locatorType = getTop().resolveLocator(
+ domainType);
+ if (locatorType == null) {
+ return null;
+ }
+ return (Locator<T, I>) getTop().createLocator(locatorType);
+ }
+
+ private <T> T newInstance(Class<T> clazz, Class<? super T> base) {
+ Throwable ex;
+ try {
+ return clazz.newInstance();
+ } catch (InstantiationException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ }
+ return this.<T> die(ex,
+ "Could not instantiate %s %s. Is it default-instantiable?",
+ base.getSimpleName(), clazz.getCanonicalName());
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Logging.java b/user/src/com/google/web/bindery/requestfactory/server/Logging.java
new file mode 100644
index 0000000..c213ab1
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/Logging.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.logging.server.RemoteLoggingServiceUtil;
+import com.google.gwt.logging.server.RemoteLoggingServiceUtil.RemoteLoggingException;
+import com.google.gwt.logging.server.StackTraceDeobfuscator;
+import com.google.gwt.user.client.rpc.RpcRequestBuilder;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Server side object that handles log messages sent by
+ * {@link com.google.web.bindery.requestfactory.gwt.client.RequestFactoryLogHandler}.
+ */
+public class Logging {
+
+ private static StackTraceDeobfuscator deobfuscator = new StackTraceDeobfuscator(
+ "");
+
+ /**
+ * Logs a message.
+ *
+ * @param logRecordJson a json serialized LogRecord, as provided by
+ * {@link com.google.gwt.logging.client.JsonLogRecordClientUtil#logRecordAsJsonObject(LogRecord)}
+ * @throws RemoteLoggingException if logging fails
+ */
+ public static void logMessage(String logRecordJson)
+ throws RemoteLoggingException {
+ /*
+ * if the header does not exist, we pass null, which is handled gracefully
+ * by the deobfuscation code.
+ */
+ HttpServletRequest threadLocalRequest = RequestFactoryServlet.getThreadLocalRequest();
+ String strongName = null;
+ if (threadLocalRequest != null) {
+ // can be null during tests
+ threadLocalRequest.getHeader(RpcRequestBuilder.STRONG_NAME_HEADER);
+ }
+ RemoteLoggingServiceUtil.logOnServer(logRecordJson, strongName,
+ deobfuscator, null);
+ }
+
+ /**
+ * This function is only for server side use which is why it's not in the
+ * LoggingRequest interface.
+ *
+ * @param dir a directory, specified as a String
+ */
+ public static void setSymbolMapsDirectory(String dir) {
+ deobfuscator.setSymbolMapsDirectory(dir);
+ }
+
+ private String id = "";
+
+ private Integer version = 0;
+
+ /**
+ * Returns the id of this instance.
+ *
+ * @return a String id
+ * @see #setId(String)
+ */
+ public String getId() {
+ return this.id;
+ }
+
+ /**
+ * Returns the version of this instance.
+ *
+ * @return an Integer version number
+ * @see #setVersion(Integer)
+ */
+ public Integer getVersion() {
+ return this.version;
+ }
+
+ /**
+ * Sets the id on this instance.
+ *
+ * @param id a String id
+ * @see #getId()
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Sets the version of this instance.
+ *
+ * @param version an Integer version number
+ * @see #getVersion()
+ */
+ public void setVersion(Integer version) {
+ this.version = version;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java
new file mode 100644
index 0000000..3433518
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ReflectiveServiceLayer.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.vm.impl.BeanMethod;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.Request;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.ValidationException;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+/**
+ * Implements all methods that interact with domain objects.
+ */
+final class ReflectiveServiceLayer extends ServiceLayerDecorator {
+ /*
+ * NB: All calls that ReflectiveServiceLayer makes to public APIs inherited
+ * from ServiceLayer should be made to use the instance returned from
+ * getTop().
+ */
+
+ private static final Validator jsr303Validator;
+ private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
+
+ static {
+ Validator found;
+ try {
+ ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
+ found = validatorFactory.getValidator();
+ } catch (ValidationException e) {
+ log.log(Level.INFO, "Unable to initialize a JSR 303 Bean Validator", e);
+ found = null;
+ }
+ jsr303Validator = found;
+ }
+
+ /**
+ * Linear search, but we want to handle getFoo, isFoo, and hasFoo. The result
+ * of this method will be cached by the ServiceLayerCache.
+ */
+ private static Method getBeanMethod(BeanMethod methodType,
+ Class<?> domainType, String property) {
+ for (Method m : domainType.getMethods()) {
+ if (methodType.matches(m) && property.equals(methodType.inferName(m))) {
+ m.setAccessible(true);
+ return m;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public <T> T createDomainObject(Class<T> clazz) {
+ Throwable ex;
+ try {
+ Constructor<T> c = clazz.getConstructor();
+ c.setAccessible(true);
+ return c.newInstance();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ return this.<T> report("Could not create a new instance of the requested type");
+ } catch (NoSuchMethodException e) {
+ return this.<T> report("The requested type is not default-instantiable");
+ } catch (InvocationTargetException e) {
+ return this.<T> report(e);
+ } catch (IllegalAccessException e) {
+ ex = e;
+ } catch (SecurityException e) {
+ ex = e;
+ } catch (IllegalArgumentException e) {
+ ex = e;
+ }
+ return this.<T> die(ex,
+ "Could not create a new instance of domain type %s",
+ clazz.getCanonicalName());
+ }
+
+ @Override
+ public Method getGetter(Class<?> domainType, String property) {
+ return getBeanMethod(BeanMethod.GET, domainType, property);
+ }
+
+ @Override
+ public Object getId(Object domainObject) {
+ return getTop().getProperty(domainObject, "id");
+ }
+
+ @Override
+ public Class<?> getIdType(Class<?> domainType) {
+ return getFind(domainType).getParameterTypes()[0];
+ }
+
+ @Override
+ public Object getProperty(Object domainObject, String property) {
+ try {
+ Method getter = getTop().getGetter(domainObject.getClass(), property);
+ if (getter == null) {
+ die(null, "Could not determine getter for property %s on type %s",
+ property, domainObject.getClass().getCanonicalName());
+ }
+ Object value = getter.invoke(domainObject);
+ return value;
+ } catch (IllegalAccessException e) {
+ return die(e, "Could not retrieve property %s", property);
+ } catch (InvocationTargetException e) {
+ return report(e);
+ }
+ }
+
+ @Override
+ public Type getRequestReturnType(Method contextMethod) {
+ Class<?> returnClass = contextMethod.getReturnType();
+ if (InstanceRequest.class.isAssignableFrom(returnClass)) {
+ Type[] params = TypeUtils.getParameterization(InstanceRequest.class,
+ contextMethod.getGenericReturnType());
+ assert params.length == 2;
+ return params[1];
+ } else if (Request.class.isAssignableFrom(returnClass)) {
+ Type param = TypeUtils.getSingleParameterization(Request.class,
+ contextMethod.getGenericReturnType());
+ return param;
+ } else {
+ return die(null, "Unknown RequestContext return type %s",
+ returnClass.getCanonicalName());
+ }
+ }
+
+ @Override
+ public Method getSetter(Class<?> domainType, String property) {
+ Method setter = getBeanMethod(BeanMethod.SET, domainType, property);
+ if (setter == null) {
+ setter = getBeanMethod(BeanMethod.SET_BUILDER, domainType, property);
+ }
+ return setter;
+ }
+
+ @Override
+ public Object getVersion(Object domainObject) {
+ return getTop().getProperty(domainObject, "version");
+ }
+
+ @Override
+ public Object invoke(Method domainMethod, Object... args) {
+ Throwable ex;
+ try {
+ domainMethod.setAccessible(true);
+ if (Modifier.isStatic(domainMethod.getModifiers())) {
+ return domainMethod.invoke(null, args);
+ } else {
+ Object[] realArgs = new Object[args.length - 1];
+ System.arraycopy(args, 1, realArgs, 0, realArgs.length);
+ return domainMethod.invoke(args[0], realArgs);
+ }
+ } catch (IllegalArgumentException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ } catch (InvocationTargetException e) {
+ return report(e);
+ }
+ return die(ex, "Could not invoke method %s", domainMethod.getName());
+ }
+
+ /**
+ * This implementation attempts to re-load the object from the backing store.
+ */
+ @Override
+ public boolean isLive(Object domainObject) {
+ Object id = getTop().getId(domainObject);
+ return getTop().invoke(getFind(domainObject.getClass()), id) != null;
+ }
+
+ @Override
+ public <T> T loadDomainObject(Class<T> clazz, Object id) {
+ if (id == null) {
+ die(null, "Cannot invoke find method with a null id");
+ }
+ return clazz.cast(getTop().invoke(getFind(clazz), id));
+ }
+
+ @Override
+ public List<Object> loadDomainObjects(List<Class<?>> classes,
+ List<Object> domainIds) {
+ if (classes.size() != domainIds.size()) {
+ die(null,
+ "Size mismatch in paramaters. classes.size() = %d domainIds.size=%d",
+ classes.size(), domainIds.size());
+ }
+ List<Object> toReturn = new ArrayList<Object>(classes.size());
+ Iterator<Class<?>> classIt = classes.iterator();
+ Iterator<Object> idIt = domainIds.iterator();
+ while (classIt.hasNext()) {
+ toReturn.add(getTop().loadDomainObject(classIt.next(), idIt.next()));
+ }
+ return toReturn;
+ }
+
+ @Override
+ public void setProperty(Object domainObject, String property,
+ Class<?> expectedType, Object value) {
+ try {
+ Method setter = getTop().getSetter(domainObject.getClass(), property);
+ if (setter == null) {
+ die(null, "Could not locate setter for property %s in type %s",
+ property, domainObject.getClass().getCanonicalName());
+ }
+ setter.invoke(domainObject, value);
+ return;
+ } catch (IllegalAccessException e) {
+ die(e, "Could not set property %s", property);
+ } catch (InvocationTargetException e) {
+ report(e);
+ }
+ }
+
+ @Override
+ public <T> Set<ConstraintViolation<T>> validate(T domainObject) {
+ if (jsr303Validator != null) {
+ return jsr303Validator.validate(domainObject);
+ }
+ return Collections.emptySet();
+ }
+
+ private Method getFind(Class<?> clazz) {
+ if (clazz == null) {
+ return die(null, "Could not find static method with a single"
+ + " parameter of a key type");
+ }
+ String searchFor = "find" + clazz.getSimpleName();
+ for (Method method : clazz.getMethods()) {
+ if (!Modifier.isStatic(method.getModifiers())) {
+ continue;
+ }
+ if (!searchFor.equals(method.getName())) {
+ continue;
+ }
+ if (method.getParameterTypes().length != 1) {
+ continue;
+ }
+ if (!isKeyType(method.getParameterTypes()[0])) {
+ continue;
+ }
+ return method;
+ }
+ return getFind(clazz.getSuperclass());
+ }
+
+ /**
+ * Returns <code>true</code> if the given class can be used as an id or
+ * version key.
+ */
+ private boolean isKeyType(Class<?> domainClass) {
+ if (ValueCodex.canDecode(domainClass)) {
+ return true;
+ }
+
+ return BaseProxy.class.isAssignableFrom(getTop().resolveClientType(
+ domainClass, BaseProxy.class, true));
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ReportableException.java b/user/src/com/google/web/bindery/requestfactory/server/ReportableException.java
new file mode 100644
index 0000000..c1e51a5
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ReportableException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * Encapsulates exceptions that should be thrown back to the client.
+ */
+@SuppressWarnings("serial")
+class ReportableException extends RuntimeException {
+ public ReportableException(String msg) {
+ super(msg);
+ }
+
+ public ReportableException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+ public ReportableException(Throwable cause) {
+ super("Server Error: " + cause.getMessage(), cause);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
new file mode 100644
index 0000000..5e3dfe3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -0,0 +1,1583 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.gwt.dev.asm.AnnotationVisitor;
+import com.google.gwt.dev.asm.ClassReader;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.EmptyVisitor;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.asm.signature.SignatureReader;
+import com.google.gwt.dev.asm.signature.SignatureVisitor;
+import com.google.gwt.dev.util.Name;
+import com.google.gwt.dev.util.Name.BinaryName;
+import com.google.gwt.dev.util.Name.SourceOrBinaryName;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Encapsulates validation logic to determine if a {@link RequestFactory}
+ * interface, its {@link RequestContext}, and associated {@link EntityProxy}
+ * interfaces match their domain counterparts. This implementation examines the
+ * classfiles directly in order to avoid the need to load the types into the
+ * JVM.
+ * <p>
+ * This class is amenable to being used as a unit test:
+ *
+ * <pre>
+ * public void testRequestFactory() {
+ * Logger logger = Logger.getLogger("");
+ * RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(
+ * logger, new ClassLoaderLoader(MyRequestContext.class.getClassLoader()));
+ * v.validateRequestContext(MyRequestContext.class.getName());
+ * assertFalse(v.isPoisoned());
+ * }
+ * </pre>
+ * This class also has a {@code main} method and can be used as a build-time
+ * tool:
+ *
+ * <pre>
+ * java -cp gwt-servlet.jar:your-code.jar \
+ * com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator \
+ * com.example.MyRequestFactory
+ * </pre>
+ */
+public class RequestFactoryInterfaceValidator {
+ /**
+ * An implementation of {@link Loader} that uses a {@link ClassLoader} to
+ * retrieve the class files.
+ */
+ public static class ClassLoaderLoader implements Loader {
+ private final ClassLoader loader;
+
+ public ClassLoaderLoader(ClassLoader loader) {
+ this.loader = loader;
+ }
+
+ public boolean exists(String resource) {
+ return loader.getResource(resource) != null;
+ }
+
+ public InputStream getResourceAsStream(String resource) {
+ return loader.getResourceAsStream(resource);
+ }
+ }
+
+ /**
+ * Abstracts the mechanism by which class files are loaded.
+ *
+ * @see ClassLoaderLoader
+ */
+ public interface Loader {
+ /**
+ * Returns true if the specified resource can be loaded.
+ *
+ * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
+ */
+ boolean exists(String resource);
+
+ /**
+ * Returns an InputStream to access the specified resource, or
+ * <code>null</code> if no such resource exists.
+ *
+ * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
+ */
+ InputStream getResourceAsStream(String resource);
+ }
+
+ /**
+ * Improves error messages by providing context for the user.
+ * <p>
+ * Visible for testing.
+ */
+ static class ErrorContext {
+ private final Logger logger;
+ private final ErrorContext parent;
+ private Type currentType;
+ private Method currentMethod;
+ private RequestFactoryInterfaceValidator validator;
+
+ public ErrorContext(Logger logger) {
+ this.logger = logger;
+ this.parent = null;
+ }
+
+ protected ErrorContext(ErrorContext parent) {
+ this.logger = parent.logger;
+ this.parent = parent;
+ this.validator = parent.validator;
+ }
+
+ public void poison(String msg, Object... args) {
+ poison();
+ logger.logp(Level.SEVERE, currentType(), currentMethod(),
+ String.format(msg, args));
+ validator.poisoned = true;
+ }
+
+ public void poison(String msg, Throwable t) {
+ poison();
+ logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
+ validator.poisoned = true;
+ }
+
+ public ErrorContext setMethod(Method method) {
+ ErrorContext toReturn = fork();
+ toReturn.currentMethod = method;
+ return toReturn;
+ }
+
+ public ErrorContext setType(Type type) {
+ ErrorContext toReturn = fork();
+ toReturn.currentType = type;
+ return toReturn;
+ }
+
+ public void spam(String msg, Object... args) {
+ logger.logp(Level.FINEST, currentType(), currentMethod(),
+ String.format(msg, args));
+ }
+
+ protected ErrorContext fork() {
+ return new ErrorContext(this);
+ }
+
+ void setValidator(RequestFactoryInterfaceValidator validator) {
+ assert this.validator == null : "Cannot set validator twice";
+ this.validator = validator;
+ }
+
+ private String currentMethod() {
+ if (currentMethod != null) {
+ return print(currentMethod);
+ }
+ if (parent != null) {
+ return parent.currentMethod();
+ }
+ return null;
+ }
+
+ private String currentType() {
+ if (currentType != null) {
+ return print(currentType);
+ }
+ if (parent != null) {
+ return parent.currentType();
+ }
+ return null;
+ }
+
+ /**
+ * Populate {@link RequestFactoryInterfaceValidator#badTypes} with the
+ * current context.
+ */
+ private void poison() {
+ if (currentType != null) {
+ validator.badTypes.add(currentType.getClassName());
+ }
+ if (parent != null) {
+ parent.poison();
+ }
+ }
+ }
+
+ /**
+ * Used internally as a placeholder for types that cannot be mapped to a
+ * domain object.
+ */
+ interface MissingDomainType {
+ }
+
+ /**
+ * Collects the ProxyFor or Service annotation from an EntityProxy or
+ * RequestContext type.
+ */
+ private class DomainMapper extends EmptyVisitor {
+ private final ErrorContext logger;
+ private String domainInternalName;
+ private List<Class<? extends Annotation>> found = new ArrayList<Class<? extends Annotation>>();
+ private String locatorInternalName;
+
+ public DomainMapper(ErrorContext logger) {
+ this.logger = logger;
+ logger.spam("Finding domain mapping annotation");
+ }
+
+ public String getDomainInternalName() {
+ return domainInternalName;
+ }
+
+ public String getLocatorInternalName() {
+ return locatorInternalName;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if ((access & Opcodes.ACC_INTERFACE) == 0) {
+ logger.poison("Type must be an interface");
+ }
+ }
+
+ /**
+ * This method examines one annotation at a time.
+ */
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // Set to true if the annotation should have class literal values
+ boolean expectClasses = false;
+ // Set to true if the annonation has string values
+ boolean expectNames = false;
+
+ if (desc.equals(Type.getDescriptor(ProxyFor.class))) {
+ expectClasses = true;
+ found.add(ProxyFor.class);
+ } else if (desc.equals(Type.getDescriptor(ProxyForName.class))) {
+ expectNames = true;
+ found.add(ProxyForName.class);
+ } else if (desc.equals(Type.getDescriptor(Service.class))) {
+ expectClasses = true;
+ found.add(Service.class);
+ } else if (desc.equals(Type.getDescriptor(ServiceName.class))) {
+ expectNames = true;
+ found.add(ServiceName.class);
+ }
+
+ if (expectClasses) {
+ return new EmptyVisitor() {
+ @Override
+ public void visit(String name, Object value) {
+ if ("value".equals(name)) {
+ domainInternalName = ((Type) value).getInternalName();
+ } else if ("locator".equals(name)) {
+ locatorInternalName = ((Type) value).getInternalName();
+ }
+ }
+ };
+ }
+
+ if (expectNames) {
+ return new EmptyVisitor() {
+ @Override
+ public void visit(String name, Object value) {
+ String sourceName;
+ boolean locatorRequired = "locator".equals(name);
+ boolean valueRequired = "value".equals(name);
+ if (valueRequired || locatorRequired) {
+ sourceName = (String) value;
+ } else {
+ return;
+ }
+
+ /*
+ * The input is a source name, so we need to convert it to an
+ * internal name. We'll do this by substituting dollar signs for the
+ * last slash in the name until there are no more slashes.
+ */
+ StringBuffer desc = new StringBuffer(sourceName.replace('.', '/'));
+ while (!loader.exists(desc.toString() + ".class")) {
+ logger.spam("Did not find " + desc.toString());
+ int idx = desc.lastIndexOf("/");
+ if (idx == -1) {
+ if (locatorRequired) {
+ logger.poison("Cannot find locator named %s", value);
+ } else if (valueRequired) {
+ logger.poison("Cannot find domain type named %s", value);
+ }
+ return;
+ }
+ desc.setCharAt(idx, '$');
+ }
+
+ if (locatorRequired) {
+ locatorInternalName = desc.toString();
+ logger.spam(locatorInternalName);
+ } else if (valueRequired) {
+ domainInternalName = desc.toString();
+ logger.spam(domainInternalName);
+ } else {
+ throw new RuntimeException("Should not reach here");
+ }
+ }
+ };
+ }
+ return null;
+ }
+
+ @Override
+ public void visitEnd() {
+ // Only allow one annotation
+ if (found.size() > 1) {
+ StringBuilder sb = new StringBuilder();
+ for (Class<?> clazz : found) {
+ sb.append(" @").append(clazz.getSimpleName());
+ }
+ logger.poison("Redundant domain mapping annotations present:%s",
+ sb.toString());
+ }
+ }
+ }
+
+ /**
+ * Collects information about domain objects. This visitor is intended to be
+ * iteratively applied to collect all methods in a type hierarchy.
+ */
+ private class MethodsInHierarchyCollector extends EmptyVisitor {
+ private final ErrorContext logger;
+ private Set<RFMethod> methods = new LinkedHashSet<RFMethod>();
+ private Set<String> seen = new HashSet<String>();
+
+ private MethodsInHierarchyCollector(ErrorContext logger) {
+ this.logger = logger;
+ }
+
+ public Set<RFMethod> exec(String internalName) {
+ RequestFactoryInterfaceValidator.this.visit(logger, internalName, this);
+
+ Map<RFMethod, RFMethod> toReturn = new HashMap<RFMethod, RFMethod>();
+ // Return most-derived methods
+ for (RFMethod method : methods) {
+ RFMethod key = new RFMethod(method.getName(), Type.getMethodDescriptor(
+ Type.VOID_TYPE, method.getArgumentTypes()));
+
+ RFMethod compareTo = toReturn.get(key);
+ if (compareTo == null) {
+ toReturn.put(key, method);
+ } else if (isAssignable(logger, compareTo.getReturnType(),
+ method.getReturnType())) {
+ toReturn.put(key, method);
+ }
+ }
+
+ return new HashSet<RFMethod>(toReturn.values());
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if (!seen.add(name)) {
+ return;
+ }
+ if (!objectType.getInternalName().equals(superName)) {
+ RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
+ }
+ if (interfaces != null) {
+ for (String intf : interfaces) {
+ RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
+ }
+ }
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ // Ignore initializers
+ if ("<clinit>".equals(name) || "<init>".equals(name)) {
+ return null;
+ }
+ final RFMethod method = new RFMethod(name, desc);
+ method.setDeclaredStatic((access & Opcodes.ACC_STATIC) != 0);
+ method.setDeclaredSignature(signature);
+ methods.add(method);
+
+ return new EmptyVisitor() {
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (desc.equals(Type.getDescriptor(SkipInterfaceValidation.class))) {
+ method.setValidationSkipped(true);
+ }
+ return null;
+ }
+ };
+ }
+ }
+
+ private static class RFMethod extends Method {
+ private boolean isDeclaredStatic;
+ private String signature;
+ private boolean isValidationSkipped;
+
+ public RFMethod(String name, String desc) {
+ super(name, desc);
+ }
+
+ public String getSignature() {
+ return signature;
+ }
+
+ public boolean isDeclaredStatic() {
+ return isDeclaredStatic;
+ }
+
+ public boolean isValidationSkipped() {
+ return isValidationSkipped;
+ }
+
+ public void setDeclaredSignature(String signature) {
+ this.signature = signature;
+ }
+
+ public void setDeclaredStatic(boolean value) {
+ isDeclaredStatic = value;
+ }
+
+ public void setValidationSkipped(boolean isValidationSkipped) {
+ this.isValidationSkipped = isValidationSkipped;
+ }
+
+ @Override
+ public String toString() {
+ return (isDeclaredStatic ? "static " : "") + super.toString();
+ }
+ }
+
+ private class SupertypeCollector extends EmptyVisitor {
+ private final ErrorContext logger;
+ private final Set<String> seen = new HashSet<String>();
+ private final List<Type> supers = new ArrayList<Type>();
+
+ public SupertypeCollector(ErrorContext logger) {
+ this.logger = logger;
+ }
+
+ public List<Type> exec(Type type) {
+ RequestFactoryInterfaceValidator.this.visit(logger,
+ type.getInternalName(), this);
+ return supers;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if (!seen.add(name)) {
+ return;
+ }
+ supers.add(Type.getObjectType(name));
+ if (!objectType.getInternalName().equals(name)) {
+ RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
+ }
+ if (interfaces != null) {
+ for (String intf : interfaces) {
+ RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return all types referenced by a method signature.
+ */
+ private static class TypesInSignatureCollector extends SignatureAdapter {
+ private final Set<Type> found = new HashSet<Type>();
+
+ public Type[] getFound() {
+ return found.toArray(new Type[found.size()]);
+ }
+
+ @Override
+ public SignatureVisitor visitArrayType() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitClassBound() {
+ return this;
+ }
+
+ @Override
+ public void visitClassType(String name) {
+ found.add(Type.getObjectType(name));
+ }
+
+ @Override
+ public SignatureVisitor visitExceptionType() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitInterface() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitInterfaceBound() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitParameterType() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitSuperclass() {
+ return this;
+ }
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return this;
+ }
+ }
+
+ static final Set<Class<?>> VALUE_TYPES = ValueCodex.getAllValueTypes();
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ System.err.println("Usage: java -cp gwt-servlet.jar:your-code.jar "
+ + RequestFactoryInterfaceValidator.class.getCanonicalName()
+ + " com.example.MyRequestFactory");
+ System.exit(1);
+ }
+ RequestFactoryInterfaceValidator validator = new RequestFactoryInterfaceValidator(
+ Logger.getLogger(RequestFactoryInterfaceValidator.class.getName()),
+ new ClassLoaderLoader(Thread.currentThread().getContextClassLoader()));
+ validator.validateRequestFactory(args[0]);
+ System.exit(validator.isPoisoned() ? 1 : 0);
+ }
+
+ static String messageCouldNotFindMethod(Type domainType,
+ List<? extends Method> methods) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format(
+ "Could not find matching method in %s.\nPossible matches:\n",
+ print(domainType)));
+ for (Method domainMethod : methods) {
+ sb.append(" ").append(print(domainMethod)).append("\n");
+ }
+ return sb.toString();
+ }
+
+ private static String print(Method method) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(print(method.getReturnType())).append(" ").append(
+ method.getName()).append("(");
+ for (Type t : method.getArgumentTypes()) {
+ sb.append(print(t)).append(" ");
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ private static String print(Type type) {
+ return SourceOrBinaryName.toSourceName(type.getClassName());
+ }
+
+ /**
+ * A set of binary type names that are known to be bad.
+ */
+ private final Set<String> badTypes = new HashSet<String>();
+ /**
+ * The type {@link BaseProxy}.
+ */
+ private final Type baseProxyIntf = Type.getType(BaseProxy.class);
+ /**
+ * Maps client types (e.g. FooProxy) to server domain types (e.g. Foo).
+ */
+ private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
+ /**
+ * Maps client types (e.g. FooProxy or FooContext) to their locator types
+ * (e.g. FooLocator or FooServiceLocator).
+ */
+ private final Map<Type, Type> clientToLocatorMap = new HashMap<Type, Type>();
+ /**
+ * Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy,
+ * FooBProxy).
+ */
+ private final Map<Type, List<Type>> domainToClientType = new HashMap<Type, List<Type>>();
+ /**
+ * The type {@link EntityProxy}.
+ */
+ private final Type entityProxyIntf = Type.getType(EntityProxy.class);
+ /**
+ * The type {@link Enum}.
+ */
+ private final Type enumType = Type.getType(Enum.class);
+ /**
+ * A placeholder type for client types that could not be resolved to a domain
+ * type.
+ */
+ private final Type errorType = Type.getType(MissingDomainType.class);
+ /**
+ * The type {@link InstanceRequest}.
+ */
+ private final Type instanceRequestIntf = Type.getType(InstanceRequest.class);
+ private final Loader loader;
+ /**
+ * A cache of all methods defined in a type hierarchy.
+ */
+ private final Map<Type, Set<RFMethod>> methodsInHierarchy = new HashMap<Type, Set<RFMethod>>();
+ /**
+ * The type {@link Object}.
+ */
+ private final Type objectType = Type.getObjectType("java/lang/Object");
+ private final ErrorContext parentLogger;
+ private boolean poisoned;
+ /**
+ * The type {@link Request}.
+ */
+ private final Type requestIntf = Type.getType(Request.class);
+ /**
+ * The type {@link RequestContext}.
+ */
+ private final Type requestContextIntf = Type.getType(RequestContext.class);
+ /**
+ * A map of a type to all types that it could be assigned to.
+ */
+ private final Map<Type, List<Type>> supertypes = new HashMap<Type, List<Type>>();
+
+ /**
+ * The type {@link ValueProxy}.
+ */
+ private final Type valueProxyIntf = Type.getType(ValueProxy.class);
+
+ /**
+ * A set to prevent re-validation of a type.
+ */
+ private final Set<String> validatedTypes = new HashSet<String>();
+
+ /**
+ * Contains vaue types (e.g. Integer).
+ */
+ private final Set<Type> valueTypes = new HashSet<Type>();
+
+ /**
+ * Maps a domain object to the type returned from its getId method.
+ */
+ private final Map<Type, Type> unresolvedKeyTypes = new HashMap<Type, Type>();
+
+ {
+ for (Class<?> clazz : VALUE_TYPES) {
+ valueTypes.add(Type.getType(clazz));
+ }
+ }
+
+ public RequestFactoryInterfaceValidator(Logger logger, Loader loader) {
+ this.parentLogger = new ErrorContext(logger);
+ parentLogger.setValidator(this);
+ this.loader = loader;
+ }
+
+ /**
+ * Visible for testing.
+ */
+ RequestFactoryInterfaceValidator(ErrorContext errorContext, Loader loader) {
+ this.parentLogger = errorContext;
+ this.loader = loader;
+ errorContext.setValidator(this);
+ }
+
+ /**
+ * Reset the poisoned status of the validator so that it may be reused without
+ * destroying cached state.
+ */
+ public void antidote() {
+ poisoned = false;
+ }
+
+ /**
+ * Returns true if validation failed.
+ */
+ public boolean isPoisoned() {
+ return poisoned;
+ }
+
+ /**
+ * This method checks an EntityProxy interface against its peer domain object
+ * to determine if the server code would be able to process a request using
+ * the methods defined in the EntityProxy interface. It does not perform any
+ * checks as to whether or not the EntityProxy could actually be generated by
+ * the Generator.
+ * <p>
+ * This method may be called repeatedly on a single instance of the validator.
+ * Doing so will amortize type calculation costs.
+ * <p>
+ * Checks implemented:
+ * <ul>
+ * <li> <code>binaryName</code> implements EntityProxy</li>
+ * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
+ * annotation</li>
+ * <li>The domain object has getId() and getVersion() methods</li>
+ * <li>All property methods in the EntityProxy can be mapped onto an
+ * equivalent domain method (unless validation is skipped for the method)</li>
+ * <li>All referenced proxy types are valid</li>
+ * </ul>
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * EntityProxy subtype
+ */
+ public void validateEntityProxy(String binaryName) {
+ validateProxy(binaryName, entityProxyIntf, true);
+ }
+
+ /**
+ * Determine if the specified type implements a proxy interface and apply the
+ * appropriate validations. This can be used as a general-purpose entry method
+ * when writing unit tests.
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * EntityProxy or ValueProxy subtype
+ */
+ public void validateProxy(String binaryName) {
+ /*
+ * Don't call fastFail() here or the proxy may not be validated, since
+ * validateXProxy delegates to validateProxy() which would re-check.
+ */
+ Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+ if (isAssignable(parentLogger, entityProxyIntf, proxyType)) {
+ validateEntityProxy(binaryName);
+ } else if (isAssignable(parentLogger, valueProxyIntf, proxyType)) {
+ validateValueProxy(binaryName);
+ } else {
+ parentLogger.poison("%s is neither an %s nor a %s", print(proxyType),
+ print(entityProxyIntf), print(valueProxyIntf));
+ }
+ }
+
+ /**
+ * This method checks a RequestContext interface against its peer domain
+ * domain object to determine if the server code would be able to process a
+ * request using the the methods defined in the RequestContext interface. It
+ * does not perform any checks as to whether or not the RequestContext could
+ * actually be generated by the Generator.
+ * <p>
+ * This method may be called repeatedly on a single instance of the validator.
+ * Doing so will amortize type calculation costs.
+ * <p>
+ * Checks implemented:
+ * <ul>
+ * <li> <code>binaryName</code> implements RequestContext</li>
+ * <li><code>binaryName</code> has a {@link Service} or {@link ServiceName}
+ * annotation</li>
+ * <li>All service methods in the RequestContext can be mapped onto an
+ * equivalent domain method (unless validation is skipped for the method)</li>
+ * <li>All referenced EntityProxy types are valid</li>
+ * </ul>
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * RequestContext subtype
+ * @see #validateEntityProxy(String)
+ */
+ public void validateRequestContext(String binaryName) {
+ if (fastFail(binaryName)) {
+ return;
+ }
+
+ Type requestContextType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+ final ErrorContext logger = parentLogger.setType(requestContextType);
+
+ // Quick sanity check for calling code
+ if (!isAssignable(logger, requestContextIntf, requestContextType)) {
+ logger.poison("%s is not a %s", print(requestContextType),
+ RequestContext.class.getSimpleName());
+ return;
+ }
+
+ Type domainServiceType = getDomainType(logger, requestContextType);
+ if (domainServiceType == errorType) {
+ logger.poison(
+ "The type %s must be annotated with a @%s or @%s annotation",
+ BinaryName.toSourceName(binaryName), Service.class.getSimpleName(),
+ ServiceName.class.getSimpleName());
+ return;
+ }
+
+ for (RFMethod method : getMethodsInHierarchy(logger, requestContextType)) {
+ // Ignore methods in RequestContext itself
+ if (findCompatibleMethod(logger, requestContextIntf, method, false, true,
+ true) != null) {
+ continue;
+ }
+
+ // Check the client method against the domain
+ checkClientMethodInDomain(logger, method, domainServiceType,
+ !clientToLocatorMap.containsKey(requestContextType));
+ maybeCheckReferredProxies(logger, method);
+ }
+
+ checkUnresolvedKeyTypes(logger);
+ }
+
+ /**
+ * This method checks a RequestFactory interface.
+ * <p>
+ * This method may be called repeatedly on a single instance of the validator.
+ * Doing so will amortize type calculation costs. It does not perform any
+ * checks as to whether or not the RequestContext could actually be generated
+ * by the Generator.
+ * <p>
+ * Checks implemented:
+ * <ul>
+ * <li> <code>binaryName</code> implements RequestFactory</li>
+ * <li>All referenced RequestContext types are valid</li>
+ * </ul>
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * RequestContext subtype
+ * @see #validateRequestContext(String)
+ */
+ public void validateRequestFactory(String binaryName) {
+ if (fastFail(binaryName)) {
+ return;
+ }
+
+ Type requestFactoryType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+ ErrorContext logger = parentLogger.setType(requestFactoryType);
+
+ // Quick sanity check for calling code
+ if (!isAssignable(logger, Type.getType(RequestFactory.class),
+ requestFactoryType)) {
+ logger.poison("%s is not a %s", print(requestFactoryType),
+ RequestFactory.class.getSimpleName());
+ return;
+ }
+
+ // Validate each RequestContext method in the RF
+ for (Method contextMethod : getMethodsInHierarchy(logger,
+ requestFactoryType)) {
+ Type returnType = contextMethod.getReturnType();
+ if (isAssignable(logger, requestContextIntf, returnType)) {
+ validateRequestContext(returnType.getClassName());
+ }
+ }
+ }
+
+ /**
+ * This method checks a ValueProxy interface against its peer domain object to
+ * determine if the server code would be able to process a request using the
+ * methods defined in the ValueProxy interface. It does not perform any checks
+ * as to whether or not the ValueProxy could actually be generated by the
+ * Generator.
+ * <p>
+ * This method may be called repeatedly on a single instance of the validator.
+ * Doing so will amortize type calculation costs.
+ * <p>
+ * Checks implemented:
+ * <ul>
+ * <li> <code>binaryName</code> implements ValueProxy</li>
+ * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
+ * annotation</li>
+ * <li>All property methods in the EntityProxy can be mapped onto an
+ * equivalent domain method (unless validation is skipped for the method)</li>
+ * <li>All referenced proxy types are valid</li>
+ * </ul>
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * EntityProxy subtype
+ */
+ public void validateValueProxy(String binaryName) {
+ validateProxy(binaryName, valueProxyIntf, false);
+ }
+
+ /**
+ * Given the binary name of a domain type, return the BaseProxy type that is
+ * assignable to {@code clientType}. This method allows multiple proxy types
+ * to be assigned to a domain type for use in different contexts (e.g. API
+ * slices). If there are multiple client types mapped to
+ * {@code domainTypeBinaryName} and assignable to {@code clientTypeBinaryName}
+ * , the first matching type will be returned.
+ */
+ String getEntityProxyTypeName(String domainTypeBinaryName,
+ String clientTypeBinaryName) {
+ Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeBinaryName));
+ List<Type> found = domainToClientType.get(key);
+
+ /*
+ * If nothing was found look for proxyable supertypes the domain object can
+ * be upcast to.
+ */
+ if (found == null || found.isEmpty()) {
+ List<Type> types = getSupertypes(parentLogger, key);
+ for (Type type : types) {
+ if (objectType.equals(type)) {
+ break;
+ }
+
+ found = domainToClientType.get(type);
+ if (found != null && !found.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ if (found == null || found.isEmpty()) {
+ return null;
+ }
+
+ Type typeToReturn = null;
+
+ // Common case
+ if (found.size() == 1) {
+ typeToReturn = found.get(0);
+ } else {
+ // Search for the first assignable type
+ Type assignableTo = Type.getObjectType(BinaryName.toInternalName(clientTypeBinaryName));
+ for (Type t : found) {
+ if (isAssignable(parentLogger, assignableTo, t)) {
+ typeToReturn = t;
+ break;
+ }
+ }
+ }
+
+ return typeToReturn == null ? null : typeToReturn.getClassName();
+ }
+
+ /**
+ * Record the mapping of a domain type to a client type. Proxy types will be
+ * added to {@link #domainToClientType}.
+ */
+ private void addToDomainMap(ErrorContext logger, Type domainType,
+ Type clientType) {
+ clientToDomainType.put(clientType, domainType);
+
+ if (isAssignable(logger, baseProxyIntf, clientType)) {
+ maybeCheckProxyType(logger, clientType);
+ List<Type> list = domainToClientType.get(domainType);
+ if (list == null) {
+ list = new ArrayList<Type>();
+ domainToClientType.put(domainType, list);
+ }
+ list.add(clientType);
+ }
+ }
+
+ /**
+ * Check that a given method RequestContext method declaration can be mapped
+ * to the server's domain type.
+ */
+ private void checkClientMethodInDomain(ErrorContext logger, RFMethod method,
+ Type domainServiceType, boolean requireStaticMethodsForRequestType) {
+ logger = logger.setMethod(method);
+
+ // Create a "translated" method declaration to search for
+ // Request<BlahProxy> foo(int a, BarProxy bar) -> Blah foo(int a, Bar bar);
+ Type returnType = getReturnType(logger, method);
+ Method searchFor = createDomainMethod(logger, new Method(method.getName(),
+ returnType, method.getArgumentTypes()));
+
+ RFMethod found = findCompatibleServiceMethod(logger, domainServiceType,
+ searchFor, !method.isValidationSkipped());
+
+ if (found != null) {
+ boolean isInstance = isAssignable(logger, instanceRequestIntf,
+ method.getReturnType());
+ if (isInstance && found.isDeclaredStatic()) {
+ logger.poison("The method %s is declared to return %s, but the"
+ + " service method is static", method.getName(),
+ InstanceRequest.class.getCanonicalName());
+ } else if (requireStaticMethodsForRequestType && !isInstance
+ && !found.isDeclaredStatic()) {
+ logger.poison("The method %s is declared to return %s, but the"
+ + " service method is not static", method.getName(),
+ Request.class.getCanonicalName());
+ }
+ }
+ }
+
+ /**
+ * Check that the domain object has <code>getId()</code> and
+ * <code>getVersion</code> methods.
+ */
+ private void checkIdAndVersion(ErrorContext logger, Type domainType) {
+ if (objectType.equals(domainType)) {
+ return;
+ }
+ logger = logger.setType(domainType);
+ String findMethodName = "find"
+ + BinaryName.getShortClassName(domainType.getClassName());
+ Type keyType = null;
+ RFMethod findMethod = null;
+
+ boolean foundFind = false;
+ boolean foundId = false;
+ boolean foundVersion = false;
+ for (RFMethod method : getMethodsInHierarchy(logger, domainType)) {
+ if ("getId".equals(method.getName())
+ && method.getArgumentTypes().length == 0) {
+ foundId = true;
+ keyType = method.getReturnType();
+ if (!isResolvedKeyType(logger, keyType)) {
+ unresolvedKeyTypes.put(domainType, keyType);
+ }
+ } else if ("getVersion".equals(method.getName())
+ && method.getArgumentTypes().length == 0) {
+ foundVersion = true;
+ if (!isResolvedKeyType(logger, method.getReturnType())) {
+ unresolvedKeyTypes.put(domainType, method.getReturnType());
+ }
+ } else if (findMethodName.equals(method.getName())
+ && method.getArgumentTypes().length == 1) {
+ foundFind = true;
+ findMethod = method;
+ }
+ if (foundFind && foundId && foundVersion) {
+ break;
+ }
+ }
+ if (!foundId) {
+ logger.poison("There is no getId() method in type %s", print(domainType));
+ }
+ if (!foundVersion) {
+ logger.poison("There is no getVersion() method in type %s",
+ print(domainType));
+ }
+
+ if (foundFind) {
+ if (keyType != null
+ && !isAssignable(logger, findMethod.getArgumentTypes()[0], keyType)) {
+ logger.poison("The key type returned by %s getId()"
+ + " cannot be used as the argument to %s(%s)", print(keyType),
+ findMethod.getName(), print(findMethod.getArgumentTypes()[0]));
+ }
+ } else {
+ logger.poison("There is no %s method in type %s that returns %2$s",
+ findMethodName, print(domainType));
+ }
+ }
+
+ /**
+ * Ensure that the given property method on an EntityProxy exists on the
+ * domain object.
+ */
+ private void checkPropertyMethod(ErrorContext logger,
+ RFMethod clientPropertyMethod, Type domainType) {
+ logger = logger.setMethod(clientPropertyMethod);
+
+ findCompatiblePropertyMethod(logger, domainType,
+ createDomainMethod(logger, clientPropertyMethod),
+ !clientPropertyMethod.isValidationSkipped());
+ }
+
+ private void checkUnresolvedKeyTypes(ErrorContext logger) {
+ unresolvedKeyTypes.values().removeAll(domainToClientType.keySet());
+ if (unresolvedKeyTypes.isEmpty()) {
+ return;
+ }
+
+ for (Map.Entry<Type, Type> type : unresolvedKeyTypes.entrySet()) {
+ logger.setType(type.getKey()).poison(
+ "The domain type %s uses a non-simple key type (%s)"
+ + " in its getId() or getVersion() method that"
+ + " does not have a proxy mapping.", print(type.getKey()),
+ print(type.getValue()));
+ }
+ }
+
+ /**
+ * Convert a method declaration using client types (e.g. FooProxy) to domain
+ * types (e.g. Foo).
+ */
+ private Method createDomainMethod(ErrorContext logger, Method clientMethod) {
+ Type[] args = clientMethod.getArgumentTypes();
+ for (int i = 0, j = args.length; i < j; i++) {
+ args[i] = getDomainType(logger, args[i]);
+ }
+ Type returnType = getDomainType(logger, clientMethod.getReturnType());
+ return new Method(clientMethod.getName(), returnType, args);
+ }
+
+ /**
+ * Common checks to quickly determine if a type needs to be checked.
+ */
+ private boolean fastFail(String binaryName) {
+ if (!Name.isBinaryName(binaryName)) {
+ parentLogger.poison("%s is not a binary name", binaryName);
+ return true;
+ }
+
+ // Allow the poisoned flag to be reset without losing data
+ if (badTypes.contains(binaryName)) {
+ parentLogger.poison("Type type %s was previously marked as bad",
+ binaryName);
+ return true;
+ }
+
+ // Don't revalidate the same type
+ if (!validatedTypes.add(binaryName)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Finds a compatible method declaration in <code>domainType</code>'s
+ * hierarchy that is assignment-compatible with the given Method.
+ */
+ private RFMethod findCompatibleMethod(final ErrorContext logger,
+ Type domainType, Method searchFor, boolean mustFind,
+ boolean allowOverloads, boolean boxReturnTypes) {
+ String methodName = searchFor.getName();
+ Type[] clientArgs = searchFor.getArgumentTypes();
+ Type clientReturnType = searchFor.getReturnType();
+ if (boxReturnTypes) {
+ clientReturnType = maybeBoxType(clientReturnType);
+ }
+ // Pull all methods out of the domain type
+ Map<String, List<RFMethod>> domainLookup = new LinkedHashMap<String, List<RFMethod>>();
+ for (RFMethod method : getMethodsInHierarchy(logger, domainType)) {
+ List<RFMethod> list = domainLookup.get(method.getName());
+ if (list == null) {
+ list = new ArrayList<RFMethod>();
+ domainLookup.put(method.getName(), list);
+ }
+ list.add(method);
+ }
+
+ // Find the matching method in the domain object
+ List<RFMethod> methods = domainLookup.get(methodName);
+ if (methods == null) {
+ if (mustFind) {
+ logger.poison("Could not find any methods named %s in %s", methodName,
+ print(domainType));
+ }
+ return null;
+ }
+ if (methods.size() > 1 && !allowOverloads) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("Method overloads found in type %s named %s:\n",
+ print(domainType), methodName));
+ for (RFMethod method : methods) {
+ sb.append(" ").append(print(method)).append("\n");
+ }
+ logger.poison(sb.toString());
+ return null;
+ }
+
+ // Check each overloaded name
+ for (RFMethod domainMethod : methods) {
+ Type[] domainArgs = domainMethod.getArgumentTypes();
+ Type domainReturnType = domainMethod.getReturnType();
+ if (boxReturnTypes) {
+ /*
+ * When looking for the implementation of a Request<Integer>, we want to
+ * match either int or Integer, so we'll box the domain method's return
+ * type.
+ */
+ domainReturnType = maybeBoxType(domainReturnType);
+ }
+
+ /*
+ * Make sure the client args can be passed into the domain args and the
+ * domain return type into the client return type.
+ */
+ if (isAssignable(logger, domainArgs, clientArgs)
+ && isAssignable(logger, clientReturnType, domainReturnType)) {
+
+ logger.spam("Mapped client method " + print(searchFor) + " to "
+ + print(domainMethod));
+ return domainMethod;
+ }
+ }
+ if (mustFind) {
+ logger.poison(messageCouldNotFindMethod(domainType, methods));
+ }
+ return null;
+ }
+
+ /**
+ * Finds a compatible method declaration in <code>domainType</code>'s
+ * hierarchy that is assignment-compatible with the given Method.
+ */
+ private RFMethod findCompatiblePropertyMethod(final ErrorContext logger,
+ Type domainType, Method searchFor, boolean mustFind) {
+ return findCompatibleMethod(logger, domainType, searchFor, mustFind, false,
+ false);
+ }
+
+ /**
+ * Finds a compatible method declaration in <code>domainType</code>'s
+ * hierarchy that is assignment-compatible with the given Method.
+ */
+ private RFMethod findCompatibleServiceMethod(final ErrorContext logger,
+ Type domainType, Method searchFor, boolean mustFind) {
+ return findCompatibleMethod(logger, domainType, searchFor, mustFind, false,
+ true);
+ }
+
+ /**
+ * This looks like it should be a utility method somewhere else, but I can't
+ * find it.
+ */
+ private Type getBoxedType(Type primitive) {
+ switch (primitive.getSort()) {
+ case Type.BOOLEAN:
+ return Type.getType(Boolean.class);
+ case Type.BYTE:
+ return Type.getType(Byte.class);
+ case Type.CHAR:
+ return Type.getType(Character.class);
+ case Type.DOUBLE:
+ return Type.getType(Double.class);
+ case Type.FLOAT:
+ return Type.getType(Float.class);
+ case Type.INT:
+ return Type.getType(Integer.class);
+ case Type.LONG:
+ return Type.getType(Long.class);
+ case Type.SHORT:
+ return Type.getType(Short.class);
+ case Type.VOID:
+ return Type.getType(Void.class);
+ }
+ throw new RuntimeException(primitive.getDescriptor()
+ + " is not a primitive type");
+ }
+
+ /**
+ * Convert the type used in a client-side EntityProxy or RequestContext
+ * declaration to the equivalent domain type. Value types and supported
+ * collections are a pass-through. EntityProxy types will be resolved to their
+ * domain object type. RequestContext types will be resolved to their service
+ * object.
+ */
+ private Type getDomainType(ErrorContext logger, Type clientType) {
+ Type domainType = clientToDomainType.get(clientType);
+ if (domainType != null) {
+ return domainType;
+ }
+ if (isValueType(logger, clientType) || isCollectionType(logger, clientType)) {
+ domainType = clientType;
+ } else if (entityProxyIntf.equals(clientType)
+ || valueProxyIntf.equals(clientType)) {
+ domainType = objectType;
+ } else {
+ logger = logger.setType(clientType);
+ DomainMapper pv = new DomainMapper(logger);
+ visit(logger, clientType.getInternalName(), pv);
+ if (pv.getDomainInternalName() == null) {
+ logger.poison("%s has no mapping to a domain type (e.g. @%s or @%s)",
+ print(clientType), ProxyFor.class.getSimpleName(),
+ Service.class.getSimpleName());
+ domainType = errorType;
+ } else {
+ domainType = Type.getObjectType(pv.getDomainInternalName());
+ }
+ if (pv.getLocatorInternalName() != null) {
+ Type locatorType = Type.getObjectType(pv.getLocatorInternalName());
+ clientToLocatorMap.put(clientType, locatorType);
+ }
+ }
+ addToDomainMap(logger, domainType, clientType);
+ maybeCheckProxyType(logger, clientType);
+ return domainType;
+ }
+
+ /**
+ * Collect all of the methods defined within a type hierarchy.
+ */
+ private Set<RFMethod> getMethodsInHierarchy(ErrorContext logger,
+ Type domainType) {
+ Set<RFMethod> toReturn = methodsInHierarchy.get(domainType);
+ if (toReturn == null) {
+ logger = logger.setType(domainType);
+ toReturn = new MethodsInHierarchyCollector(logger).exec(domainType.getInternalName());
+ methodsInHierarchy.put(domainType, Collections.unmodifiableSet(toReturn));
+ }
+ return toReturn;
+ }
+
+ /**
+ * Examines a generic RequestContext method declaration and determines the
+ * expected domain return type. This implementation is limited in that it will
+ * not attempt to resolve type bounds since that would essentially require
+ * implementing TypeOracle. In the case where the type bound cannot be
+ * resolved, this method will return Object's type.
+ */
+ private Type getReturnType(ErrorContext logger, RFMethod method) {
+ logger = logger.setMethod(method);
+ final String[] returnType = {objectType.getInternalName()};
+ String signature = method.getSignature();
+
+ final int expectedCount;
+ if (method.getReturnType().equals(instanceRequestIntf)) {
+ expectedCount = 2;
+ } else if (method.getReturnType().equals(requestIntf)) {
+ expectedCount = 1;
+ } else {
+ logger.spam("Punting on " + signature);
+ return Type.getObjectType(returnType[0]);
+ }
+
+ // TODO(bobv): If a class-based TypeOracle is built, use that instead
+ new SignatureReader(signature).accept(new SignatureAdapter() {
+ @Override
+ public SignatureVisitor visitReturnType() {
+ return new SignatureAdapter() {
+ int count;
+
+ @Override
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ if (++count == expectedCount) {
+ return new SignatureAdapter() {
+ @Override
+ public void visitClassType(String name) {
+ returnType[0] = name;
+ }
+ };
+ }
+ return super.visitTypeArgument(wildcard);
+ }
+ };
+ }
+ });
+
+ logger.spam("Extracted " + returnType[0]);
+ return Type.getObjectType(returnType[0]);
+ }
+
+ private List<Type> getSupertypes(ErrorContext logger, Type type) {
+ if (type.getSort() != Type.OBJECT) {
+ return Collections.emptyList();
+ }
+ List<Type> toReturn = supertypes.get(type);
+ if (toReturn != null) {
+ return toReturn;
+ }
+
+ logger = logger.setType(type);
+
+ toReturn = new SupertypeCollector(logger).exec(type);
+ supertypes.put(type, Collections.unmodifiableList(toReturn));
+ return toReturn;
+ }
+
+ private boolean isAssignable(ErrorContext logger, Type possibleSupertype,
+ Type possibleSubtype) {
+ // Fast-path for same type
+ if (possibleSupertype.equals(possibleSubtype)) {
+ return true;
+ }
+
+ // Supertype calculation is cached
+ List<Type> allSupertypes = getSupertypes(logger, possibleSubtype);
+ return allSupertypes.contains(possibleSupertype);
+ }
+
+ private boolean isAssignable(ErrorContext logger, Type[] possibleSupertypes,
+ Type[] possibleSubtypes) {
+ // Check the same number of types
+ if (possibleSupertypes.length != possibleSubtypes.length) {
+ return false;
+ }
+ for (int i = 0, j = possibleSupertypes.length; i < j; i++) {
+ if (!isAssignable(logger, possibleSupertypes[i], possibleSubtypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isCollectionType(
+ @SuppressWarnings("unused") ErrorContext logger, Type type) {
+ // keeping the logger arg just for internal consistency for our small minds
+ return "java/util/List".equals(type.getInternalName())
+ || "java/util/Set".equals(type.getInternalName());
+ }
+
+ /**
+ * Keep in sync with {@code ReflectiveServiceLayer.isKeyType()}.
+ */
+ private boolean isResolvedKeyType(ErrorContext logger, Type type) {
+ if (isValueType(logger, type)) {
+ return true;
+ }
+
+ // We have already seen a mapping for the key type
+ if (domainToClientType.containsKey(type)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean isValueType(ErrorContext logger, Type type) {
+ if (type.getSort() != Type.OBJECT) {
+ return true;
+ }
+ if (valueTypes.contains(type)) {
+ return true;
+ }
+ logger = logger.setType(type);
+ if (isAssignable(logger, enumType, type)) {
+ return true;
+ }
+ return false;
+ }
+
+ private Type maybeBoxType(Type maybePrimitive) {
+ if (maybePrimitive.getSort() == Type.OBJECT) {
+ return maybePrimitive;
+ }
+ return getBoxedType(maybePrimitive);
+ }
+
+ /**
+ * Examine an array of Types and call {@link #validateEntityProxy(String)} or
+ * {@link #validateValueProxy(String)} if the type is a proxy.
+ */
+ private void maybeCheckProxyType(ErrorContext logger, Type... types) {
+ for (Type type : types) {
+ if (isAssignable(logger, entityProxyIntf, type)) {
+ validateEntityProxy(type.getClassName());
+ } else if (isAssignable(logger, valueProxyIntf, type)) {
+ validateValueProxy(type.getClassName());
+ } else if (isAssignable(logger, baseProxyIntf, type)) {
+ logger.poison(
+ "Invalid type hierarchy for %s. Only types derived from %s or %s may be used.",
+ print(type), print(entityProxyIntf), print(valueProxyIntf));
+ }
+ }
+ }
+
+ /**
+ * Examine the arguments and return value of a method and check any
+ * EntityProxies referred.
+ */
+ private void maybeCheckReferredProxies(ErrorContext logger, RFMethod method) {
+ if (method.getSignature() != null) {
+ TypesInSignatureCollector collector = new TypesInSignatureCollector();
+ SignatureReader reader = new SignatureReader(method.getSignature());
+ reader.accept(collector);
+ maybeCheckProxyType(logger, collector.getFound());
+ } else {
+ Type[] argTypes = method.getArgumentTypes();
+ Type returnType = getReturnType(logger, method);
+
+ // Check EntityProxy args ond return types against the domain
+ maybeCheckProxyType(logger, argTypes);
+ maybeCheckProxyType(logger, returnType);
+ }
+ }
+
+ private void validateProxy(String binaryName, Type expectedType,
+ boolean requireId) {
+ if (fastFail(binaryName)) {
+ return;
+ }
+
+ Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+ ErrorContext logger = parentLogger.setType(proxyType);
+
+ // Quick sanity check for calling code
+ if (!isAssignable(logger, expectedType, proxyType)) {
+ parentLogger.poison("%s is not a %s", print(proxyType),
+ print(expectedType));
+ return;
+ }
+
+ // Find the domain type
+ Type domainType = getDomainType(logger, proxyType);
+ if (domainType == errorType) {
+ logger.poison(
+ "The type %s must be annotated with a @%s or @%s annotation",
+ BinaryName.toSourceName(binaryName), ProxyFor.class.getSimpleName(),
+ ProxyForName.class.getSimpleName());
+ return;
+ }
+
+ // Check for getId() and getVersion() in domain if no locator is specified
+ if (requireId) {
+ Type locatorType = clientToLocatorMap.get(proxyType);
+ if (locatorType == null) {
+ checkIdAndVersion(logger, domainType);
+ }
+ }
+
+ // Collect all methods in the client proxy type
+ Set<RFMethod> clientPropertyMethods = getMethodsInHierarchy(logger,
+ proxyType);
+
+ // Find the equivalent domain getter/setter method
+ for (RFMethod clientPropertyMethod : clientPropertyMethods) {
+ // Ignore stableId(). Can't use descriptor due to overrides
+ if ("stableId".equals(clientPropertyMethod.getName())
+ && clientPropertyMethod.getArgumentTypes().length == 0) {
+ continue;
+ }
+ checkPropertyMethod(logger, clientPropertyMethod, domainType);
+ maybeCheckReferredProxies(logger, clientPropertyMethod);
+ }
+ }
+
+ /**
+ * Load the classfile for the given binary name and apply the provided
+ * visitor.
+ *
+ * @return <code>true</code> if the visitor was successfully visited
+ */
+ private boolean visit(ErrorContext logger, String internalName,
+ ClassVisitor visitor) {
+ assert Name.isInternalName(internalName) : "internalName";
+ logger.spam("Visiting " + internalName);
+ InputStream inputStream = null;
+ try {
+ inputStream = loader.getResourceAsStream(internalName + ".class");
+ if (inputStream == null) {
+ logger.poison("Could not find class file for " + internalName);
+ return false;
+ }
+ ClassReader reader = new ClassReader(inputStream);
+ reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG
+ | ClassReader.SKIP_FRAMES);
+ return true;
+ } catch (IOException e) {
+ logger.poison("Unable to open " + internalName, e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
new file mode 100644
index 0000000..1395ba8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
@@ -0,0 +1,877 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.dev.asm.AnnotationVisitor;
+import com.google.gwt.dev.asm.Attribute;
+import com.google.gwt.dev.asm.ClassAdapter;
+import com.google.gwt.dev.asm.ClassReader;
+import com.google.gwt.dev.asm.ClassVisitor;
+import com.google.gwt.dev.asm.ClassWriter;
+import com.google.gwt.dev.asm.FieldVisitor;
+import com.google.gwt.dev.asm.Label;
+import com.google.gwt.dev.asm.MethodAdapter;
+import com.google.gwt.dev.asm.MethodVisitor;
+import com.google.gwt.dev.asm.Opcodes;
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.gwt.dev.util.Name;
+import com.google.gwt.dev.util.Util;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ErrorContext;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.Loader;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.DefaultProxyStore;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.JsonRpcContent;
+import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.JsonRpcWireName;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.LoggingRequest;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.ProxySerializer;
+import com.google.web.bindery.requestfactory.shared.ProxyStore;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.RequestTransport;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.Violation;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+import com.google.web.bindery.requestfactory.vm.RequestFactorySource;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+
+/**
+ * Used to extract RequestFactory client jars from {@code gwt-user.jar}.
+ */
+public class RequestFactoryJarExtractor {
+ /*
+ * The FooProcessor types are ASM visitors that traverse the bytecode, calling
+ * one of the various processFoo() methods. The visitors will also update the
+ * bytecode with the rebased type names that are returned from the
+ * processFoo() methods.
+ */
+
+ /**
+ * Describes a way to emit the contents of a classpath, typically into a JAR
+ * or filesystem directory.
+ */
+ public interface Emitter {
+ void close() throws IOException;
+
+ void emit(String path, InputStream contents) throws IOException;
+ }
+
+ /**
+ * An Emitter implementation that creates a jar file.
+ */
+ public static class JarEmitter implements Emitter {
+ private int rawByteSize;
+ private final JarOutputStream out;
+
+ public JarEmitter(File outFile) throws IOException {
+ Manifest m = new Manifest();
+ m.getMainAttributes().putValue("Created-By",
+ RequestFactoryJarExtractor.class.getCanonicalName());
+ m.getMainAttributes();
+ out = new JarOutputStream(new FileOutputStream(outFile), m);
+ }
+
+ public void close() throws IOException {
+ out.close();
+ }
+
+ public void emit(String path, InputStream contents) throws IOException {
+ ZipEntry entry = new ZipEntry(path);
+ out.putNextEntry(entry);
+ byte[] bytes = new byte[4096];
+ int read;
+ for (;;) {
+ read = contents.read(bytes);
+ if (read == -1) {
+ break;
+ }
+ rawByteSize += read;
+ out.write(bytes, 0, read);
+ }
+ out.closeEntry();
+ }
+ }
+
+ /**
+ * Controls what is emitted by the tool.
+ */
+ public enum Mode {
+ BOTH(true, true) {
+ @Override
+ protected boolean matches(String target) {
+ return target.endsWith(CODE_AND_SOURCE);
+ }
+ },
+ SOURCE(false, true) {
+ @Override
+ protected boolean matches(String target) {
+ return target.endsWith(SOURCE_ONLY);
+ }
+ },
+ // Order is important, must be last
+ CLASSES(true, false) {
+ @Override
+ protected boolean matches(String target) {
+ return true;
+ }
+ };
+
+ public static Mode match(String target) {
+ for (Mode mode : Mode.values()) {
+ if (mode.matches(target)) {
+ return mode;
+ }
+ }
+ return null;
+ }
+
+ private final boolean emitClasses;
+ private final boolean emitSource;
+
+ private Mode(boolean emitClasses, boolean emitSource) {
+ this.emitClasses = emitClasses;
+ this.emitSource = emitSource;
+ }
+
+ public boolean isEmitClasses() {
+ return emitClasses;
+ }
+
+ public boolean isEmitSource() {
+ return emitSource;
+ }
+
+ protected abstract boolean matches(String target);
+ }
+
+ private class AnnotationProcessor implements AnnotationVisitor {
+ private final String sourceType;
+ private final AnnotationVisitor av;
+
+ public AnnotationProcessor(String sourceType, AnnotationVisitor av) {
+ this.sourceType = sourceType;
+ this.av = av;
+ }
+
+ public void visit(String name, Object value) {
+ value = processConstant(sourceType, value);
+ av.visit(name, value);
+ }
+
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ desc = processDescriptor(sourceType, desc);
+ return new AnnotationProcessor(desc, av.visitAnnotation(name, desc));
+ }
+
+ public AnnotationVisitor visitArray(String name) {
+ return new AnnotationProcessor(name, av.visitArray(name));
+ }
+
+ public void visitEnd() {
+ av.visitEnd();
+ }
+
+ public void visitEnum(String name, String desc, String value) {
+ desc = processDescriptor(sourceType, desc);
+ av.visitEnum(name, desc, value);
+ }
+ }
+
+ private class ClassProcessor extends ClassAdapter {
+ private State state;
+ private String sourceType;
+
+ public ClassProcessor(String sourceType, ClassVisitor cv, State state) {
+ super(cv);
+ this.sourceType = sourceType;
+ this.state = state;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ name = processInternalName(sourceType, name);
+ superName = processInternalName(sourceType, superName);
+ if (interfaces != null) {
+ for (int i = 0, j = interfaces.length; i < j; i++) {
+ interfaces[i] = processInternalName(sourceType, interfaces[i]);
+ }
+ }
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ desc = processDescriptor(sourceType, desc);
+ return new AnnotationProcessor(sourceType, super.visitAnnotation(desc, visible));
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ desc = processDescriptor(sourceType, desc);
+ return new FieldProcessor(sourceType, super.visitField(access, name, desc, signature,
+ value));
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName,
+ String innerName, int access) {
+ name = processInternalName(sourceType, name);
+ outerName = processInternalName(sourceType, outerName);
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ Method method = processMethod(sourceType, name, desc);
+ desc = method.getDescriptor();
+ if (exceptions != null) {
+ for (int i = 0, j = exceptions.length; i < j; i++) {
+ exceptions[i] = processInternalName(sourceType, exceptions[i]);
+ }
+ }
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature,
+ exceptions);
+ if (mv != null) {
+ mv = new MethodProcessor(sourceType, mv);
+ }
+ return mv;
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ owner = processInternalName(sourceType, owner);
+ if (desc != null) {
+ desc = processMethod(sourceType, name, desc).getDescriptor();
+ }
+ super.visitOuterClass(owner, name, desc);
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ if (source != null) {
+ state.source = source;
+ }
+ super.visitSource(source, debug);
+ }
+ }
+
+ /**
+ * A unit of work to write one class and its source file into the Emitter.
+ */
+ private class EmitOneType implements Callable<Void> {
+ private final State state;
+
+ /**
+ * @param state
+ */
+ private EmitOneType(State state) {
+ this.state = state;
+ }
+
+ public Void call() throws Exception {
+ if (mode.isEmitClasses()) {
+ String fileName = state.type.getInternalName();
+ if (fileName == null) {
+ System.err.println("Got null filename from " + state.type);
+ return null;
+ }
+ fileName += ".class";
+ emitter.emit(fileName, state.contents);
+ }
+ if (mode.isEmitSource()) {
+ String sourcePath = getPackagePath(state.originalType) + state.source;
+ String destPath = getPackagePath(state.type) + state.source;
+ if (sources.add(sourcePath) && loader.exists(sourcePath)) {
+ String contents = Util.readStreamAsString(loader.getResourceAsStream(sourcePath));
+ emitter.emit(destPath,
+ new ByteArrayInputStream(Util.getBytes(contents)));
+ }
+ }
+ return null;
+ }
+ }
+
+ // There is no FieldAdapter type
+ private class FieldProcessor implements FieldVisitor {
+ private final String sourceType;
+ private final FieldVisitor fv;
+
+ public FieldProcessor(String sourceType, FieldVisitor fv) {
+ this.sourceType = sourceType;
+ this.fv = fv;
+ }
+
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return new AnnotationProcessor(sourceType, fv.visitAnnotation(desc, visible));
+ }
+
+ public void visitAttribute(Attribute attr) {
+ fv.visitAttribute(attr);
+ }
+
+ public void visitEnd() {
+ fv.visitEnd();
+ }
+ }
+
+ private class MethodProcessor extends MethodAdapter {
+ private final String sourceType;
+
+ public MethodProcessor(String sourceType, MethodVisitor mv) {
+ super(mv);
+ this.sourceType = sourceType;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ desc = processDescriptor(sourceType, desc);
+ return super.visitAnnotation(desc, visible);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ return new AnnotationProcessor(sourceType, super.visitAnnotationDefault());
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name,
+ String desc) {
+ owner = processInternalName(sourceType, owner);
+ desc = processDescriptor(sourceType, desc);
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack,
+ Object[] stack) {
+ for (int i = 0, j = local.length; i < j; i++) {
+ if (local[i] instanceof String) {
+ local[i] = processInternalName(sourceType, (String) local[i]);
+ }
+ }
+ for (int i = 0, j = stack.length; i < j; i++) {
+ if (stack[i] instanceof String) {
+ stack[i] = processInternalName(sourceType, (String) stack[i]);
+ }
+ }
+ super.visitFrame(type, nLocal, local, nStack, stack);
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ cst = processConstant(sourceType, cst);
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ desc = processDescriptor(sourceType, desc);
+ super.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc) {
+ owner = processInternalName(sourceType, owner);
+ desc = processMethod(sourceType, name, desc).getDescriptor();
+ super.visitMethodInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ desc = processDescriptor(sourceType, desc);
+ super.visitMultiANewArrayInsn(desc, dims);
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(int parameter,
+ String desc, boolean visible) {
+ desc = processDescriptor(sourceType, desc);
+ return super.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler,
+ String type) {
+ type = processInternalName(sourceType, type);
+ super.visitTryCatchBlock(start, end, handler, type);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ type = processInternalName(sourceType, type);
+ super.visitTypeInsn(opcode, type);
+ }
+ }
+
+ /**
+ * Replaces native methods with stub implementations that throw an exception.
+ * This allows any dangling GWT types to be loaded by a JVM without triggering
+ * an {@link UnsatisfiedLinkError}.
+ */
+ private class NativeMethodDefanger extends ClassAdapter {
+ public NativeMethodDefanger(ClassVisitor cv) {
+ super(cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ if ((access & Opcodes.ACC_NATIVE) != 0) {
+ MethodVisitor mv = super.visitMethod(access & ~Opcodes.ACC_NATIVE,
+ name, desc, signature, exceptions);
+ if (mv != null) {
+ mv.visitCode();
+ String exceptionName = Type.getInternalName(RuntimeException.class);
+ // <empty>
+ mv.visitTypeInsn(Opcodes.NEW, exceptionName);
+ // obj
+ mv.visitInsn(Opcodes.DUP);
+ // obj, obj
+ mv.visitLdcInsn(NATIVE_METHOD_ERROR);
+ // obj, obj, string
+ mv.visitMethodInsn(Opcodes.INVOKESPECIAL, exceptionName, "<init>",
+ "(Ljava/lang/String;)V");
+ // obj
+ mv.visitInsn(Opcodes.ATHROW);
+
+ // Count argument slots - long and double arguments each take up 2 slots
+ int numSlots = 0;
+ for (Type t : Type.getArgumentTypes(desc)) {
+ numSlots += t.getSize();
+ }
+ if ((access & Opcodes.ACC_STATIC) == 0) {
+ // Add one for 'this' reference
+ numSlots++;
+ }
+ mv.visitMaxs(3, numSlots);
+ mv.visitEnd();
+ }
+ return null;
+ } else {
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ }
+ }
+
+ /**
+ * This is the main bytecode-processing entry point. It will read in one
+ * classfile and produce a mutated copy. Any referenced types will be enqueued
+ * via {@link RequestFactoryJarExtractor#processType(String, Type)}.
+ */
+ private class ProcessOneType implements Callable<State> {
+
+ private final State state;
+ private final String typeName;
+
+ public ProcessOneType(Type type) {
+ state = new State(type);
+ typeName = type.getClassName();
+ }
+
+ public State call() {
+ ClassWriter writer = new ClassWriter(0);
+ ClassVisitor cv = writer;
+ cv = new ClassProcessor(typeName, cv, state);
+ cv = new NativeMethodDefanger(cv);
+ visit(logger.setType(state.type), loader, state.type.getInternalName(),
+ cv);
+ state.contents = new ByteArrayInputStream(writer.toByteArray());
+ assert seen.containsKey(state.originalType) : "No type for "
+ + state.type.getClassName();
+ state.type = seen.get(state.originalType);
+
+ emit(state);
+ return state;
+ }
+ }
+
+ /**
+ * Metadata about a single type.
+ */
+ private static class State {
+ boolean containsNativeMethods;
+ /**
+ * Will contain the data to be written to disk, possibly mutated class data.
+ */
+ InputStream contents;
+ String source;
+ /**
+ * The possibly rebased type name.
+ */
+ Type type;
+ final Type originalType;
+
+ public State(Type type) {
+ this.originalType = this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(type.getInternalName());
+ if (containsNativeMethods) {
+ sb.append(" NATIVE");
+ }
+ if (source != null) {
+ sb.append(" ").append(source);
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * If true, print a trace of dependencies to System.out.
+ */
+ private static final boolean VERBOSE = false;
+
+ /**
+ *
+ */
+ private static final String CODE_AND_SOURCE = "+src";
+
+ /**
+ *
+ */
+ private static final String SOURCE_ONLY = "-src";
+
+ private static final String NATIVE_METHOD_ERROR = "Cannot call native method";
+
+ /**
+ * A map of target names to the types that target should use as a base.
+ */
+ private static final Map<String, List<Class<?>>> SEEDS = new LinkedHashMap<String, List<Class<?>>>();
+
+ /**
+ * Server public API classes and interfaces.
+ */
+ private static final Class<?>[] SERVER_CLASSES = {
+ DefaultExceptionHandler.class, ExceptionHandler.class, Logging.class, LoggingRequest.class,
+ RequestFactoryServlet.class, ServiceLayer.class, ServiceLayerDecorator.class,
+ SimpleRequestProcessor.class
+ };
+
+ /**
+ * Shared public API classes and interfaces.
+ */
+ private static final Class<?>[] SHARED_CLASSES = {
+ BaseProxy.class, DefaultProxyStore.class, EntityProxy.class, EntityProxyChange.class,
+ EntityProxyId.class, InstanceRequest.class, JsonRpcContent.class, JsonRpcProxy.class,
+ JsonRpcService.class, JsonRpcWireName.class, Locator.class, ProxyFor.class,
+ ProxyForName.class, ProxySerializer.class, ProxyStore.class, Receiver.class, Request.class,
+ RequestContext.class, RequestFactory.class, RequestTransport.class, ServerFailure.class,
+ Service.class, ServiceLocator.class, ServiceName.class, ValueProxy.class, Violation.class,
+ WriteOperation.class, RequestFactorySource.class, SimpleEventBus.class
+ };
+
+ /**
+ * Maximum number of threads to use to run the Extractor.
+ */
+ private static final int MAX_THREADS = 4;
+
+ static {
+ List<Class<?>> sharedClasses = Arrays.<Class<?>> asList(SHARED_CLASSES);
+
+ List<Class<?>> clientClasses = new ArrayList<Class<?>>();
+ clientClasses.addAll(sharedClasses);
+
+ List<Class<?>> serverClasses = new ArrayList<Class<?>>();
+ serverClasses.addAll(Arrays.<Class<?>> asList(SERVER_CLASSES));
+ serverClasses.addAll(sharedClasses);
+
+ SEEDS.put("client", Collections.unmodifiableList(clientClasses));
+ SEEDS.put("server", Collections.unmodifiableList(serverClasses));
+
+ Set<Class<?>> all = new LinkedHashSet<Class<?>>();
+ for (List<Class<?>> value : SEEDS.values()) {
+ all.addAll(value);
+ }
+ SEEDS.put("all", Collections.unmodifiableList(new ArrayList<Class<?>>(all)));
+
+ for (String target : new ArrayList<String>(SEEDS.keySet())) {
+ SEEDS.put(target + SOURCE_ONLY, SEEDS.get(target));
+ SEEDS.put(target + CODE_AND_SOURCE, SEEDS.get(target));
+ }
+
+ /*
+ * Allows the rebased package to be tested. This is done with a by-name
+ * lookup, since the gwt-user code is compiled separately from its tests.
+ */
+ try {
+ SEEDS.put(
+ "test" + CODE_AND_SOURCE,
+ Collections.unmodifiableList(Arrays.<Class<?>> asList(
+ Class.forName("com.google.web.bindery.requestfactory.vm.RequestFactoryJreSuite"),
+ Class.forName("com.google.web.bindery.requestfactory.server.SimpleBar"))));
+ } catch (ClassNotFoundException ignored) {
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length < 2) {
+ System.err.println("Usage: java -cp gwt-dev.jar:gwt-user.jar:json.jar"
+ + RequestFactoryJarExtractor.class.getCanonicalName()
+ + " <target-name> outfile.jar");
+ System.err.println("Valid targets:");
+ for (String target : SEEDS.keySet()) {
+ System.err.println(" " + target);
+ }
+ System.exit(1);
+ }
+ String target = args[0];
+ List<Class<?>> seeds = SEEDS.get(target);
+ if (seeds == null) {
+ System.err.println("Unknown target: " + target);
+ System.exit(1);
+ }
+ Mode mode = Mode.match(target);
+ Logger errorContext = Logger.getLogger(RequestFactoryJarExtractor.class.getName());
+ ClassLoaderLoader classLoader = new ClassLoaderLoader(
+ Thread.currentThread().getContextClassLoader());
+ JarEmitter jarEmitter = new JarEmitter(new File(args[1]));
+ RequestFactoryJarExtractor extractor = new RequestFactoryJarExtractor(
+ errorContext, classLoader, jarEmitter, seeds, mode);
+ extractor.run();
+ System.exit(extractor.isExecutionFailed() ? 1 : 0);
+ }
+
+ /**
+ * Given a Type, return a path-prefix based on the type's package.
+ */
+ private static String getPackagePath(Type t) {
+ String name = t.getInternalName();
+ return name.substring(0, name.lastIndexOf('/') + 1);
+ }
+
+ /**
+ * Load the classfile for the given binary name and apply the provided
+ * visitor.
+ *
+ * @return <code>true</code> if the visitor was successfully visited
+ */
+ private static boolean visit(ErrorContext logger, Loader loader,
+ String internalName, ClassVisitor visitor) {
+ assert Name.isInternalName(internalName) : "internalName";
+ logger.spam("Visiting " + internalName);
+ InputStream inputStream = null;
+ try {
+ inputStream = loader.getResourceAsStream(internalName + ".class");
+ if (inputStream == null) {
+ System.err.println("Could not find class file for " + internalName);
+ logger.poison("Could not find class file for " + internalName);
+ return false;
+ }
+ ClassReader reader = new ClassReader(inputStream);
+ reader.accept(visitor, 0);
+ return true;
+ } catch (IOException e) {
+ logger.poison("Unable to open " + internalName, e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean executionFailed = false;
+ private final Emitter emitter;
+ private final ExecutorService ex;
+ private final BlockingQueue<Future<?>> inProcess = new LinkedBlockingQueue<Future<?>>();
+ private final ErrorContext logger;
+ private final Loader loader;
+ private final Mode mode;
+ private final List<Class<?>> seeds;
+ private final Map<Type, Type> seen = new ConcurrentHashMap<Type, Type>();
+ private final Set<String> sources = new ConcurrentSkipListSet<String>();
+ private final ExecutorService writerService;
+
+ public RequestFactoryJarExtractor(Logger logger, Loader loader,
+ Emitter emitter, List<Class<?>> seeds, Mode mode) {
+ this.logger = new ErrorContext(logger);
+ this.loader = loader;
+ this.emitter = emitter;
+ this.seeds = seeds;
+ this.mode = mode;
+
+ int numThreads = Math.min(MAX_THREADS, Runtime.getRuntime().availableProcessors());
+ ex = Executors.newFixedThreadPool(numThreads);
+ writerService = Executors.newSingleThreadExecutor();
+ }
+
+ /**
+ * Blocks until all work has been finished.
+ */
+ public void run() throws IOException {
+ for (Class<?> seed : seeds) {
+ processType("seeds", Type.getType(seed));
+ }
+ // Wait for all tasks to be completed
+ while (!inProcess.isEmpty()) {
+ try {
+ Future<?> task = inProcess.take();
+ task.get();
+ } catch (InterruptedException retry) {
+ } catch (ExecutionException e) {
+ e.getCause().printStackTrace();
+ executionFailed = true;
+ }
+ }
+ emitter.close();
+ }
+
+ /**
+ * Write one type into the output.
+ */
+ private void emit(final State state) {
+ inProcess.add(writerService.submit(new EmitOneType(state)));
+ }
+
+ private boolean isExecutionFailed() {
+ return executionFailed;
+ }
+
+ /**
+ * Look at constant values from the bytecode, processing referenced types.
+ */
+ private Object processConstant(String sourceType, Object value) {
+ if (value instanceof Type) {
+ value = processType(sourceType, (Type) value);
+ }
+ return value;
+ }
+
+ /**
+ * Process the type represented by the descriptor, possibly returning a
+ * rebased descriptor.
+ */
+ private String processDescriptor(String sourceType, String desc) {
+ if (desc == null) {
+ return null;
+ }
+ return processType(sourceType, Type.getType(desc)).getDescriptor();
+ }
+
+ /**
+ * Process the type represented by the name, possibly returning a rebased
+ * name.
+ */
+ private String processInternalName(String sourceType, String internalName) {
+ if (internalName == null) {
+ return null;
+ }
+ return processType(sourceType, Type.getObjectType(internalName)).getInternalName();
+ }
+
+ /**
+ * Produce a rebased method declaration, also visiting referenced types.
+ */
+ private Method processMethod(String sourceType, String name, String desc) {
+ Method method = new Method(name, desc);
+ Type[] argumentTypes = method.getArgumentTypes();
+ for (int i = 0, j = argumentTypes.length; i < j; i++) {
+ argumentTypes[i] = processType(sourceType, argumentTypes[i]);
+ }
+ method = new Method(name, processType(sourceType, method.getReturnType()),
+ argumentTypes);
+ return method;
+ }
+
+ /**
+ * Process a type, possibly returning a rebased type.
+ * @param sourceType TODO
+ */
+ private Type processType(String sourceType, Type type) {
+ Type toReturn;
+ synchronized (seen) {
+ toReturn = seen.get(type);
+ if (toReturn != null) {
+ return toReturn;
+ }
+ toReturn = Type.getType(type.getDescriptor());
+ seen.put(type, toReturn);
+ }
+ int sort = type.getSort();
+ if (sort != Type.OBJECT && sort != Type.ARRAY) {
+ return toReturn;
+ }
+ if (sort == Type.ARRAY) {
+ processType(sourceType, type.getElementType());
+ return toReturn;
+ }
+ assert type.getInternalName().charAt(0) != 'L';
+ if (type.getInternalName().startsWith("java/")
+ || type.getInternalName().startsWith("javax/")) {
+ return toReturn;
+ }
+ if (VERBOSE) {
+ System.out.println(sourceType + " -> " + type.getClassName());
+ }
+ Future<State> future = ex.submit(new ProcessOneType(type));
+ inProcess.add(future);
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryServlet.java
new file mode 100644
index 0000000..9d45633
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryServlet.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.user.server.rpc.RPCServletUtils;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Handles GWT RequestFactory JSON requests.
+ */
+@SuppressWarnings("serial")
+public class RequestFactoryServlet extends HttpServlet {
+
+ private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
+ private static final String JSON_CHARSET = "UTF-8";
+ private static final String JSON_CONTENT_TYPE = "application/json";
+ private static final Logger log = Logger.getLogger(RequestFactoryServlet.class.getCanonicalName());
+
+ /**
+ * These ThreadLocals are used to allow service objects to obtain access to
+ * the HTTP transaction.
+ */
+ private static final ThreadLocal<HttpServletRequest> perThreadRequest = new ThreadLocal<HttpServletRequest>();
+ private static final ThreadLocal<HttpServletResponse> perThreadResponse = new ThreadLocal<HttpServletResponse>();
+
+ /**
+ * Returns the thread-local {@link HttpServletRequest}.
+ *
+ * @return an {@link HttpServletRequest} instance
+ */
+ public static HttpServletRequest getThreadLocalRequest() {
+ return perThreadRequest.get();
+ }
+
+ /**
+ * Returns the thread-local {@link HttpServletResponse}.
+ *
+ * @return an {@link HttpServletResponse} instance
+ */
+ public static HttpServletResponse getThreadLocalResponse() {
+ return perThreadResponse.get();
+ }
+
+ private final SimpleRequestProcessor processor;
+
+ /**
+ * Constructs a new {@link RequestFactoryServlet} with a
+ * {@code DefaultExceptionHandler}.
+ */
+ public RequestFactoryServlet() {
+ this(new DefaultExceptionHandler());
+ }
+
+ /**
+ * Use this constructor in subclasses to provide a custom
+ * {@link ExceptionHandler}.
+ *
+ * @param exceptionHandler an {@link ExceptionHandler} instance
+ * @param serviceDecorators an array of ServiceLayerDecorators that change how
+ * the RequestFactory request processor interact with the domain
+ * objects
+ */
+ public RequestFactoryServlet(ExceptionHandler exceptionHandler,
+ ServiceLayerDecorator... serviceDecorators) {
+ processor = new SimpleRequestProcessor(
+ ServiceLayer.create(serviceDecorators));
+ processor.setExceptionHandler(exceptionHandler);
+ }
+
+ /**
+ * Processes a POST to the server.
+ *
+ * @param request an {@link HttpServletRequest} instance
+ * @param response an {@link HttpServletResponse} instance
+ * @throws IOException if an internal I/O error occurs
+ * @throws ServletException if an error occurs in the servlet
+ */
+ @Override
+ protected void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException {
+
+ perThreadRequest.set(request);
+ perThreadResponse.set(response);
+
+ // No new code should be placed outside of this try block.
+ try {
+ ensureConfig();
+ String jsonRequestString = RPCServletUtils.readContent(request,
+ JSON_CONTENT_TYPE, JSON_CHARSET);
+ if (DUMP_PAYLOAD) {
+ System.out.println(">>> " + jsonRequestString);
+ }
+
+ try {
+ String payload = processor.process(jsonRequestString);
+ if (DUMP_PAYLOAD) {
+ System.out.println("<<< " + payload);
+ }
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType(RequestFactory.JSON_CONTENT_TYPE_UTF8);
+ // The Writer must be obtained after setting the content type
+ PrintWriter writer = response.getWriter();
+ writer.print(payload);
+ writer.flush();
+ } catch (RuntimeException e) {
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ log.log(Level.SEVERE, "Unexpected error", e);
+ }
+ } finally {
+ perThreadRequest.set(null);
+ perThreadResponse.set(null);
+ }
+ }
+
+ private void ensureConfig() {
+ String symbolMapsDirectory = getServletConfig().getInitParameter(
+ "symbolMapsDirectory");
+ if (symbolMapsDirectory != null) {
+ Logging.setSymbolMapsDirectory(symbolMapsDirectory);
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestState.java b/user/src/com/google/web/bindery/requestfactory/server/RequestState.java
new file mode 100644
index 0000000..e167c41
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestState.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
+import com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.IdToEntityMap;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+import com.google.web.bindery.requestfactory.shared.impl.EntityCodex;
+import com.google.web.bindery.requestfactory.shared.impl.IdFactory;
+import com.google.web.bindery.requestfactory.shared.impl.MessageFactoryHolder;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Encapsulates all state relating to the processing of a single request so that
+ * the SimpleRequestProcessor can be stateless.
+ */
+class RequestState implements EntityCodex.EntitySource {
+ final IdToEntityMap beans = new IdToEntityMap();
+ private final Map<Object, SimpleProxyId<?>> domainObjectsToId;
+ private final IdFactory idFactory;
+ private final ServiceLayer service;
+ private final Resolver resolver;
+
+ public RequestState(RequestState parent) {
+ idFactory = parent.idFactory;
+ domainObjectsToId = parent.domainObjectsToId;
+ service = parent.service;
+ resolver = new Resolver(this);
+ }
+
+ public RequestState(final ServiceLayer service) {
+ this.service = service;
+ idFactory = new IdFactory() {
+ @Override
+ public boolean isEntityType(Class<?> clazz) {
+ return EntityProxy.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ public boolean isValueType(Class<?> clazz) {
+ return ValueProxy.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
+ return (Class<P>) service.resolveClass(typeToken);
+ }
+
+ @Override
+ protected String getTypeToken(Class<? extends BaseProxy> clazz) {
+ return service.resolveTypeToken(clazz);
+ }
+ };
+ domainObjectsToId = new IdentityHashMap<Object, SimpleProxyId<?>>();
+ resolver = new Resolver(this);
+ }
+
+ /**
+ * Turn a domain value into a wire format message.
+ */
+ public Splittable flatten(Object domainValue) {
+ Splittable flatValue;
+ if (ValueCodex.canDecode(domainValue.getClass())) {
+ flatValue = ValueCodex.encode(domainValue);
+ } else {
+ flatValue = new SimpleRequestProcessor(service).createOobMessage(Collections.singletonList(domainValue));
+ }
+ return flatValue;
+ }
+
+ /**
+ * Get or create a BaseProxy AutoBean for the given id.
+ */
+ public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+ SimpleProxyId<Q> id, Object domainObject) {
+ @SuppressWarnings("unchecked")
+ AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
+ if (toReturn == null) {
+ toReturn = createProxyBean(id, domainObject);
+ }
+ return toReturn;
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+ Splittable serializedProxyId) {
+ IdMessage idMessage = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
+ IdMessage.class, serializedProxyId).as();
+ @SuppressWarnings("unchecked")
+ AutoBean<Q> toReturn = (AutoBean<Q>) getBeansForPayload(
+ Collections.singletonList(idMessage)).get(0);
+ return toReturn;
+ }
+
+ /**
+ * Get or create BaseProxy AutoBeans for a list of id-bearing messages.
+ */
+ public List<AutoBean<? extends BaseProxy>> getBeansForPayload(
+ List<? extends IdMessage> idMessages) {
+ List<SimpleProxyId<?>> ids = new ArrayList<SimpleProxyId<?>>(
+ idMessages.size());
+ for (IdMessage idMessage : idMessages) {
+ SimpleProxyId<?> id;
+ if (Strength.SYNTHETIC.equals(idMessage.getStrength())) {
+ Class<? extends BaseProxy> clazz = service.resolveClass(idMessage.getTypeToken());
+ id = idFactory.allocateSyntheticId(clazz, idMessage.getSyntheticId());
+ } else {
+ String decodedId = idMessage.getServerId() == null ? null
+ : SimpleRequestProcessor.fromBase64(idMessage.getServerId());
+ id = idFactory.getId(idMessage.getTypeToken(), decodedId,
+ idMessage.getClientId());
+ }
+ ids.add(id);
+ }
+ return getBeansForIds(ids);
+ }
+
+ public IdFactory getIdFactory() {
+ return idFactory;
+ }
+
+ public Resolver getResolver() {
+ return resolver;
+ }
+
+ /**
+ * EntityCodex support. This method is identical to
+ * {@link IdFactory#getHistoryToken(SimpleProxyId)} except that it
+ * base64-encodes the server ids.
+ * <p>
+ * XXX: Merge this with AbstsractRequestContext's implementation
+ */
+ public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
+ AutoBean<IdMessage> bean = MessageFactoryHolder.FACTORY.id();
+ IdMessage ref = bean.as();
+ ref.setTypeToken(service.resolveTypeToken(stableId.getProxyClass()));
+ if (stableId.isSynthetic()) {
+ ref.setStrength(Strength.SYNTHETIC);
+ ref.setSyntheticId(stableId.getSyntheticId());
+ } else if (stableId.isEphemeral()) {
+ ref.setStrength(Strength.EPHEMERAL);
+ ref.setClientId(stableId.getClientId());
+ } else {
+ ref.setServerId(SimpleRequestProcessor.toBase64(stableId.getServerId()));
+ }
+ return AutoBeanCodex.encode(bean);
+ }
+
+ public ServiceLayer getServiceLayer() {
+ return service;
+ }
+
+ /**
+ * If the given domain object has been previously associated with an id,
+ * return it.
+ */
+ public SimpleProxyId<?> getStableId(Object domain) {
+ return domainObjectsToId.get(domain);
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public boolean isEntityType(Class<?> clazz) {
+ return idFactory.isEntityType(clazz);
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public boolean isValueType(Class<?> clazz) {
+ return idFactory.isValueType(clazz);
+ }
+
+ /**
+ * Creates an AutoBean for the given id, tracking a domain object.
+ */
+ private <Q extends BaseProxy> AutoBean<Q> createProxyBean(
+ SimpleProxyId<Q> id, Object domainObject) {
+ AutoBean<Q> toReturn = AutoBeanFactorySource.createBean(id.getProxyClass(),
+ SimpleRequestProcessor.CONFIGURATION);
+ toReturn.setTag(Constants.STABLE_ID, id);
+ toReturn.setTag(Constants.DOMAIN_OBJECT, domainObject);
+ beans.put(id, toReturn);
+ return toReturn;
+ }
+
+ /**
+ * Returns the AutoBeans corresponding to the given ids, or creates them if
+ * they do not yet exist.
+ */
+ private List<AutoBean<? extends BaseProxy>> getBeansForIds(
+ List<SimpleProxyId<?>> ids) {
+ List<Class<?>> domainClasses = new ArrayList<Class<?>>(ids.size());
+ List<Object> domainIds = new ArrayList<Object>(ids.size());
+ List<SimpleProxyId<?>> idsToLoad = new ArrayList<SimpleProxyId<?>>();
+
+ /*
+ * Create proxies for ephemeral or synthetic ids that we haven't seen. Queue
+ * up the domain ids for entities that need to be loaded.
+ */
+ for (SimpleProxyId<?> id : ids) {
+ Class<?> domainClass = service.resolveDomainClass(id.getProxyClass());
+ if (beans.containsKey(id)) {
+ // Already have a proxy for this id, no-op
+ } else if (id.isEphemeral() || id.isSynthetic()) {
+ // Create a new domain object for the short-lived id
+ Object domain = service.createDomainObject(domainClass);
+ if (domain == null) {
+ throw new UnexpectedException("Could not create instance of "
+ + domainClass.getCanonicalName(), null);
+ }
+ AutoBean<? extends BaseProxy> bean = createProxyBean(id, domain);
+ beans.put(id, bean);
+ domainObjectsToId.put(domain, id);
+ } else {
+ // Decode the domain parameter
+ Splittable split = StringQuoter.split(id.getServerId());
+ Class<?> param = service.getIdType(domainClass);
+ Object domainParam;
+ if (ValueCodex.canDecode(param)) {
+ domainParam = ValueCodex.decode(param, split);
+ } else {
+ domainParam = new SimpleRequestProcessor(service).decodeOobMessage(
+ param, split).get(0);
+ }
+
+ // Enqueue
+ domainClasses.add(service.resolveDomainClass(id.getProxyClass()));
+ domainIds.add(domainParam);
+ idsToLoad.add(id);
+ }
+ }
+
+ // Actually load the data
+ if (!domainClasses.isEmpty()) {
+ assert domainClasses.size() == domainIds.size()
+ && domainClasses.size() == idsToLoad.size();
+ List<Object> loaded = service.loadDomainObjects(domainClasses, domainIds);
+ if (idsToLoad.size() != loaded.size()) {
+ throw new UnexpectedException("Expected " + idsToLoad.size()
+ + " objects to be loaded, got " + loaded.size(), null);
+ }
+
+ Iterator<Object> itLoaded = loaded.iterator();
+ for (SimpleProxyId<?> id : idsToLoad) {
+ Object domain = itLoaded.next();
+ domainObjectsToId.put(domain, id);
+ AutoBean<? extends BaseProxy> bean = createProxyBean(id, domain);
+ beans.put(id, bean);
+ }
+ }
+
+ // Construct the return value
+ List<AutoBean<? extends BaseProxy>> toReturn = new ArrayList<AutoBean<? extends BaseProxy>>(
+ ids.size());
+ for (SimpleProxyId<?> id : ids) {
+ toReturn.add(beans.get(id));
+ }
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Resolver.java b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
new file mode 100644
index 0000000..9f641eb
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Responsible for converting between domain and client entities. This class has
+ * a small amount of temporary state used to handle graph cycles and assignment
+ * of synthetic ids.
+ *
+ * @see RequestState#getResolver()
+ */
+class Resolver {
+ /**
+ * A parameterized type with a single parameter.
+ */
+ private static class CollectionType implements ParameterizedType {
+ private final Class<?> rawType;
+ private final Class<?> elementType;
+
+ private CollectionType(Class<?> rawType, Class<?> elementType) {
+ this.rawType = rawType;
+ this.elementType = elementType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof CollectionType)) {
+ return false;
+ }
+ CollectionType other = (CollectionType) o;
+ return rawType.equals(other.rawType)
+ && elementType.equals(other.elementType);
+ }
+
+ public Type[] getActualTypeArguments() {
+ return new Type[] {elementType};
+ }
+
+ public Type getOwnerType() {
+ return null;
+ }
+
+ public Type getRawType() {
+ return rawType;
+ }
+
+ @Override
+ public int hashCode() {
+ return rawType.hashCode() * 13 + elementType.hashCode() * 7;
+ }
+ }
+
+ /**
+ * Used to map the objects being resolved and its API slice to the client-side
+ * value. This handles the case where a domain object is returned to the
+ * client mapped to two proxies of differing types.
+ */
+ private static class ResolutionKey {
+ private final Object domainObject;
+ private final int hashCode;
+ private final Type requestedType;
+
+ public ResolutionKey(Object domainObject, Type requestedType) {
+ this.domainObject = domainObject;
+ this.requestedType = requestedType;
+ this.hashCode = System.identityHashCode(domainObject) * 13
+ + requestedType.hashCode() * 7;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ResolutionKey)) {
+ return false;
+ }
+ ResolutionKey other = (ResolutionKey) o;
+ // Object identity comparison intentional
+ if (domainObject != other.domainObject) {
+ return false;
+ }
+ if (!requestedType.equals(other.requestedType)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return domainObject.toString() + " => " + requestedType.toString();
+ }
+ }
+
+ /**
+ * Returns the trailing {@code [n]} index value from a path.
+ */
+ static int index(String path) {
+ int idx = path.lastIndexOf('[');
+ if (idx == -1) {
+ return -1;
+ }
+ return Integer.parseInt(path.substring(idx + 1, path.lastIndexOf(']')));
+ }
+
+ /**
+ * Returns {@code true} if the given prefix is one of the requested property
+ * references.
+ */
+ static boolean matchesPropertyRef(Set<String> propertyRefs, String newPrefix) {
+ return propertyRefs.contains(newPrefix.replaceAll("\\[\\d+\\]", ""));
+ }
+
+ /**
+ * Removes the trailing {@code [n]} from a path.
+ */
+ static String snipIndex(String path) {
+ int idx = path.lastIndexOf('[');
+ if (idx == -1) {
+ return path;
+ }
+ return path.substring(0, idx);
+ }
+
+ /**
+ * Maps domain values to client values. This map prevents cycles in the object
+ * graph from causing infinite recursion.
+ */
+ private final Map<ResolutionKey, Object> resolved = new HashMap<ResolutionKey, Object>();
+ private final ServiceLayer service;
+ private final RequestState state;
+ private int syntheticId;
+
+ /**
+ * Should only be called from {@link RequestState}.
+ */
+ Resolver(RequestState state) {
+ this.state = state;
+ this.service = state.getServiceLayer();
+ }
+
+ /**
+ * Given a domain object, return a value that can be encoded by the client.
+ *
+ * @param domainValue the domain object to be converted into a client-side
+ * value
+ * @param assignableTo the type in the client to which the resolved value
+ * should be assignable. A value of {@code null} indicates that any
+ * resolution will suffice.
+ * @param propertyRefs the property references requested by the client
+ */
+ public Object resolveClientValue(Object domainValue, Type assignableTo,
+ Set<String> propertyRefs) {
+ return resolveClientValue(domainValue, assignableTo,
+ getPropertyRefs(propertyRefs), "");
+ }
+
+ /**
+ * Convert a client-side value into a domain value.
+ *
+ * @param maybeEntityProxy the client object to resolve
+ * @param detectDeadEntities if <code>true</code> this method will throw a
+ * ReportableException containing a {@link DeadEntityException} if an
+ * EntityProxy cannot be resolved
+ */
+ public Object resolveDomainValue(Object maybeEntityProxy,
+ boolean detectDeadEntities) {
+ if (maybeEntityProxy instanceof BaseProxy) {
+ AutoBean<BaseProxy> bean = AutoBeanUtils.getAutoBean((BaseProxy) maybeEntityProxy);
+ Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
+ if (domain == null && detectDeadEntities) {
+ throw new ReportableException(new DeadEntityException(
+ "The requested entity is not available on the server"));
+ }
+ return domain;
+ } else if (maybeEntityProxy instanceof Collection<?>) {
+ Collection<Object> accumulator;
+ if (maybeEntityProxy instanceof List<?>) {
+ accumulator = new ArrayList<Object>();
+ } else if (maybeEntityProxy instanceof Set<?>) {
+ accumulator = new HashSet<Object>();
+ } else {
+ throw new ReportableException("Unsupported collection type "
+ + maybeEntityProxy.getClass().getName());
+ }
+ for (Object o : (Collection<?>) maybeEntityProxy) {
+ accumulator.add(resolveDomainValue(o, detectDeadEntities));
+ }
+ return accumulator;
+ }
+ return maybeEntityProxy;
+ }
+
+ /**
+ * Expand the property references in an InvocationMessage into a
+ * fully-expanded list of properties. For example, <code>[foo.bar.baz]</code>
+ * will be converted into <code>[foo, foo.bar, foo.bar.baz]</code>.
+ */
+ private Set<String> getPropertyRefs(Set<String> refs) {
+ if (refs == null) {
+ return Collections.emptySet();
+ }
+
+ Set<String> toReturn = new TreeSet<String>();
+ for (String raw : refs) {
+ for (int idx = raw.length(); idx >= 0; idx = raw.lastIndexOf('.', idx - 1)) {
+ toReturn.add(raw.substring(0, idx));
+ }
+ }
+ return toReturn;
+ }
+
+ /**
+ * Converts a domain entity into an EntityProxy that will be sent to the
+ * client.
+ */
+ private <T extends BaseProxy> T resolveClientProxy(final Object domainEntity,
+ Class<T> proxyType, final Set<String> propertyRefs, ResolutionKey key,
+ final String prefix) {
+ if (domainEntity == null) {
+ return null;
+ }
+
+ SimpleProxyId<? extends BaseProxy> id = state.getStableId(domainEntity);
+
+ boolean isEntityProxy = state.isEntityType(proxyType);
+ final boolean isOwnerValueProxy = state.isValueType(proxyType);
+ Object domainVersion;
+
+ // Create the id or update an ephemeral id by calculating its address
+ if (id == null || id.isEphemeral()) {
+ // The address is an id or an id plus a path
+ Object domainId;
+ if (isEntityProxy) {
+ // Compute data needed to return id to the client
+ domainId = service.getId(domainEntity);
+ domainVersion = service.getVersion(domainEntity);
+ } else {
+ domainId = null;
+ domainVersion = null;
+ }
+ if (id == null) {
+ if (domainId == null) {
+ /*
+ * This will happen when server code attempts to return an unpersisted
+ * object to the client. In this case, we'll assign a synthetic id
+ * that is valid for the duration of the response. The client is
+ * expected to assign a client-local id to this object and then it
+ * will behave as though it were an object newly-created by the
+ * client.
+ */
+ id = state.getIdFactory().allocateSyntheticId(proxyType,
+ ++syntheticId);
+ } else {
+ Splittable flatValue = state.flatten(domainId);
+ id = state.getIdFactory().getId(proxyType, flatValue.getPayload(), 0);
+ }
+ } else if (domainId != null) {
+ // Mark an ephemeral id as having been persisted
+ Splittable flatValue = state.flatten(domainId);
+ id.setServerId(flatValue.getPayload());
+ }
+ } else if (isEntityProxy) {
+ // Already have the id, just pull the current version
+ domainVersion = service.getVersion(domainEntity);
+ } else {
+ // The version of a value object is always null
+ domainVersion = null;
+ }
+
+ @SuppressWarnings("unchecked")
+ AutoBean<T> bean = (AutoBean<T>) state.getBeanForPayload(id, domainEntity);
+ resolved.put(key, bean.as());
+ bean.setTag(Constants.IN_RESPONSE, true);
+ if (domainVersion != null) {
+ Splittable flatVersion = state.flatten(domainVersion);
+ bean.setTag(Constants.VERSION_PROPERTY_B64,
+ SimpleRequestProcessor.toBase64(flatVersion.getPayload()));
+ }
+
+ bean.accept(new AutoBeanVisitor() {
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName,
+ AutoBean<?> value, PropertyContext ctx) {
+ // Does the user care about the property?
+ String newPrefix = (prefix.length() > 0 ? (prefix + ".") : "")
+ + propertyName;
+
+ /*
+ * Send the property if the enclosing type is a ValueProxy, if the owner
+ * requested the property, or if the property is a list of values.
+ */
+ Class<?> elementType = ctx instanceof CollectionPropertyContext
+ ? ((CollectionPropertyContext) ctx).getElementType() : null;
+ boolean shouldSend = isOwnerValueProxy
+ || matchesPropertyRef(propertyRefs, newPrefix)
+ || elementType != null && ValueCodex.canDecode(elementType);
+
+ if (!shouldSend) {
+ return false;
+ }
+
+ // Call the getter
+ Object domainValue = service.getProperty(domainEntity, propertyName);
+ if (domainValue == null) {
+ return false;
+ }
+
+ // Turn the domain object into something usable on the client side
+ Type type;
+ if (elementType == null) {
+ type = ctx.getType();
+ } else {
+ type = new CollectionType(ctx.getType(), elementType);
+ }
+ Object clientValue = resolveClientValue(domainValue, type,
+ propertyRefs, newPrefix);
+
+ ctx.set(clientValue);
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value,
+ PropertyContext ctx) {
+ // Limit unrequested value properties?
+ value = service.getProperty(domainEntity, propertyName);
+ ctx.set(value);
+ return false;
+ }
+ });
+
+ return bean.as();
+ }
+
+ /**
+ * Recursive-descent implementation.
+ */
+ private Object resolveClientValue(Object domainValue, Type returnType,
+ Set<String> propertyRefs, String prefix) {
+ if (domainValue == null) {
+ return null;
+ }
+
+ boolean anyType = returnType == null;
+ if (anyType) {
+ returnType = Object.class;
+ }
+
+ Class<?> assignableTo = TypeUtils.ensureBaseType(returnType);
+ ResolutionKey key = new ResolutionKey(domainValue, returnType);
+
+ Object previous = resolved.get(key);
+ if (previous != null && assignableTo.isInstance(previous)) {
+ return assignableTo.cast(previous);
+ }
+
+ Class<?> returnClass = service.resolveClientType(domainValue.getClass(),
+ assignableTo, true);
+
+ if (anyType) {
+ assignableTo = returnClass;
+ }
+
+ // Pass simple values through
+ if (ValueCodex.canDecode(returnClass)) {
+ return assignableTo.cast(domainValue);
+ }
+
+ // Convert entities to EntityProxies or EntityProxyIds
+ boolean isProxy = BaseProxy.class.isAssignableFrom(returnClass);
+ boolean isId = EntityProxyId.class.isAssignableFrom(returnClass);
+ if (isProxy || isId) {
+ Class<? extends BaseProxy> proxyClass = assignableTo.asSubclass(BaseProxy.class);
+ BaseProxy entity = resolveClientProxy(domainValue, proxyClass,
+ propertyRefs, key, prefix);
+ if (isId) {
+ return assignableTo.cast(((EntityProxy) entity).stableId());
+ }
+ return assignableTo.cast(entity);
+ }
+
+ // Convert collections
+ if (Collection.class.isAssignableFrom(returnClass)) {
+ Collection<Object> accumulator;
+ if (List.class.isAssignableFrom(returnClass)) {
+ accumulator = new ArrayList<Object>();
+ } else if (Set.class.isAssignableFrom(returnClass)) {
+ accumulator = new HashSet<Object>();
+ } else {
+ throw new ReportableException("Unsupported collection type"
+ + returnClass.getName());
+ }
+ resolved.put(key, accumulator);
+
+ Type elementType = TypeUtils.getSingleParameterization(Collection.class,
+ returnType);
+ for (Object o : (Collection<?>) domainValue) {
+ accumulator.add(resolveClientValue(o, elementType, propertyRefs, prefix));
+ }
+ return assignableTo.cast(accumulator);
+ }
+
+ throw new ReportableException("Unsupported domain type "
+ + returnClass.getCanonicalName());
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
new file mode 100644
index 0000000..f95e896
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * Implements all of the resolution methods in ServiceLayer.
+ */
+final class ResolverServiceLayer extends ServiceLayerDecorator {
+
+ private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
+
+ /**
+ * All instances of the service layer that are loaded by the same classloader
+ * can use a shared validator. The use of the validator should be
+ * synchronized, since it is stateful.
+ */
+ private static final RequestFactoryInterfaceValidator validator =
+ new RequestFactoryInterfaceValidator(log,
+ new RequestFactoryInterfaceValidator.ClassLoaderLoader(ServiceLayer.class
+ .getClassLoader()));
+
+ @Override
+ public ClassLoader getDomainClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ @Override
+ public Class<? extends BaseProxy> resolveClass(String typeToken) {
+ Class<?> found = forName(typeToken);
+ if (!EntityProxy.class.isAssignableFrom(found) && !ValueProxy.class.isAssignableFrom(found)) {
+ die(null, "The requested type %s is not assignable to %s or %s", typeToken, EntityProxy.class
+ .getCanonicalName(), ValueProxy.class.getCanonicalName());
+ }
+ synchronized (validator) {
+ validator.antidote();
+ validator.validateProxy(found.getName());
+ if (validator.isPoisoned()) {
+ die(null, "The type %s did not pass RequestFactory validation", found.getCanonicalName());
+ }
+ }
+ return found.asSubclass(BaseProxy.class);
+ }
+
+ @Override
+ public <T> Class<? extends T> resolveClientType(Class<?> domainClass, Class<T> clientClass,
+ boolean required) {
+ String name;
+ synchronized (validator) {
+ name = validator.getEntityProxyTypeName(domainClass.getName(), clientClass.getName());
+ }
+ if (name != null) {
+ return forName(name).asSubclass(clientClass);
+ }
+ if (List.class.isAssignableFrom(domainClass)) {
+ return List.class.asSubclass(clientClass);
+ }
+ if (Set.class.isAssignableFrom(domainClass)) {
+ return Set.class.asSubclass(clientClass);
+ }
+ if (TypeUtils.isValueType(domainClass)) {
+ return domainClass.asSubclass(clientClass);
+ }
+ if (required) {
+ die(null, "The domain type %s cannot be sent to the client", domainClass.getCanonicalName());
+ }
+ return null;
+ }
+
+ @Override
+ public Class<?> resolveDomainClass(Class<?> clazz) {
+ if (List.class.equals(clazz)) {
+ return List.class;
+ } else if (Set.class.equals(clazz)) {
+ return Set.class;
+ } else if (BaseProxy.class.isAssignableFrom(clazz)) {
+ ProxyFor pf = clazz.getAnnotation(ProxyFor.class);
+ if (pf != null) {
+ return pf.value();
+ }
+ ProxyForName pfn = clazz.getAnnotation(ProxyForName.class);
+ if (pfn != null) {
+ Class<?> toReturn = forName(pfn.value());
+ return toReturn;
+ }
+ }
+ return die(null, "Could not resolve a domain type for client type %s", clazz.getCanonicalName());
+ }
+
+ @Override
+ public Method resolveDomainMethod(Method requestContextMethod) {
+ Class<?> declaringClass = requestContextMethod.getDeclaringClass();
+ Class<?> searchIn =
+ getTop().resolveServiceClass(declaringClass.asSubclass(RequestContext.class));
+ Class<?>[] parameterTypes = requestContextMethod.getParameterTypes();
+ Class<?>[] domainArgs = new Class<?>[parameterTypes.length];
+ for (int i = 0, j = domainArgs.length; i < j; i++) {
+ if (BaseProxy.class.isAssignableFrom(parameterTypes[i])) {
+ domainArgs[i] = getTop().resolveDomainClass(parameterTypes[i].asSubclass(BaseProxy.class));
+ } else if (EntityProxyId.class.isAssignableFrom(parameterTypes[i])) {
+ domainArgs[i] =
+ TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(EntityProxyId.class,
+ requestContextMethod.getGenericParameterTypes()[i]));
+ } else {
+ domainArgs[i] = parameterTypes[i];
+ }
+ }
+
+ Throwable ex;
+ try {
+ return searchIn.getMethod(requestContextMethod.getName(), domainArgs);
+ } catch (SecurityException e) {
+ ex = e;
+ } catch (NoSuchMethodException e) {
+ return report("Could not locate domain method %s", requestContextMethod.getName());
+ }
+ return die(ex, "Could not get domain method %s in type %s", requestContextMethod.getName(),
+ searchIn.getCanonicalName());
+ }
+
+ @Override
+ public Method resolveRequestContextMethod(String requestContextClass, String methodName) {
+ synchronized (validator) {
+ validator.antidote();
+ validator.validateRequestContext(requestContextClass);
+ if (validator.isPoisoned()) {
+ die(null, "The RequestContext type %s did not pass validation", requestContextClass);
+ }
+ }
+ Class<?> searchIn = forName(requestContextClass);
+ for (Method method : searchIn.getMethods()) {
+ if (method.getName().equals(methodName)) {
+ return method;
+ }
+ }
+ return report("Could not locate %s method %s::%s", RequestContext.class.getSimpleName(),
+ requestContextClass, methodName);
+ }
+
+ @Override
+ public Class<?> resolveServiceClass(Class<? extends RequestContext> requestContextClass) {
+ Class<?> searchIn = null;
+ Service s = requestContextClass.getAnnotation(Service.class);
+ // TODO Handle case when both annotations are present
+ if (s != null) {
+ searchIn = s.value();
+ }
+ ServiceName sn = requestContextClass.getAnnotation(ServiceName.class);
+ if (sn != null) {
+ searchIn = forName(sn.value());
+ }
+ if (searchIn == null) {
+ die(null, "The %s type %s did not specify a service type", RequestContext.class
+ .getSimpleName(), requestContextClass.getCanonicalName());
+ }
+ return searchIn;
+ }
+
+ @Override
+ public String resolveTypeToken(Class<? extends BaseProxy> clazz) {
+ return clazz.getName();
+ }
+
+ /**
+ * Call {@link Class#forName(String)} and report any errors through
+ * {@link #die()}.
+ */
+ private Class<?> forName(String name) {
+ try {
+ return Class.forName(name, false, getTop().getDomainClassLoader());
+ } catch (ClassNotFoundException e) {
+ return die(e, "Could not locate class %s", name);
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayer.java
new file mode 100644
index 0000000..484f954
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayer.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * The ServiceLayer mediates all interactions between the
+ * {@link SimpleRequestProcessor} and the domain environment. The core service
+ * logic can be decorated by extending an {@link ServiceLayerDecorator}.
+ * <p>
+ * This API is subject to change in future releases.
+ */
+public abstract class ServiceLayer {
+ /*
+ * NB: This type cannot be directly extended by the user since it has a
+ * package-protected constructor. This means that any API-compatibility work
+ * that needs to happen can be done in ServiceLayerDecorator in order to keep
+ * this interface as clean as possible.
+ */
+
+ /**
+ * Provides a flag to disable the ServiceLayerCache for debugging purposes.
+ */
+ private static final boolean ENABLE_CACHE = Boolean.valueOf(System.getProperty(
+ "gwt.rf.ServiceLayerCache", "true"));
+
+ /**
+ * Create a RequestFactory ServiceLayer that is optionally modified by the
+ * given decorators.
+ *
+ * @param decorators the decorators that will modify the behavior of the core
+ * service layer implementation
+ * @return a ServiceLayer instance
+ */
+ public static ServiceLayer create(ServiceLayerDecorator... decorators) {
+ List<ServiceLayerDecorator> list = new ArrayList<ServiceLayerDecorator>();
+ // Always hit the cache first
+ ServiceLayerDecorator cache = ENABLE_CACHE ? new ServiceLayerCache()
+ : new ServiceLayerDecorator();
+ list.add(cache);
+ // The the user-provided decorators
+ if (decorators != null) {
+ list.addAll(Arrays.asList(decorators));
+ }
+ // Support for Locator objects
+ list.add(new LocatorServiceLayer());
+ // Interact with domain objects
+ list.add(new ReflectiveServiceLayer());
+ // Locate domain objects
+ list.add(new ResolverServiceLayer());
+
+ // Make the last layer point to the cache
+ list.get(list.size() - 1).top = cache;
+
+ // Point each entry at the next
+ for (int i = list.size() - 2; i >= 0; i--) {
+ ServiceLayerDecorator layer = list.get(i);
+ layer.next = list.get(i + 1);
+ layer.top = cache;
+ }
+
+ return cache;
+ }
+
+ /**
+ * A pointer to the top-most ServiceLayer instance.
+ */
+ ServiceLayer top;
+
+ /**
+ * Not generally-extensible.
+ */
+ ServiceLayer() {
+ }
+
+ /**
+ * Create an instance of the requested domain type.
+ *
+ * @param <T> the requested domain type
+ * @param clazz the requested domain type
+ * @return an instance of the requested domain type
+ */
+ public abstract <T> T createDomainObject(Class<T> clazz);
+
+ /**
+ * Create an instance of the requested {@link Locator} type.
+ *
+ * @param <T> the requested Locator type
+ * @param clazz the requested Locator type
+ * @return an instance of the requested Locator type
+ */
+ public abstract <T extends Locator<?, ?>> T createLocator(Class<T> clazz);
+
+ /**
+ * Create an instance of a service object that can be used as the target for
+ * the given method invocation.
+ *
+ * @param contextMethod a method defined in a RequestContext
+ * @param domainMethod the method that the service object must implement
+ * @return an instance of the requested service object
+ */
+ public abstract Object createServiceInstance(Method contextMethod,
+ Method domainMethod);
+
+ /**
+ * Create an instance of the requested {@link ServiceLocator} type.
+ *
+ * @param <T> the requested ServiceLocator type
+ * @param clazz the requested ServiceLocator type
+ * @return an instance of the requested ServiceLocator type
+ */
+ public abstract <T extends ServiceLocator> T createServiceLocator(
+ Class<T> clazz);
+
+ /**
+ * Returns the ClassLoader that should be used when attempting to access
+ * domain classes or resources.
+ * <p>
+ * The default implementation returns
+ * {@code Thread.currentThread().getContextClassLoader()}.
+ */
+ public abstract ClassLoader getDomainClassLoader();
+
+ /**
+ * Determine the method to invoke when retrieving the given property.
+ *
+ * @param domainType a domain entity type
+ * @param property the name of the property to be retrieved
+ * @return the Method that should be invoked to retrieve the property or
+ * {@code null} if the method could not be located
+ */
+ public abstract Method getGetter(Class<?> domainType, String property);
+
+ /**
+ * Return the persistent id for a domain object. May return {@code null} to
+ * indicate that the domain object has not been persisted. The value returned
+ * from this method must be a simple type (e.g. Integer, String) or a domain
+ * type for which a mapping to an EntityProxy or Value proxy exists.
+ * <p>
+ * The values returned from this method may be passed to
+ * {@link #loadDomainObject(Class, Object)} in the future.
+ *
+ * @param domainObject a domain object
+ * @return the persistent id of the domain object or {@code null} if the
+ * object is not persistent
+ */
+ public abstract Object getId(Object domainObject);
+
+ /**
+ * Returns the type of object the domain type's {@code findFoo()} or
+ * {@link com.google.web.bindery.requestfactory.shared.Locator#getId(Object)
+ * Locator.getId()} expects to receive.
+ *
+ * @param domainType a domain entity type
+ * @return the type of the persistent id value used to represent the domain
+ * type
+ */
+ public abstract Class<?> getIdType(Class<?> domainType);
+
+ /**
+ * Retrieve the named property from the domain object.
+ *
+ * @param domainObject the domain object being examined
+ * @param property the property name
+ * @return the value of the property
+ */
+ public abstract Object getProperty(Object domainObject, String property);
+
+ /**
+ * Compute the return type for a method declared in a RequestContext by
+ * analyzing the generic method declaration.
+ */
+ public abstract Type getRequestReturnType(Method contextMethod);
+
+ /**
+ * Determine the method to invoke when setting the given property.
+ *
+ * @param domainType a domain entity type
+ * @param property the name of the property to be set
+ * @return the Method that should be invoked to set the property or
+ * {@code null} if the method could not be located
+ */
+ public abstract Method getSetter(Class<?> domainType, String property);
+
+ /**
+ * May return {@code null} to indicate that the domain object has not been
+ * persisted. The value returned from this method must be a simple type (e.g.
+ * Integer, String) or a domain type for which a mapping to an EntityProxy or
+ * Value proxy exists.
+ *
+ * @param domainObject a domain object
+ * @return the version of the domain object or {@code null} if the object is
+ * not persistent
+ */
+ public abstract Object getVersion(Object domainObject);
+
+ /**
+ * Invoke a domain service method. The underlying eventually calls
+ * {@link Method#invoke(Object, Object...)}.
+ *
+ * @param domainMethod the method to invoke
+ * @param args the arguments to pass to the method
+ * @return the value returned from the method invocation
+ */
+ public abstract Object invoke(Method domainMethod, Object... args);
+
+ /**
+ * Returns {@code true} if the given domain object is still live (i.e. not
+ * deleted) in the backing store.
+ *
+ * @param domainObject a domain entity
+ * @return {@code true} if {@code domainObject} could be retrieved at a later
+ * point in time
+ */
+ public abstract boolean isLive(Object domainObject);
+
+ /**
+ * Load an object from the backing store. This method may return {@code null}
+ * to indicate that the requested object is no longer available.
+ *
+ * @param <T> the type of object to load
+ * @param clazz the type of object to load
+ * @param domainId an id previously returned from {@link #getId(Object)}
+ * @return the requested object or {@code null} if it is irretrievable
+ */
+ public abstract <T> T loadDomainObject(Class<T> clazz, Object domainId);
+
+ /**
+ * Load multiple objects from the backing store. This method is intended to
+ * allow more efficient access to the backing store by providing all objects
+ * referenced in an incoming payload.
+ * <p>
+ * The default implementation of this method will delegate to
+ * {@link #loadDomainObject(Class, Object)}.
+ *
+ * @param classes type type of each object to load
+ * @param domainIds the ids previously returned from {@link #getId(Object)}
+ * @return the requested objects, elements of which may be {@code null} if the
+ * requested objects were irretrievable
+ */
+ public abstract List<Object> loadDomainObjects(List<Class<?>> classes,
+ List<Object> domainIds);
+
+ /**
+ * Determines if the invocation of a domain method requires a
+ * {@link ServiceLocator} as the 0th parameter when passed into
+ * {@link #invoke(Method, Object...)}.
+ *
+ * @param contextMethod a method defined in a RequestContext
+ * @param domainMethod a domain method
+ * @return {@code true} if a ServiceLocator is required
+ */
+ public abstract boolean requiresServiceLocator(Method contextMethod,
+ Method domainMethod);
+
+ /**
+ * Given a type token previously returned from
+ * {@link #resolveTypeToken(Class)}, return the Class literal associated with
+ * the token.
+ *
+ * @param typeToken a string token
+ * @return the type represented by the token
+ */
+ public abstract Class<? extends BaseProxy> resolveClass(String typeToken);
+
+ /**
+ * Determine the type used by the client code to represent a given domain
+ * type. If multiple proxy types have been mapped to the same domain type, the
+ * {@code clientType} parameter is used to ensure assignability.
+ *
+ * @param domainClass the server-side type to be transported to the client
+ * @param clientType the type to which the returned type must be assignable
+ * @param required if {@code true} and no mapping is available, throw an
+ * exception, otherwise the method will return {@code null}
+ * @return a class that represents {@code domainClass} on the client which is
+ * assignable to {@code clientType}
+ */
+ public abstract <T> Class<? extends T> resolveClientType(
+ Class<?> domainClass, Class<T> clientType, boolean required);
+
+ /**
+ * Determine the domain (server-side) type that the given client type is
+ * mapped to.
+ *
+ * @param clientType a client-side type
+ * @return the domain type that {@code clientType} represents
+ */
+ public abstract Class<?> resolveDomainClass(Class<?> clientType);
+
+ /**
+ * Return the domain service method associated with a RequestContext method
+ * declaration. The {@code requestContextMethod} will have been previously
+ * resolved by {@link #resolveRequestContextMethod(String, String)}.
+ *
+ * @param requestContextMethod a RequestContext method declaration.
+ * @return the domain service method that should be invoked
+ */
+ public abstract Method resolveDomainMethod(Method requestContextMethod);
+
+ /**
+ * Return the type of {@link Locator} that should be used to access the given
+ * domain type.
+ *
+ * @param domainType a domain (server-side) type
+ * @return the type of Locator to use, or {@code null} if the type conforms to
+ * the RequestFactory entity protocol
+ */
+ public abstract Class<? extends Locator<?, ?>> resolveLocator(
+ Class<?> domainType);
+
+ /**
+ * Find a RequestContext method declaration by name.
+ *
+ * @param requestContextClass the fully-qualified binary name of the
+ * RequestContext
+ * @param methodName the name of the service method declared within the
+ * RequestContext
+ * @return the method declaration, or {@code null} if the method does not
+ * exist
+ */
+ public abstract Method resolveRequestContextMethod(
+ String requestContextClass, String methodName);
+
+ /**
+ * Given a {@link RequestContext} method, find the service class referenced in
+ * the {@link Service} or {@link ServiceName} annotation.
+ *
+ * @param requestContextClass a RequestContext interface
+ * @return the type of service to use
+ */
+ public abstract Class<?> resolveServiceClass(
+ Class<? extends RequestContext> requestContextClass);
+
+ /**
+ * Given a RequestContext method declaration, resolve the
+ * {@link ServiceLocator} that should be used when invoking the domain method.
+ * This method will only be called if
+ * {@link #requiresServiceLocator(Method, Method)} returned {@code true} for
+ * the associated domain method.
+ *
+ * @param contextMethod a RequestContext method declaration
+ * @param domainMethod the domain method that will be invoked
+ * @return the type of ServiceLocator to use
+ */
+ public abstract Class<? extends ServiceLocator> resolveServiceLocator(
+ Method contextMethod, Method domainMethod);
+
+ /**
+ * Return a string used to represent the given type in the wire protocol.
+ *
+ * @param proxyType a client-side EntityProxy or ValueProxy type
+ * @return the type token used to represent the proxy type
+ */
+ public abstract String resolveTypeToken(Class<? extends BaseProxy> proxyType);
+
+ /**
+ * Sets a property on a domain object.
+ *
+ * @param domainObject the domain object to operate on
+ * @param property the name of the property to set
+ * @param expectedType the type of the property
+ * @param value the new value
+ */
+ public abstract void setProperty(Object domainObject, String property,
+ Class<?> expectedType, Object value);
+
+ /**
+ * Invoke a JSR 303 validator on the given domain object. If no validator is
+ * available, this method is a no-op.
+ *
+ * @param <T> the type of data being validated
+ * @param domainObject the domain objcet to validate
+ * @return the violations associated with the domain object
+ */
+ public abstract <T> Set<ConstraintViolation<T>> validate(T domainObject);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerCache.java b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerCache.java
new file mode 100644
index 0000000..4d8ac6e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerCache.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.rpc.server.Pair;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+import java.lang.ref.SoftReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A cache for idempotent methods in {@link ServiceLayer}. The caching is
+ * separate from {@link ReflectiveServiceLayer} so that the cache can be applied
+ * to any decorators injected by the user.
+ */
+class ServiceLayerCache extends ServiceLayerDecorator {
+
+ /**
+ * ConcurrentHashMaps don't allow null keys or values, but sometimes we want
+ * to cache a null value.
+ */
+ private static final Object NULL_MARKER = new Object();
+
+ private static SoftReference<Map<Method, Map<Object, Object>>> methodCache;
+
+ private static final Method createLocator;
+ private static final Method createServiceInstance;
+ private static final Method getDomainClassLoader;
+ private static final Method getGetter;
+ private static final Method getIdType;
+ private static final Method getRequestReturnType;
+ private static final Method getSetter;
+ private static final Method requiresServiceLocator;
+ private static final Method resolveClass;
+ private static final Method resolveClientType;
+ private static final Method resolveDomainClass;
+ private static final Method resolveDomainMethod;
+ private static final Method resolveLocator;
+ private static final Method resolveRequestContextMethod;
+ private static final Method resolveServiceLocator;
+ private static final Method resolveTypeToken;
+
+ static {
+ createLocator = getMethod("createLocator", Class.class);
+ createServiceInstance = getMethod("createServiceInstance", Method.class, Method.class);
+ getDomainClassLoader = getMethod("getDomainClassLoader");
+ getGetter = getMethod("getGetter", Class.class, String.class);
+ getIdType = getMethod("getIdType", Class.class);
+ getRequestReturnType = getMethod("getRequestReturnType", Method.class);
+ getSetter = getMethod("getSetter", Class.class, String.class);
+ requiresServiceLocator = getMethod("requiresServiceLocator", Method.class, Method.class);
+ resolveClass = getMethod("resolveClass", String.class);
+ resolveClientType = getMethod("resolveClientType", Class.class, Class.class, boolean.class);
+ resolveDomainClass = getMethod("resolveDomainClass", Class.class);
+ resolveDomainMethod = getMethod("resolveDomainMethod", Method.class);
+ resolveLocator = getMethod("resolveLocator", Class.class);
+ resolveRequestContextMethod =
+ getMethod("resolveRequestContextMethod", String.class, String.class);
+ resolveServiceLocator = getMethod("resolveServiceLocator", Method.class, Method.class);
+ resolveTypeToken = getMethod("resolveTypeToken", Class.class);
+ }
+
+ private static Map<Method, Map<Object, Object>> getCache() {
+ Map<Method, Map<Object, Object>> toReturn = methodCache == null ? null : methodCache.get();
+ if (toReturn == null) {
+ toReturn = new ConcurrentHashMap<Method, Map<Object, Object>>();
+ methodCache = new SoftReference<Map<Method, Map<Object, Object>>>(toReturn);
+ }
+ return toReturn;
+ }
+
+ private static Method getMethod(String name, Class<?>... argTypes) {
+ try {
+ return ServiceLayer.class.getMethod(name, argTypes);
+ } catch (SecurityException e) {
+ throw new RuntimeException("Could not set up ServiceLayerCache Methods", e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Could not set up ServiceLayerCache Methods", e);
+ }
+ }
+
+ private final Map<Method, Map<Object, Object>> methodMap = getCache();
+
+ @Override
+ public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+ return getOrCache(createLocator, clazz, clazz, clazz);
+ }
+
+ @Override
+ public Object createServiceInstance(Method contextMethod, Method domainMethod) {
+ return getOrCache(createServiceInstance, new Pair<Method, Method>(contextMethod, domainMethod),
+ Object.class, contextMethod, domainMethod);
+ }
+
+ @Override
+ public ClassLoader getDomainClassLoader() {
+ return getOrCache(getDomainClassLoader, NULL_MARKER, ClassLoader.class);
+ }
+
+ @Override
+ public Method getGetter(Class<?> domainType, String property) {
+ return getOrCache(getGetter, new Pair<Class<?>, String>(domainType, property), Method.class,
+ domainType, property);
+ }
+
+ @Override
+ public Class<?> getIdType(Class<?> domainType) {
+ return getOrCache(getIdType, domainType, Class.class, domainType);
+ }
+
+ @Override
+ public Type getRequestReturnType(Method contextMethod) {
+ return getOrCache(getRequestReturnType, contextMethod, Type.class, contextMethod);
+ }
+
+ @Override
+ public Method getSetter(Class<?> domainType, String property) {
+ return getOrCache(getSetter, new Pair<Class<?>, String>(domainType, property), Method.class,
+ domainType, property);
+ }
+
+ @Override
+ public boolean requiresServiceLocator(Method contextMethod, Method domainMethod) {
+ return getOrCache(requiresServiceLocator,
+ new Pair<Method, Method>(contextMethod, domainMethod), Boolean.class, contextMethod,
+ domainMethod);
+ }
+
+ @Override
+ public Class<? extends BaseProxy> resolveClass(String typeToken) {
+ Class<?> found = getOrCache(resolveClass, typeToken, Class.class, typeToken);
+ return found.asSubclass(BaseProxy.class);
+ }
+
+ @Override
+ public <T> Class<? extends T> resolveClientType(Class<?> domainClass, Class<T> clientType,
+ boolean required) {
+ Class<?> clazz =
+ getOrCache(resolveClientType, new Pair<Class<?>, Class<?>>(domainClass, clientType),
+ Class.class, domainClass, clientType, required);
+ return clazz == null ? null : clazz.asSubclass(clientType);
+ }
+
+ @Override
+ public Class<?> resolveDomainClass(Class<?> clazz) {
+ return getOrCache(resolveDomainClass, clazz, Class.class, clazz);
+ }
+
+ @Override
+ public Method resolveDomainMethod(Method requestContextMethod) {
+ return getOrCache(resolveDomainMethod, requestContextMethod, Method.class, requestContextMethod);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+ return getOrCache(resolveLocator, domainType, Class.class, domainType);
+ }
+
+ @Override
+ public Method resolveRequestContextMethod(String requestContextClass, String methodName) {
+ return getOrCache(resolveRequestContextMethod, new Pair<String, String>(requestContextClass,
+ methodName), Method.class, requestContextClass, methodName);
+ }
+
+ @Override
+ public Class<? extends ServiceLocator> resolveServiceLocator(Method contextMethod,
+ Method domainMethod) {
+ Class<?> clazz =
+ getOrCache(resolveServiceLocator, new Pair<Method, Method>(contextMethod, domainMethod),
+ Class.class, contextMethod, domainMethod);
+ return clazz == null ? null : clazz.asSubclass(ServiceLocator.class);
+ }
+
+ @Override
+ public String resolveTypeToken(Class<? extends BaseProxy> domainClass) {
+ return getOrCache(resolveTypeToken, domainClass, String.class, domainClass);
+ }
+
+ private <K, T> T getOrCache(Method method, K key, Class<T> valueType, Object... args) {
+ Map<Object, Object> map = methodMap.get(method);
+ if (map == null) {
+ map = new ConcurrentHashMap<Object, Object>();
+ methodMap.put(method, map);
+ }
+ Object raw = map.get(key);
+ if (raw == NULL_MARKER) {
+ return null;
+ }
+ T toReturn = valueType.cast(raw);
+ if (toReturn == null) {
+ Throwable ex = null;
+ try {
+ toReturn = valueType.cast(method.invoke(getNext(), args));
+ map.put(key, toReturn == null ? NULL_MARKER : toReturn);
+ } catch (InvocationTargetException e) {
+ // The next layer threw an exception
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ // Re-throw RuntimeExceptions, which likely originate from die()
+ throw ((RuntimeException) cause);
+ }
+ die(cause, "Unexpected checked exception");
+ } catch (IllegalArgumentException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ }
+ if (ex != null) {
+ die(ex, "Bad method invocation");
+ }
+ }
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerDecorator.java b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerDecorator.java
new file mode 100644
index 0000000..b2a7f89
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/ServiceLayerDecorator.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Users that intend to alter how RequestFactory interacts with the domain
+ * environment can extend this type and provide it to
+ * {@link ServiceLayer#create(ServiceLayerDecorator...)}. The methods defined in
+ * this type will automatically delegate to the next decorator or the root
+ * service object after being processed by{@code create()}.
+ */
+public class ServiceLayerDecorator extends ServiceLayer {
+ private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
+
+ /**
+ * A pointer to the next deepest layer.
+ */
+ ServiceLayer next;
+
+ @Override
+ public <T> T createDomainObject(Class<T> clazz) {
+ return getNext().createDomainObject(clazz);
+ }
+
+ @Override
+ public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
+ return getNext().createLocator(clazz);
+ }
+
+ @Override
+ public Object createServiceInstance(Method contextMethod, Method domainMethod) {
+ return getNext().createServiceInstance(contextMethod, domainMethod);
+ }
+
+ @Override
+ public <T extends ServiceLocator> T createServiceLocator(Class<T> clazz) {
+ return getNext().createServiceLocator(clazz);
+ }
+
+ @Override
+ public ClassLoader getDomainClassLoader() {
+ return getNext().getDomainClassLoader();
+ }
+
+ @Override
+ public Method getGetter(Class<?> domainType, String property) {
+ return getNext().getGetter(domainType, property);
+ }
+
+ @Override
+ public Object getId(Object domainObject) {
+ return getNext().getId(domainObject);
+ }
+
+ @Override
+ public Class<?> getIdType(Class<?> domainType) {
+ return getNext().getIdType(domainType);
+ }
+
+ @Override
+ public Object getProperty(Object domainObject, String property) {
+ return getNext().getProperty(domainObject, property);
+ }
+
+ @Override
+ public Type getRequestReturnType(Method contextMethod) {
+ return getNext().getRequestReturnType(contextMethod);
+ }
+
+ @Override
+ public Method getSetter(Class<?> domainType, String property) {
+ return getNext().getSetter(domainType, property);
+ }
+
+ @Override
+ public Object getVersion(Object domainObject) {
+ return getNext().getVersion(domainObject);
+ }
+
+ @Override
+ public Object invoke(Method domainMethod, Object... args) {
+ return getNext().invoke(domainMethod, args);
+ }
+
+ @Override
+ public boolean isLive(Object domainObject) {
+ return getNext().isLive(domainObject);
+ }
+
+ @Override
+ public <T> T loadDomainObject(Class<T> clazz, Object domainId) {
+ return getNext().loadDomainObject(clazz, domainId);
+ }
+
+ @Override
+ public List<Object> loadDomainObjects(List<Class<?>> classes,
+ List<Object> domainIds) {
+ return getNext().loadDomainObjects(classes, domainIds);
+ }
+
+ @Override
+ public boolean requiresServiceLocator(Method contextMethod,
+ Method domainMethod) {
+ return getNext().requiresServiceLocator(contextMethod, domainMethod);
+ }
+
+ @Override
+ public Class<? extends BaseProxy> resolveClass(String typeToken) {
+ return getNext().resolveClass(typeToken);
+ }
+
+ @Override
+ public <T> Class<? extends T> resolveClientType(Class<?> domainClass,
+ Class<T> clientType, boolean required) {
+ return getNext().resolveClientType(domainClass, clientType, required);
+ }
+
+ @Override
+ public Class<?> resolveDomainClass(Class<?> clazz) {
+ return getNext().resolveDomainClass(clazz);
+ }
+
+ @Override
+ public Method resolveDomainMethod(Method requestContextMethod) {
+ return getNext().resolveDomainMethod(requestContextMethod);
+ }
+
+ @Override
+ public Class<? extends Locator<?, ?>> resolveLocator(Class<?> domainType) {
+ return getNext().resolveLocator(domainType);
+ }
+
+ @Override
+ public Method resolveRequestContextMethod(String requestContextClass,
+ String methodName) {
+ return getNext().resolveRequestContextMethod(requestContextClass,
+ methodName);
+ }
+
+ @Override
+ public Class<?> resolveServiceClass(
+ Class<? extends RequestContext> requestContextClass) {
+ return getNext().resolveServiceClass(requestContextClass);
+ }
+
+ @Override
+ public Class<? extends ServiceLocator> resolveServiceLocator(
+ Method contextMethod, Method domainMethod) {
+ return getNext().resolveServiceLocator(contextMethod, domainMethod);
+ }
+
+ @Override
+ public String resolveTypeToken(Class<? extends BaseProxy> proxyType) {
+ return getNext().resolveTypeToken(proxyType);
+ }
+
+ @Override
+ public void setProperty(Object domainObject, String property,
+ Class<?> expectedType, Object value) {
+ getNext().setProperty(domainObject, property, expectedType, value);
+ }
+
+ @Override
+ public <T> Set<ConstraintViolation<T>> validate(T domainObject) {
+ return getNext().validate(domainObject);
+ }
+
+ /**
+ * Throw a fatal error up into the top-level processing code. This method
+ * should be used to provide diagnostic information that will help the
+ * end-developer track down problems when that data would expose
+ * implementation details of the server to the client.
+ *
+ * @param e a throwable with more data, may be {@code null}
+ * @param message a printf-style format string
+ * @param args arguments for the message
+ * @throws UnexpectedException this method never returns normally
+ * @see #report(String, Object...)
+ */
+ protected final <T> T die(Throwable e, String message, Object... args)
+ throws UnexpectedException {
+ String msg = String.format(message, args);
+ log.log(Level.SEVERE, msg, e);
+ throw new UnexpectedException(msg, e);
+ }
+
+ /**
+ * Returns the top-most service layer. General-purpose ServiceLayer decorators
+ * should use the instance provided by {@code getTop()} when calling public
+ * methods on the ServiceLayer API to allow higher-level decorators to
+ * override behaviors built into lower-level decorators.
+ *
+ * @return the ServiceLayer returned by
+ * {@link #create(ServiceLayerDecorator...)}
+ */
+ protected final ServiceLayer getTop() {
+ return top;
+ }
+
+ /**
+ * Report an exception thrown by code that is under the control of the
+ * end-developer.
+ *
+ * @param userGeneratedException an {@link InvocationTargetException} thrown
+ * by an invocation of user-provided code
+ * @throws ReportableException this method never returns normally
+ */
+ protected final <T> T report(InvocationTargetException userGeneratedException)
+ throws ReportableException {
+ throw new ReportableException(userGeneratedException.getCause());
+ }
+
+ /**
+ * Return a message to the client. This method should not include any data
+ * that was not sent to the server by the client to avoid leaking data.
+ *
+ * @param msg a printf-style format string
+ * @param args arguments for the message
+ * @throws ReportableException this method never returns normally
+ * @see #die(Throwable, String, Object...)
+ */
+ protected final <T> T report(String msg, Object... args)
+ throws ReportableException {
+ throw new ReportableException(String.format(msg, args));
+ }
+
+ /**
+ * Retrieves the next service layer. Used only by the server-package code and
+ * accessed by used code via {@code super.doSomething()}.
+ */
+ final ServiceLayer getNext() {
+ if (next == null) {
+ // Unexpected, all methods should be implemented by some layer
+ throw new UnsupportedOperationException();
+ }
+ return next;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/SignatureAdapter.java b/user/src/com/google/web/bindery/requestfactory/server/SignatureAdapter.java
new file mode 100644
index 0000000..e33cf0b
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/SignatureAdapter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.dev.asm.signature.SignatureVisitor;
+
+/**
+ * An empty implementation of SignatureVisitor, used by
+ * {@link RequestFactoryInterfaceValidator}. This is a copy of the dev package's
+ * EmptySignatureVisitor.
+ */
+class SignatureAdapter implements SignatureVisitor {
+
+ private static final SignatureAdapter ignore = new SignatureAdapter();
+
+ public SignatureVisitor visitArrayType() {
+ return ignore;
+ }
+
+ public void visitBaseType(char descriptor) {
+ }
+
+ public SignatureVisitor visitClassBound() {
+ return ignore;
+ }
+
+ public void visitClassType(String name) {
+ }
+
+ public void visitEnd() {
+ }
+
+ public SignatureVisitor visitExceptionType() {
+ return ignore;
+ }
+
+ public void visitFormalTypeParameter(String name) {
+ }
+
+ public void visitInnerClassType(String name) {
+ }
+
+ public SignatureVisitor visitInterface() {
+ return ignore;
+ }
+
+ public SignatureVisitor visitInterfaceBound() {
+ return ignore;
+ }
+
+ public SignatureVisitor visitParameterType() {
+ return ignore;
+ }
+
+ public SignatureVisitor visitReturnType() {
+ return ignore;
+ }
+
+ public SignatureVisitor visitSuperclass() {
+ return ignore;
+ }
+
+ public void visitTypeArgument() {
+ }
+
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return ignore;
+ }
+
+ public void visitTypeVariable(String name) {
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java
new file mode 100644
index 0000000..a763dbb
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/SimpleRequestProcessor.java
@@ -0,0 +1,556 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.user.server.Base64Utils;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
+import com.google.web.bindery.autobean.vm.Configuration;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+import com.google.web.bindery.requestfactory.shared.impl.EntityCodex;
+import com.google.web.bindery.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
+import com.google.web.bindery.requestfactory.shared.impl.ValueProxyCategory;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
+import com.google.web.bindery.requestfactory.shared.messages.InvocationMessage;
+import com.google.web.bindery.requestfactory.shared.messages.MessageFactory;
+import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
+import com.google.web.bindery.requestfactory.shared.messages.RequestMessage;
+import com.google.web.bindery.requestfactory.shared.messages.ResponseMessage;
+import com.google.web.bindery.requestfactory.shared.messages.ServerFailureMessage;
+import com.google.web.bindery.requestfactory.shared.messages.ViolationMessage;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+
+/**
+ * Processes request payloads from a RequestFactory client. This implementation
+ * is stateless. A single instance may be reused and is thread-safe.
+ */
+public class SimpleRequestProcessor {
+ /**
+ * This parameterization is so long, it improves readability to have a
+ * specific type.
+ */
+ @SuppressWarnings("serial")
+ static class IdToEntityMap extends
+ HashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> {
+ }
+
+ /**
+ * Allows the creation of properly-configured AutoBeans without having to
+ * create an AutoBeanFactory with the desired annotations.
+ */
+ static final Configuration CONFIGURATION = new Configuration.Builder().setCategories(
+ EntityProxyCategory.class, ValueProxyCategory.class,
+ BaseProxyCategory.class).setNoWrap(EntityProxyId.class).build();
+
+ /**
+ * Vends message objects.
+ */
+ static final MessageFactory FACTORY = AutoBeanFactorySource.create(MessageFactory.class);
+
+ static String fromBase64(String encoded) {
+ try {
+ return new String(Base64Utils.fromBase64(encoded), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new UnexpectedException(e);
+ }
+ }
+
+ static String toBase64(String data) {
+ try {
+ return Base64Utils.toBase64(data.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new UnexpectedException(e);
+ }
+ }
+
+ private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
+ private final ServiceLayer service;
+
+ public SimpleRequestProcessor(ServiceLayer serviceLayer) {
+ this.service = serviceLayer;
+ }
+
+ /**
+ * Process a payload sent by a RequestFactory client.
+ *
+ * @param payload the payload sent by the client
+ * @return a payload to return to the client
+ */
+ public String process(String payload) {
+ RequestMessage req = AutoBeanCodex.decode(FACTORY, RequestMessage.class,
+ payload).as();
+ AutoBean<ResponseMessage> responseBean = FACTORY.response();
+ try {
+ process(req, responseBean.as());
+ } catch (ReportableException e) {
+ e.printStackTrace();
+ // Create a new response envelope, since the state is unknown
+ responseBean = FACTORY.response();
+ responseBean.as().setGeneralFailure(createFailureMessage(e).as());
+ }
+ // Return a JSON-formatted payload
+ return AutoBeanCodex.encode(responseBean).getPayload();
+ }
+
+ public void setExceptionHandler(ExceptionHandler exceptionHandler) {
+ this.exceptionHandler = exceptionHandler;
+ }
+
+ /**
+ * Encode a list of objects into a self-contained message that can be used for
+ * out-of-band communication.
+ */
+ <T> Splittable createOobMessage(List<T> domainValues) {
+ RequestState state = new RequestState(service);
+
+ List<Splittable> encodedValues = new ArrayList<Splittable>(
+ domainValues.size());
+ for (T domainValue : domainValues) {
+ Object clientValue;
+ if (domainValue == null) {
+ clientValue = null;
+ } else {
+ Class<?> clientType = service.resolveClientType(domainValue.getClass(),
+ BaseProxy.class, true);
+ clientValue = state.getResolver().resolveClientValue(domainValue,
+ clientType, Collections.<String> emptySet());
+ }
+ encodedValues.add(EntityCodex.encode(state, clientValue));
+ }
+
+ IdToEntityMap map = new IdToEntityMap();
+ map.putAll(state.beans);
+ List<OperationMessage> operations = new ArrayList<OperationMessage>();
+ createReturnOperations(operations, state, map);
+
+ InvocationMessage invocation = FACTORY.invocation().as();
+ invocation.setParameters(encodedValues);
+
+ AutoBean<RequestMessage> bean = FACTORY.request();
+ RequestMessage resp = bean.as();
+ resp.setInvocations(Collections.singletonList(invocation));
+ resp.setOperations(operations);
+ return AutoBeanCodex.encode(bean);
+ }
+
+ /**
+ * Decode an out-of-band message.
+ */
+ <T> List<T> decodeOobMessage(Class<T> domainClass, Splittable payload) {
+ Class<?> proxyType = service.resolveClientType(domainClass,
+ BaseProxy.class, true);
+ RequestState state = new RequestState(service);
+ RequestMessage message = AutoBeanCodex.decode(FACTORY,
+ RequestMessage.class, payload).as();
+ processOperationMessages(state, message);
+ List<Object> decoded = decodeInvocationArguments(state,
+ message.getInvocations().get(0).getParameters(),
+ new Class<?>[] {proxyType}, new Type[] {domainClass});
+
+ @SuppressWarnings("unchecked")
+ List<T> toReturn = (List<T>) decoded;
+ return toReturn;
+ }
+
+ /**
+ * Main processing method.
+ */
+ void process(RequestMessage req, ResponseMessage resp) {
+ final RequestState source = new RequestState(service);
+ // Apply operations
+ processOperationMessages(source, req);
+
+ // Validate entities
+ List<ViolationMessage> errorMessages = validateEntities(source);
+
+ if (!errorMessages.isEmpty()) {
+ resp.setViolations(errorMessages);
+ return;
+ }
+
+ RequestState returnState = new RequestState(source);
+
+ // Invoke methods
+ List<Splittable> invocationResults = new ArrayList<Splittable>();
+ List<Boolean> invocationSuccess = new ArrayList<Boolean>();
+ processInvocationMessages(source, req, invocationResults,
+ invocationSuccess, returnState);
+
+ // Store return objects
+ List<OperationMessage> operations = new ArrayList<OperationMessage>();
+ IdToEntityMap toProcess = new IdToEntityMap();
+ toProcess.putAll(source.beans);
+ toProcess.putAll(returnState.beans);
+ createReturnOperations(operations, returnState, toProcess);
+
+ assert invocationResults.size() == invocationSuccess.size();
+ if (!invocationResults.isEmpty()) {
+ resp.setInvocationResults(invocationResults);
+ resp.setStatusCodes(invocationSuccess);
+ }
+ if (!operations.isEmpty()) {
+ resp.setOperations(operations);
+ }
+ }
+
+ private AutoBean<ServerFailureMessage> createFailureMessage(
+ ReportableException e) {
+ ServerFailure failure = exceptionHandler.createServerFailure(e.getCause() == null
+ ? e : e.getCause());
+ AutoBean<ServerFailureMessage> bean = FACTORY.failure();
+ ServerFailureMessage msg = bean.as();
+ msg.setExceptionType(failure.getExceptionType());
+ msg.setMessage(failure.getMessage());
+ msg.setStackTrace(failure.getStackTraceString());
+ msg.setFatal(failure.isFatal());
+ return bean;
+ }
+
+ private void createReturnOperations(List<OperationMessage> operations,
+ RequestState returnState, IdToEntityMap toProcess) {
+ for (Map.Entry<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> entry : toProcess.entrySet()) {
+ SimpleProxyId<?> id = entry.getKey();
+
+ AutoBean<? extends BaseProxy> bean = entry.getValue();
+ Object domainObject = bean.getTag(Constants.DOMAIN_OBJECT);
+ WriteOperation writeOperation;
+
+ if (id.isEphemeral()) {
+ // See if the entity has been persisted in the meantime
+ returnState.getResolver().resolveClientValue(domainObject,
+ id.getProxyClass(), Collections.<String> emptySet());
+ }
+
+ if (id.isEphemeral() || id.isSynthetic() || domainObject == null) {
+ // If the object isn't persistent, there's no reason to send an update
+ writeOperation = null;
+ } else if (!service.isLive(domainObject)) {
+ writeOperation = WriteOperation.DELETE;
+ } else if (id.wasEphemeral()) {
+ writeOperation = WriteOperation.PERSIST;
+ } else {
+ writeOperation = WriteOperation.UPDATE;
+ }
+
+ Splittable version = null;
+ if (writeOperation == WriteOperation.PERSIST
+ || writeOperation == WriteOperation.UPDATE) {
+ /*
+ * If we're sending an operation, the domain object must be persistent.
+ * This means that it must also have a non-null version.
+ */
+ Object domainVersion = service.getVersion(domainObject);
+ if (domainVersion == null) {
+ throw new UnexpectedException("The persisted entity with id "
+ + service.getId(domainObject) + " has a null version", null);
+ }
+ version = returnState.flatten(domainVersion);
+ }
+
+ boolean inResponse = bean.getTag(Constants.IN_RESPONSE) != null;
+
+ /*
+ * Don't send any data back to the client for an update on an object that
+ * isn't part of the response payload when the client's version matches
+ * the domain version.
+ */
+ if (WriteOperation.UPDATE.equals(writeOperation) && !inResponse) {
+ String previousVersion = bean.<String> getTag(Constants.VERSION_PROPERTY_B64);
+ if (version != null && previousVersion != null
+ && version.equals(fromBase64(previousVersion))) {
+ continue;
+ }
+ }
+
+ OperationMessage op = FACTORY.operation().as();
+
+ /*
+ * Send a client id if the id is ephemeral or was previously associated
+ * with a client id.
+ */
+ if (id.wasEphemeral()) {
+ op.setClientId(id.getClientId());
+ }
+
+ op.setOperation(writeOperation);
+
+ // Only send properties for entities that are part of the return graph
+ if (inResponse) {
+ Map<String, Splittable> propertyMap = new LinkedHashMap<String, Splittable>();
+ // Add all non-null properties to the serialized form
+ Map<String, Object> diff = AutoBeanUtils.getAllProperties(bean);
+ for (Map.Entry<String, Object> d : diff.entrySet()) {
+ Object value = d.getValue();
+ if (value != null) {
+ propertyMap.put(d.getKey(), EntityCodex.encode(returnState, value));
+ }
+ }
+ op.setPropertyMap(propertyMap);
+ }
+
+ if (!id.isEphemeral() && !id.isSynthetic()) {
+ // Send the server address only for persistent objects
+ op.setServerId(toBase64(id.getServerId()));
+ }
+
+ if (id.isSynthetic()) {
+ op.setStrength(Strength.SYNTHETIC);
+ op.setSyntheticId(id.getSyntheticId());
+ } else if (id.isEphemeral()) {
+ op.setStrength(Strength.EPHEMERAL);
+ }
+
+ op.setTypeToken(service.resolveTypeToken(id.getProxyClass()));
+ if (version != null) {
+ op.setVersion(toBase64(version.getPayload()));
+ }
+
+ operations.add(op);
+ }
+ }
+
+ /**
+ * Decode the arguments to pass into the domain method. If the domain method
+ * is not static, the instance object will be in the 0th position.
+ */
+ private List<Object> decodeInvocationArguments(RequestState source,
+ InvocationMessage invocation, Method contextMethod) {
+ boolean isStatic = Request.class.isAssignableFrom(contextMethod.getReturnType());
+ int baseLength = contextMethod.getParameterTypes().length;
+ int length = baseLength + (isStatic ? 0 : 1);
+ int offset = isStatic ? 0 : 1;
+ Class<?>[] contextArgs = new Class<?>[length];
+ Type[] genericArgs = new Type[length];
+
+ if (!isStatic) {
+ genericArgs[0] = TypeUtils.getSingleParameterization(
+ InstanceRequest.class, contextMethod.getGenericReturnType());
+ contextArgs[0] = TypeUtils.ensureBaseType(genericArgs[0]);
+ }
+ System.arraycopy(contextMethod.getParameterTypes(), 0, contextArgs, offset,
+ baseLength);
+ System.arraycopy(contextMethod.getGenericParameterTypes(), 0, genericArgs,
+ offset, baseLength);
+
+ List<Object> args = decodeInvocationArguments(source,
+ invocation.getParameters(), contextArgs, genericArgs);
+ return args;
+ }
+
+ /**
+ * Handles instance invocations as the instance at the 0th parameter.
+ */
+ private List<Object> decodeInvocationArguments(RequestState source,
+ List<Splittable> parameters, Class<?>[] contextArgs, Type[] genericArgs) {
+ if (parameters == null) {
+ // Can't return Collections.emptyList() because this must be mutable
+ return new ArrayList<Object>();
+ }
+
+ assert parameters.size() == contextArgs.length;
+ List<Object> args = new ArrayList<Object>(contextArgs.length);
+ for (int i = 0, j = contextArgs.length; i < j; i++) {
+ Class<?> type = contextArgs[i];
+ Class<?> elementType = null;
+ Splittable split;
+ if (Collection.class.isAssignableFrom(type)) {
+ elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+ Collection.class, genericArgs[i]));
+ split = parameters.get(i);
+ } else {
+ split = parameters.get(i);
+ }
+ Object arg = EntityCodex.decode(source, type, elementType, split);
+ arg = source.getResolver().resolveDomainValue(arg,
+ !EntityProxyId.class.equals(contextArgs[i]));
+ args.add(arg);
+ }
+
+ return args;
+ }
+
+ private void processInvocationMessages(RequestState state,
+ RequestMessage req, List<Splittable> results, List<Boolean> success,
+ RequestState returnState) {
+ List<InvocationMessage> invocations = req.getInvocations();
+ if (invocations == null) {
+ // No method invocations which can happen via RequestContext.fire()
+ return;
+ }
+ for (InvocationMessage invocation : invocations) {
+ try {
+ // Find the Method
+ String[] operation = invocation.getOperation().split("::");
+ Method contextMethod = service.resolveRequestContextMethod(
+ operation[0], operation[1]);
+ if (contextMethod == null) {
+ throw new UnexpectedException("Cannot resolve operation "
+ + invocation.getOperation(), null);
+ }
+ Method domainMethod = service.resolveDomainMethod(contextMethod);
+ if (domainMethod == null) {
+ throw new UnexpectedException("Cannot resolve domain method "
+ + invocation.getOperation(), null);
+ }
+
+ // Compute the arguments
+ List<Object> args = decodeInvocationArguments(state, invocation,
+ contextMethod);
+ // Possibly use a ServiceLocator
+ if (service.requiresServiceLocator(contextMethod, domainMethod)) {
+ Object serviceInstance = service.createServiceInstance(contextMethod,
+ domainMethod);
+ args.add(0, serviceInstance);
+ }
+ // Invoke it
+ Object returnValue = service.invoke(domainMethod, args.toArray());
+
+ // Convert domain object to client object
+ Type requestReturnType = service.getRequestReturnType(contextMethod);
+ returnValue = state.getResolver().resolveClientValue(returnValue,
+ requestReturnType, invocation.getPropertyRefs());
+
+ // Convert the client object to a string
+ results.add(EntityCodex.encode(returnState, returnValue));
+ success.add(true);
+ } catch (ReportableException e) {
+ results.add(AutoBeanCodex.encode(createFailureMessage(e)));
+ success.add(false);
+ }
+ }
+ }
+
+ private void processOperationMessages(final RequestState state,
+ RequestMessage req) {
+ List<OperationMessage> operations = req.getOperations();
+ if (operations == null) {
+ return;
+ }
+
+ List<AutoBean<? extends BaseProxy>> beans = state.getBeansForPayload(operations);
+ assert operations.size() == beans.size();
+
+ Iterator<OperationMessage> itOp = operations.iterator();
+ for (AutoBean<? extends BaseProxy> bean : beans) {
+ OperationMessage operation = itOp.next();
+ // Save the client's version information to reduce payload size later
+ bean.setTag(Constants.VERSION_PROPERTY_B64, operation.getVersion());
+
+ // Load the domain object with properties, if it exists
+ final Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
+ if (domain != null) {
+ // Apply any property updates
+ final Map<String, Splittable> flatValueMap = operation.getPropertyMap();
+ if (flatValueMap != null) {
+ bean.accept(new AutoBeanVisitor() {
+ @Override
+ public boolean visitReferenceProperty(String propertyName,
+ AutoBean<?> value, PropertyContext ctx) {
+ // containsKey to distinguish null from unknown
+ if (flatValueMap.containsKey(propertyName)) {
+ Class<?> elementType = ctx instanceof CollectionPropertyContext
+ ? ((CollectionPropertyContext) ctx).getElementType() : null;
+ Object newValue = EntityCodex.decode(state, ctx.getType(),
+ elementType, flatValueMap.get(propertyName));
+ Object resolved = state.getResolver().resolveDomainValue(
+ newValue, false);
+ service.setProperty(domain, propertyName,
+ service.resolveDomainClass(ctx.getType()), resolved);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName,
+ Object value, PropertyContext ctx) {
+ if (flatValueMap.containsKey(propertyName)) {
+ Splittable split = flatValueMap.get(propertyName);
+ Object newValue = ValueCodex.decode(ctx.getType(), split);
+ Object resolved = state.getResolver().resolveDomainValue(
+ newValue, false);
+ service.setProperty(domain, propertyName, ctx.getType(),
+ resolved);
+ }
+ return false;
+ }
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Validate all of the entities referenced in a RequestState.
+ */
+ private List<ViolationMessage> validateEntities(RequestState source) {
+ List<ViolationMessage> errorMessages = new ArrayList<ViolationMessage>();
+ for (Map.Entry<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> entry : source.beans.entrySet()) {
+ AutoBean<? extends BaseProxy> bean = entry.getValue();
+ Object domainObject = bean.getTag(Constants.DOMAIN_OBJECT);
+
+ // The object could have been deleted
+ if (domainObject != null) {
+ Set<ConstraintViolation<Object>> errors = service.validate(domainObject);
+ if (errors != null && !errors.isEmpty()) {
+ SimpleProxyId<?> id = entry.getKey();
+ for (ConstraintViolation<Object> error : errors) {
+ ViolationMessage message = FACTORY.violation().as();
+ message.setClientId(id.getClientId());
+ message.setMessage(error.getMessage());
+ message.setPath(error.getPropertyPath().toString());
+ if (id.isEphemeral()) {
+ message.setClientId(id.getClientId());
+ message.setStrength(Strength.EPHEMERAL);
+ } else {
+ message.setServerId(toBase64(id.getServerId()));
+ }
+ message.setTypeToken(service.resolveTypeToken(id.getProxyClass()));
+ errorMessages.add(message);
+ }
+ }
+ }
+ }
+ return errorMessages;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/UnexpectedException.java b/user/src/com/google/web/bindery/requestfactory/server/UnexpectedException.java
new file mode 100644
index 0000000..8ca35ae
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/UnexpectedException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * Encapsulates exceptions that indicate something went wrong in RequestFactory
+ * code.
+ */
+class UnexpectedException extends RuntimeException {
+ public UnexpectedException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+ public UnexpectedException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/impl/FindService.java b/user/src/com/google/web/bindery/requestfactory/server/impl/FindService.java
new file mode 100644
index 0000000..66d27ba
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/impl/FindService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server.impl;
+
+/**
+ * Server side service to support a generic find method.
+ */
+public class FindService {
+
+ /**
+ * For now, a simple implementation of find will work.
+ *
+ * @param entityInstance an entity instance
+ * @return the passed-in entity instance
+ */
+ public static <T> T find(T entityInstance) {
+ return entityInstance;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/package-info.java b/user/src/com/google/web/bindery/requestfactory/server/package-info.java
new file mode 100644
index 0000000..d14e6b9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/**
+ * Server side classes for mediating between the client side and the persistent
+ * datastore.
+ *
+ * This package contains classes that can receive client side read and write
+ * requests in the JSON format, perform the necessary operations on the
+ * persistent datastore, and return the results in JSON format.
+ *
+ * @since GWT 2.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.server;
+
diff --git a/user/src/com/google/web/bindery/requestfactory/server/testing/InProcessRequestTransport.java b/user/src/com/google/web/bindery/requestfactory/server/testing/InProcessRequestTransport.java
new file mode 100644
index 0000000..7b8c99e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/server/testing/InProcessRequestTransport.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server.testing;
+
+import com.google.web.bindery.requestfactory.server.SimpleRequestProcessor;
+import com.google.web.bindery.requestfactory.shared.RequestTransport;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+/**
+ * A RequestTransport that calls a {@link SimpleRequestProcessor}. This test can
+ * be used for end-to-end tests of RequestFactory service methods in non-GWT
+ * test suites.
+ *
+ * <pre>
+ * ServiceLayer serviceLayer = ServiceLayer.create();
+ * SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
+ * EventBus eventBus = new SimpleEventBus();
+ * MyRequestFactory f = RequestFactorySource.create(MyRequestFactory.class);
+ * f.initialize(eventBus, new InProcessRequestTransport(processor));
+ * </pre>
+ *
+ * @see com.google.web.bindery.vm.RequestFactorySource
+ * @see com.google.web.bindery.requestfactory.server.ServiceLayer#create(com.google.gwt.requestfactory.server.ServiceLayerDecorator...)
+ * ServiceLayer.create()
+ * @see com.google.gwt.event.shared.SimpleEventBus SimpleEventBus
+ * @see SimpleRequestProcessor
+ */
+public class InProcessRequestTransport implements RequestTransport {
+ private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");
+ private final SimpleRequestProcessor processor;
+
+ public InProcessRequestTransport(SimpleRequestProcessor processor) {
+ this.processor = processor;
+ }
+
+ public void send(String payload, TransportReceiver receiver) {
+ String result;
+ try {
+ if (DUMP_PAYLOAD) {
+ System.out.println(">>> " + payload);
+ }
+ result = processor.process(payload);
+ if (DUMP_PAYLOAD) {
+ System.out.println("<<< " + result);
+ }
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ receiver.onTransportFailure(new ServerFailure(e.getMessage()));
+ return;
+ }
+ receiver.onTransportSuccess(result);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/BaseProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/BaseProxy.java
new file mode 100644
index 0000000..0dd2900
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/BaseProxy.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * The root type from which all client-side proxy objects are derived. Users
+ * should not extend the BaseProxy type directly, but use either
+ * {@link EntityProxy} or {@link ValueProxy}.
+ *
+ * @see EntityProxy
+ * @see ValueProxy
+ */
+public interface BaseProxy {
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java b/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java
new file mode 100644
index 0000000..44dcbce
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/DefaultProxyStore.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.requestfactory.shared.impl.MessageFactoryHolder;
+import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An in-memory ProxyStore store that can encode its state as a JSON object
+ * literal.
+ */
+public class DefaultProxyStore implements ProxyStore {
+ /**
+ * Provide a little bit of future-proofing to at least detect an encoded
+ * payload that can't be decoded.
+ */
+ private static final String EXPECTED_VERSION = "211";
+ private final AutoBean<OperationMessage> messageBean;
+ private final Map<String, Splittable> map;
+
+ /**
+ * Construct an empty DefaultProxyStore.
+ */
+ public DefaultProxyStore() {
+ messageBean = MessageFactoryHolder.FACTORY.operation();
+ map = new HashMap<String, Splittable>();
+
+ OperationMessage message = messageBean.as();
+ message.setPropertyMap(map);
+ message.setVersion(EXPECTED_VERSION);
+ }
+
+ /**
+ * Construct a DefaultProxyStore using the a value returned from
+ * {@link #encode()}.
+ *
+ * @param payload a String previously returned from {@link #encode()}
+ * @throws IllegalArgumentException if the payload cannot be parsed
+ */
+ public DefaultProxyStore(String payload) throws IllegalArgumentException {
+ messageBean = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
+ OperationMessage.class, payload);
+
+ OperationMessage message = messageBean.as();
+ if (!EXPECTED_VERSION.equals(message.getVersion())) {
+ throw new IllegalArgumentException(
+ "Unexpected version string in payload " + message.getVersion());
+ }
+ map = message.getPropertyMap();
+ }
+
+ /**
+ * Return a JSON object literal with the contents of the store.
+ */
+ public String encode() {
+ return AutoBeanCodex.encode(messageBean).getPayload();
+ }
+
+ public Splittable get(String key) {
+ return map.get(key);
+ }
+
+ public int nextId() {
+ return map.size();
+ }
+
+ public void put(String key, Splittable value) {
+ map.put(key, value);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java
new file mode 100644
index 0000000..eaab4bc
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * A proxy for a server-side domain object.
+ */
+public interface EntityProxy extends BaseProxy {
+ /**
+ * Returns the {@link EntityProxyId} that identifies a particular instance of
+ * the type proxied by the receiver.
+ * <p>
+ * An id returned by a proxy newly created by {@link RequestContext#create}
+ * {@link Object#equals(Object) equals} those returned later by proxies to the
+ * persisted object.
+ * <p>
+ * Subtypes should override to declare they return a stable id of their own
+ * type, to allow type safe use of the request objects returned by
+ * {@link RequestFactory#find(EntityProxyId)}.
+ *
+ * @return an {@link EntityProxyId} instance
+ */
+ EntityProxyId<?> stableId();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyChange.java b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyChange.java
new file mode 100644
index 0000000..ab013cb
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyChange.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.event.shared.Event;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.HandlerRegistration;
+
+/**
+ * Event posted by a {@link RequestFactory} when changes to an entity are
+ * detected. Provides a {@link WriteOperation} value describing the change, and
+ * the {@link EntityProxyId} of the entity in question.
+ * <p>
+ * EntityProxyChange events are posted with the relevant EntityProxy
+ * Class as their source, allowing handlers to register for changes only
+ * of the type they care about via
+ * {@link #registerForProxyType(EventBus, Class, Handler)}.
+ *
+ * @param <P> the type of the proxy
+ *
+ * @see RequestFactory#initialize(EventBus)
+ * @see RequestFactory#find(EntityProxyId)
+ */
+public class EntityProxyChange<P extends EntityProxy> extends
+ Event<EntityProxyChange.Handler<P>> {
+
+ /**
+ * Implemented by methods that handle EntityProxyChange events.
+ *
+ * @param <P> the proxy type
+ */
+ public interface Handler<P extends EntityProxy> {
+ /**
+ * Called when an {@link EntityProxyChange} event is fired.
+ *
+ * @param event an {@link EntityProxyChange} instance
+ */
+ void onProxyChange(EntityProxyChange<P> event);
+ }
+
+ private static final Type<EntityProxyChange.Handler<?>> TYPE = new Type<EntityProxyChange.Handler<?>>();
+
+ /**
+ * Register a handler for a EntityProxyChange events for a particular proxy
+ * class.
+ *
+ * @param eventBus the {@link EventBus}
+ * @param proxyType a Class instance of type P
+ * @param handler an {@link EntityProxyChange.Handler} instance of type P
+ * @return an {@link EntityProxy} instance
+ */
+ public static <P extends EntityProxy> HandlerRegistration registerForProxyType(
+ EventBus eventBus, Class<P> proxyType,
+ EntityProxyChange.Handler<P> handler) {
+ return eventBus.addHandlerToSource(TYPE, proxyType, handler);
+ }
+
+ private P proxy;
+
+ private WriteOperation writeOperation;
+
+ /**
+ * Constructs an EntityProxyChange object.
+ *
+ * @param proxy an {@link EntityProxy} instance of type P
+ * @param writeOperation a {@link WriteOperation} instance
+ */
+ public EntityProxyChange(P proxy, WriteOperation writeOperation) {
+ this.proxy = proxy;
+ this.writeOperation = writeOperation;
+ }
+
+ /**
+ * Returns the type associated with this instance.
+ *
+ * @return an instance of {@link Event.Type Type} of type Handler<P>
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @Override
+ public Event.Type<Handler<P>> getAssociatedType() {
+ /*
+ * The instance knows its handler is of type P, but the TYPE field itself
+ * does not, so we have to do an unsafe cast here.
+ */
+
+ return (Type) TYPE;
+ }
+
+ /**
+ * Returns an unpopulated copy of the changed proxy — all properties are
+ * undefined except its id.
+ *
+ * @return an instance of {@link EntityProxyId}<P>
+ */
+ @SuppressWarnings("unchecked")
+ public EntityProxyId<P> getProxyId() {
+ return (EntityProxyId<P>) proxy.stableId();
+ }
+
+ /**
+ * Returns the {@link WriteOperation} associated with this instance.
+ *
+ * @return a {@link WriteOperation} instance
+ */
+ public WriteOperation getWriteOperation() {
+ return writeOperation;
+ }
+
+ @Override
+ protected void dispatch(Handler<P> handler) {
+ handler.onProxyChange(this);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyId.java b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyId.java
new file mode 100644
index 0000000..5a5bd4c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxyId.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * A stable, opaque id of an {@link EntityProxy} that remains stable across
+ * updates, creates, deletes on the client.
+ * <p>
+ * In particular, an {@link EntityProxy} foo that is yet to be persisted and a
+ * copy of foo after being persisted have equal {@link EntityProxyId}.
+ *
+ * @param <P> the entity type
+ */
+@ProxyFor(Object.class)
+public interface EntityProxyId<P extends EntityProxy> {
+
+ /**
+ * Returns the class of the proxy identified.
+ *
+ * @return a Class object of type P
+ */
+ Class<P> getProxyClass();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/InstanceRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/InstanceRequest.java
new file mode 100644
index 0000000..fff1a78
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/InstanceRequest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Used to call instance methods. Note that this does not extend {@link Request}
+ * — rather it vends one. There is no way to get an instance method's
+ * {@link Request#fire} without assigning an instance by calling {@link #using}.
+ *
+ * @param <P> the instance type of BaseProxy
+ * @param <T> the type eventually returned by the method invocation
+ */
+public interface InstanceRequest<P extends BaseProxy, T> {
+
+ /**
+ * Provide the instance on which the request will be invoked.
+ *
+ * @param instanceObject an {@link BaseProxy} instance of type P
+ * @return an instance of {@link Request}<T>
+ */
+ Request<T> using(P instanceObject);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcContent.java b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcContent.java
new file mode 100644
index 0000000..ad0f7c9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcContent.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <b>Experimental API, subject to change.</b> Applied to a Request method
+ * declaration to indicate that a particular parameter is used as the
+ * {@code request} portion of the JSON-RPC request. This is analogous to the
+ * payload body in a REST-style request.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface JsonRpcContent {
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcProxy.java
new file mode 100644
index 0000000..00ed40c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcProxy.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared;
+
+/**
+ * <b>Experimental API, subject to change</b> Used instead of the
+ * {@link ProxyFor} annotation.
+ */
+public @interface JsonRpcProxy {
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcService.java b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcService.java
new file mode 100644
index 0000000..2847fe9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcService.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <b>Experimental API, subject to change</b> Indicates that a RequestContext
+ * should be encoded as a JSON-RPC request.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface JsonRpcService {
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcWireName.java b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcWireName.java
new file mode 100644
index 0000000..34969ce
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/JsonRpcWireName.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * <b>Experimental API, subject to change</b> Provides the method name for a
+ * JSON-RPC invocation.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonRpcWireName {
+ String value();
+
+ String version() default "";
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Locator.java b/user/src/com/google/web/bindery/requestfactory/shared/Locator.java
new file mode 100644
index 0000000..e2c5723
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Locator.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * A Locator allows entity types that do not conform to the RequestFactory
+ * entity protocol to be used. Instead of attempting to use a {@code findFoo()},
+ * {@code getId()}, and {@code getVersion()} declared in the domain entity type,
+ * an instance of a Locator will be created to provide implementations of these
+ * methods.
+ * <p>
+ * Locator subtypes must be default instantiable (i.e. public static types with
+ * a no-arg constructor). Instances of Locators may be retained and reused by
+ * the RequestFactory service layer.
+ *
+ * @param <T> the type of domain object the Locator will operate on
+ * @param <I> the type of object the Locator expects to use as an id for the
+ * domain object
+ * @see ProxyFor#locator()
+ */
+public abstract class Locator<T, I> {
+ /**
+ * Create a new instance of the requested type.
+ *
+ * @param clazz the type of object to create
+ * @return the new instance of the domain type
+ */
+ public abstract T create(Class<? extends T> clazz);
+
+ /**
+ * Retrieve an object. May return {@code null} to indicate that the requested
+ * object could not be found.
+ *
+ * @param clazz the type of object to retrieve
+ * @param id an id previously returned from {@link #getId(Object)}
+ * @return the requested object or {@code null} if it could not be found
+ */
+ public abstract T find(Class<? extends T> clazz, I id);
+
+ /**
+ * Returns the {@code T} type.
+ */
+ public abstract Class<T> getDomainType();
+
+ /**
+ * Returns a domain object to be used as the id for the given object. This
+ * method may return {@code null} if the object has not been persisted or
+ * should be treated as irretrievable.
+ *
+ * @param domainObject the object to obtain an id for
+ * @return the object's id or {@code null}
+ */
+ public abstract I getId(T domainObject);
+
+ /**
+ * Returns the {@code I} type.
+ */
+ public abstract Class<I> getIdType();
+
+ /**
+ * Returns a domain object to be used as the version for the given object.
+ * This method may return {@code null} if the object has not been persisted or
+ * should be treated as irretrievable.
+ *
+ * @param domainObject the object to obtain an id for
+ * @return the object's version or {@code null}
+ */
+ public abstract Object getVersion(T domainObject);
+
+ /**
+ * Returns a value indicating if the domain object should no longer be
+ * considered accessible. This method might return false if the record
+ * underlying the domain object had been deleted as a side-effect of
+ * processing a request.
+ * <p>
+ * The default implementation of this method uses {@link #getId(Object)} and
+ * {@link #find(Class, Object)} to determine if an object can be retrieved.
+ */
+ public boolean isLive(T domainObject) {
+ // Can't us Class.asSubclass() in web-mode code
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) domainObject.getClass();
+ return find(clazz, getId(domainObject)) != null;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/LoggingRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/LoggingRequest.java
new file mode 100644
index 0000000..3cdfdb3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/LoggingRequest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.Logging;
+
+/**
+ * "API Generated" request selector interface implemented by objects that give
+ * client access to the methods of {@link Logging}.
+ */
+@Service(Logging.class)
+public interface LoggingRequest extends RequestContext {
+
+ // TODO(unnurg): Pass a SerializableLogRecord here rather than it's
+ // serialized string.
+ /**
+ * Log a message on the server.
+ *
+ * @param serializedLogRecordString a json serialized LogRecord, as provided by
+ * {@link com.google.gwt.logging.client.JsonLogRecordClientUtil#logRecordAsJsonObject(LogRecord)}
+ * @return a Void {@link Request}
+ */
+ Request<Void> logMessage(String serializedLogRecordString);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxyFor.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxyFor.java
new file mode 100644
index 0000000..77dc4c2
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxyFor.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on EntityProxy and ValueProxy classes specifying the domain
+ * (server-side) object type.
+ *
+ * @see ProxyForName
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ProxyFor {
+ /**
+ * The domain type that the proxy is mapped to.
+ */
+ Class<?> value();
+
+ /**
+ * An optional {@link Locator} that provides instances of the domain objects.
+ */
+ @SuppressWarnings("rawtypes")
+ Class<? extends Locator> locator() default Locator.class;
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxyForName.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxyForName.java
new file mode 100644
index 0000000..226e4f4
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxyForName.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on EntityProxy classes specifying the domain (server-side) object
+ * type. This annotation can be used in place of {@link ProxyFor} if the domain
+ * object is not available to the GWT compiler or DevMode runtime.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ProxyForName {
+ /**
+ * The name of the domain type that the proxy is mapped to.
+ */
+ String value();
+
+ /**
+ * An optional name of a {@link Locator} that provides instances of the domain
+ * objects.
+ */
+ String locator() default "";
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxySerializer.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxySerializer.java
new file mode 100644
index 0000000..59da4c7
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxySerializer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Serializes graphs of EntityProxy objects. A ProxySerializer is associated
+ * with an instance of a {@link ProxyStore} when it is created via
+ * {@link RequestFactory#getSerializer(ProxyStore)}.
+ * <p>
+ * The {@link EntityProxy#stableId()} of non-persisted (i.e. newly
+ * {@link RequestContext#create(Class) created}) {@link EntityProxy} instances
+ * are not stable.
+ * <p>
+ * To create a self-contained message that encapsulates a proxy:
+ *
+ * <pre>
+ * RequestFactory myFactory = ...;
+ * MyFooProxy someProxy = ...;
+ *
+ * DefaultProxyStore store = new DefaultProxyStore();
+ * ProxySerializer ser = myFactory.getSerializer(store);
+ * // More than one proxy could be serialized
+ * String key = ser.serialize(someProxy);
+ * // Create the flattened representation
+ * String payload = store.encode();
+ * </pre>
+ *
+ * To recreate the object:
+ *
+ * <pre>
+ * ProxyStore store = new DefaultProxyStore(payload);
+ * ProxySerializer ser = myFactory.getSerializer(store);
+ * MyFooProxy someProxy = ser.deserialize(MyFooProxy.class, key);
+ * </pre>
+ *
+ * If two objects refer to different EntityProxy instances that have the same
+ * stableId(), the last mutable proxy encountered will be preferred, otherwise
+ * the first immutable proxy will be used.
+ *
+ * @see DefaultProxyStore
+ */
+public interface ProxySerializer {
+ /**
+ * Recreate a proxy instance that was previously passed to
+ * {@link #serialize(BaseProxy)}.
+ *
+ * @param <T> the type of proxy object to create
+ * @param proxyType the type of proxy object to create
+ * @param key a value previously returned from {@link #serialize(BaseProxy)}
+ * @return a new, immutable instance of the proxy or {@code null} if the data
+ * needed to deserialize the proxy is not present in the ProxyStore
+ */
+ <T extends BaseProxy> T deserialize(Class<T> proxyType, String key);
+
+ /**
+ * Recreate a {@link EntityProxy} instance that was previously passed to
+ * {@link #serialize(BaseProxy)}.
+ *
+ * @param <T> the type of proxy object to create
+ * @param id the {@link EntityProxyId} of the desired entity
+ * @return a new, immutable instance of the proxy or {@code null} if the data
+ * needed to deserialize the proxy is not present in the ProxyStore
+ */
+ <T extends EntityProxy> T deserialize(EntityProxyId<T> id);
+
+ /**
+ * Store a proxy into the backing store.
+ *
+ * @param proxy the proxy to store
+ * @return a key value that can be passed to
+ * {@link #deserialize(Class, String)}
+ */
+ String serialize(BaseProxy proxy);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java b/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java
new file mode 100644
index 0000000..ffb3f87
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ProxyStore.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+
+/**
+ * A ProxyStore provides a {@link ProxySerializer} with access to a low-level
+ * persistence mechanism. The ProxyStore does not need to be able to interpret
+ * the data sent to it by the ProxySerializer; it is merely a wrapper around a
+ * persistence mechanism.
+ *
+ * @see DefaultProxyStore
+ */
+public interface ProxyStore {
+ /**
+ * Called by {@link ProxySerializer} to retrieve a value previously provided
+ * to {@link #put(String, Splittable)}.
+ *
+ * @param key the key
+ * @return the associated value or {@code null} if {@code key} is unknown
+ */
+ Splittable get(String key);
+
+ /**
+ * Returns a non-negative sequence number. The actual sequence of values
+ * returned by this method is unimportant, as long as the numbers in the
+ * sequence are unique.
+ */
+ int nextId();
+
+ /**
+ * Called by {@link ProxySerializer} to store a value.
+ *
+ * @param key a key value that will be passed to {@link #get(String)}
+ * @param value the data to store
+ * @see Splittable#getPayload()
+ */
+ void put(String key, Splittable value);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Receiver.java b/user/src/com/google/web/bindery/requestfactory/shared/Receiver.java
new file mode 100644
index 0000000..1bd6fed
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Receiver.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import java.util.Set;
+
+/**
+ * Callback object for {@link Request#fire(Receiver)} and
+ * {@link RequestContext#fire(Receiver)}.
+ *
+ * @param <V> value type
+ */
+public abstract class Receiver<V> {
+ /**
+ * Receives general failure notifications. The default implementation looks at
+ * {@link ServerFailure#isFatal()}, and throws a runtime exception with the
+ * failure object's error message if it is true.
+ *
+ * @param error a {@link ServerFailure} instance
+ */
+ public void onFailure(ServerFailure error) {
+ if (error.isFatal()) {
+ throw new RuntimeException(error.getMessage());
+ }
+ }
+
+ /**
+ * Called when a Request has been successfully executed on the server.
+ *
+ * @param response a response of type V
+ */
+ public abstract void onSuccess(V response);
+
+ /**
+ * Called if an object sent to the server could not be validated. The default
+ * implementation calls {@link #onFailure(ServerFailure)} if <code>errors
+ * </code> is not empty.
+ *
+ * @param errors a Set of {@link Violation} instances
+ */
+ public void onViolation(Set<Violation> errors) {
+ if (!errors.isEmpty()) {
+ onFailure(new ServerFailure(
+ "The call failed on the server due to a ConstraintViolation"));
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Request.java b/user/src/com/google/web/bindery/requestfactory/shared/Request.java
new file mode 100644
index 0000000..7a2edbe
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Request.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Implemented by the request objects created by this factory.
+ *
+ * @param <T> The return type of objects in the corresponding response.
+ */
+public interface Request<T> {
+
+ /**
+ * Submit this request. Failures will be reported through the global uncaught
+ * exception handler, if any.
+ */
+ void fire();
+
+ /**
+ * Convenience method equivalent to calling <code>to(...).fire()</code>.
+ *
+ * @param receiver a {@link Receiver} instance
+ */
+ void fire(Receiver<? super T> receiver);
+
+ /**
+ * Specify the object that will receive the result of the method invocation.
+ *
+ * @param receiver a {@link Receiver} instance
+ * @return a {@link RequestContext} instance
+ */
+ RequestContext to(Receiver<? super T> receiver);
+
+ /**
+ * Request additional reference properties to fetch with the return value.
+ *
+ * @param propertyRefs a list of reference property names as Strings
+ * @return a {@link Request} instance of type T
+ */
+ Request<T> with(String... propertyRefs);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java b/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java
new file mode 100644
index 0000000..91e45d0
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * The base interface for RequestFactory service endpoints.
+ */
+public interface RequestContext {
+ /**
+ * Returns a new mutable proxy that this request can carry to the server,
+ * perhaps to be persisted. If the object is succesfully persisted, a PERSIST
+ * event will be posted including the EntityProxyId of this proxy.
+ *
+ * @param clazz a Class object of type T
+ * @return an {@link BaseProxy} instance of type T
+ */
+ <T extends BaseProxy> T create(Class<T> clazz);
+
+ /**
+ * Returns a mutable version of the proxy, whose mutations will accumulate in
+ * this context. Proxies reached via getters on this mutable proxy will also
+ * be mutable.
+ *
+ * @param object an instance of type T
+ * @return an {@link EntityProxy} or {@link ValueProxy} instance of type T
+ */
+ <T extends BaseProxy> T edit(T object);
+
+ /**
+ * Send the accumulated changes and method invocations associated with the
+ * RequestContext.
+ * <p>
+ * If {@link Request#to(Receiver)} has not been called, this method will
+ * install a default receiver that will throw a RuntimeException if there is a
+ * server failure.
+ */
+ void fire();
+
+ /**
+ * For receiving errors or validation failures only.
+ *
+ * @param receiver a {@link Receiver} instance
+ * @throws IllegalArgumentException if <code>receiver</code> is
+ * <code>null</code>
+ */
+ void fire(Receiver<Void> receiver);
+
+ /**
+ * Returns true if any changes have been made to proxies mutable under this
+ * context. Note that vacuous changes — e.g. foo.setName(foo.getName()
+ * — will not trip the changed flag. Similarly, "unmaking" a change will
+ * clear the isChanged flag
+ *
+ * <pre>
+ * String name = bar.getName();
+ * bar.setName("something else");
+ * assertTrue(context.isChanged());
+ * bar.setName(name);
+ * assertFalse(context.isChanged());
+ * </pre>
+ *
+ * @return {@code true} if any changes have been made
+ */
+ boolean isChanged();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/RequestFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/RequestFactory.java
new file mode 100644
index 0000000..c265cd9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/RequestFactory.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.event.shared.EventBus;
+
+/**
+ * Marker interface for the RequestFactory code generator.
+ * <p>
+ * <b>Limitations on the transferrability of types.</b> <br>
+ * RequestFactory currently supports the transfer of basic value types, entity
+ * types, and collections, with limitations. The basic value types supported are
+ * {@link String}, {@link Enum}, {@link Boolean}, {@link Character}, subtypes of
+ * {@link Number}, and {@link java.util.Date}. Any value type not included in
+ * this list may not be declared in the type signature of a service method, or
+ * {@link EntityProxy}. {@link java.util.Collection} types supported are
+ * {@link java.util.List} and {@link java.util.Set} with the restriction that a
+ * collection must be homogeneous and only hold one type of value.
+ * </p>
+ * <p>
+ * Polymorphism is not supported at this time. RequestFactory encoding and
+ * decoding requires exact knowledge of the concrete type. If a method declares
+ * a given type <code>T</code> as a parameter or return type, only
+ * <code>T</code>'s transferrable properties will be sent over the wire if it is
+ * a proxy, even if the underlying domain value contains extra fields, in
+ * effect, treating it as an instance of the supertype. Returning abstract
+ * supertypes of value types is not supported (e.g. Object, Enum, Number).
+ * </p>
+ *
+ * @see com.google.web.bindery.requestfactory.server.testing.InProcessRequestTransport
+ */
+public interface RequestFactory {
+ /**
+ * The JSON content type String.
+ */
+ String JSON_CONTENT_TYPE_UTF8 = "application/json; charset=utf-8";
+
+ /**
+ * Return a request to find a fresh instance of the referenced proxy.
+ *
+ * @param proxyId an {@link EntityProxyId} instance of type P
+ * @return a {@link Request} object
+ */
+ <P extends EntityProxy> Request<P> find(EntityProxyId<P> proxyId);
+
+ /**
+ * Returns the event bus this factory's events are posted on, which was set
+ * via {@link #initialize}.
+ *
+ * @return the {@link EventBus} associated with this instance
+ */
+ EventBus getEventBus();
+
+ /**
+ * Get a {@link com.google.gwt.user.client.History} compatible token that
+ * represents the given class. It can be processed by
+ * {@link #getProxyClass(String)}
+ *
+ * @param clazz a Class object for an {@link EntityProxy} subclass
+ * @return a {@link com.google.gwt.user.client.History} compatible token
+ */
+ String getHistoryToken(Class<? extends EntityProxy> clazz);
+
+ /**
+ * Get a {@link com.google.gwt.user.client.History} compatible token that
+ * represents the given proxy class. It can be processed by
+ * {@link #getProxyClass(String)}.
+ * <p>
+ * The history token returned for an EntityProxyId associated with a
+ * newly-created (future) EntityProxy will differ from the token returned by
+ * this method after the EntityProxy has been persisted. Once an EntityProxy
+ * has been persisted, the return value for this method will always be stable,
+ * regardless of when the EntityProxyId was retrieved relative to the persist
+ * operation. In other words, the "future" history token returned for an
+ * as-yet-unpersisted EntityProxy is only valid for the duration of the
+ * RequestFactory's lifespan.
+ *
+ * @param proxy an {@link EntityProxyId} instance
+ * @return a {@link com.google.gwt.user.client.History} compatible token
+ */
+ String getHistoryToken(EntityProxyId<?> proxy);
+
+ /**
+ * Return the class object which may be used to create new instances of the
+ * type of this token, via {@link RequestContext#create}. The token may
+ * represent either a proxy instance (see {@link #getHistoryToken}) or a proxy
+ * class (see {@link #getProxyClass}).
+ *
+ * @param historyToken a String token
+ * @return a Class object for an {@link EntityProxy} subclass
+ */
+ Class<? extends EntityProxy> getProxyClass(String historyToken);
+
+ /**
+ * Return the appropriate {@link EntityProxyId} using a string returned from
+ * {@link #getHistoryToken(EntityProxyId)}.
+ *
+ * @param historyToken a String token
+ * @return an {@link EntityProxyId}
+ */
+ <T extends EntityProxy> EntityProxyId<T> getProxyId(String historyToken);
+
+ /**
+ * Returns a ProxySerializer that can encode and decode the various
+ * EntityProxy and ValueProxy types reachable from the RequestFactory.
+ *
+ * @param store a helper object for the ProxySerializer to provide low-level
+ * storage access
+ * @return a new ProxySerializer
+ * @see DefaultProxyStore
+ */
+ ProxySerializer getSerializer(ProxyStore store);
+
+ /**
+ * Start this request factory with a
+ * {@link com.google.web.bindery.requestfactory.gwt.client.DefaultRequestTransport}.
+ *
+ * @param eventBus an {@link EventBus}
+ */
+ void initialize(EventBus eventBus);
+
+ /**
+ * Start this request factory with a user-provided transport.
+ *
+ * @param eventBus an {@link EventBus}
+ * @param transport a {@link RequestTransport} instance
+ */
+ void initialize(EventBus eventBus, RequestTransport transport);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/RequestTransport.java b/user/src/com/google/web/bindery/requestfactory/shared/RequestTransport.java
new file mode 100644
index 0000000..53ba20b
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/RequestTransport.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Abstracts the mechanism by which a RequestFactory instance transmits its
+ * payload to the backend.
+ *
+ * @see com.google.web.bindery.requestfactory.gwt.client.DefaultRequestTransport
+ */
+public interface RequestTransport {
+ /**
+ * A callback interface.
+ */
+ public interface TransportReceiver {
+ /**
+ * Called when the transmission succeeds.
+ *
+ * @param payload the String payload
+ */
+ void onTransportSuccess(String payload);
+
+ /**
+ * Called to report a transmission failure as a ServerFailure.
+ *
+ * @param failure a ServerFailure instance indicating the nature
+ * of the failure
+ */
+ void onTransportFailure(ServerFailure failure);
+ }
+
+ /**
+ * Called by the RequestFactory implementation.
+ *
+ * @param payload the String payload
+ * @param receiver the {@link TransportReceiver} instance that will
+ * receive the payload
+ */
+ void send(String payload, TransportReceiver receiver);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ServerFailure.java b/user/src/com/google/web/bindery/requestfactory/shared/ServerFailure.java
new file mode 100644
index 0000000..a4721fe
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ServerFailure.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Describes a request failure on the server.
+ * <p>
+ * This error reporting mechanism is adequate at best. When RequestFactory is
+ * extended to handle polymorphic types, this class will likely be replaced with
+ * something more expressive.
+ */
+public class ServerFailure {
+ private final String message;
+ private final String stackTraceString;
+ private final String exceptionType;
+ private final boolean fatal;
+
+ /**
+ * Constructs a ServerFailure with null properties.
+ */
+ public ServerFailure() {
+ this(null);
+ }
+
+ /**
+ * Constructs a fatal ServerFailure with null type and null stack trace.
+ */
+ public ServerFailure(String message) {
+ this(message, null, null, true);
+ }
+
+ /**
+ * Constructs a ServerFailure object.
+ *
+ * @param message a String containing the failure message
+ * @param exceptionType a String containing the exception type
+ * @param stackTraceString a String containing the stack trace
+ */
+ public ServerFailure(String message, String exceptionType,
+ String stackTraceString, boolean fatal) {
+ this.message = message;
+ this.exceptionType = exceptionType;
+ this.stackTraceString = stackTraceString;
+ this.fatal = fatal;
+ }
+
+ /**
+ * Return the exception type.
+ *
+ * @return the exception type as a String
+ */
+ public String getExceptionType() {
+ return exceptionType;
+ }
+
+ /**
+ * Return the failure message.
+ *
+ * @return the message as a String
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Return the failure stack trace.
+ *
+ * @return the stack trace as a String
+ */
+ public String getStackTraceString() {
+ return stackTraceString;
+ }
+
+ /**
+ * Return true if this is a fatal error. The default implementation of
+ * {@link Receiver#onFailure} throws a runtime exception for fatal failures.
+ *
+ * @return whether this is a fatal failure
+ */
+ public boolean isFatal() {
+ return fatal;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Service.java b/user/src/com/google/web/bindery/requestfactory/shared/Service.java
new file mode 100644
index 0000000..27f8b5a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Service.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on Request classes specifying the server side implementations that
+ * back them.
+ *
+ * @see ServiceName
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Service {
+ /**
+ * The domain type that provides the implementations for the methods defined
+ * in the RequestContext.
+ */
+ Class<?> value();
+
+ /**
+ * An optional {@link ServiceLocator} that provides instances of service
+ * objects used when invoking instance methods on the type returned by
+ * {@link #value()}.
+ */
+ Class<? extends ServiceLocator> locator() default ServiceLocator.class;
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ServiceLocator.java b/user/src/com/google/web/bindery/requestfactory/shared/ServiceLocator.java
new file mode 100644
index 0000000..0b53733
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ServiceLocator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * A ServiceLocator provides instances of a type specified by a {@link Service}
+ * when {@link Request} methods declared in a {@link RequestContext}are mapped
+ * onto instance (non-static) methods.
+ * <p>
+ * ServiceLocator subtypes must be default instantiable (i.e. public static
+ * types with a no-arg constructor). Instances of ServiceLocators may be
+ * retained and reused by the RequestFactory service layer.
+ *
+ * @see Service#locator()
+ */
+public interface ServiceLocator {
+ /**
+ * Returns an instance of the service object.
+ *
+ * @param clazz the requested type of service object
+ * @return an instance of the service object
+ */
+ Object getInstance(Class<?> clazz);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ServiceName.java b/user/src/com/google/web/bindery/requestfactory/shared/ServiceName.java
new file mode 100644
index 0000000..6ea2abe
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ServiceName.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on Request classes specifying the server side implementations that
+ * back them.This annotation can be used in place of {@link Service} if the
+ * service type is not available to the GWT compiler or DevMode runtime.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ServiceName {
+ /**
+ * The binary name of the domain type that provides the implementations for
+ * the methods defined in the RequestContext.
+ */
+ String value();
+
+ /**
+ * An optional binary name of a {@link ServiceLocator} that provides instances
+ * of service objects used when invoking instance methods on the type returned
+ * by {@link #value()}.
+ */
+ String locator() default "";
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
new file mode 100644
index 0000000..ad6575f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation on methods of {@link RequestContext}, {@link EntityProxy}, or
+ * {@link ValueProxy} interfaces so that the
+ * {@link com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator
+ * RequestFactoryInterfaceValidator} doesn't enforce the presence of a
+ * corresponding method on the domain type.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface SkipInterfaceValidation {
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java
new file mode 100644
index 0000000..bbd7760
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * An analog to EntityProxy for domain types that do not have an identity
+ * concept.
+ */
+public interface ValueProxy extends BaseProxy {
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/Violation.java b/user/src/com/google/web/bindery/requestfactory/shared/Violation.java
new file mode 100644
index 0000000..3d68373
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/Violation.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * A lightweight representation of a
+ * {@link javax.validation.ConstraintViolation}.
+ */
+public interface Violation {
+ /**
+ * If the ConstraintViolation occurred while validating a object, this method
+ * will return a BaseProxy that contains the invalid values.
+ *
+ * @return the BaseProxy that caused the ConstraintViolation
+ */
+ BaseProxy getInvalidProxy();
+
+ /**
+ * Returns the message associated with this {@link Violation}.
+ *
+ * @return a String message
+ */
+ String getMessage();
+
+ /**
+ * If the ConstraintViolation occurred while validating a value object that
+ * originated from the server, this method will return a BaseProxy that
+ * contains the original values.
+ *
+ * @return the BaseProxy originally sent by the server or {@code null} if the
+ * BaseProxy was created on the client.
+ */
+ BaseProxy getOriginalProxy();
+
+ /**
+ * Returns the path associated with this {@link Violation}.
+ *
+ * @return a String path
+ */
+ String getPath();
+
+ /**
+ * Returns the proxy id associated with this {@link Violation} if the object
+ * associated with the violation is an {@link EntityProxy}.
+ *
+ * @return an {@link EntityProxyId} instance or {@code null} if the object is
+ * a ValueProxy.
+ */
+ EntityProxyId<?> getProxyId();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/WriteOperation.java b/user/src/com/google/web/bindery/requestfactory/shared/WriteOperation.java
new file mode 100644
index 0000000..9f74a53
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/WriteOperation.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * The values returned by {@link EntityProxyChange#getWriteOperation()} to
+ * describe the type of change being announced.
+ *
+ * <dl>
+ * <dt>PERSIST
+ * <dd>An {@link EntityProxy} that was created on the client has been persisted
+ * on the server
+ *
+ * <dt>UPDATE
+ * <dd>An {@link EntityProxy} has been encountered by the client for the first
+ * time, or its version value has changed
+ *
+ * <dt>DELETE
+ * <dd>The server has confirmed the success of a client request to delete an
+ * {@link EntityProxy}
+ * </dl>
+ *
+ * @see EntityProxyChange#getWriteOperation()
+ */
+public enum WriteOperation {
+ PERSIST("PERSIST"), UPDATE("UPDATE"), DELETE("DELETE");
+
+ // use an unObfuscatedEnumName field to bypass the implicit name() method,
+ // to be safe in the case enum name obfuscation is enabled.
+ private final String unObfuscatedEnumName;
+
+ private WriteOperation(String unObfuscatedEnumName) {
+ this.unObfuscatedEnumName = unObfuscatedEnumName;
+ }
+
+ /**
+ * Returns the unobfuscated name of the event associated with this
+ * {@link WriteOperation}.
+ *
+ * @return one of "PERSIST", "UPDATE", or "DELETE"
+ */
+ public String getUnObfuscatedEnumName() {
+ return this.unObfuscatedEnumName;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java
new file mode 100644
index 0000000..f7faed5
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Abstract implementation of {@link Request}. Each request stores a
+ * {@link DeltaValueStoreJsonImpl}.
+ *
+ * @param <T> return type
+ */
+public abstract class AbstractRequest<T> implements Request<T>,
+ InstanceRequest<BaseProxy, T> {
+
+ /**
+ * Used by generated subtypes.
+ */
+ protected final Set<String> propertyRefs = new HashSet<String>();
+ protected final AbstractRequestContext requestContext;
+ private Receiver<? super T> receiver;
+ private RequestData requestData;
+
+ protected AbstractRequest(AbstractRequestContext requestContext) {
+ this.requestContext = requestContext;
+ }
+
+ public void fire() {
+ requestContext.fire();
+ }
+
+ public void fire(Receiver<? super T> receiver) {
+ to(receiver);
+ fire();
+ }
+
+ /**
+ * Returns the properties.
+ */
+ public Set<String> getPropertyRefs() {
+ return Collections.unmodifiableSet(propertyRefs);
+ }
+
+ public RequestData getRequestData() {
+ if (requestData == null) {
+ requestData = makeRequestData();
+ }
+ return requestData;
+ }
+
+ public RequestContext to(Receiver<? super T> receiver) {
+ this.receiver = receiver;
+ return requestContext;
+ }
+
+ /**
+ * This method comes from the {@link InstanceRequest} interface. Instance
+ * methods place the instance in the first parameter slot.
+ */
+ public Request<T> using(BaseProxy instanceObject) {
+ getRequestData().getOrderedParameters()[0] = instanceObject;
+ /*
+ * Instance methods enqueue themselves when their using() method is called.
+ * This ensures that the instance parameter will have been set when
+ * AbstractRequestContext.retainArg() is called.
+ */
+ requestContext.addInvocation(this);
+ return this;
+ }
+
+ public Request<T> with(String... propertyRefs) {
+ this.propertyRefs.addAll(Arrays.asList(propertyRefs));
+ return this;
+ }
+
+ protected abstract RequestData makeRequestData();
+
+ Receiver<? super T> getReceiver() {
+ return receiver;
+ }
+
+ boolean hasReceiver() {
+ return receiver != null;
+ }
+
+ void onFail(ServerFailure failure) {
+ if (receiver != null) {
+ receiver.onFailure(failure);
+ }
+ }
+
+ void onSuccess(Splittable split) {
+ // The user may not have called to()
+ if (receiver != null) {
+ @SuppressWarnings("unchecked")
+ T result = (T) EntityCodex.decode(requestContext,
+ requestData.getReturnType(), requestData.getElementType(), split);
+ receiver.onSuccess(result);
+ }
+ }
+
+ void onViolation(Set<Violation> errors) {
+ // The user may not have called to()
+ if (receiver != null) {
+ receiver.onViolation(errors);
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
new file mode 100644
index 0000000..0e10a3a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
@@ -0,0 +1,1093 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.stableId;
+import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
+import com.google.web.bindery.autobean.shared.impl.EnumMap;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+import com.google.web.bindery.event.shared.UmbrellaException;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestTransport.TransportReceiver;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.Violation;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+import com.google.web.bindery.requestfactory.shared.impl.posers.DatePoser;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
+import com.google.web.bindery.requestfactory.shared.messages.InvocationMessage;
+import com.google.web.bindery.requestfactory.shared.messages.JsonRpcRequest;
+import com.google.web.bindery.requestfactory.shared.messages.MessageFactory;
+import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
+import com.google.web.bindery.requestfactory.shared.messages.RequestMessage;
+import com.google.web.bindery.requestfactory.shared.messages.ResponseMessage;
+import com.google.web.bindery.requestfactory.shared.messages.ServerFailureMessage;
+import com.google.web.bindery.requestfactory.shared.messages.ViolationMessage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base implementations for RequestContext services.
+ */
+public abstract class AbstractRequestContext implements RequestContext, EntityCodex.EntitySource {
+ /**
+ * Allows the payload dialect to be injected into the AbstractRequestContext
+ * without the caller needing to be concerned with how the implementation
+ * object is instantiated.
+ */
+ public enum Dialect {
+ STANDARD {
+ @Override
+ DialectImpl create(AbstractRequestContext context) {
+ return context.new StandardPayloadDialect();
+ }
+ },
+ JSON_RPC {
+ @Override
+ DialectImpl create(AbstractRequestContext context) {
+ return context.new JsonRpcPayloadDialect();
+ }
+ };
+ abstract DialectImpl create(AbstractRequestContext context);
+ }
+
+ interface DialectImpl {
+
+ void addInvocation(AbstractRequest<?> request);
+
+ String makePayload();
+
+ void processPayload(Receiver<Void> receiver, String payload);
+ }
+
+ class JsonRpcPayloadDialect implements DialectImpl {
+ /**
+ * Called by generated subclasses to enqueue a method invocation.
+ */
+ public void addInvocation(AbstractRequest<?> request) {
+ /*
+ * TODO(bobv): Support for multiple invocations per request needs to be
+ * ironed out. Once this is done, addInvocation() can be removed from the
+ * DialectImpl interface and restored to to AbstractRequestContext.
+ */
+ if (!invocations.isEmpty()) {
+ throw new RuntimeException("Only one invocation per request, pending backend support");
+ }
+ invocations.add(request);
+ for (Object arg : request.getRequestData().getOrderedParameters()) {
+ retainArg(arg);
+ }
+ }
+
+ public String makePayload() {
+ RequestData data = invocations.get(0).getRequestData();
+
+ AutoBean<JsonRpcRequest> bean = MessageFactoryHolder.FACTORY.jsonRpcRequest();
+ JsonRpcRequest request = bean.as();
+
+ request.setVersion("2.0");
+ request.setApiVersion(data.getApiVersion());
+ request.setId(payloadId++);
+
+ Map<String, Splittable> params = new HashMap<String, Splittable>();
+ for (Map.Entry<String, Object> entry : data.getNamedParameters().entrySet()) {
+ Object obj = entry.getValue();
+ Splittable value = encode(obj);
+ params.put(entry.getKey(), value);
+ }
+ if (data.getRequestResource() != null) {
+ params.put("resource", encode(data.getRequestResource()));
+ }
+ request.setParams(params);
+ request.setMethod(data.getOperation());
+
+ return AutoBeanCodex.encode(bean).getPayload();
+ }
+
+ public void processPayload(Receiver<Void> receiver, String payload) {
+ Splittable raw = StringQuoter.split(payload);
+
+ @SuppressWarnings("unchecked")
+ Receiver<Object> callback = (Receiver<Object>) invocations.get(0).getReceiver();
+
+ if (!raw.isNull("error")) {
+ Splittable error = raw.get("error");
+ ServerFailure failure =
+ new ServerFailure(error.get("message").asString(), error.get("code").asString(),
+ payload, true);
+ fail(receiver, failure);
+ return;
+ }
+
+ Splittable result = raw.get("result");
+ @SuppressWarnings("unchecked")
+ Class<BaseProxy> target =
+ (Class<BaseProxy>) invocations.get(0).getRequestData().getReturnType();
+
+ SimpleProxyId<BaseProxy> id = getRequestFactory().allocateId(target);
+ AutoBean<BaseProxy> bean = createProxy(target, id);
+ // XXX expose this as a proper API
+ ((AbstractAutoBean<?>) bean).setData(result);
+ // AutoBeanCodex.decodeInto(result, bean);
+
+ if (callback != null) {
+ callback.onSuccess(bean.as());
+ }
+ if (receiver != null) {
+ receiver.onSuccess(null);
+ }
+ }
+
+ Splittable encode(Object obj) {
+ Splittable value;
+ if (obj == null) {
+ return Splittable.NULL;
+ } else if (obj.getClass().isEnum() && getAutoBeanFactory() instanceof EnumMap) {
+ value = ValueCodex.encode(((EnumMap) getAutoBeanFactory()).getToken((Enum<?>) obj));
+ } else if (ValueCodex.canDecode(obj.getClass())) {
+ value = ValueCodex.encode(obj);
+ } else {
+ // XXX user-provided implementation of interface?
+ value = AutoBeanCodex.encode(AutoBeanUtils.getAutoBean(obj));
+ }
+ return value;
+ }
+ }
+
+ class StandardPayloadDialect implements DialectImpl {
+
+ /**
+ * Called by generated subclasses to enqueue a method invocation.
+ */
+ public void addInvocation(AbstractRequest<?> request) {
+ invocations.add(request);
+ for (Object arg : request.getRequestData().getOrderedParameters()) {
+ retainArg(arg);
+ }
+ }
+
+ /**
+ * Assemble all of the state that has been accumulated in this context. This
+ * includes:
+ * <ul>
+ * <li>Diffs accumulated on objects passed to {@link #edit}.
+ * <li>Invocations accumulated as Request subtypes passed to
+ * {@link #addInvocation}.
+ * </ul>
+ */
+ public String makePayload() {
+ // Get the factory from the runtime-specific holder.
+ MessageFactory f = MessageFactoryHolder.FACTORY;
+
+ List<OperationMessage> operations = makePayloadOperations();
+ List<InvocationMessage> invocationMessages = makePayloadInvocations();
+
+ // Create the outer envelope message
+ AutoBean<RequestMessage> bean = f.request();
+ RequestMessage requestMessage = bean.as();
+ if (!invocationMessages.isEmpty()) {
+ requestMessage.setInvocations(invocationMessages);
+ }
+ if (!operations.isEmpty()) {
+ requestMessage.setOperations(operations);
+ }
+ return AutoBeanCodex.encode(bean).getPayload();
+ }
+
+ public void processPayload(final Receiver<Void> receiver, String payload) {
+ ResponseMessage response =
+ AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, ResponseMessage.class, payload).as();
+ if (response.getGeneralFailure() != null) {
+ ServerFailureMessage failure = response.getGeneralFailure();
+ ServerFailure fail =
+ new ServerFailure(failure.getMessage(), failure.getExceptionType(), failure
+ .getStackTrace(), failure.isFatal());
+
+ fail(receiver, fail);
+ return;
+ }
+
+ // Process violations and then stop
+ if (response.getViolations() != null) {
+ Set<Violation> errors = new HashSet<Violation>();
+ for (ViolationMessage message : response.getViolations()) {
+ errors.add(new MyViolation(message));
+ }
+
+ violation(receiver, errors);
+ return;
+ }
+
+ // Process operations
+ processReturnOperations(response);
+
+ // Send return values
+ Set<Throwable> causes = null;
+ for (int i = 0, j = invocations.size(); i < j; i++) {
+ try {
+ if (response.getStatusCodes().get(i)) {
+ invocations.get(i).onSuccess(response.getInvocationResults().get(i));
+ } else {
+ ServerFailureMessage failure =
+ AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, ServerFailureMessage.class,
+ response.getInvocationResults().get(i)).as();
+ invocations.get(i).onFail(
+ new ServerFailure(failure.getMessage(), failure.getExceptionType(), failure
+ .getStackTrace(), failure.isFatal()));
+ }
+ } catch (Throwable t) {
+ if (causes == null) {
+ causes = new HashSet<Throwable>();
+ }
+ causes.add(t);
+ }
+ }
+
+ if (receiver != null) {
+ try {
+ receiver.onSuccess(null);
+ } catch (Throwable t) {
+ if (causes == null) {
+ causes = new HashSet<Throwable>();
+ }
+ causes.add(t);
+ }
+ }
+ // After success, shut down the context
+ editedProxies.clear();
+ invocations.clear();
+ returnedProxies.clear();
+
+ if (causes != null) {
+ throw new UmbrellaException(causes);
+ }
+ }
+ }
+
+ private class MyViolation implements Violation {
+
+ private final BaseProxy currentProxy;
+ private final EntityProxyId<?> id;
+ private final String message;
+ private final String path;
+ private final BaseProxy parentProxy;
+
+ public MyViolation(ViolationMessage message) {
+ // Support violations for value objects.
+ SimpleProxyId<BaseProxy> baseId = getId(message);
+ if (baseId instanceof EntityProxyId<?>) {
+ id = (EntityProxyId<?>) baseId;
+ } else {
+ id = null;
+ }
+ // The stub is empty, since we don't process any OperationMessages
+ AutoBean<BaseProxy> stub = getProxyForReturnPayloadGraph(baseId);
+
+ // So pick up the instance that we just sent to the server
+ AutoBean<?> edited = editedProxies.get(BaseProxyCategory.stableId(stub));
+ currentProxy = (BaseProxy) edited.as();
+
+ // Try to find the original, immutable version.
+ AutoBean<BaseProxy> parentBean = edited.getTag(Constants.PARENT_OBJECT);
+ parentProxy = parentBean == null ? null : parentBean.as();
+ path = message.getPath();
+ this.message = message.getMessage();
+ }
+
+ public BaseProxy getInvalidProxy() {
+ return currentProxy;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public BaseProxy getOriginalProxy() {
+ return parentProxy;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public EntityProxyId<?> getProxyId() {
+ return id;
+ }
+ }
+
+ private static final WriteOperation[] DELETE_ONLY = {WriteOperation.DELETE};
+ private static final WriteOperation[] PERSIST_AND_UPDATE = {
+ WriteOperation.PERSIST, WriteOperation.UPDATE};
+ private static final WriteOperation[] UPDATE_ONLY = {WriteOperation.UPDATE};
+ private static int payloadId = 100;
+ protected final List<AbstractRequest<?>> invocations = new ArrayList<AbstractRequest<?>>();
+ private boolean locked;
+
+ private final AbstractRequestFactory requestFactory;
+ /**
+ * A map of all EntityProxies that the RequestContext has interacted with.
+ * Objects are placed into this map by being passed into {@link #edit} or as
+ * an invocation argument.
+ */
+ private final Map<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> editedProxies =
+ new LinkedHashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>>();
+ /**
+ * A map that contains the canonical instance of an entity to return in the
+ * return graph, since this is built from scratch.
+ */
+ private final Map<SimpleProxyId<?>, AutoBean<?>> returnedProxies =
+ new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+
+ /**
+ * A map that allows us to handle the case where the server has sent back an
+ * unpersisted entity. Because we assume that the server is stateless, the
+ * client will need to swap out the request-local ids with a regular
+ * client-allocated id.
+ */
+ private final Map<Integer, SimpleProxyId<?>> syntheticIds =
+ new HashMap<Integer, SimpleProxyId<?>>();
+
+ private final DialectImpl dialect;
+
+ protected AbstractRequestContext(AbstractRequestFactory factory, Dialect dialect) {
+ this.requestFactory = factory;
+ this.dialect = dialect.create(this);
+ }
+
+ /**
+ * Create a new object, with an ephemeral id.
+ */
+ public <T extends BaseProxy> T create(Class<T> clazz) {
+ checkLocked();
+
+ SimpleProxyId<T> id = requestFactory.allocateId(clazz);
+ AutoBean<T> created = createProxy(clazz, id);
+ return takeOwnership(created);
+ }
+
+ /**
+ * Creates a new proxy with an assigned ID.
+ */
+ public <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz, SimpleProxyId<T> id) {
+ AutoBean<T> created = getAutoBeanFactory().create(clazz);
+ if (created == null) {
+ throw new IllegalArgumentException("Unknown proxy type " + clazz.getName());
+ }
+ created.setTag(STABLE_ID, id);
+ return created;
+ }
+
+ public <T extends BaseProxy> T edit(T object) {
+ return editProxy(object);
+ }
+
+ /**
+ * Take ownership of a proxy instance and make it editable.
+ */
+ public <T extends BaseProxy> T editProxy(T object) {
+ AutoBean<T> bean = checkStreamsNotCrossed(object);
+ checkLocked();
+
+ @SuppressWarnings("unchecked")
+ AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(BaseProxyCategory.stableId(bean));
+ if (previouslySeen != null && !previouslySeen.isFrozen()) {
+ /*
+ * If we've seen the object before, it might be because it was passed in
+ * as a method argument. This does not guarantee its mutability, so check
+ * that here before returning the cached object.
+ */
+ return previouslySeen.as();
+ }
+
+ // Create editable copies
+ AutoBean<T> parent = bean;
+ bean = cloneBeanAndCollections(bean);
+ bean.setTag(Constants.PARENT_OBJECT, parent);
+ return bean.as();
+ }
+
+ /**
+ * Make sure there's a default receiver so errors don't get dropped. This
+ * behavior should be revisited when chaining is supported, depending on
+ * whether or not chained invocations can fail independently.
+ */
+ public void fire() {
+ boolean needsReceiver = true;
+ for (AbstractRequest<?> request : invocations) {
+ if (request.hasReceiver()) {
+ needsReceiver = false;
+ break;
+ }
+ }
+
+ if (needsReceiver) {
+ doFire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ // Don't care
+ }
+ });
+ } else {
+ doFire(null);
+ }
+ }
+
+ public void fire(final Receiver<Void> receiver) {
+ if (receiver == null) {
+ throw new IllegalArgumentException();
+ }
+ doFire(receiver);
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(Splittable serializedProxyId) {
+ IdMessage ref =
+ AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, IdMessage.class, serializedProxyId).as();
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<Q> id = (SimpleProxyId<Q>) getId(ref);
+ return getProxyForReturnPayloadGraph(id);
+ }
+
+ public AbstractRequestFactory getRequestFactory() {
+ return requestFactory;
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
+ AutoBean<IdMessage> bean = MessageFactoryHolder.FACTORY.id();
+ IdMessage ref = bean.as();
+ ref.setServerId(stableId.getServerId());
+ ref.setTypeToken(getRequestFactory().getTypeToken(stableId.getProxyClass()));
+ if (stableId.isSynthetic()) {
+ ref.setStrength(Strength.SYNTHETIC);
+ ref.setSyntheticId(stableId.getSyntheticId());
+ } else if (stableId.isEphemeral()) {
+ ref.setStrength(Strength.EPHEMERAL);
+ ref.setClientId(stableId.getClientId());
+ }
+ return AutoBeanCodex.encode(bean);
+ }
+
+ public boolean isChanged() {
+ /*
+ * NB: Don't use the presence of ephemeral objects for this test.
+ *
+ * Diff the objects until one is found to be different. It's not just a
+ * simple flag-check because of the possibility of "unmaking" a change, per
+ * the JavaDoc.
+ */
+ for (AutoBean<? extends BaseProxy> bean : editedProxies.values()) {
+ AutoBean<?> previous = bean.getTag(Constants.PARENT_OBJECT);
+ if (previous == null) {
+ // Compare to empty object
+ Class<?> proxyClass = stableId(bean).getProxyClass();
+ previous = getAutoBeanFactory().create(proxyClass);
+ }
+ if (!AutoBeanUtils.diff(previous, bean).isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public boolean isEntityType(Class<?> clazz) {
+ return requestFactory.isEntityType(clazz);
+ }
+
+ public boolean isLocked() {
+ return locked;
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public boolean isValueType(Class<?> clazz) {
+ return requestFactory.isValueType(clazz);
+ }
+
+ /**
+ * Called by generated subclasses to enqueue a method invocation.
+ */
+ protected void addInvocation(AbstractRequest<?> request) {
+ dialect.addInvocation(request);
+ };
+
+ /**
+ * Invoke the appropriate {@code onFailure} callbacks, possibly throwing an
+ * {@link UmbrellaException} if one or more callbacks fails.
+ */
+ protected void fail(Receiver<Void> receiver, ServerFailure failure) {
+ reuse();
+ Set<Throwable> causes = null;
+ for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(invocations)) {
+ try {
+ request.onFail(failure);
+ } catch (Throwable t) {
+ if (causes == null) {
+ causes = new HashSet<Throwable>();
+ }
+ causes.add(t);
+ }
+ }
+ if (receiver != null) {
+ try {
+ receiver.onFailure(failure);
+ } catch (Throwable t) {
+ if (causes == null) {
+ causes = new HashSet<Throwable>();
+ }
+ causes.add(t);
+ }
+ }
+
+ if (causes != null) {
+ throw new UmbrellaException(causes);
+ }
+ }
+
+ /**
+ * Returns an AutoBeanFactory that can produce the types reachable only from
+ * this RequestContext.
+ */
+ protected abstract AutoBeanFactory getAutoBeanFactory();
+
+ /**
+ * Invoke the appropriate {@code onViolation} callbacks, possibly throwing an
+ * {@link UmbrellaException} if one or more callbacks fails.
+ */
+ protected void violation(final Receiver<Void> receiver, Set<Violation> errors) {
+ reuse();
+ Set<Throwable> causes = null;
+ for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(invocations)) {
+ try {
+ request.onViolation(errors);
+ } catch (Throwable t) {
+ if (causes == null) {
+ causes = new HashSet<Throwable>();
+ }
+ causes.add(t);
+ }
+ }
+ if (receiver != null) {
+ try {
+ receiver.onViolation(errors);
+ } catch (Throwable t) {
+ if (causes == null) {
+ causes = new HashSet<Throwable>();
+ }
+ causes.add(t);
+ }
+ }
+
+ if (causes != null) {
+ throw new UmbrellaException(causes);
+ }
+ }
+
+ /**
+ * Resolves an IdMessage into an SimpleProxyId.
+ */
+ SimpleProxyId<BaseProxy> getId(IdMessage op) {
+ if (Strength.SYNTHETIC.equals(op.getStrength())) {
+ return allocateSyntheticId(op.getTypeToken(), op.getSyntheticId());
+ }
+ return requestFactory.getId(op.getTypeToken(), op.getServerId(), op.getClientId());
+ }
+
+ /**
+ * Creates or retrieves a new canonical AutoBean to represent the given id in
+ * the returned payload.
+ */
+ <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(SimpleProxyId<Q> id) {
+ @SuppressWarnings("unchecked")
+ AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
+ if (bean == null) {
+ Class<Q> proxyClass = id.getProxyClass();
+ bean = createProxy(proxyClass, id);
+ returnedProxies.put(id, bean);
+ }
+
+ return bean;
+ }
+
+ /**
+ * Create a single OperationMessage that encapsulates the state of a proxy
+ * AutoBean.
+ */
+ AutoBean<OperationMessage> makeOperationMessage(SimpleProxyId<BaseProxy> stableId,
+ AutoBean<?> proxyBean, boolean useDelta) {
+
+ // The OperationMessages describes operations on exactly one entity
+ AutoBean<OperationMessage> toReturn = MessageFactoryHolder.FACTORY.operation();
+ OperationMessage operation = toReturn.as();
+ operation.setTypeToken(requestFactory.getTypeToken(stableId.getProxyClass()));
+
+ // Find the object to compare against
+ AutoBean<?> parent;
+ if (stableId.isEphemeral()) {
+ // Newly-created object, use a blank object to compare against
+ parent = createProxy(stableId.getProxyClass(), stableId);
+
+ // Newly-created objects go into the persist operation bucket
+ operation.setOperation(WriteOperation.PERSIST);
+ // The ephemeral id is passed to the server
+ operation.setClientId(stableId.getClientId());
+ operation.setStrength(Strength.EPHEMERAL);
+ } else if (stableId.isSynthetic()) {
+ // Newly-created object, use a blank object to compare against
+ parent = createProxy(stableId.getProxyClass(), stableId);
+
+ // Newly-created objects go into the persist operation bucket
+ operation.setOperation(WriteOperation.PERSIST);
+ // The ephemeral id is passed to the server
+ operation.setSyntheticId(stableId.getSyntheticId());
+ operation.setStrength(Strength.SYNTHETIC);
+ } else {
+ parent = proxyBean.getTag(Constants.PARENT_OBJECT);
+ // Requests involving existing objects use the persisted id
+ operation.setServerId(stableId.getServerId());
+ operation.setOperation(WriteOperation.UPDATE);
+ }
+ assert !useDelta || parent != null;
+
+ // Send our version number to the server to cut down on future payloads
+ String version = proxyBean.getTag(Constants.VERSION_PROPERTY_B64);
+ if (version != null) {
+ operation.setVersion(version);
+ }
+
+ Map<String, Object> diff = Collections.emptyMap();
+ if (isEntityType(stableId.getProxyClass())) {
+ // Compute what's changed on the client
+ diff =
+ useDelta ? AutoBeanUtils.diff(parent, proxyBean) : AutoBeanUtils
+ .getAllProperties(proxyBean);
+ } else if (isValueType(stableId.getProxyClass())) {
+ // Send everything
+ diff = AutoBeanUtils.getAllProperties(proxyBean);
+ }
+
+ if (!diff.isEmpty()) {
+ Map<String, Splittable> propertyMap = new HashMap<String, Splittable>();
+ for (Map.Entry<String, Object> entry : diff.entrySet()) {
+ propertyMap.put(entry.getKey(), EntityCodex.encode(this, entry.getValue()));
+ }
+ operation.setPropertyMap(propertyMap);
+ }
+ return toReturn;
+ }
+
+ /**
+ * Create a new EntityProxy from a snapshot in the return payload.
+ *
+ * @param id the EntityProxyId of the object
+ * @param returnRecord the JSON map containing property/value pairs
+ * @param operations the WriteOperation eventns to broadcast over the EventBus
+ */
+ <Q extends BaseProxy> Q processReturnOperation(SimpleProxyId<Q> id, OperationMessage op,
+ WriteOperation... operations) {
+
+ AutoBean<Q> toMutate = getProxyForReturnPayloadGraph(id);
+ toMutate.setTag(Constants.VERSION_PROPERTY_B64, op.getVersion());
+
+ final Map<String, Splittable> properties = op.getPropertyMap();
+ if (properties != null) {
+ // Apply updates
+ toMutate.accept(new AutoBeanVisitor() {
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ if (ctx.canSet()) {
+ if (properties.containsKey(propertyName)) {
+ Splittable raw = properties.get(propertyName);
+ Class<?> elementType =
+ ctx instanceof CollectionPropertyContext ? ((CollectionPropertyContext) ctx)
+ .getElementType() : null;
+ Object decoded =
+ EntityCodex.decode(AbstractRequestContext.this, ctx.getType(), elementType, raw);
+ ctx.set(decoded);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ if (ctx.canSet()) {
+ if (properties.containsKey(propertyName)) {
+ Splittable raw = properties.get(propertyName);
+ Object decoded = ValueCodex.decode(ctx.getType(), raw);
+ /*
+ * Hack for Date subtypes, consider generalizing for
+ * "custom serializers"
+ */
+ if (decoded != null && Date.class.equals(ctx.getType())) {
+ decoded = new DatePoser((Date) decoded);
+ }
+ ctx.set(decoded);
+ }
+ }
+ return false;
+ }
+ });
+ }
+
+ // Finished applying updates, freeze the bean
+ makeImmutable(toMutate);
+ Q proxy = toMutate.as();
+
+ /*
+ * Notify subscribers if the object differs from when it first came into the
+ * RequestContext.
+ */
+ if (operations != null && requestFactory.isEntityType(id.getProxyClass())) {
+ for (WriteOperation writeOperation : operations) {
+ if (writeOperation.equals(WriteOperation.UPDATE)
+ && !requestFactory.hasVersionChanged(id, op.getVersion())) {
+ // No updates if the server reports no change
+ continue;
+ }
+ requestFactory.getEventBus().fireEventFromSource(
+ new EntityProxyChange<EntityProxy>((EntityProxy) proxy, writeOperation),
+ id.getProxyClass());
+ }
+ }
+ return proxy;
+ }
+
+ /**
+ * Get-or-create method for synthetic ids.
+ *
+ * @see #syntheticIds
+ */
+ private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(String typeToken,
+ int syntheticId) {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<Q> toReturn = (SimpleProxyId<Q>) syntheticIds.get(syntheticId);
+ if (toReturn == null) {
+ toReturn = requestFactory.allocateId(requestFactory.<Q> getTypeFromToken(typeToken));
+ syntheticIds.put(syntheticId, toReturn);
+ }
+ return toReturn;
+ }
+
+ private void checkLocked() {
+ if (locked) {
+ throw new IllegalStateException("A request is already in progress");
+ }
+ }
+
+ /**
+ * This method checks that a proxy object is either immutable, or already
+ * edited by this context.
+ */
+ private <T> AutoBean<T> checkStreamsNotCrossed(T object) {
+ AutoBean<T> bean = AutoBeanUtils.getAutoBean(object);
+ if (bean == null) {
+ // Unexpected; some kind of foreign implementation?
+ throw new IllegalArgumentException(object.getClass().getName());
+ }
+
+ RequestContext context = bean.getTag(REQUEST_CONTEXT);
+ if (!bean.isFrozen() && context != this) {
+ /*
+ * This means something is way off in the weeds. If a bean is editable,
+ * it's supposed to be associated with a RequestContext.
+ */
+ assert context != null : "Unfrozen bean with null RequestContext";
+
+ /*
+ * Already editing the object in another context or it would have been in
+ * the editing map.
+ */
+ throw new IllegalArgumentException("Attempting to edit an EntityProxy"
+ + " previously edited by another RequestContext");
+ }
+ return bean;
+ }
+
+ /**
+ * Shallow-clones an autobean and makes duplicates of the collection types. A
+ * regular {@link AutoBean#clone} won't duplicate reference properties.
+ */
+ private <T extends BaseProxy> AutoBean<T> cloneBeanAndCollections(final AutoBean<T> toClone) {
+ AutoBean<T> clone = toClone.getFactory().create(toClone.getType());
+ clone.setTag(STABLE_ID, toClone.getTag(STABLE_ID));
+ clone.setTag(Constants.VERSION_PROPERTY_B64, toClone.getTag(Constants.VERSION_PROPERTY_B64));
+ /*
+ * Take ownership here to prevent cycles in value objects from overflowing
+ * the stack.
+ */
+ takeOwnership(clone);
+ clone.accept(new AutoBeanVisitor() {
+ final Map<String, Object> values = AutoBeanUtils.getAllProperties(toClone);
+
+ @Override
+ public boolean visitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+ CollectionPropertyContext ctx) {
+ // javac generics bug
+ value =
+ AutoBeanUtils.<Collection<?>, Collection<?>> getAutoBean((Collection<?>) values
+ .get(propertyName));
+ if (value != null) {
+ Collection<Object> collection;
+ if (List.class == ctx.getType()) {
+ collection = new ArrayList<Object>();
+ } else if (Set.class == ctx.getType()) {
+ collection = new HashSet<Object>();
+ } else {
+ // Should not get here if the validator works correctly
+ throw new IllegalArgumentException(ctx.getType().getName());
+ }
+
+ if (isValueType(ctx.getElementType()) || isEntityType(ctx.getElementType())) {
+ /*
+ * Proxies must be edited up-front so that the elements in the
+ * collection have stable identity.
+ */
+ for (Object o : value.as()) {
+ if (o == null) {
+ collection.add(null);
+ } else {
+ collection.add(editProxy((BaseProxy) o));
+ }
+ }
+ } else {
+ // For simple values, just copy the values
+ collection.addAll(value.as());
+ }
+
+ ctx.set(collection);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ value = AutoBeanUtils.getAutoBean(values.get(propertyName));
+ if (value != null) {
+ if (isValueType(ctx.getType()) || isEntityType(ctx.getType())) {
+ /*
+ * Value proxies must be cloned upfront, since the value is replaced
+ * outright.
+ */
+ @SuppressWarnings("unchecked")
+ AutoBean<BaseProxy> valueBean = (AutoBean<BaseProxy>) value;
+ ctx.set(editProxy(valueBean.as()));
+ } else {
+ ctx.set(value.as());
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ ctx.set(values.get(propertyName));
+ return false;
+ }
+ });
+ return clone;
+ }
+
+ private void doFire(final Receiver<Void> receiver) {
+ checkLocked();
+ locked = true;
+
+ freezeEntities(true);
+
+ String payload = dialect.makePayload();
+ requestFactory.getRequestTransport().send(payload, new TransportReceiver() {
+ public void onTransportFailure(ServerFailure failure) {
+ fail(receiver, failure);
+ }
+
+ public void onTransportSuccess(String payload) {
+ dialect.processPayload(receiver, payload);
+ }
+ });
+ }
+
+ /**
+ * Set the frozen status of all EntityProxies owned by this context.
+ */
+ private void freezeEntities(boolean frozen) {
+ for (AutoBean<?> bean : editedProxies.values()) {
+ bean.setFrozen(frozen);
+ }
+ }
+
+ /**
+ * Make an EntityProxy immutable.
+ */
+ private void makeImmutable(final AutoBean<? extends BaseProxy> toMutate) {
+ // Always diff'ed against itself, producing a no-op
+ toMutate.setTag(Constants.PARENT_OBJECT, toMutate);
+ // Act with entity-identity semantics
+ toMutate.setTag(REQUEST_CONTEXT, null);
+ toMutate.setFrozen(true);
+ }
+
+ /**
+ * Create an InvocationMessage for each remote method call being made by the
+ * context.
+ */
+ private List<InvocationMessage> makePayloadInvocations() {
+ MessageFactory f = MessageFactoryHolder.FACTORY;
+
+ List<InvocationMessage> invocationMessages = new ArrayList<InvocationMessage>();
+ for (AbstractRequest<?> invocation : invocations) {
+ // RequestData is produced by the generated subclass
+ RequestData data = invocation.getRequestData();
+ InvocationMessage message = f.invocation().as();
+
+ // Operation; essentially a method descriptor
+ message.setOperation(data.getOperation());
+
+ // The arguments to the with() calls
+ Set<String> refsToSend = data.getPropertyRefs();
+ if (!refsToSend.isEmpty()) {
+ message.setPropertyRefs(refsToSend);
+ }
+
+ // Parameter values or references
+ List<Splittable> parameters = new ArrayList<Splittable>(data.getOrderedParameters().length);
+ for (Object param : data.getOrderedParameters()) {
+ parameters.add(EntityCodex.encode(this, param));
+ }
+ if (!parameters.isEmpty()) {
+ message.setParameters(parameters);
+ }
+
+ invocationMessages.add(message);
+ }
+ return invocationMessages;
+ }
+
+ /**
+ * Compute deltas for each entity seen by the context.
+ */
+ private List<OperationMessage> makePayloadOperations() {
+ List<OperationMessage> operations = new ArrayList<OperationMessage>();
+ for (AutoBean<? extends BaseProxy> currentView : editedProxies.values()) {
+ OperationMessage operation =
+ makeOperationMessage(BaseProxyCategory.stableId(currentView), currentView, true).as();
+ operations.add(operation);
+ }
+ return operations;
+ }
+
+ /**
+ * Process an array of OperationMessages.
+ */
+ private void processReturnOperations(ResponseMessage response) {
+ List<OperationMessage> ops = response.getOperations();
+
+ // If there are no observable effects, this will be null
+ if (ops == null) {
+ return;
+ }
+
+ for (OperationMessage op : ops) {
+ SimpleProxyId<?> id = getId(op);
+ WriteOperation[] toPropagate = null;
+
+ // May be null if the server is returning an unpersisted object
+ WriteOperation effect = op.getOperation();
+ if (effect != null) {
+ switch (effect) {
+ case DELETE:
+ toPropagate = DELETE_ONLY;
+ break;
+ case PERSIST:
+ toPropagate = PERSIST_AND_UPDATE;
+ break;
+ case UPDATE:
+ toPropagate = UPDATE_ONLY;
+ break;
+ default:
+ // Should never reach here
+ throw new RuntimeException(effect.toString());
+ }
+ }
+ processReturnOperation(id, op, toPropagate);
+ }
+ }
+
+ /**
+ * Ensures that any method arguments are retained in the context's sphere of
+ * influence.
+ */
+ private void retainArg(Object arg) {
+ if (arg instanceof Iterable<?>) {
+ for (Object o : (Iterable<?>) arg) {
+ retainArg(o);
+ }
+ } else if (arg instanceof BaseProxy) {
+ // Calling edit will validate and set up the tracking we need
+ edit((BaseProxy) arg);
+ }
+ }
+
+ /**
+ * Returns the requests that were dequeued as part of reusing the context.
+ */
+ private void reuse() {
+ freezeEntities(false);
+ locked = false;
+ }
+
+ /**
+ * Make the EnityProxy bean edited and owned by this RequestContext.
+ */
+ private <T extends BaseProxy> T takeOwnership(AutoBean<T> bean) {
+ editedProxies.put(stableId(bean), bean);
+ bean.setTag(REQUEST_CONTEXT, this);
+ return bean.as();
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java
new file mode 100644
index 0000000..4b3d69e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.ProxySerializer;
+import com.google.web.bindery.requestfactory.shared.ProxyStore;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.RequestTransport;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Base type for generated RF interfaces.
+ */
+public abstract class AbstractRequestFactory extends IdFactory implements
+ RequestFactory {
+ private static final int MAX_VERSION_ENTRIES = 10000;
+
+ private EventBus eventBus;
+
+ @SuppressWarnings("serial")
+ private final Map<String, String> version = new LinkedHashMap<String, String>(
+ 16, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(Entry<String, String> eldest) {
+ return size() > MAX_VERSION_ENTRIES;
+ }
+ };
+ private RequestTransport transport;
+
+ public <P extends EntityProxy> Request<P> find(final EntityProxyId<P> proxyId) {
+ if (((SimpleEntityProxyId<P>) proxyId).isEphemeral()) {
+ throw new IllegalArgumentException("Cannot fetch unpersisted entity");
+ }
+
+ AbstractRequestContext context = new AbstractRequestContext(
+ AbstractRequestFactory.this, AbstractRequestContext.Dialect.STANDARD) {
+ @Override
+ protected AutoBeanFactory getAutoBeanFactory() {
+ return AbstractRequestFactory.this.getAutoBeanFactory();
+ }
+ };
+ return new AbstractRequest<P>(context) {
+ {
+ requestContext.addInvocation(this);
+ }
+
+ @Override
+ protected RequestData makeRequestData() {
+ return new RequestData(
+ "com.google.web.bindery.requestfactory.shared.impl.FindRequest::find",
+ new Object[] {proxyId}, propertyRefs, proxyId.getProxyClass(), null);
+ }
+ };
+ }
+
+ public EventBus getEventBus() {
+ return eventBus;
+ }
+
+ public String getHistoryToken(Class<? extends EntityProxy> clazz) {
+ return getTypeToken(clazz);
+ }
+
+ public String getHistoryToken(EntityProxyId<?> proxy) {
+ return getHistoryToken((SimpleProxyId<?>) proxy);
+ }
+
+ public Class<? extends EntityProxy> getProxyClass(String historyToken) {
+ String typeToken = IdUtil.getTypeToken(historyToken);
+ if (typeToken != null) {
+ return getTypeFromToken(typeToken);
+ }
+ return getTypeFromToken(historyToken);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends EntityProxy> EntityProxyId<T> getProxyId(String historyToken) {
+ return (EntityProxyId<T>) getBaseProxyId(historyToken);
+ }
+
+ public RequestTransport getRequestTransport() {
+ return transport;
+ }
+
+ public ProxySerializer getSerializer(ProxyStore store) {
+ return new ProxySerializerImpl(this, store);
+ }
+
+ /**
+ * The choice of a default request transport is runtime-specific.
+ */
+ public abstract void initialize(EventBus eventBus);
+
+ public void initialize(EventBus eventBus, RequestTransport transport) {
+ this.eventBus = eventBus;
+ this.transport = transport;
+ }
+
+ /**
+ * Implementations of EntityProxies are provided by an AutoBeanFactory, which
+ * is itself a generated type. This method knows about all proxy types used in
+ * the RequestFactory interface, which prevents pruning of any proxy type. If
+ * the {@link #find(EntityProxyId)} and {@link #getSerializer(ProxyStore)}
+ * were provided by {@link AbstractRequestContext}, this method could be
+ * removed.
+ */
+ protected abstract AutoBeanFactory getAutoBeanFactory();
+
+ /**
+ * Used by {@link AbstractRequestContext} to quiesce update events for objects
+ * that haven't truly changed.
+ */
+ protected boolean hasVersionChanged(SimpleProxyId<?> id,
+ String observedVersion) {
+ assert id != null : "id";
+ assert observedVersion != null : "observedVersion";
+ String key = getHistoryToken(id);
+ String existingVersion = version.get(key);
+ // Return true if we haven't seen this before or the versions differ
+ boolean toReturn = existingVersion == null
+ || !existingVersion.equals(observedVersion);
+ if (toReturn) {
+ version.put(key, observedVersion);
+ }
+ return toReturn;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
new file mode 100644
index 0000000..481f4c8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+
+/**
+ * Contains behaviors common to all proxy instances.
+ */
+public class BaseProxyCategory {
+ /**
+ * Sniff all return values and ensure that if the current bean is a mutable
+ * EntityProxy, that its return values are mutable.
+ */
+ // CHECKSTYLE_OFF
+ public static <T> T __intercept(AutoBean<?> bean, T returnValue) {
+ // CHECKSTYLE_ON
+
+ AbstractRequestContext context = requestContext(bean);
+
+ /*
+ * The context will be null if the bean is immutable. If the context is
+ * locked, don't try to edit.
+ */
+ if (context == null || context.isLocked()) {
+ return returnValue;
+ }
+
+ /*
+ * EntityProxies need to be recorded specially by the RequestContext, so
+ * delegate to the edit() method for wiring up the context.
+ */
+ if (returnValue instanceof BaseProxy) {
+ @SuppressWarnings("unchecked")
+ T toReturn = (T) context.editProxy((BaseProxy) returnValue);
+ return toReturn;
+ }
+
+ if (returnValue instanceof Poser<?>) {
+ ((Poser<?>) returnValue).setFrozen(false);
+ }
+
+ /*
+ * We're returning some object that's not an EntityProxy, most likely a
+ * Collection type. At the very least, propagate the current RequestContext
+ * so that editable chains can be constructed.
+ */
+ AutoBean<T> otherBean = AutoBeanUtils.getAutoBean(returnValue);
+ if (otherBean != null) {
+ otherBean.setTag(REQUEST_CONTEXT, bean.getTag(REQUEST_CONTEXT));
+ }
+ return returnValue;
+ }
+
+ public static AbstractRequestContext requestContext(AutoBean<?> bean) {
+ return bean.getTag(REQUEST_CONTEXT);
+ }
+
+ public static <T extends BaseProxy> SimpleProxyId<T> stableId(
+ AutoBean<? extends T> bean) {
+ return bean.getTag(STABLE_ID);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java
new file mode 100644
index 0000000..33d914e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+/**
+ * Contains a variety of AutoBean tag constants to prevent typos.
+ */
+public interface Constants {
+ String DOMAIN_OBJECT = "domainObject";
+ String IN_RESPONSE = "inResponse";
+ String PARENT_OBJECT = "parentObject";
+ String REQUEST_CONTEXT = "requestContext";
+ String STABLE_ID = "stableId";
+ String VERSION_PROPERTY_B64 = "version";
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java
new file mode 100644
index 0000000..f498ac3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityCodex.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.ValueCodex;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Analogous to {@link ValueCodex}, but for object types.
+ */
+public class EntityCodex {
+ /**
+ * Abstracts the process by which EntityProxies are created.
+ */
+ public interface EntitySource {
+ /**
+ * Expects an encoded
+ * {@link com.google.web.bindery.requestfactory.shared.messages.IdMessage}.
+ */
+ <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(Splittable serializedIdMessage);
+
+ /**
+ * Should return an encoded
+ * {@link com.google.web.bindery.requestfactory.shared.messages.IdMessage}.
+ */
+ Splittable getSerializedProxyId(SimpleProxyId<?> stableId);
+
+ boolean isEntityType(Class<?> clazz);
+
+ boolean isValueType(Class<?> clazz);
+ }
+
+ /**
+ * Collection support is limited to value types and resolving ids.
+ */
+ public static Object decode(EntitySource source, Class<?> type, Class<?> elementType,
+ Splittable split) {
+ if (split == null || split == Splittable.NULL) {
+ return null;
+ }
+
+ // Collection support
+ if (elementType != null) {
+ Collection<Object> collection = null;
+ if (List.class.equals(type)) {
+ collection = new ArrayList<Object>();
+ } else if (Set.class.equals(type)) {
+ collection = new HashSet<Object>();
+ } else {
+ throw new UnsupportedOperationException();
+ }
+
+ // Decode values
+ if (ValueCodex.canDecode(elementType)) {
+ for (int i = 0, j = split.size(); i < j; i++) {
+ if (split.isNull(i)) {
+ collection.add(null);
+ } else {
+ Object element = ValueCodex.decode(elementType, split.get(i));
+ collection.add(element);
+ }
+ }
+ } else {
+ for (int i = 0, j = split.size(); i < j; i++) {
+ if (split.isNull(i)) {
+ collection.add(null);
+ } else {
+ Object element = decode(source, elementType, null, split.get(i));
+ collection.add(element);
+ }
+ }
+ }
+ return collection;
+ }
+
+ if (source.isEntityType(type) || source.isValueType(type) || EntityProxyId.class.equals(type)) {
+ return source.getBeanForPayload(split).as();
+ }
+
+ // Fall back to values
+ return ValueCodex.decode(type, split);
+ }
+
+ /**
+ * Collection support is limited to value types and resolving ids.
+ */
+ public static Object decode(EntitySource source, Class<?> type, Class<?> elementType,
+ String jsonPayload) {
+ Splittable split = StringQuoter.split(jsonPayload);
+ return decode(source, type, elementType, split);
+ }
+
+ /**
+ * Create a wire-format representation of an object.
+ */
+ public static Splittable encode(EntitySource source, Object value) {
+ if (value == null) {
+ return Splittable.NULL;
+ }
+
+ if (value instanceof Poser<?>) {
+ value = ((Poser<?>) value).getPosedValue();
+ }
+
+ if (value instanceof Iterable<?>) {
+ StringBuffer toReturn = new StringBuffer();
+ toReturn.append('[');
+ boolean first = true;
+ for (Object val : ((Iterable<?>) value)) {
+ if (!first) {
+ toReturn.append(',');
+ } else {
+ first = false;
+ }
+ if (val == null) {
+ toReturn.append("null");
+ } else {
+ toReturn.append(encode(source, val).getPayload());
+ }
+ }
+ toReturn.append(']');
+ return StringQuoter.split(toReturn.toString());
+ }
+
+ if (value instanceof BaseProxy) {
+ AutoBean<BaseProxy> autoBean = AutoBeanUtils.getAutoBean((BaseProxy) value);
+ value = BaseProxyCategory.stableId(autoBean);
+ }
+
+ if (value instanceof SimpleProxyId<?>) {
+ return source.getSerializedProxyId((SimpleProxyId<?>) value);
+ }
+
+ return ValueCodex.encode(value);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java
new file mode 100644
index 0000000..4349d23
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/EntityProxyCategory.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.requestContext;
+import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+
+/**
+ * Contains static implementation of EntityProxy-specific methods.
+ */
+public class EntityProxyCategory {
+
+ /**
+ * EntityProxies are equal if they are from the same RequestContext and their
+ * stableIds are equal.
+ */
+ public static boolean equals(AutoBean<? extends EntityProxy> bean, Object o) {
+ if (!(o instanceof EntityProxy)) {
+ return false;
+ }
+ AutoBean<EntityProxy> other = AutoBeanUtils.getAutoBean((EntityProxy) o);
+ if (other == null) {
+ // Unexpected, could be an user-provided implementation?
+ return false;
+ }
+
+ // Object comparison intentional. True if both null or both the same
+ return stableId(bean).equals(stableId(other))
+ && requestContext(bean) == requestContext(other);
+ }
+
+ /**
+ * Hashcode is always that of the stableId, since it's stable across time.
+ */
+ public static int hashCode(AutoBean<? extends EntityProxy> bean) {
+ return stableId(bean).hashCode();
+ }
+
+ /**
+ * Effectively overrides {@link BaseProxyCategory#stableId(AutoBean)} to
+ * return a narrower bound.
+ */
+ public static <T extends EntityProxy> SimpleEntityProxyId<T> stableId(
+ AutoBean<? extends T> bean) {
+ return bean.getTag(STABLE_ID);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java
new file mode 100644
index 0000000..5061b7f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.server.impl.FindService;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+
+/**
+ * Request selector interface for implementing a find method.
+ */
+@Service(FindService.class)
+public interface FindRequest extends RequestContext {
+ /**
+ * Use the implicit lookup in passing EntityProxy types to service methods.
+ */
+ Request<EntityProxy> find(EntityProxyId<?> proxy);
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/IdFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/IdFactory.java
new file mode 100644
index 0000000..c93d893
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/IdFactory.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Handles common code for creating SimpleProxyIds.
+ */
+public abstract class IdFactory {
+ /**
+ * Maps ephemeral history tokens to an id object. This canonicalizing mapping
+ * resolves the problem of EntityProxyIds hashcodes changing after persist.
+ * Only ids that are created in the RequestFactory are stored here.
+ */
+ private final Map<String, SimpleProxyId<?>> ephemeralIds = new HashMap<String, SimpleProxyId<?>>();
+
+ /**
+ * Allocates an ephemeral proxy id. This object is only valid for the lifetime
+ * of the RequestFactory.
+ */
+ public <P extends BaseProxy> SimpleProxyId<P> allocateId(Class<P> clazz) {
+ SimpleProxyId<P> toReturn = createId(clazz, ephemeralIds.size() + 1);
+ ephemeralIds.put(getHistoryToken(toReturn), toReturn);
+ return toReturn;
+ }
+
+ /**
+ * Allocates a synthetic proxy id. This object is only valid for the lifetime
+ * of a request.
+ */
+ public <P extends BaseProxy> SimpleProxyId<P> allocateSyntheticId(
+ Class<P> clazz, int syntheticId) {
+ assert syntheticId > 0;
+ SimpleProxyId<P> toReturn = createId(clazz, "%" + syntheticId);
+ toReturn.setSyntheticId(syntheticId);
+ return toReturn;
+ }
+
+ /**
+ * A utility function to handle generic type conversion. This method will also
+ * assert that {@code clazz} is actually an EntityProxy type.
+ */
+ @SuppressWarnings("unchecked")
+ public <P extends EntityProxy> Class<P> asEntityProxy(
+ Class<? extends BaseProxy> clazz) {
+ assert isEntityType(clazz) : clazz.getName()
+ + " is not an EntityProxy type";
+ return (Class<P>) clazz;
+ }
+
+ /**
+ * A utility function to handle generic type conversion. This method will also
+ * assert that {@code clazz} is actually a ValueProxy type.
+ */
+ @SuppressWarnings("unchecked")
+ public <P extends ValueProxy> Class<P> asValueProxy(
+ Class<? extends BaseProxy> clazz) {
+ assert isValueType(clazz) : clazz.getName() + " is not a ValueProxy type";
+ return (Class<P>) clazz;
+ }
+
+ public <P extends BaseProxy> SimpleProxyId<P> getBaseProxyId(
+ String historyToken) {
+ assert !IdUtil.isSynthetic(historyToken) : "Synthetic id resolution"
+ + " should be handled by AbstractRequestContext";
+ if (IdUtil.isPersisted(historyToken)) {
+ return getId(IdUtil.getTypeToken(historyToken),
+ IdUtil.getServerId(historyToken));
+ }
+ if (IdUtil.isEphemeral(historyToken)) {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(historyToken);
+
+ /*
+ * This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
+ * order to get here, the user would have to get an unpersisted history
+ * token and attempt to use it with a different RequestFactory instance.
+ * This could occur if an ephemeral token were bookmarked. In this case,
+ * we'll create a token, however it will never match anything.
+ */
+ if (toReturn == null) {
+ Class<P> clazz = checkTypeToken(IdUtil.getTypeToken(historyToken));
+ toReturn = createId(clazz, -1 * ephemeralIds.size());
+ ephemeralIds.put(historyToken, toReturn);
+ }
+
+ return toReturn;
+ }
+ throw new IllegalArgumentException(historyToken);
+ }
+
+ public String getHistoryToken(SimpleProxyId<?> proxy) {
+ SimpleProxyId<?> id = (SimpleProxyId<?>) proxy;
+ String token = getTypeToken(proxy.getProxyClass());
+ if (id.isEphemeral()) {
+ return IdUtil.ephemeralId(id.getClientId(), token);
+ } else if (id.isSynthetic()) {
+ return IdUtil.syntheticId(id.getSyntheticId(), token);
+ } else {
+ return IdUtil.persistedId(id.getServerId(), token);
+ }
+ }
+
+ /**
+ * Create or retrieve a SimpleProxyId. If both the serverId and clientId are
+ * specified and the id is ephemeral, it will be updated with the server id.
+ */
+ public <P extends BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
+ String serverId, int clientId) {
+ return getId(getTypeToken(clazz), serverId, clientId);
+ }
+
+ /**
+ * Create or retrieve a SimpleProxyId.
+ */
+ public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
+ String serverId) {
+ return getId(typeToken, serverId, 0);
+ }
+
+ /**
+ * Create or retrieve a SimpleEntityProxyId. If both the serverId and clientId
+ * are specified and the id is ephemeral, it will be updated with the server
+ * id.
+ */
+ public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
+ String serverId, int clientId) {
+ /*
+ * If there's a clientId, that probably means we've just created a brand-new
+ * EntityProxy or have just persisted something on the server.
+ */
+ if (clientId > 0) {
+ // Try a cache lookup for the ephemeral key
+ String ephemeralKey = IdUtil.ephemeralId(clientId, typeToken);
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(ephemeralKey);
+
+ // Do we need to allocate an ephemeral id?
+ if (toReturn == null) {
+ Class<P> clazz = getTypeFromToken(typeToken);
+ toReturn = createId(clazz, clientId);
+ ephemeralIds.put(ephemeralKey, toReturn);
+ }
+
+ // If it's ephemeral, see if we have a serverId and save it
+ if (toReturn.isEphemeral()) {
+ // Sanity check
+ assert toReturn.getProxyClass().equals(getTypeFromToken(typeToken));
+
+ if (serverId != null) {
+ /*
+ * Record the server id so a later "find" operation will have an equal
+ * stableId.
+ */
+ toReturn.setServerId(serverId);
+ String serverKey = IdUtil.persistedId(serverId, typeToken);
+ ephemeralIds.put(serverKey, toReturn);
+ }
+ }
+ return toReturn;
+ }
+
+ // Should never get this far without a server id
+ assert serverId != null : "serverId";
+
+ String serverKey = IdUtil.persistedId(serverId, typeToken);
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(serverKey);
+ if (toReturn != null) {
+ // A cache hit for a locally-created object that has been persisted
+ return toReturn;
+ }
+
+ /*
+ * No existing id, so it was never an ephemeral id created by this
+ * RequestFactory, so we don't need to record it. This should be the normal
+ * case for read-dominated applications.
+ */
+ Class<P> clazz = getTypeFromToken(typeToken);
+ assert clazz != null : "No class literal for " + typeToken;
+ return createId(clazz, serverId);
+ }
+
+ public abstract boolean isEntityType(Class<?> clazz);
+
+ public abstract boolean isValueType(Class<?> clazz);
+
+ protected abstract <P extends BaseProxy> Class<P> getTypeFromToken(
+ String typeToken);
+
+ protected abstract String getTypeToken(Class<? extends BaseProxy> clazz);
+
+ private <P> Class<P> checkTypeToken(String token) {
+ @SuppressWarnings("unchecked")
+ Class<P> clazz = (Class<P>) getTypeFromToken(token);
+ if (clazz == null) {
+ throw new IllegalArgumentException("Unknnown type");
+ }
+ return clazz;
+ }
+
+ private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
+ int clientId) {
+ SimpleProxyId<P> toReturn;
+ if (isValueType(clazz)) {
+ toReturn = new SimpleProxyId<P>(clazz, clientId);
+ } else {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
+ asEntityProxy(clazz), clientId);
+ toReturn = (SimpleProxyId<P>) temp;
+ }
+ return toReturn;
+ }
+
+ private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
+ String serverId) {
+ SimpleProxyId<P> toReturn;
+ if (isValueType(clazz)) {
+ toReturn = new SimpleProxyId<P>(clazz, serverId);
+ } else {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
+ asEntityProxy(clazz), serverId);
+ toReturn = (SimpleProxyId<P>) temp;
+ }
+ return toReturn;
+ }
+
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/IdUtil.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/IdUtil.java
new file mode 100644
index 0000000..148d076
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/IdUtil.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+/**
+ * Common functions for slicing and dicing EntityProxy ids.
+ */
+class IdUtil {
+ private static final String ANY_SEPARATOR_PATTERN = "@[012]@";
+ private static final String EPHEMERAL_SEPARATOR = "@1@";
+ private static final String TOKEN_SEPARATOR = "@0@";
+ private static final String SYNTHETIC_SEPARATOR = "@2@";
+
+ private static final int ID_TOKEN_INDEX = 0;
+ private static final int TYPE_TOKEN_INDEX = 1;
+
+ public static String ephemeralId(int clientId, String typeToken) {
+ return clientId + EPHEMERAL_SEPARATOR + typeToken;
+ }
+
+ public static int getClientId(String encodedId) {
+ return Integer.valueOf(asEphemeral(encodedId)[ID_TOKEN_INDEX]);
+ }
+
+ public static String getServerId(String encodedId) {
+ return asPersisted(encodedId)[ID_TOKEN_INDEX];
+ }
+
+ public static int getSyntheticId(String encodedId) {
+ return Integer.valueOf(asSynthetic(encodedId)[ID_TOKEN_INDEX]);
+ }
+
+ public static String getTypeToken(String encodedId) {
+ String[] split = asAny(encodedId);
+ if (split.length == 2) {
+ return split[TYPE_TOKEN_INDEX];
+ }
+ return null;
+ }
+
+ public static boolean isEphemeral(String encodedId) {
+ return encodedId.contains(EPHEMERAL_SEPARATOR);
+ }
+
+ public static boolean isPersisted(String encodedId) {
+ return encodedId.contains(TOKEN_SEPARATOR);
+ }
+
+ public static boolean isSynthetic(String encodedId) {
+ return encodedId.contains(SYNTHETIC_SEPARATOR);
+ }
+
+ public static String persistedId(String serverId, String typeToken) {
+ return serverId + TOKEN_SEPARATOR + typeToken;
+ }
+
+ public static String syntheticId(int syntheticId, String historyToken) {
+ return syntheticId + SYNTHETIC_SEPARATOR + historyToken;
+ }
+
+ private static String[] asAny(String encodedId) {
+ return encodedId.split(ANY_SEPARATOR_PATTERN);
+ }
+
+ private static String[] asEphemeral(String encodedId) {
+ return encodedId.split(EPHEMERAL_SEPARATOR);
+ }
+
+ private static String[] asPersisted(String encodedId) {
+ return encodedId.split(TOKEN_SEPARATOR);
+ }
+
+ private static String[] asSynthetic(String encodedId) {
+ return encodedId.split(SYNTHETIC_SEPARATOR);
+ }
+
+ /**
+ * Utility class.
+ */
+ private IdUtil() {
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
new file mode 100644
index 0000000..9dc6b4a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
+import com.google.web.bindery.requestfactory.shared.messages.MessageFactory;
+
+/**
+ * This class has a super-source version with a client-only implementation.
+ */
+public interface MessageFactoryHolder {
+ MessageFactory FACTORY = AutoBeanFactorySource.create(MessageFactory.class);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/Poser.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/Poser.java
new file mode 100644
index 0000000..07ba283
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/Poser.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+/**
+ * Used to lock down mutable, non-proxy, value objects when their owning proxy
+ * is frozen.
+ *
+ * @param <T> the type of simple value the Poser is standing in for
+ */
+public interface Poser<T> {
+ T getPosedValue();
+
+ boolean isFrozen();
+
+ void setFrozen(boolean frozen);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
new file mode 100644
index 0000000..f13942b
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanCodex;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.ProxySerializer;
+import com.google.web.bindery.requestfactory.shared.ProxyStore;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage;
+import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
+import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The default implementation of ProxySerializer.
+ */
+class ProxySerializerImpl extends AbstractRequestContext implements
+ ProxySerializer {
+
+ /**
+ * Used internally to unwind the stack if data cannot be found in the backing
+ * store.
+ */
+ private static class NoDataException extends RuntimeException {
+ }
+
+ private final ProxyStore store;
+ /**
+ * If the user wants to serialize a proxy with a non-persistent id (including
+ * ValueProxy), we'll assign a synthetic id that is local to the store being
+ * used.
+ */
+ private final Map<SimpleProxyId<?>, SimpleProxyId<?>> syntheticIds = new HashMap<SimpleProxyId<?>, SimpleProxyId<?>>();
+
+ /**
+ * The ids of proxies whose content has been reloaded.
+ */
+ private final Set<SimpleProxyId<?>> restored = new HashSet<SimpleProxyId<?>>();
+ private final Map<SimpleProxyId<?>, AutoBean<?>> serialized = new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+
+ public ProxySerializerImpl(AbstractRequestFactory factory, ProxyStore store) {
+ super(factory, Dialect.STANDARD);
+ this.store = store;
+ }
+
+ public <T extends BaseProxy> T deserialize(Class<T> proxyType, String key) {
+ // Fast exit to prevent getOperation from throwing an exception
+ if (store.get(key) == null) {
+ return null;
+ }
+ OperationMessage op = getOperation(proxyType, key);
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<T> id = (SimpleProxyId<T>) getId(op);
+ return doDeserialize(id);
+ }
+
+ public <T extends EntityProxy> T deserialize(EntityProxyId<T> id) {
+ return doDeserialize((SimpleEntityProxyId<T>) id);
+ }
+
+ /**
+ * Replace non-persistent ids with store-local ids.
+ */
+ @Override
+ public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
+ return super.getSerializedProxyId(serializedId(stableId));
+ }
+
+ public String serialize(BaseProxy rootObject) {
+ final AutoBean<? extends BaseProxy> root = AutoBeanUtils.getAutoBean(rootObject);
+ if (root == null) {
+ // Unexpected, some kind of foreign implementation of the BaseProxy?
+ throw new IllegalArgumentException();
+ }
+
+ final SimpleProxyId<?> id = serializedId(BaseProxyCategory.stableId(root));
+ // Only persistent and synthetic ids expected
+ assert !id.isEphemeral() : "Unexpected ephemeral id " + id.toString();
+
+ /*
+ * Don't repeatedly serialize the same proxy, unless we're looking at a
+ * mutable instance.
+ */
+ AutoBean<?> previous = serialized.get(id);
+ if (previous == null || !previous.isFrozen()) {
+ serialized.put(id, root);
+ serializeOneProxy(id, root);
+ root.accept(new AutoBeanVisitor() {
+ @Override
+ public void endVisit(AutoBean<?> bean, Context ctx) {
+ // Avoid unnecessary method call
+ if (bean == root) {
+ return;
+ }
+ if (isEntityType(bean.getType()) || isValueType(bean.getType())) {
+ serialize((BaseProxy) bean.as());
+ }
+ }
+
+ @Override
+ public void endVisitCollectionProperty(String propertyName,
+ AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+ if (value == null) {
+ return;
+ }
+ if (isEntityType(ctx.getElementType())
+ || isValueType(ctx.getElementType())) {
+ for (Object o : value.as()) {
+ serialize((BaseProxy) o);
+ }
+ }
+ }
+ });
+ }
+
+ return getRequestFactory().getHistoryToken(id);
+ }
+
+ @Override
+ protected AutoBeanFactory getAutoBeanFactory() {
+ return getRequestFactory().getAutoBeanFactory();
+ }
+
+ @Override
+ SimpleProxyId<BaseProxy> getId(IdMessage op) {
+ if (Strength.SYNTHETIC.equals(op.getStrength())) {
+ return getRequestFactory().allocateSyntheticId(
+ getRequestFactory().getTypeFromToken(op.getTypeToken()),
+ op.getSyntheticId());
+ }
+ return super.getId(op);
+ }
+
+ @Override
+ <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
+ SimpleProxyId<Q> id) {
+ AutoBean<Q> toReturn = super.getProxyForReturnPayloadGraph(id);
+ if (restored.add(id)) {
+ /*
+ * If we haven't seen the id before, use the data in the OperationMessage
+ * to repopulate the properties of the canonical bean for this id.
+ */
+ OperationMessage op = getOperation(id.getProxyClass(),
+ getRequestFactory().getHistoryToken(id));
+ this.processReturnOperation(id, op);
+ toReturn.setTag(Constants.STABLE_ID, super.getId(op));
+ }
+ return toReturn;
+ }
+
+ /**
+ * Reset all temporary state.
+ */
+ private void clear() {
+ syntheticIds.clear();
+ restored.clear();
+ serialized.clear();
+ }
+
+ private <T extends BaseProxy> T doDeserialize(SimpleProxyId<T> id) {
+ try {
+ return getProxyForReturnPayloadGraph(id).as();
+ } catch (NoDataException e) {
+ return null;
+ } finally {
+ clear();
+ }
+ }
+
+ /**
+ * Load the OperationMessage containing the object state from the backing
+ * store.
+ */
+ private <T> OperationMessage getOperation(Class<T> proxyType, String key) {
+ Splittable data = store.get(key);
+ if (data == null) {
+ throw new NoDataException();
+ }
+
+ OperationMessage op = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
+ OperationMessage.class, data).as();
+ return op;
+ }
+
+ /**
+ * Convert any non-persistent ids into store-local synthetic ids.
+ */
+ private <T extends BaseProxy> SimpleProxyId<T> serializedId(
+ SimpleProxyId<T> stableId) {
+ assert !stableId.isSynthetic();
+ if (stableId.isEphemeral()) {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<T> syntheticId = (SimpleProxyId<T>) syntheticIds.get(stableId);
+ if (syntheticId == null) {
+ int nextId = store.nextId();
+ assert nextId >= 0 : "ProxyStore.nextId() returned a negative number "
+ + nextId;
+ syntheticId = getRequestFactory().allocateSyntheticId(
+ stableId.getProxyClass(), nextId + 1);
+ syntheticIds.put(stableId, syntheticId);
+ }
+ return syntheticId;
+ }
+ return stableId;
+ }
+
+ private void serializeOneProxy(SimpleProxyId<?> idForSerialization,
+ AutoBean<? extends BaseProxy> bean) {
+ AutoBean<OperationMessage> op = makeOperationMessage(
+ serializedId(BaseProxyCategory.stableId(bean)), bean, false);
+
+ store.put(getRequestFactory().getHistoryToken(idForSerialization),
+ AutoBeanCodex.encode(op));
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/RequestData.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/RequestData.java
new file mode 100644
index 0000000..e8b0959
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/RequestData.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class that encapsulates the parameters and method name to be invoked on the
+ * server.
+ */
+public class RequestData {
+ private final Class<?> elementType;
+ private final String operation;
+ private final Object[] parameters;
+ private Set<String> propertyRefs;
+ private final Class<?> returnType;
+ private Map<String, Object> requestParameters;
+ private Object requestContent;
+ private String apiVersion;
+
+ public RequestData(String operation, Object[] parameters,
+ Class<?> returnType, Class<?> elementType) {
+ this.operation = operation;
+ this.parameters = parameters;
+ this.returnType = returnType;
+ this.elementType = elementType;
+ }
+
+ /**
+ * Used by generated code.
+ */
+ public RequestData(String operation, Object[] parameters,
+ Set<String> propertyRefs, Class<?> returnType, Class<?> elementType) {
+ this(operation, parameters, returnType, elementType);
+ setPropertyRefs(propertyRefs);
+ }
+
+ public String getApiVersion() {
+ return apiVersion;
+ }
+
+ /**
+ * Used to interpret the returned payload.
+ */
+ public Class<?> getElementType() {
+ return elementType;
+ }
+
+ public Map<String, Object> getNamedParameters() {
+ return requestParameters == null ? Collections.<String, Object> emptyMap()
+ : requestParameters;
+ }
+
+ public String getOperation() {
+ return operation;
+ }
+
+ /**
+ * Used by standard-mode payloads and InstanceRequest subtypes to reset the
+ * instance object in the <code>using</code> method.
+ */
+ public Object[] getOrderedParameters() {
+ return parameters;
+ }
+
+ public Set<String> getPropertyRefs() {
+ return propertyRefs;
+ }
+
+ public Object getRequestResource() {
+ return requestContent;
+ }
+
+ /**
+ * Used to interpret the returned payload.
+ */
+ public Class<?> getReturnType() {
+ return returnType;
+ }
+
+ public void setApiVersion(String apiVersion) {
+ this.apiVersion = apiVersion;
+ }
+
+ public void setNamedParameter(String key, Object value) {
+ if (requestParameters == null) {
+ requestParameters = new HashMap<String, Object>();
+ }
+ requestParameters.put(key, value);
+ }
+
+ public void setPropertyRefs(Set<String> propertyRefs) {
+ this.propertyRefs = propertyRefs;
+ }
+
+ /**
+ * Represents the {@code request} object in a JSON-RPC request.
+ *
+ * @see com.google.web.bindery.requestfactory.shared.JsonRpcContent
+ */
+ public void setRequestContent(Object requestContent) {
+ this.requestContent = requestContent;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyId.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyId.java
new file mode 100644
index 0000000..77d7451
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyId.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+
+/**
+ * Extends SimpleProxyId with the correct parameterization to implement
+ * EntityProxyId.
+ *
+ * @param <P> the type of EntityProxy object the id describes
+ */
+public class SimpleEntityProxyId<P extends EntityProxy> extends
+ SimpleProxyId<P> implements EntityProxyId<P> {
+
+ /**
+ * Construct an ephemeral id. May be called only from
+ * {@link IdFactory#getId()}.
+ */
+ SimpleEntityProxyId(Class<P> proxyClass, int clientId) {
+ super(proxyClass, clientId);
+ }
+
+ /**
+ * Construct a stable id. May only be called from {@link IdFactory#getId()}
+ */
+ SimpleEntityProxyId(Class<P> proxyClass, String encodedAddress) {
+ super(proxyClass, encodedAddress);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleProxyId.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleProxyId.java
new file mode 100644
index 0000000..a1946d0
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/SimpleProxyId.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+
+/**
+ * The base implementation of id objects in the RequestFactory system. This type
+ * exists to allow ValueProxies to be implemented in the same manner as an
+ * EntityProxy as far as metadata maintenance is concerned. There is a specific
+ * subtype {@link SimpleEntityProxyId} which implements the requisite public
+ * interface for EntityProxy types.
+ *
+ * @param <P> the type of BaseProxy object the id describes
+ */
+public class SimpleProxyId<P extends BaseProxy> {
+ /**
+ * A placeholder value for {@link #clientId} to indicate the id was not
+ * created locally.
+ */
+ public static final int NEVER_EPHEMERAL = -1;
+
+ /**
+ * The client-side id is ephemeral, and is valid only during the lifetime of a
+ * module. Any use of the client-side id except to send to the server as a
+ * bookkeeping exercise is wrong.
+ */
+ private final int clientId;
+
+ /**
+ * The encodedAddress is totally opaque to the client. It's probably a
+ * base64-encoded string, but it could be digits of pi. Any code that does
+ * anything other than send the contents of this field back to the server is
+ * wrong.
+ */
+ private String encodedAddress;
+
+ /**
+ * The hashcode of the id must remain stable, even if the server id is later
+ * assigned.
+ */
+ private final int hashCode;
+
+ /**
+ * The EntityProxy type.
+ */
+ private final Class<P> proxyClass;
+
+ /**
+ * A flag to indicate that the id is synthetic, that it is not valid beyond
+ * the duration of the request.
+ */
+ private int syntheticId;
+
+ /**
+ * Construct an ephemeral id. May be called only from
+ * {@link IdFactory#createId()}.
+ */
+ SimpleProxyId(Class<P> proxyClass, int clientId) {
+ assert proxyClass != null;
+ this.clientId = clientId;
+ this.proxyClass = proxyClass;
+ hashCode = clientId;
+ }
+
+ /**
+ * Construct a stable id. May only be called from {@link IdFactory#createId()}
+ */
+ SimpleProxyId(Class<P> proxyClass, String encodedAddress) {
+ assert proxyClass != null;
+ assert encodedAddress != null;
+ setServerId(encodedAddress);
+ clientId = NEVER_EPHEMERAL;
+ hashCode = encodedAddress.hashCode();
+ this.proxyClass = proxyClass;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof SimpleProxyId<?>)) {
+ return false;
+ }
+ SimpleProxyId<?> other = (SimpleProxyId<?>) o;
+ if (!proxyClass.equals(other.proxyClass)) {
+ return false;
+ }
+
+ if (clientId != NEVER_EPHEMERAL && clientId == other.clientId) {
+ /*
+ * Unexpected: It should be the case that locally-created ids are never
+ * aliased and will be caught by the first if statement.
+ */
+ return true;
+ }
+
+ if (encodedAddress != null && encodedAddress.equals(other.encodedAddress)) {
+ return true;
+ }
+ return false;
+ }
+
+ public int getClientId() {
+ return clientId;
+ }
+
+ public Class<P> getProxyClass() {
+ return proxyClass;
+ }
+
+ /**
+ * TODO: Rename to getAddress().
+ */
+ public String getServerId() {
+ return encodedAddress;
+ }
+
+ /**
+ * A flag to indicate that the id is synthetic, that it is not valid beyond
+ * the duration of the request.
+ */
+ public int getSyntheticId() {
+ return syntheticId;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ public boolean isEphemeral() {
+ return encodedAddress == null;
+ }
+
+ public boolean isSynthetic() {
+ return syntheticId > 0;
+ }
+
+ /**
+ * Allows the server address token to be set. This method may be called
+ * exactly once over the lifetime of an id.
+ */
+ public void setServerId(String encodedAddress) {
+ if (this.encodedAddress != null) {
+ throw new IllegalStateException();
+ }
+ assert !"null".equals(encodedAddress);
+ this.encodedAddress = encodedAddress;
+ }
+
+ public void setSyntheticId(int syntheticId) {
+ this.syntheticId = syntheticId;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ if (isEphemeral()) {
+ return IdUtil.ephemeralId(clientId, proxyClass.getName());
+ } else if (isSynthetic()) {
+ return IdUtil.syntheticId(syntheticId, proxyClass.getName());
+ } else {
+ return IdUtil.persistedId(encodedAddress, proxyClass.getName());
+ }
+ }
+
+ /**
+ * Returns <code>true</code> if the id was created as an ephemeral id.
+ */
+ public boolean wasEphemeral() {
+ return clientId != NEVER_EPHEMERAL;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/TypeLibrary.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/TypeLibrary.java
new file mode 100644
index 0000000..1d3d969
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/TypeLibrary.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility methods for querying, encoding, and decoding typed
+ * payload data.
+ */
+public class TypeLibrary {
+
+ public static final Collection<Class<?>> VALUE_TYPES;
+
+ static {
+ HashSet<Class<?>> valueTypes = new HashSet<Class<?>>();
+ valueTypes.add(BigDecimal.class);
+ valueTypes.add(BigInteger.class);
+ valueTypes.add(Boolean.class);
+ valueTypes.add(Byte.class);
+ valueTypes.add(Character.class);
+ valueTypes.add(Date.class);
+ valueTypes.add(Double.class);
+ valueTypes.add(Enum.class);
+ valueTypes.add(Float.class);
+ valueTypes.add(Integer.class);
+ valueTypes.add(Long.class);
+ valueTypes.add(Short.class);
+ valueTypes.add(String.class);
+ VALUE_TYPES = Collections.unmodifiableSet(valueTypes);
+ }
+
+ public static boolean isCollectionType(Class<?> type) {
+ return type == List.class || type == Set.class;
+ }
+
+ public static boolean isProxyType(Class<?> type) {
+ return !isValueType(type) && !isCollectionType(type);
+ }
+
+ public static boolean isValueType(Class<?> type) {
+ return VALUE_TYPES.contains(type);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java
new file mode 100644
index 0000000..95c5ca2
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ValueProxyCategory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.stableId;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+/**
+ * Contains static implementation of ValueProxy-specific methods.
+ */
+public class ValueProxyCategory {
+
+ /**
+ * ValueProxies are equal if they are from the same RequestContext and all of
+ * their properties are equal.
+ */
+ public static boolean equals(AutoBean<? extends ValueProxy> bean, Object o) {
+ if (!(o instanceof ValueProxy)) {
+ return false;
+ }
+ AutoBean<ValueProxy> other = AutoBeanUtils.getAutoBean((ValueProxy) o);
+ if (other == null) {
+ // Unexpected, could be an user-provided implementation?
+ return false;
+ }
+ if (!stableId(bean).getProxyClass().equals(stableId(other).getProxyClass())) {
+ // Compare AppleProxies to AppleProxies
+ return false;
+ }
+
+ // Compare the entire object graph
+ return AutoBeanUtils.deepEquals(bean, other);
+ }
+
+ /**
+ * Hashcode depends on property values.
+ */
+ public static int hashCode(AutoBean<? extends ValueProxy> bean) {
+ return AutoBeanUtils.getAllProperties(bean).hashCode();
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/posers/DatePoser.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/posers/DatePoser.java
new file mode 100644
index 0000000..3d71032
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/posers/DatePoser.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl.posers;
+
+import com.google.web.bindery.requestfactory.shared.impl.Poser;
+
+import java.util.Date;
+
+/**
+ * A sometimes-mutable implementation of {@link Date}.
+ */
+@SuppressWarnings("deprecation")
+public class DatePoser extends Date implements Poser<Date> {
+ private boolean frozen;
+
+ public DatePoser(Date copy) {
+ super(copy.getTime());
+ setFrozen(true);
+ }
+
+ public Date getPosedValue() {
+ return new Date(getTime());
+ }
+
+ public boolean isFrozen() {
+ return frozen;
+ }
+
+ @Override
+ public void setDate(int date) {
+ checkFrozen();
+ super.setDate(date);
+ }
+
+ public void setFrozen(boolean frozen) {
+ this.frozen = frozen;
+ }
+
+ @Override
+ public void setHours(int hours) {
+ checkFrozen();
+ super.setHours(hours);
+ }
+
+ @Override
+ public void setMinutes(int minutes) {
+ checkFrozen();
+ super.setMinutes(minutes);
+ }
+
+ @Override
+ public void setMonth(int month) {
+ checkFrozen();
+ super.setMonth(month);
+ }
+
+ @Override
+ public void setSeconds(int seconds) {
+ checkFrozen();
+ super.setSeconds(seconds);
+ }
+
+ @Override
+ public void setTime(long time) {
+ checkFrozen();
+ super.setTime(time);
+ }
+
+ @Override
+ public void setYear(int year) {
+ checkFrozen();
+ super.setYear(year);
+ }
+
+ private void checkFrozen() {
+ if (frozen) {
+ throw new IllegalStateException("The Date has been frozen");
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java
new file mode 100644
index 0000000..6d1d6c1
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/IdMessage.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Used as a base type for messages that are about a particular id.
+ */
+public interface IdMessage {
+ /**
+ * Describes the longevity of the id.
+ */
+ public enum Strength {
+ /**
+ * The id is indefinitely persistent and can be freely reused by the client
+ * and the server.
+ */
+ @PropertyName("0")
+ PERSISTED,
+
+ /**
+ * The id is managed by the client and is generally unknown to the server.
+ */
+ @PropertyName("1")
+ EPHEMERAL,
+
+ /**
+ * The id not not managed by the client or server and is valid only for the
+ * duration of a single request or response.
+ */
+ @PropertyName("2")
+ SYNTHETIC;
+ }
+
+ String CLIENT_ID = "C";
+ String SERVER_ID = "S";
+ String TYPE_TOKEN = "T";
+ String STRENGTH = "R";
+ String SYNTHETIC_ID = "Y";
+
+ @PropertyName(CLIENT_ID)
+ int getClientId();
+
+ @PropertyName(SERVER_ID)
+ String getServerId();
+
+ @PropertyName(STRENGTH)
+ Strength getStrength();
+
+ @PropertyName(SYNTHETIC_ID)
+ int getSyntheticId();
+
+ @PropertyName(TYPE_TOKEN)
+ String getTypeToken();
+
+ @PropertyName(CLIENT_ID)
+ void setClientId(int value);
+
+ @PropertyName(SERVER_ID)
+ void setServerId(String value);
+
+ @PropertyName(STRENGTH)
+ void setStrength(Strength value);
+
+ @PropertyName(SYNTHETIC_ID)
+ void setSyntheticId(int value);
+
+ @PropertyName(TYPE_TOKEN)
+ void setTypeToken(String value);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java
new file mode 100644
index 0000000..00fcac3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/InvocationMessage.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Describes a method invocation.
+ */
+public interface InvocationMessage {
+ String OPERATIONS = "O";
+ String PARAMETERS = "P";
+ String PROPERTY_REFS = "R";
+
+ @PropertyName(OPERATIONS)
+ String getOperation();
+
+ @PropertyName(PARAMETERS)
+ List<Splittable> getParameters();
+
+ @PropertyName(PROPERTY_REFS)
+ Set<String> getPropertyRefs();
+
+ @PropertyName(OPERATIONS)
+ void setOperation(String value);
+
+ @PropertyName(PARAMETERS)
+ void setParameters(List<Splittable> value);
+
+ @PropertyName(PROPERTY_REFS)
+ void setPropertyRefs(Set<String> value);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java
new file mode 100644
index 0000000..275c7d5
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/JsonRpcRequest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
+
+import java.util.Map;
+
+/**
+ * A JSON-RPC request payload.
+ */
+public interface JsonRpcRequest {
+ String getApiVersion();
+
+ int getId();
+
+ String getMethod();
+
+ Map<String, Splittable> getParams();
+
+ @PropertyName("jsonrpc")
+ String getVersion();
+
+ void setApiVersion(String version);
+
+ void setId(int id);
+
+ void setMethod(String method);
+
+ void setParams(Map<String, Splittable> params);
+
+ @PropertyName("jsonrpc")
+ void setVersion(String version);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java
new file mode 100644
index 0000000..e22048b
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/MessageFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+
+/**
+ * The factory for creating RequestFactory wire messages.
+ */
+public interface MessageFactory extends AutoBeanFactory {
+ AutoBean<ServerFailureMessage> failure();
+
+ AutoBean<IdMessage> id();
+
+ AutoBean<InvocationMessage> invocation();
+
+ AutoBean<JsonRpcRequest> jsonRpcRequest();
+
+ AutoBean<OperationMessage> operation();
+
+ AutoBean<RequestMessage> request();
+
+ AutoBean<ResponseMessage> response();
+
+ AutoBean<ViolationMessage> violation();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java
new file mode 100644
index 0000000..9ed7e67
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/OperationMessage.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.requestfactory.shared.WriteOperation;
+
+import java.util.Map;
+
+/**
+ * Represents an operation to be carried out on a single entity on the server.
+ */
+public interface OperationMessage extends IdMessage, VersionedMessage {
+ String OPERATION = "O";
+ String PROPERTY_MAP = "P";
+
+ @PropertyName(OPERATION)
+ WriteOperation getOperation();
+
+ @PropertyName(PROPERTY_MAP)
+ Map<String, Splittable> getPropertyMap();
+
+ @PropertyName(OPERATION)
+ void setOperation(WriteOperation value);
+
+ @PropertyName(PROPERTY_MAP)
+ void setPropertyMap(Map<String, Splittable> map);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java
new file mode 100644
index 0000000..8f35588
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/RequestMessage.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+import java.util.List;
+
+/**
+ * The message sent from the client to the server.
+ */
+public interface RequestMessage extends VersionedMessage {
+ String INVOCATION = "I";
+ String OPERATIONS = "O";
+
+ @PropertyName(INVOCATION)
+ List<InvocationMessage> getInvocations();
+
+ @PropertyName(OPERATIONS)
+ List<OperationMessage> getOperations();
+
+ @PropertyName(INVOCATION)
+ void setInvocations(List<InvocationMessage> value);
+
+ @PropertyName(OPERATIONS)
+ void setOperations(List<OperationMessage> value);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java
new file mode 100644
index 0000000..47efd4d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ResponseMessage.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.Splittable;
+
+import java.util.List;
+
+/**
+ * The result of fulfilling a request on the server.
+ */
+public interface ResponseMessage extends VersionedMessage {
+ String GENERAL_FAILURE = "F";
+ String INVOCATION_RESULTS = "I";
+ String OPERATIONS = "O";
+ String STATUS_CODES = "S";
+ // V would conflict with versionedMessage
+ String VIOLATIONS = "X";
+
+ @PropertyName(GENERAL_FAILURE)
+ ServerFailureMessage getGeneralFailure();
+
+ @PropertyName(INVOCATION_RESULTS)
+ List<Splittable> getInvocationResults();
+
+ @PropertyName(OPERATIONS)
+ List<OperationMessage> getOperations();
+
+ @PropertyName(STATUS_CODES)
+ List<Boolean> getStatusCodes();
+
+ @PropertyName(VIOLATIONS)
+ List<ViolationMessage> getViolations();
+
+ @PropertyName(GENERAL_FAILURE)
+ void setGeneralFailure(ServerFailureMessage failure);
+
+ @PropertyName(INVOCATION_RESULTS)
+ void setInvocationResults(List<Splittable> value);
+
+ @PropertyName(OPERATIONS)
+ void setOperations(List<OperationMessage> value);
+
+ @PropertyName(STATUS_CODES)
+ void setStatusCodes(List<Boolean> value);
+
+ @PropertyName(VIOLATIONS)
+ void setViolations(List<ViolationMessage> value);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java
new file mode 100644
index 0000000..7e101e3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ServerFailureMessage.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Encapsulates a ServerFailure object.
+ */
+public interface ServerFailureMessage {
+ String EXCEPTION_TYPE = "X";
+ String MESSAGE = "M";
+ String STACK_TRACE = "S";
+ String FATAL = "F";
+
+ @PropertyName(EXCEPTION_TYPE)
+ String getExceptionType();
+
+ @PropertyName(MESSAGE)
+ String getMessage();
+
+ @PropertyName(STACK_TRACE)
+ String getStackTrace();
+
+ @PropertyName(FATAL)
+ boolean isFatal();
+
+ @PropertyName(EXCEPTION_TYPE)
+ void setExceptionType(String exceptionType);
+
+ @PropertyName(FATAL)
+ void setFatal(boolean significant);
+
+ @PropertyName(MESSAGE)
+ void setMessage(String message);
+
+ @PropertyName(STACK_TRACE)
+ void setStackTrace(String stackTrace);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java
new file mode 100644
index 0000000..51fe40e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/VersionedMessage.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Describes a message that contains version information.
+ */
+public interface VersionedMessage {
+ String VERSION = "V";
+
+ @PropertyName(VERSION)
+ String getVersion();
+
+ @PropertyName(VERSION)
+ void setVersion(String version);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java
new file mode 100644
index 0000000..5b7725f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/ViolationMessage.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.messages;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+
+/**
+ * Represents a ConstraintViolation.
+ */
+public interface ViolationMessage extends IdMessage {
+ String MESSAGE = "M";
+ String PATH = "P";
+
+ @PropertyName(MESSAGE)
+ String getMessage();
+
+ @PropertyName(PATH)
+ String getPath();
+
+ @PropertyName(MESSAGE)
+ void setMessage(String value);
+
+ @PropertyName(PATH)
+ void setPath(String value);
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/messages/package-info.java b/user/src/com/google/web/bindery/requestfactory/shared/messages/package-info.java
new file mode 100644
index 0000000..80b060d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/messages/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/**
+ * Contains classes that define the RequestFactory wire format.
+ *
+ * @since GWT 2.1.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.shared.messages;
+
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/package-info.java b/user/src/com/google/web/bindery/requestfactory/shared/package-info.java
new file mode 100644
index 0000000..837f728
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 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.
+ */
+
+/**
+ * Shared classes used on both the client and the server side for transmitting data between the
+ * server and the client in JSON format.
+ *
+ * @since GWT 2.1
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.shared;
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
new file mode 100644
index 0000000..bcdb434
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.vm;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.vm.impl.BeanMethod;
+import com.google.web.bindery.autobean.vm.impl.TypeUtils;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.JsonRpcContent;
+import com.google.web.bindery.requestfactory.shared.JsonRpcWireName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequest;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.RequestData;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+/**
+ * An in-process implementation of RequestContext.
+ */
+class InProcessRequestContext extends AbstractRequestContext {
+ class RequestContextHandler implements InvocationHandler {
+ public Object invoke(Object proxy, Method method, final Object[] args)
+ throws Throwable {
+ // Maybe delegate to superclass
+ Class<?> owner = method.getDeclaringClass();
+ if (Object.class.equals(owner) || RequestContext.class.equals(owner)
+ || AbstractRequestContext.class.equals(owner)) {
+ try {
+ return method.invoke(InProcessRequestContext.this, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+
+ /*
+ * Instance methods treat the 0-th argument as the instance on which to
+ * invoke the method.
+ */
+ final Object[] actualArgs;
+ Type returnGenericType;
+ boolean isInstance = InstanceRequest.class.isAssignableFrom(method.getReturnType());
+ if (isInstance) {
+ returnGenericType = TypeUtils.getParameterization(
+ InstanceRequest.class, method.getGenericReturnType(),
+ method.getReturnType())[1];
+ if (args == null) {
+ actualArgs = new Object[1];
+ } else {
+ // Save a slot for the this argument
+ actualArgs = new Object[args.length + 1];
+ System.arraycopy(args, 0, actualArgs, 1, args.length);
+ }
+ } else {
+ returnGenericType = TypeUtils.getSingleParameterization(Request.class,
+ method.getGenericReturnType(), method.getReturnType());
+ if (args == null) {
+ actualArgs = NO_ARGS;
+ } else {
+ actualArgs = args;
+ }
+ }
+
+ Class<?> returnType = TypeUtils.ensureBaseType(returnGenericType);
+ Class<?> elementType = Collection.class.isAssignableFrom(returnType)
+ ? TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+ Collection.class, returnGenericType)) : null;
+
+ final RequestData data;
+ if (dialect.equals(Dialect.STANDARD)) {
+ String operation = method.getDeclaringClass().getName() + "::"
+ + method.getName();
+
+ data = new RequestData(operation, actualArgs, returnType, elementType);
+ } else {
+ // Calculate request metadata
+ JsonRpcWireName wireInfo = method.getReturnType().getAnnotation(
+ JsonRpcWireName.class);
+ String apiVersion = wireInfo.version();
+ String operation = wireInfo.value();
+
+ int foundContent = -1;
+ final String[] parameterNames = args == null ? new String[0]
+ : new String[args.length];
+ Annotation[][] parameterAnnotations = method.getParameterAnnotations();
+ parameter : for (int i = 0, j = parameterAnnotations.length; i < j; i++) {
+ for (Annotation annotation : parameterAnnotations[i]) {
+ if (PropertyName.class.equals(annotation.annotationType())) {
+ parameterNames[i] = ((PropertyName) annotation).value();
+ continue parameter;
+ } else if (JsonRpcContent.class.equals(annotation.annotationType())) {
+ foundContent = i;
+ continue parameter;
+ }
+ }
+ throw new UnsupportedOperationException("No "
+ + PropertyName.class.getCanonicalName()
+ + " annotation on parameter " + i + " of method "
+ + method.toString());
+ }
+ final int contentIdx = foundContent;
+
+ data = new RequestData(operation, actualArgs, returnType, elementType);
+ for (int i = 0, j = args.length; i < j; i++) {
+ if (i != contentIdx) {
+ data.setNamedParameter(parameterNames[i], args[i]);
+ } else {
+ data.setRequestContent(args[i]);
+ }
+ data.setApiVersion(apiVersion);
+ }
+ }
+
+ // Create the request, just filling in the RequestData details
+ final AbstractRequest<Object> req = new AbstractRequest<Object>(
+ InProcessRequestContext.this) {
+ @Override
+ protected RequestData makeRequestData() {
+ data.setPropertyRefs(propertyRefs);
+ return data;
+ }
+ };
+
+ if (!isInstance) {
+ // Instance invocations are enqueued when using() is called
+ addInvocation(req);
+ }
+
+ if (dialect.equals(Dialect.STANDARD)) {
+ return req;
+ } else if (dialect.equals(Dialect.JSON_RPC)) {
+ // Support optional parameters for JSON-RPC payloads
+ Class<?> requestType = method.getReturnType().asSubclass(Request.class);
+ return Proxy.newProxyInstance(requestType.getClassLoader(),
+ new Class<?>[] {requestType}, new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ if (Object.class.equals(method.getDeclaringClass())
+ || Request.class.equals(method.getDeclaringClass())) {
+ return method.invoke(req, args);
+ } else if (BeanMethod.SET.matches(method)
+ || BeanMethod.SET_BUILDER.matches(method)) {
+ req.getRequestData().setNamedParameter(
+ BeanMethod.SET.inferName(method), args[0]);
+ return Void.TYPE.equals(method.getReturnType()) ? null
+ : proxy;
+ }
+ throw new UnsupportedOperationException(method.toString());
+ }
+ });
+ } else {
+ throw new RuntimeException("Should not reach here");
+ }
+ }
+ }
+
+ static final Object[] NO_ARGS = new Object[0];
+ private final Dialect dialect;
+
+ protected InProcessRequestContext(AbstractRequestFactory factory,
+ Dialect dialect) {
+ super(factory, dialect);
+ this.dialect = dialect;
+ }
+
+ @Override
+ protected AutoBeanFactory getAutoBeanFactory() {
+ return ((InProcessRequestFactory) getRequestFactory()).getAutoBeanFactory();
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
new file mode 100644
index 0000000..d9129c3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.vm;
+
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.NoWrap;
+import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext.Dialect;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.ValueProxyCategory;
+import com.google.web.bindery.requestfactory.vm.InProcessRequestContext.RequestContextHandler;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * A JRE-compatible implementation of RequestFactory.
+ */
+class InProcessRequestFactory extends AbstractRequestFactory {
+ @Category(value = {
+ EntityProxyCategory.class, ValueProxyCategory.class,
+ BaseProxyCategory.class})
+ @NoWrap(EntityProxyId.class)
+ interface Factory extends AutoBeanFactory {
+ }
+
+ class RequestFactoryHandler implements InvocationHandler {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ if (Object.class.equals(method.getDeclaringClass())
+ || RequestFactory.class.equals(method.getDeclaringClass())) {
+ try {
+ return method.invoke(InProcessRequestFactory.this, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+
+ Class<? extends RequestContext> context = method.getReturnType().asSubclass(
+ RequestContext.class);
+ Dialect dialect = method.getReturnType().isAnnotationPresent(
+ JsonRpcService.class) ? Dialect.JSON_RPC : Dialect.STANDARD;
+ RequestContextHandler handler = new InProcessRequestContext(
+ InProcessRequestFactory.this, dialect).new RequestContextHandler();
+ return context.cast(Proxy.newProxyInstance(
+ Thread.currentThread().getContextClassLoader(),
+ new Class<?>[] {context}, handler));
+ }
+ }
+
+ @Override
+ public void initialize(EventBus eventBus) {
+ throw new UnsupportedOperationException(
+ "An explicit RequestTransport must be provided");
+ }
+
+ @Override
+ public boolean isEntityType(Class<?> clazz) {
+ return EntityProxy.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ public boolean isValueType(Class<?> clazz) {
+ return ValueProxy.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ protected AutoBeanFactory getAutoBeanFactory() {
+ return AutoBeanFactorySource.create(Factory.class);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
+ try {
+ Class<? extends BaseProxy> found = Class.forName(typeToken, false,
+ Thread.currentThread().getContextClassLoader()).asSubclass(
+ BaseProxy.class);
+ return (Class<P>) found;
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected String getTypeToken(Class<? extends BaseProxy> clazz) {
+ return isEntityType(clazz) || isValueType(clazz) ? clazz.getName() : null;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/RequestFactorySource.java b/user/src/com/google/web/bindery/requestfactory/vm/RequestFactorySource.java
new file mode 100644
index 0000000..6ac518e
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/RequestFactorySource.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.vm;
+
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.vm.InProcessRequestFactory.RequestFactoryHandler;
+
+import java.lang.reflect.Proxy;
+
+/**
+ * Create JRE-compatible instances of a RequestFactory interface.
+ *
+ * <span style='color: red'>This is experimental, unsupported code.</span>
+ */
+public class RequestFactorySource {
+ /**
+ * Create an instance of a RequestFactory. The returned RequestFactory must be
+ * initialized with an explicit
+ * {@link com.google.web.bindery.requestfactory.shared.RequestTransport
+ * RequestTransport} via the
+ * {@link RequestFactory#initialize(com.google.gwt.event.shared.EventBus, com.google.web.bindery.requestfactory.shared.RequestTransport)
+ * initialize(EventBus, RequestTransport} method.
+ *
+ * @param <T> the RequestFactory type
+ * @param requestFactory the RequestFactory type
+ * @return an instance of the RequestFactory type
+ * @see InProcessRequestTransport
+ */
+ public static <T extends RequestFactory> T create(Class<T> requestFactory) {
+ RequestFactoryHandler handler = new InProcessRequestFactory().new RequestFactoryHandler();
+ return requestFactory.cast(Proxy.newProxyInstance(
+ Thread.currentThread().getContextClassLoader(),
+ new Class<?>[] {requestFactory}, handler));
+ }
+
+ private RequestFactorySource() {
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/package-info.java b/user/src/com/google/web/bindery/requestfactory/vm/package-info.java
new file mode 100644
index 0000000..f78d89f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.
+ */
+
+/**
+ * Classes to use RequestFactory in a non-GWT (e.g., Android or desktop) JRE environment.
+ *
+ * @since GWT 2.3
+ */
+@com.google.gwt.util.PreventSpuriousRebuilds
+package com.google.web.bindery.requestfactory.vm;
diff --git a/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/ValueCodexHelper.java b/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/ValueCodexHelper.java
new file mode 100644
index 0000000..bab08af
--- /dev/null
+++ b/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/ValueCodexHelper.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+/**
+ * A no-op super-source version of ValueCodexHelper for web-mode compilations.
+ */
+class ValueCodexHelper {
+ /**
+ * Returns {@code false}.
+ */
+ static boolean canDecode(Class<?> clazz) {
+ return false;
+ }
+}
diff --git a/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/impl/StringQuoter.java b/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
new file mode 100644
index 0000000..d92b3bf
--- /dev/null
+++ b/user/super/com/google/web/bindery/autobean/super/com/google/web/bindery/autobean/shared/impl/StringQuoter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared.impl;
+
+import com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable;
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GwtScriptOnly;
+import com.google.gwt.core.client.JavaScriptException;
+import com.google.gwt.core.client.JsDate;
+import com.google.gwt.core.client.JsonUtils;
+import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
+
+import java.util.Date;
+
+/**
+ * This a super-source version with a client-only implementation.
+ */
+@GwtScriptOnly
+public class StringQuoter {
+ public static Splittable create(boolean value) {
+ return JsoSplittable.create(value);
+ }
+
+ public static Splittable create(double value) {
+ return JsoSplittable.create(value);
+ }
+
+ public static Splittable create(String value) {
+ return JsoSplittable.create(value);
+ }
+
+ public static Splittable createIndexed() {
+ return JsoSplittable.createIndexed();
+ }
+
+ public static Splittable createSplittable() {
+ return JsoSplittable.create();
+ }
+
+ public static Splittable nullValue() {
+ return JsoSplittable.nullValue();
+ }
+
+ public static String quote(String raw) {
+ return JsonUtils.escapeValue(raw);
+ }
+
+ public static Splittable split(String payload) {
+ char c = payload.charAt(0);
+ boolean isSimple = c != '{' && c != '[';
+ if (isSimple) {
+ payload = "[" + payload + "]";
+ }
+ Splittable toReturn = JsonUtils.safeEval(payload).<JsoSplittable> cast();
+ if (isSimple) {
+ toReturn = toReturn.get(0);
+ }
+ return toReturn;
+ }
+
+ public static Date tryParseDate(String date) {
+ try {
+ return new Date(Long.parseLong(date));
+ } catch (NumberFormatException ignored) {
+ }
+ try {
+ JsDate js = JsDate.create(date);
+ return new Date((long) js.getTime());
+ } catch (JavaScriptException ignored) {
+ }
+ return null;
+ }
+}
diff --git a/user/super/com/google/web/bindery/requestfactory/super/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java b/user/super/com/google/web/bindery/requestfactory/super/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
new file mode 100644
index 0000000..1745d79
--- /dev/null
+++ b/user/super/com/google/web/bindery/requestfactory/super/com/google/web/bindery/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.web.bindery.requestfactory.shared.messages.MessageFactory;
+
+/**
+ * This a super-source version with a client-only implementation.
+ */
+public interface MessageFactoryHolder {
+ MessageFactory FACTORY = GWT.create(MessageFactory.class);
+}
diff --git a/user/test/com/google/gwt/event/EventSuite.java b/user/test/com/google/gwt/event/EventSuite.java
index 008e88c..b21cc0d 100644
--- a/user/test/com/google/gwt/event/EventSuite.java
+++ b/user/test/com/google/gwt/event/EventSuite.java
@@ -17,9 +17,10 @@
import com.google.gwt.event.dom.client.DomEventTest;
import com.google.gwt.event.logical.shared.LogicalEventsTest;
+import com.google.gwt.event.shared.EventBusTest;
import com.google.gwt.event.shared.HandlerManagerTest;
-import com.google.gwt.event.shared.SimpleEventBusTest;
import com.google.gwt.event.shared.ResettableEventBusTest;
+import com.google.gwt.event.shared.SimpleEventBusTest;
import com.google.gwt.junit.tools.GWTTestSuite;
import junit.framework.Test;
@@ -32,11 +33,12 @@
GWTTestSuite suite = new GWTTestSuite(
"Test for suite for the com.google.gwt.event module.");
- suite.addTestSuite(LogicalEventsTest.class);
suite.addTestSuite(DomEventTest.class);
+ suite.addTestSuite(EventBusTest.class);
suite.addTestSuite(HandlerManagerTest.class);
- suite.addTestSuite(SimpleEventBusTest.class);
+ suite.addTestSuite(LogicalEventsTest.class);
suite.addTestSuite(ResettableEventBusTest.class);
+ suite.addTestSuite(SimpleEventBusTest.class);
return suite;
}
diff --git a/user/test/com/google/gwt/event/shared/EventBusTest.java b/user/test/com/google/gwt/event/shared/EventBusTest.java
new file mode 100644
index 0000000..16d03d3
--- /dev/null
+++ b/user/test/com/google/gwt/event/shared/EventBusTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.event.shared;
+
+import com.google.gwt.event.shared.GwtEvent.Type;
+
+import junit.framework.TestCase;
+
+/**
+ * Test that EventBus is api compatible after its retrofit to extend
+ * {@link com.google.web.bindery.event.shared.EventBus}.
+ */
+public class EventBusTest extends TestCase {
+ EventBus bus = new EventBus() {
+
+ @Override
+ public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type, H handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <H extends EventHandler> HandlerRegistration addHandlerToSource(Type<H> type,
+ Object source, H handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void fireEvent(GwtEvent<?> event) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void fireEventFromSource(GwtEvent<?> event, Object source) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ public void testOne() {
+ // Nothing to test, really, just make sure it still compiles.
+ assertNotNull(bus);
+ }
+}
diff --git a/user/test/com/google/gwt/event/shared/SimpleEventBusTest.java b/user/test/com/google/gwt/event/shared/SimpleEventBusTest.java
index 3221e83..9b5ae7c 100644
--- a/user/test/com/google/gwt/event/shared/SimpleEventBusTest.java
+++ b/user/test/com/google/gwt/event/shared/SimpleEventBusTest.java
@@ -17,82 +17,21 @@
package com.google.gwt.event.shared;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.DomEvent.Type;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
-import com.google.gwt.event.shared.testing.CountingEventBus;
import junit.framework.AssertionFailedError;
import java.util.Set;
/**
- * Eponymous unit test.
+ * Eponymous unit test. Redundant with
+ * {@link com.google.web.bindery.event.shared.SimpleEventBusTest}, here to
+ * ensure legacy compatibility.
*/
public class SimpleEventBusTest extends HandlerTestBase {
- public void testAddAndRemoveHandlers() {
- CountingEventBus eventBus = new CountingEventBus(new SimpleEventBus());
- eventBus.addHandler(MouseDownEvent.getType(), mouse1);
- eventBus.addHandler(MouseDownEvent.getType(), mouse2);
- HandlerRegistration reg1 = eventBus.addHandler(MouseDownEvent.getType(),
- adaptor1);
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertEquals(3, eventBus.getCount(MouseDownEvent.getType()));
- assertFired(mouse1, mouse2, adaptor1);
- eventBus.addHandler(MouseDownEvent.getType(), mouse3);
- assertEquals(4, eventBus.getCount(MouseDownEvent.getType()));
-
- eventBus.addHandler(MouseDownEvent.getType(), mouse1);
- eventBus.addHandler(MouseDownEvent.getType(), mouse2);
- HandlerRegistration reg2 = eventBus.addHandler(MouseDownEvent.getType(),
- adaptor1);
-
- /*
- * You can indeed add handlers twice, they will only be removed one at a
- * time though.
- */
- assertEquals(7, eventBus.getCount(MouseDownEvent.getType()));
- eventBus.addHandler(ClickEvent.getType(), adaptor1);
- eventBus.addHandler(ClickEvent.getType(), click1);
- eventBus.addHandler(ClickEvent.getType(), click2);
-
- assertEquals(7, eventBus.getCount(MouseDownEvent.getType()));
- assertEquals(3, eventBus.getCount(ClickEvent.getType()));
-
- reset();
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertFired(mouse1, mouse2, mouse3, adaptor1);
- assertNotFired(click1, click2);
-
- // Gets rid of first instance.
- reg1.removeHandler();
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertFired(mouse1, mouse2, mouse3, adaptor1);
- assertNotFired(click1, click2);
-
- // Gets rid of second instance.
- reg2.removeHandler();
- reset();
- eventBus.fireEvent(new MouseDownEvent() {
- });
-
- assertFired(mouse1, mouse2, mouse3);
- assertNotFired(adaptor1, click1, click2);
-
- // Checks to see if click events are still working.
- reset();
- eventBus.fireEvent(new ClickEvent() {
- });
-
- assertNotFired(mouse1, mouse2, mouse3);
- assertFired(click1, click2, adaptor1);
- }
-
public void testConcurrentAdd() {
final SimpleEventBus eventBus = new SimpleEventBus();
final MouseDownHandler two = new MouseDownHandler() {
@@ -130,26 +69,6 @@
}
}
- public void testConcurrentRemove() {
- final SimpleEventBus eventBus = new SimpleEventBus();
-
- ShyHandler h = new ShyHandler();
-
- eventBus.addHandler(MouseDownEvent.getType(), mouse1);
- h.r = eventBus.addHandler(MouseDownEvent.getType(), h);
- eventBus.addHandler(MouseDownEvent.getType(), mouse2);
- eventBus.addHandler(MouseDownEvent.getType(), mouse3);
-
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertFired(h, mouse1, mouse2, mouse3);
- reset();
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertFired(mouse1, mouse2, mouse3);
- assertNotFired(h);
- }
-
class SourcedHandler implements MouseDownHandler {
final String expectedSource;
@@ -311,109 +230,6 @@
assertNotFired(two);
}
- public void testRemoveSelf() {
- final SimpleEventBus eventBus = new SimpleEventBus();
-
- MouseDownHandler h = new MouseDownHandler() {
- HandlerRegistration reg = eventBus.addHandler(MouseDownEvent.getType(),
- this);
-
- public void onMouseDown(MouseDownEvent event) {
- add(this);
- reg.removeHandler();
- }
- };
-
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertFired(h);
-
- reset();
-
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertNotFired(h);
- }
-
- public void testNoDoubleRemove() {
- final SimpleEventBus eventBus = new SimpleEventBus();
- HandlerRegistration reg = eventBus.addHandler(MouseDownEvent.getType(),
- mouse1);
- reg.removeHandler();
-
- boolean assertsOn = getClass().desiredAssertionStatus();
-
- if (assertsOn) {
- try {
- reg.removeHandler();
- fail("Should have thrown on remove");
- } catch (AssertionError e) { /* pass */
- }
- } else {
- reg.removeHandler();
- // Succeed on no assert failure
- }
- }
-
- public void testConcurrentAddAfterRemoveIsNotClobbered() {
- final SimpleEventBus eventBus = new SimpleEventBus();
-
- MouseDownHandler one = new MouseDownHandler() {
- HandlerRegistration reg = addIt();
-
- public void onMouseDown(MouseDownEvent event) {
- reg.removeHandler();
- addIt();
- add(this);
- }
-
- private HandlerRegistration addIt() {
- return eventBus.addHandler(MouseDownEvent.getType(), mouse1);
- }
- };
-
- eventBus.addHandler(MouseDownEvent.getType(), one);
-
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertFired(one);
-
- reset();
-
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertFired(one, mouse1);
- }
-
- public void testReverseOrder() {
- @SuppressWarnings("deprecation")
- final SimpleEventBus eventBus = new SimpleEventBus(true);
- final MouseDownHandler handler0 = new MouseDownHandler() {
- public void onMouseDown(MouseDownEvent event) {
- add(this);
- }
- };
- final MouseDownHandler handler1 = new MouseDownHandler() {
- public void onMouseDown(MouseDownEvent event) {
- assertNotFired(handler0);
- add(this);
- }
- };
- final MouseDownHandler handler2 = new MouseDownHandler() {
- public void onMouseDown(MouseDownEvent event) {
- assertNotFired(handler0, handler1);
- add(this);
- }
- };
- eventBus.addHandler(MouseDownEvent.getType(), handler0);
- eventBus.addHandler(MouseDownEvent.getType(), handler1);
- eventBus.addHandler(MouseDownEvent.getType(), handler2);
-
- reset();
- eventBus.fireEvent(new MouseDownEvent() {
- });
- assertFired(handler0, handler1, handler2);
- }
static class ThrowingHandler implements MouseDownHandler {
private final RuntimeException e;
diff --git a/user/test/com/google/web/bindery/autobean/AutoBeanSuite.java b/user/test/com/google/web/bindery/autobean/AutoBeanSuite.java
new file mode 100644
index 0000000..387dc9d
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/AutoBeanSuite.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean;
+
+import com.google.web.bindery.autobean.gwt.client.AutoBeanTest;
+import com.google.web.bindery.autobean.shared.AutoBeanCodexTest;
+import com.google.web.bindery.autobean.shared.SplittableTest;
+import com.google.web.bindery.autobean.vm.AutoBeanCodexJreTest;
+import com.google.web.bindery.autobean.vm.AutoBeanJreTest;
+import com.google.web.bindery.autobean.vm.SplittableJreTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Tests of the Editor framework. These tests focus on core Editor behaviors,
+ * rather than on integration with backing stores.
+ */
+public class AutoBeanSuite {
+ public static Test suite() {
+ GWTTestSuite suite = new GWTTestSuite("Test suite for AutoBean functions");
+ suite.addTestSuite(AutoBeanCodexJreTest.class);
+ suite.addTestSuite(AutoBeanCodexTest.class);
+ suite.addTestSuite(AutoBeanJreTest.class);
+ suite.addTestSuite(AutoBeanTest.class);
+ suite.addTestSuite(SplittableJreTest.class);
+ suite.addTestSuite(SplittableTest.class);
+ return suite;
+ }
+}
diff --git a/user/test/com/google/web/bindery/autobean/gwt/client/AutoBeanTest.java b/user/test/com/google/web/bindery/autobean/gwt/client/AutoBeanTest.java
new file mode 100644
index 0000000..3124590
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/gwt/client/AutoBeanTest.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.gwt.client;
+
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory;
+import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
+import com.google.web.bindery.autobean.shared.AutoBeanVisitor.ParameterizationVisitor;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * Tests runtime behavior of AutoBean framework.
+ */
+public class AutoBeanTest extends GWTTestCase {
+
+ /**
+ * Static implementation of {@link HasCall}.
+ */
+ public static class CallImpl {
+ public static Object seen;
+
+ public static <T> T __intercept(AutoBean<HasCall> bean, T value) {
+ seen = value;
+ return value;
+ }
+
+ public static int add(AutoBean<HasCall> bean, int a, int b) {
+ assertNotNull(bean);
+ return ((Integer) bean.getTag("offset")) + a + b;
+ }
+ }
+
+ /**
+ * The factory being tested.
+ */
+ @Category(CallImpl.class)
+ protected interface Factory extends AutoBeanFactory {
+ AutoBean<HasBoolean> hasBoolean();
+
+ AutoBean<HasCall> hasCall();
+
+ AutoBean<HasChainedSetters> hasChainedSetters();
+
+ AutoBean<HasList> hasList();
+
+ AutoBean<HasComplexTypes> hasListOfList();
+
+ AutoBean<HasMoreChainedSetters> hasMoreChainedSetters();
+
+ AutoBean<Intf> intf();
+
+ AutoBean<Intf> intf(RealIntf wrapped);
+
+ AutoBean<OtherIntf> otherIntf();
+ }
+
+ interface HasBoolean {
+ boolean getGet();
+
+ boolean hasHas();
+
+ boolean isIs();
+
+ void setGet(boolean value);
+
+ void setHas(boolean value);
+
+ void setIs(boolean value);
+ }
+
+ interface HasCall {
+ int add(int a, int b);
+ }
+
+ interface HasChainedSetters {
+ int getInt();
+
+ String getString();
+
+ HasChainedSetters setInt(int value);
+
+ HasChainedSetters setString(String value);
+ }
+
+ interface HasComplexTypes {
+ List<List<Intf>> getList();
+
+ List<Map<String, Intf>> getListOfMap();
+
+ Map<Map<String, String>, List<List<Intf>>> getMap();
+ }
+
+ interface HasList {
+ List<Intf> getList();
+
+ void setList(List<Intf> list);
+ }
+
+ interface HasMoreChainedSetters extends HasChainedSetters {
+ boolean isBoolean();
+
+ HasMoreChainedSetters setBoolean(boolean value);
+
+ HasMoreChainedSetters setInt(int value);
+ }
+
+ interface Intf {
+ int getInt();
+
+ String getString();
+
+ void setInt(int number);
+
+ void setString(String value);
+ }
+
+ interface OtherIntf {
+ HasBoolean getHasBoolean();
+
+ Intf getIntf();
+
+ UnreferencedInFactory getUnreferenced();
+
+ void setHasBoolean(HasBoolean value);
+
+ void setIntf(Intf intf);
+ }
+
+ static class RealIntf implements Intf {
+ int i;
+ String string;
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof Intf) && (((Intf) o).getInt() == getInt());
+ }
+
+ public int getInt() {
+ return i;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ @Override
+ public int hashCode() {
+ return i;
+ }
+
+ public void setInt(int number) {
+ this.i = number;
+ }
+
+ public void setString(String value) {
+ this.string = value;
+ }
+
+ public String toString() {
+ return "toString";
+ }
+ }
+
+ interface UnreferencedInFactory {
+ }
+
+ private static class ParameterizationTester extends ParameterizationVisitor {
+ private final StringBuilder sb;
+ private Stack<Boolean> isOpen = new Stack<Boolean>();
+
+ private ParameterizationTester(StringBuilder sb) {
+ this.sb = sb;
+ }
+
+ @Override
+ public void endVisitType(Class<?> type) {
+ if (isOpen.pop()) {
+ sb.append(">");
+ }
+ }
+
+ @Override
+ public boolean visitParameter() {
+ if (isOpen.peek()) {
+ sb.append(",");
+ } else {
+ sb.append("<");
+ isOpen.pop();
+ isOpen.push(true);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visitType(Class<?> type) {
+ sb.append(type.getName());
+ isOpen.push(false);
+ return true;
+ }
+ }
+
+ protected Factory factory;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.autobean.AutoBean";
+ }
+
+ public void testBooleanIsHasMethods() {
+ HasBoolean b = factory.hasBoolean().as();
+ assertFalse(b.getGet());
+ assertFalse(b.hasHas());
+ assertFalse(b.isIs());
+
+ b.setGet(true);
+ b.setHas(true);
+ b.setIs(true);
+
+ assertTrue(b.getGet());
+ assertTrue(b.hasHas());
+ assertTrue(b.isIs());
+ }
+
+ public void testCategory() {
+ AutoBean<HasCall> call = factory.hasCall();
+ call.setTag("offset", 1);
+ assertEquals(6, call.as().add(2, 3));
+ assertEquals(6, CallImpl.seen);
+ }
+
+ public void testChainedSetters() {
+ AutoBean<HasChainedSetters> bean = factory.hasChainedSetters();
+ bean.as().setInt(42).setString("Blah");
+ assertEquals(42, bean.as().getInt());
+ assertEquals("Blah", bean.as().getString());
+
+ AutoBean<HasMoreChainedSetters> more = factory.hasMoreChainedSetters();
+ more.as().setInt(42).setBoolean(true).setString("Blah");
+ assertEquals(42, more.as().getInt());
+ assertTrue(more.as().isBoolean());
+ assertEquals("Blah", more.as().getString());
+ }
+
+ public void testDiff() {
+ AutoBean<Intf> a1 = factory.intf();
+ AutoBean<Intf> a2 = factory.intf();
+
+ assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
+
+ a2.as().setInt(42);
+ Map<String, Object> diff = AutoBeanUtils.diff(a1, a2);
+ assertEquals(1, diff.size());
+ assertEquals(42, diff.get("int"));
+ }
+
+ public void testDiffWithListPropertyAssignment() {
+ AutoBean<HasList> a1 = factory.hasList();
+ AutoBean<HasList> a2 = factory.hasList();
+
+ assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
+
+ List<Intf> l1 = new ArrayList<Intf>();
+ a1.as().setList(l1);
+ List<Intf> l2 = new ArrayList<Intf>();
+ a2.as().setList(l2);
+
+ assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
+
+ l2.add(factory.intf().as());
+ Map<String, Object> diff = AutoBeanUtils.diff(a1, a2);
+ assertEquals(1, diff.size());
+ assertEquals(l2, diff.get("list"));
+
+ l1.add(l2.get(0));
+ assertTrue(AutoBeanUtils.diff(a1, a2).isEmpty());
+ }
+
+ public void testDynamicMethods() {
+ AutoBean<Intf> intf = factory.create(Intf.class);
+ assertNotNull(intf);
+
+ RealIntf real = new RealIntf();
+ real.i = 42;
+ intf = factory.create(Intf.class, real);
+ assertNotNull(intf);
+ assertEquals(42, intf.as().getInt());
+ }
+
+ public void testEquality() {
+ AutoBean<Intf> a1 = factory.intf();
+ AutoBean<Intf> a2 = factory.intf();
+
+ assertNotSame(a1, a2);
+ assertFalse(a1.equals(a2));
+
+ // Make sure as() is stable
+ assertSame(a1.as(), a1.as());
+ assertEquals(a1.as(), a1.as());
+
+ // When wrapping, use underlying object's equality
+ RealIntf real = new RealIntf();
+ real.i = 42;
+ AutoBean<Intf> w = factory.intf(real);
+ // AutoBean interface never equals wrapped object
+ assertFalse(w.equals(real));
+ // Wrapper interface should delegate hashCode(), equals(), and toString()
+ assertEquals(real.hashCode(), w.as().hashCode());
+ assertEquals(real, w.as());
+ assertEquals(real.toString(), w.as().toString());
+ assertEquals(w.as(), real);
+ }
+
+ public void testFactory() {
+ AutoBean<Intf> auto = factory.intf();
+ assertSame(factory, auto.getFactory());
+ }
+
+ public void testFreezing() {
+ AutoBean<Intf> auto = factory.intf();
+ Intf intf = auto.as();
+ intf.setInt(42);
+ auto.setFrozen(true);
+ try {
+ intf.setInt(55);
+ fail("Should have thrown an exception");
+ } catch (IllegalStateException expected) {
+ }
+
+ assertTrue(auto.isFrozen());
+ assertEquals(42, intf.getInt());
+ }
+
+ public void testNested() {
+ AutoBean<OtherIntf> auto = factory.otherIntf();
+ OtherIntf other = auto.as();
+
+ assertNull(other.getIntf());
+
+ Intf intf = new RealIntf();
+ intf.setString("Hello world!");
+ other.setIntf(intf);
+ Intf retrieved = other.getIntf();
+ assertEquals("Hello world!", retrieved.getString());
+ assertNotNull(AutoBeanUtils.getAutoBean(retrieved));
+ }
+
+ public void testParameterizationVisitor() {
+ AutoBean<HasComplexTypes> auto = factory.hasListOfList();
+ auto.accept(new AutoBeanVisitor() {
+ int count = 0;
+
+ @Override
+ public void endVisit(AutoBean<?> bean, Context ctx) {
+ assertEquals(3, count);
+ }
+
+ @Override
+ public void endVisitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+ CollectionPropertyContext ctx) {
+ check(propertyName, ctx);
+ }
+
+ @Override
+ public void endVisitMapProperty(String propertyName, AutoBean<Map<?, ?>> value,
+ MapPropertyContext ctx) {
+ check(propertyName, ctx);
+ }
+
+ private void check(String propertyName, PropertyContext ctx) {
+ count++;
+ StringBuilder sb = new StringBuilder();
+ ctx.accept(new ParameterizationTester(sb));
+
+ if ("list".equals(propertyName)) {
+ // List<List<Intf>>
+ assertEquals(List.class.getName() + "<" + List.class.getName() + "<"
+ + Intf.class.getName() + ">>", sb.toString());
+ } else if ("listOfMap".equals(propertyName)) {
+ // List<Map<String, Intf>>
+ assertEquals(List.class.getName() + "<" + Map.class.getName() + "<"
+ + String.class.getName() + "," + Intf.class.getName() + ">>", sb.toString());
+ } else if ("map".equals(propertyName)) {
+ // Map<Map<String, String>, List<List<Intf>>>
+ assertEquals(Map.class.getName() + "<" + Map.class.getName() + "<"
+ + String.class.getName() + "," + String.class.getName() + ">," + List.class.getName()
+ + "<" + List.class.getName() + "<" + Intf.class.getName() + ">>>", sb.toString());
+ } else {
+ throw new RuntimeException(propertyName);
+ }
+ }
+ });
+ }
+
+ /**
+ * Make sure primitive properties can be returned.
+ */
+ public void testPrimitiveProperty() {
+ AutoBean<Intf> auto = factory.intf();
+ Intf intf = auto.as();
+
+ assertNull(intf.getString());
+ intf.setString("Hello world!");
+ assertEquals("Hello world!", intf.getString());
+
+ assertEquals(0, intf.getInt());
+ intf.setInt(42);
+ assertEquals(42, intf.getInt());
+ }
+
+ public void testTags() {
+ AutoBean<Intf> auto = factory.intf();
+ auto.setTag("test", 42);
+ assertEquals(42, auto.getTag("test"));
+ }
+
+ public void testTraversal() {
+ final AutoBean<OtherIntf> other = factory.otherIntf();
+ final AutoBean<Intf> intf = factory.intf();
+ final AutoBean<HasBoolean> hasBoolean = factory.hasBoolean();
+ other.as().setIntf(intf.as());
+ other.as().setHasBoolean(hasBoolean.as());
+ intf.as().setInt(42);
+ hasBoolean.as().setGet(true);
+ hasBoolean.as().setHas(true);
+ hasBoolean.as().setIs(true);
+
+ class Checker extends AutoBeanVisitor {
+ boolean seenHasBoolean;
+ boolean seenIntf;
+ boolean seenOther;
+
+ @Override
+ public void endVisitReferenceProperty(String propertyName, AutoBean<?> value,
+ PropertyContext ctx) {
+ if ("hasBoolean".equals(propertyName)) {
+ assertSame(hasBoolean, value);
+ assertEquals(HasBoolean.class, ctx.getType());
+ } else if ("intf".equals(propertyName)) {
+ assertSame(intf, value);
+ assertEquals(Intf.class, ctx.getType());
+ } else if ("unreferenced".equals(propertyName)) {
+ assertNull(value);
+ assertEquals(UnreferencedInFactory.class, ctx.getType());
+ } else {
+ fail("Unexpecetd property " + propertyName);
+ }
+ }
+
+ @Override
+ public void endVisitValueProperty(String propertyName, Object value, PropertyContext ctx) {
+ if ("int".equals(propertyName)) {
+ assertEquals(42, value);
+ assertEquals(int.class, ctx.getType());
+ } else if ("string".equals(propertyName)) {
+ assertNull(value);
+ assertEquals(String.class, ctx.getType());
+ } else if ("get".equals(propertyName) || "has".equals(propertyName)
+ || "is".equals(propertyName)) {
+ assertEquals(boolean.class, ctx.getType());
+ assertTrue((Boolean) value);
+ } else {
+ fail("Unknown value property " + propertyName);
+ }
+ }
+
+ @Override
+ public boolean visit(AutoBean<?> bean, Context ctx) {
+ if (bean == hasBoolean) {
+ seenHasBoolean = true;
+ } else if (bean == intf) {
+ seenIntf = true;
+ } else if (bean == other) {
+ seenOther = true;
+ } else {
+ fail("Unknown AutoBean");
+ }
+ return true;
+ }
+
+ void check() {
+ assertTrue(seenHasBoolean);
+ assertTrue(seenIntf);
+ assertTrue(seenOther);
+ }
+ }
+ Checker c = new Checker();
+ other.accept(c);
+ c.check();
+ }
+
+ public void testType() {
+ assertEquals(Intf.class, factory.intf().getType());
+ }
+
+ /**
+ * Ensure that a totally automatic bean can't be unwrapped, since the
+ * generated mapper depends on the AutoBean.
+ */
+ public void testUnwrappingSimpleBean() {
+ AutoBean<Intf> auto = factory.intf();
+ try {
+ auto.unwrap();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testWrapped() {
+ RealIntf real = new RealIntf();
+ AutoBean<Intf> auto = factory.intf(real);
+ Intf intf = auto.as();
+
+ assertNotSame(real, intf);
+ assertNull(intf.getString());
+ assertEquals(0, intf.getInt());
+
+ real.string = "blah";
+ assertEquals("blah", intf.getString());
+ real.i = 42;
+ assertEquals(42, intf.getInt());
+
+ intf.setString("bar");
+ assertEquals("bar", real.string);
+
+ intf.setInt(41);
+ assertEquals(41, real.i);
+
+ AutoBean<Intf> rewrapped = factory.intf(real);
+ assertSame(auto, rewrapped);
+
+ // Disconnect the wrapper, make sure it shuts down correctly.
+ Intf unwrapped = auto.unwrap();
+ assertSame(real, unwrapped);
+ assertNull(AutoBeanUtils.getAutoBean(real));
+ try {
+ intf.setInt(42);
+ fail("Should have thrown exception");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ factory = GWT.create(Factory.class);
+ }
+}
diff --git a/user/test/com/google/web/bindery/autobean/shared/AutoBeanCodexTest.java b/user/test/com/google/web/bindery/autobean/shared/AutoBeanCodexTest.java
new file mode 100644
index 0000000..13f69b3
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/shared/AutoBeanCodexTest.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.shared.AutoBean.PropertyName;
+import com.google.web.bindery.autobean.shared.impl.EnumMap;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Simple encoding / decoding tests for the AutoBeanCodex.
+ */
+public class AutoBeanCodexTest extends GWTTestCase {
+ /**
+ * Protected so that the JRE-only test can instantiate instances.
+ */
+ protected interface Factory extends AutoBeanFactory {
+ AutoBean<HasSplittable> hasAutoBean();
+
+ AutoBean<HasCycle> hasCycle();
+
+ AutoBean<HasEnum> hasEnum();
+
+ AutoBean<HasList> hasList();
+
+ AutoBean<HasMap> hasMap();
+
+ AutoBean<HasSimple> hasSimple();
+
+ AutoBean<Simple> simple();
+ }
+
+ /*
+ * These enums are used to verify that a List<Enum> or Map<Enum, Enum> pulls
+ * in the necessary metadata.
+ */
+ enum EnumReachableThroughList {
+ FOO_LIST
+ }
+
+ enum EnumReachableThroughMapKey {
+ FOO_KEY
+ }
+
+ enum EnumReachableThroughMapValue {
+ FOO_VALUE
+ }
+
+ /**
+ * Used to test that cycles are detected.
+ */
+ interface HasCycle {
+ List<HasCycle> getCycle();
+
+ void setCycle(List<HasCycle> cycle);
+ }
+
+ interface HasEnum {
+ MyEnum getEnum();
+
+ List<MyEnum> getEnums();
+
+ Map<MyEnum, Integer> getMap();
+
+ List<EnumReachableThroughList> getParameterizedList();
+
+ Map<EnumReachableThroughMapKey, EnumReachableThroughMapValue> getParameterizedMap();
+
+ void setEnum(MyEnum value);
+
+ void setEnums(List<MyEnum> value);
+
+ void setMap(Map<MyEnum, Integer> value);
+ }
+
+ interface HasList {
+ List<Integer> getIntList();
+
+ List<Simple> getList();
+
+ void setIntList(List<Integer> list);
+
+ void setList(List<Simple> list);
+ }
+
+ interface HasMap {
+ Map<Simple, Simple> getComplexMap();
+
+ Map<Map<String, String>, Map<String, String>> getNestedMap();
+
+ Map<String, Simple> getSimpleMap();
+
+ void setComplexMap(Map<Simple, Simple> map);
+
+ void setNestedMap(Map<Map<String, String>, Map<String, String>> map);
+
+ void setSimpleMap(Map<String, Simple> map);
+ }
+
+ interface HasSimple {
+ Simple getSimple();
+
+ void setSimple(Simple s);
+ }
+
+ interface HasSplittable {
+ Splittable getSimple();
+
+ List<Splittable> getSimpleList();
+
+ Map<Splittable, Splittable> getSplittableMap();
+
+ Splittable getString();
+
+ void setSimple(Splittable simple);
+
+ void setSimpleList(List<Splittable> simple);
+
+ void setSplittableMap(Map<Splittable, Splittable> map);
+
+ void setString(Splittable s);
+ }
+
+ enum MyEnum {
+ FOO, BAR,
+ // The eclipse formatter wants to put this annotation inline
+ @PropertyName("quux")
+ BAZ;
+ }
+
+ interface ReachableOnlyFromParameterization extends Simple {
+ }
+
+ interface Simple {
+ int getInt();
+
+ String getString();
+
+ Boolean hasOtherBoolean();
+
+ boolean isBoolean();
+
+ void setBoolean(boolean b);
+
+ void setInt(int i);
+
+ void setOtherBoolean(Boolean b);
+
+ void setString(String s);
+ }
+
+ protected Factory f;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.autobean.AutoBean";
+ }
+
+ public void testCycle() {
+ AutoBean<HasCycle> bean = f.hasCycle();
+ bean.as().setCycle(Arrays.asList(bean.as()));
+ try {
+ checkEncode(bean);
+ fail("Should not have encoded");
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ public void testEmptyList() {
+ AutoBean<HasList> bean = f.hasList();
+ bean.as().setList(Collections.<Simple> emptyList());
+ AutoBean<HasList> decodedBean = checkEncode(bean);
+ assertNotNull(decodedBean.as().getList());
+ assertTrue(decodedBean.as().getList().isEmpty());
+ }
+
+ public void testEnum() {
+ EnumMap map = (EnumMap) f;
+ assertEquals("BAR", map.getToken(MyEnum.BAR));
+ assertEquals("quux", map.getToken(MyEnum.BAZ));
+ assertEquals(MyEnum.BAR, map.getEnum(MyEnum.class, "BAR"));
+ assertEquals(MyEnum.BAZ, map.getEnum(MyEnum.class, "quux"));
+
+ List<MyEnum> arrayValue = Arrays.asList(MyEnum.FOO, MyEnum.BAR, null, MyEnum.BAZ);
+ Map<MyEnum, Integer> mapValue = new HashMap<MyEnum, Integer>();
+ mapValue.put(MyEnum.FOO, 0);
+ mapValue.put(MyEnum.BAR, 1);
+ mapValue.put(MyEnum.BAZ, 2);
+
+ AutoBean<HasEnum> bean = f.hasEnum();
+ bean.as().setEnum(MyEnum.BAZ);
+ bean.as().setEnums(arrayValue);
+ bean.as().setMap(mapValue);
+
+ Splittable split = AutoBeanCodex.encode(bean);
+ // Make sure the overridden form is always used
+ assertFalse(split.getPayload().contains("BAZ"));
+
+ AutoBean<HasEnum> decoded = checkEncode(bean);
+ assertEquals(MyEnum.BAZ, decoded.as().getEnum());
+ assertEquals(arrayValue, decoded.as().getEnums());
+ assertEquals(mapValue, decoded.as().getMap());
+
+ assertEquals(MyEnum.BAZ, AutoBeanUtils.getAllProperties(bean).get("enum"));
+ bean.as().setEnum(null);
+ assertNull(bean.as().getEnum());
+ assertNull(AutoBeanUtils.getAllProperties(bean).get("enum"));
+ decoded = checkEncode(bean);
+ assertNull(decoded.as().getEnum());
+ }
+
+ /**
+ * Ensures that enum types that are reachable only through a method
+ * parameterization are included in the enum map.
+ */
+ public void testEnumReachableOnlyThroughParameterization() {
+ EnumMap map = (EnumMap) f;
+ assertEquals("FOO_LIST", map.getToken(EnumReachableThroughList.FOO_LIST));
+ assertEquals("FOO_KEY", map.getToken(EnumReachableThroughMapKey.FOO_KEY));
+ assertEquals("FOO_VALUE", map.getToken(EnumReachableThroughMapValue.FOO_VALUE));
+ assertEquals(EnumReachableThroughList.FOO_LIST, map.getEnum(EnumReachableThroughList.class,
+ "FOO_LIST"));
+ assertEquals(EnumReachableThroughMapKey.FOO_KEY, map.getEnum(EnumReachableThroughMapKey.class,
+ "FOO_KEY"));
+ assertEquals(EnumReachableThroughMapValue.FOO_VALUE, map.getEnum(
+ EnumReachableThroughMapValue.class, "FOO_VALUE"));
+ }
+
+ public void testMap() {
+ AutoBean<HasMap> bean = f.hasMap();
+ Map<String, Simple> map = new HashMap<String, Simple>();
+ Map<Simple, Simple> complex = new HashMap<Simple, Simple>();
+ bean.as().setSimpleMap(map);
+ bean.as().setComplexMap(complex);
+
+ for (int i = 0, j = 5; i < j; i++) {
+ Simple s = f.simple().as();
+ s.setInt(i);
+ map.put(String.valueOf(i), s);
+
+ Simple key = f.simple().as();
+ key.setString(String.valueOf(i));
+ complex.put(key, s);
+ }
+
+ AutoBean<HasMap> decoded = checkEncode(bean);
+ map = decoded.as().getSimpleMap();
+ complex = decoded.as().getComplexMap();
+ assertEquals(5, map.size());
+ for (int i = 0, j = 5; i < j; i++) {
+ Simple s = map.get(String.valueOf(i));
+ assertNotNull(s);
+ assertEquals(i, s.getInt());
+ }
+ assertEquals(5, complex.size());
+ for (Map.Entry<Simple, Simple> entry : complex.entrySet()) {
+ assertEquals(entry.getKey().getString(), String.valueOf(entry.getValue().getInt()));
+ }
+ }
+
+ /**
+ * Verify that arbitrarily complicated Maps of Maps work.
+ */
+ public void testNestedMap() {
+ Map<String, String> key = new HashMap<String, String>();
+ key.put("a", "b");
+
+ Map<String, String> value = new HashMap<String, String>();
+ value.put("c", "d");
+
+ Map<Map<String, String>, Map<String, String>> test =
+ new HashMap<Map<String, String>, Map<String, String>>();
+ test.put(key, value);
+
+ AutoBean<HasMap> bean = f.hasMap();
+ bean.as().setNestedMap(test);
+
+ AutoBean<HasMap> decoded = checkEncode(bean);
+ assertEquals(1, decoded.as().getNestedMap().size());
+ }
+
+ public void testNull() {
+ AutoBean<Simple> bean = f.simple();
+ AutoBean<Simple> decodedBean = checkEncode(bean);
+ assertNull(decodedBean.as().getString());
+ }
+
+ public void testSimple() {
+ AutoBean<Simple> bean = f.simple();
+ Simple simple = bean.as();
+ simple.setBoolean(true);
+ simple.setInt(42);
+ simple.setOtherBoolean(true);
+ simple.setString("Hello World!");
+
+ AutoBean<Simple> decodedBean = checkEncode(bean);
+ assertTrue(AutoBeanUtils.diff(bean, decodedBean).isEmpty());
+ assertTrue(decodedBean.as().isBoolean());
+ assertTrue(decodedBean.as().hasOtherBoolean());
+
+ AutoBean<HasSimple> bean2 = f.hasSimple();
+ bean2.as().setSimple(simple);
+
+ AutoBean<HasSimple> decodedBean2 = checkEncode(bean2);
+ assertNotNull(decodedBean2.as().getSimple());
+ assertTrue(AutoBeanUtils.diff(bean, AutoBeanUtils.getAutoBean(decodedBean2.as().getSimple()))
+ .isEmpty());
+
+ AutoBean<HasList> bean3 = f.hasList();
+ bean3.as().setIntList(Arrays.asList(1, 2, 3, null, 4, 5));
+ bean3.as().setList(Arrays.asList(simple));
+
+ AutoBean<HasList> decodedBean3 = checkEncode(bean3);
+ assertNotNull(decodedBean3.as().getIntList());
+ assertEquals(Arrays.asList(1, 2, 3, null, 4, 5), decodedBean3.as().getIntList());
+ assertNotNull(decodedBean3.as().getList());
+ assertEquals(1, decodedBean3.as().getList().size());
+ assertTrue(AutoBeanUtils.diff(bean,
+ AutoBeanUtils.getAutoBean(decodedBean3.as().getList().get(0))).isEmpty());
+ }
+
+ public void testSplittable() {
+ AutoBean<Simple> simple = f.simple();
+ simple.as().setString("Simple");
+ AutoBean<HasSplittable> bean = f.hasAutoBean();
+ bean.as().setSimple(AutoBeanCodex.encode(simple));
+ bean.as().setString(ValueCodex.encode("Hello ['\"] world"));
+ List<Splittable> testList =
+ Arrays.asList(AutoBeanCodex.encode(simple), null, AutoBeanCodex.encode(simple));
+ bean.as().setSimpleList(testList);
+ Map<Splittable, Splittable> testMap =
+ Collections.singletonMap(ValueCodex.encode("12345"), ValueCodex.encode("5678"));
+ bean.as().setSplittableMap(testMap);
+
+ AutoBean<HasSplittable> decoded = checkEncode(bean);
+ Splittable toDecode = decoded.as().getSimple();
+ AutoBean<Simple> decodedSimple = AutoBeanCodex.decode(f, Simple.class, toDecode);
+ assertEquals("Simple", decodedSimple.as().getString());
+ assertEquals("Hello ['\"] world", ValueCodex.decode(String.class, decoded.as().getString()));
+ assertEquals("12345", decoded.as().getSplittableMap().keySet().iterator().next().asString());
+ assertEquals("5678", decoded.as().getSplittableMap().values().iterator().next().asString());
+
+ List<Splittable> list = decoded.as().getSimpleList();
+ assertEquals(3, list.size());
+ assertNull(list.get(1));
+ assertEquals("Simple", AutoBeanCodex.decode(f, Simple.class, list.get(2)).as().getString());
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ f = GWT.create(Factory.class);
+ }
+
+ private <T> AutoBean<T> checkEncode(AutoBean<T> bean) {
+ Splittable split = AutoBeanCodex.encode(bean);
+ AutoBean<T> decoded = AutoBeanCodex.decode(f, bean.getType(), split);
+ assertTrue(AutoBeanUtils.deepEquals(bean, decoded));
+ return decoded;
+ }
+}
diff --git a/user/test/com/google/web/bindery/autobean/shared/SplittableTest.java b/user/test/com/google/web/bindery/autobean/shared/SplittableTest.java
new file mode 100644
index 0000000..46a6baf
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/shared/SplittableTest.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.shared;
+
+import com.google.web.bindery.autobean.gwt.client.impl.JsoSplittable;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
+import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
+import com.google.web.bindery.autobean.shared.impl.SplittableList;
+import com.google.web.bindery.autobean.shared.impl.SplittableSimpleMap;
+import com.google.web.bindery.autobean.shared.impl.StringQuoter;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests for the underlying Splittable implementation. This test class is not
+ * indicative of code that users would write, it's simply doing spot-checks of
+ * functionality that AbstractAutoBean depends on.
+ */
+public class SplittableTest extends GWTTestCase {
+
+ /**
+ *
+ */
+ private static final EncodeState testState = EncodeState.forTesting();
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.autobean.AutoBean";
+ }
+
+ public void testBasicProperties() {
+ Splittable data = StringQuoter.split("{\"a\":true, \"b\":3, \"c\":\"string\", \"d\":null}");
+ assertTrue("isBoolean", data.get("a").isBoolean());
+ assertTrue("asBoolean", data.get("a").asBoolean());
+ assertTrue("isNumber", data.get("b").isNumber());
+ assertEquals(3.0, data.get("b").asNumber());
+ assertTrue("isString", data.get("c").isString());
+ assertEquals("string", data.get("c").asString());
+ assertTrue("isNull", data.isNull("d"));
+ assertNull("should be null", data.get("d"));
+ }
+
+ /**
+ * Ensure that hashcodes don't leak into the payload.
+ */
+ public void testHashCode() {
+ Splittable data = StringQuoter.split("{\"a\":\"b\"}");
+ int hash = data.hashCode();
+ String payload = data.getPayload();
+ assertFalse(payload, payload.contains("$H"));
+ assertFalse(payload, payload.contains(String.valueOf(hash)));
+ assertEquals(hash, data.hashCode());
+ }
+
+ /**
+ * Splittables are implemented by a couple of different concrete types. We'll
+ * use this method to make sure that the correct implementation type is being
+ * used in various circumstances.
+ */
+ public void testImplementationChoice() {
+ Splittable s = StringQuoter.split("[1,false,\"true\"]");
+ if (GWT.isScript()) {
+ assertTrue("s should be JsoSplittable", s instanceof JsoSplittable);
+ assertTrue("s[0] should be JsoSplittable", s.get(0) instanceof JsoSplittable);
+ assertTrue("s[1] should be JsoSplittable", s.get(1) instanceof JsoSplittable);
+ assertTrue("s[2] should be JsoSplittable", s.get(2) instanceof JsoSplittable);
+ } else {
+ // Using the same types in both pure-JRE and DevMode to avoid JSNI
+ // overhead
+ assertTrue("s should be JsonSplittable", s.getClass().getName().endsWith("JsonSplittable"));
+ assertTrue("s[0] should be JsonSplittable", s.get(0).getClass().getName().endsWith(
+ "JsonSplittable"));
+ assertTrue("s[1] should be JsonSplittable", s.get(1).getClass().getName().endsWith(
+ "JsonSplittable"));
+ assertTrue("s[2] should be JsonSplittable", s.get(2).getClass().getName().endsWith(
+ "JsonSplittable"));
+ }
+ }
+
+ public void testIndexed() {
+ Splittable s = StringQuoter.createIndexed();
+ assertTrue(s.isIndexed());
+ assertFalse(s.isKeyed());
+ assertFalse(s.isString());
+ assertEquals(0, s.size());
+
+ string("foo").assign(s, 0);
+ string("bar").assign(s, 1);
+ string("baz").assign(s, 2);
+
+ assertEquals(3, s.size());
+ assertEquals("[\"foo\",\"bar\",\"baz\"]", s.getPayload());
+
+ string("quux").assign(s, 1);
+ assertEquals("[\"foo\",\"quux\",\"baz\"]", s.getPayload());
+
+ Splittable s2 = s.deepCopy();
+ assertNotSame(s, s2);
+ assertEquals(s.size(), s2.size());
+ for (int i = 0, j = s.size(); i < j; i++) {
+ assertEquals(s.get(i).asString(), s2.get(i).asString());
+ }
+
+ s.setSize(2);
+ assertEquals(2, s.size());
+ assertEquals("[\"foo\",\"quux\"]", s.getPayload());
+
+ // Make sure reified values aren't in the payload
+ Object o = new Object();
+ s.setReified("reified", o);
+ assertFalse(s.getPayload().contains("reified"));
+ assertFalse(s.getPayload().contains("__s"));
+ assertSame(o, s.getReified("reified"));
+ }
+
+ public void testKeyed() {
+ Splittable s = StringQuoter.createSplittable();
+ assertFalse(s.isIndexed());
+ assertTrue(s.isKeyed());
+ assertFalse(s.isString());
+ assertTrue(s.getPropertyKeys().isEmpty());
+
+ string("bar").assign(s, "foo");
+ string("quux").assign(s, "baz");
+
+ // Actual iteration order is undefined
+ assertEquals(new HashSet<String>(Arrays.asList("foo", "baz")), new HashSet<String>(s
+ .getPropertyKeys()));
+
+ assertFalse(s.isNull("foo"));
+ assertTrue(s.isNull("bar"));
+
+ assertEquals("bar", s.get("foo").asString());
+ assertEquals("quux", s.get("baz").asString());
+
+ String payload = s.getPayload();
+ assertTrue(payload.startsWith("{"));
+ assertTrue(payload.endsWith("}"));
+ assertTrue(payload.contains("\"foo\":\"bar\""));
+ assertTrue(payload.contains("\"baz\":\"quux\""));
+
+ Splittable s2 = s.deepCopy();
+ assertNotSame(s, s2);
+ assertEquals("bar", s2.get("foo").asString());
+ assertEquals("quux", s2.get("baz").asString());
+
+ // Make sure reified values aren't in the payload
+ Object o = new Object();
+ s.setReified("reified", o);
+ assertFalse("Should not see reified in " + s.getPayload(), s.getPayload().contains("reified"));
+ assertFalse("Should not see __s in " + s.getPayload(), s.getPayload().contains("__s"));
+ assertSame(o, s.getReified("reified"));
+ }
+
+ public void testNested() {
+ Splittable s = StringQuoter.split("{\"a\":{\"foo\":\"bar\"}}");
+ Splittable a = s.get("a");
+ assertNotNull(a);
+ assertEquals("bar", a.get("foo").asString());
+ assertSame(a, s.get("a"));
+ assertEquals(a, s.get("a"));
+ }
+
+ /**
+ * Tests attributes of the {@link Splittable#NULL} field.
+ */
+ public void testNull() {
+ Splittable n = Splittable.NULL;
+ if (GWT.isScript()) {
+ assertNull(n);
+ } else {
+ assertNotNull(n);
+ }
+ assertFalse("boolean", n.isBoolean());
+ assertFalse("indexed", n.isIndexed());
+ assertFalse("keyed", n.isKeyed());
+ assertFalse("string", n.isString());
+ assertEquals("null", n.getPayload());
+ }
+
+ /**
+ * Extra tests in here due to potential to confuse {@code false} and
+ * {@code null} values.
+ */
+ public void testSplittableListBoolean() {
+ Coder boolCoder = AutoBeanCodexImpl.valueCoder(Boolean.class);
+ Splittable s = StringQuoter.createIndexed();
+ bool(false).assign(s, 0);
+ assertFalse("0 should not be null", s.isNull(0));
+ assertTrue("s[0] should be a boolean", s.get(0).isBoolean());
+ assertFalse("s[0] should be false", s.get(0).asBoolean());
+ assertNotNull("Null decode", ValueCodex.decode(Boolean.class, s.get(0)));
+ Object decodedBoolean = boolCoder.decode(testState, s.get(0));
+ assertNotNull("decode should not return null", decodedBoolean);
+ assertFalse("decoded value should be false", (Boolean) decodedBoolean);
+
+ bool(true).assign(s, 1);
+ assertTrue("s[1] should be a boolean", s.get(1).isBoolean());
+ assertTrue("s[1] should be true", s.get(1).asBoolean());
+ assertTrue("boolCoder 1", (Boolean) boolCoder.decode(testState, s.get(1)));
+
+ Splittable.NULL.assign(s, 2);
+ assertTrue("3 should be null", s.isNull(3));
+ assertEquals("payload", "[false,true,null]", s.getPayload());
+ List<Boolean> boolList = new SplittableList<Boolean>(s, boolCoder, testState);
+ assertEquals("boolList", Arrays.<Boolean> asList(false, true, null), boolList);
+ }
+
+ /**
+ * Extra tests in here due to potential to confuse 0 and {@code null} values.
+ */
+ public void testSplittableListNumbers() {
+ Coder intCoder = AutoBeanCodexImpl.valueCoder(Integer.class);
+ Coder doubleCoder = AutoBeanCodexImpl.valueCoder(Double.class);
+ Splittable s = StringQuoter.createIndexed();
+ number(0).assign(s, 0);
+ assertFalse("0 should not be null", s.isNull(0));
+ assertTrue("s[0] should be a number", s.get(0).isNumber());
+ assertNotNull("Null decode", ValueCodex.decode(Integer.class, s.get(0)));
+ Object decodedInt = intCoder.decode(testState, s.get(0));
+ assertNotNull("decode should not return null", decodedInt);
+ assertEquals("intCoder 0", Integer.valueOf(0), decodedInt);
+ assertEquals("doubleCoder 0", Double.valueOf(0), doubleCoder.decode(testState, s.get(0)));
+
+ number(3.141592).assign(s, 1);
+ assertEquals("intCoder 1", Integer.valueOf(3), intCoder.decode(testState, s.get(1)));
+ assertEquals("doubleCoder 1", Double.valueOf(3.141592), doubleCoder.decode(testState, s.get(1)));
+
+ number(42).assign(s, 2);
+ Splittable.NULL.assign(s, 3);
+ assertTrue("3 should be null", s.isNull(3));
+ assertEquals("payload", "[0,3.141592,42,null]", s.getPayload());
+ List<Double> doubleList = new SplittableList<Double>(s, doubleCoder, testState);
+ assertEquals(Double.valueOf(0), doubleList.get(0));
+ assertEquals("doubleList", Arrays.<Double> asList(0d, 3.141592, 42d, null), doubleList);
+
+ // Don't share backing data between lists
+ s = StringQuoter.split("[0,3.141592,42,null]");
+ List<Integer> intList = new SplittableList<Integer>(s, intCoder, testState);
+ assertEquals("intList", Arrays.<Integer> asList(0, 3, 42, null), intList);
+ }
+
+ public void testSplittableListString() {
+ Splittable data = StringQuoter.split("[\"Hello\",\"World\"]");
+ SplittableList<String> list =
+ new SplittableList<String>(data, AutoBeanCodexImpl.valueCoder(String.class), testState);
+ assertEquals(2, list.size());
+ assertEquals(Arrays.asList("Hello", "World"), list);
+ list.set(0, "Goodbye");
+ assertEquals(Arrays.asList("Goodbye", "World"), list);
+ assertEquals("[\"Goodbye\",\"World\"]", data.getPayload());
+ list.remove(0);
+ assertEquals(Arrays.asList("World"), list);
+ assertEquals("[\"World\"]", data.getPayload());
+ list.add("Wide");
+ list.add("Web");
+ assertEquals(Arrays.asList("World", "Wide", "Web"), list);
+ assertEquals("[\"World\",\"Wide\",\"Web\"]", data.getPayload());
+ }
+
+ public void testSplittableMapStringString() {
+ Splittable data = StringQuoter.split("{\"foo\":\"bar\",\"baz\":\"quux\",\"isNull\":null}");
+ assertTrue("isNull should be null", data.isNull("isNull"));
+ assertFalse("isNull should not be undefined", data.isUndefined("isNull"));
+ Map<String, String> map =
+ new SplittableSimpleMap<String, String>(data, AutoBeanCodexImpl.valueCoder(String.class),
+ AutoBeanCodexImpl.valueCoder(String.class), testState);
+ assertEquals(3, map.size());
+ assertEquals("bar", map.get("foo"));
+ assertEquals("quux", map.get("baz"));
+ assertTrue("Map should have isNull key", map.containsKey("isNull"));
+ assertNull(map.get("isNull"));
+ assertFalse("Map should not have unknown key", map.containsKey("unknown"));
+
+ map.put("bar", "foo2");
+ assertEquals("foo2", map.get("bar"));
+ String payload = data.getPayload();
+ assertTrue(payload.contains("\"bar\":\"foo2\""));
+ assertTrue(payload.contains("\"isNull\":null"));
+ }
+
+ public void testString() {
+ Splittable s = string("Hello '\" World!");
+ assertFalse(s.isIndexed());
+ assertFalse(s.isKeyed());
+ assertTrue(s.isString());
+ assertEquals("Hello '\" World!", s.asString());
+ assertEquals("\"Hello '\\\" World!\"", s.getPayload());
+ }
+
+ public void testStringEmpty() {
+ Splittable s = string("");
+ assertFalse(s.isIndexed());
+ assertFalse(s.isKeyed());
+ assertTrue(s.isString());
+ assertEquals("", s.asString());
+ assertEquals("\"\"", s.getPayload());
+ }
+
+ private Splittable bool(boolean value) {
+ return StringQuoter.split(String.valueOf(value));
+ }
+
+ private Splittable number(double number) {
+ return StringQuoter.split(String.valueOf(number));
+ }
+
+ private Splittable string(String value) {
+ return StringQuoter.split(StringQuoter.quote(value));
+ }
+}
diff --git a/user/test/com/google/web/bindery/autobean/vm/AutoBeanCodexJreTest.java b/user/test/com/google/web/bindery/autobean/vm/AutoBeanCodexJreTest.java
new file mode 100644
index 0000000..46adb69
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/vm/AutoBeanCodexJreTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm;
+
+import com.google.web.bindery.autobean.shared.AutoBeanCodexTest;
+
+/**
+ * Runs AutoBeanCodexTest in pure-JRE mode.
+ */
+public class AutoBeanCodexJreTest extends AutoBeanCodexTest {
+
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ f = AutoBeanFactorySource.create(Factory.class);
+ }
+
+}
diff --git a/user/test/com/google/web/bindery/autobean/vm/AutoBeanJreTest.java b/user/test/com/google/web/bindery/autobean/vm/AutoBeanJreTest.java
new file mode 100644
index 0000000..e56be29
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/vm/AutoBeanJreTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 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.web.bindery.autobean.vm;
+
+import com.google.web.bindery.autobean.gwt.client.AutoBeanTest;
+
+/**
+ * Runs the AutoBeanTests against the JRE implementation.
+ */
+public class AutoBeanJreTest extends AutoBeanTest {
+
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ factory = AutoBeanFactorySource.create(Factory.class);
+ }
+}
diff --git a/user/test/com/google/web/bindery/autobean/vm/SplittableJreTest.java b/user/test/com/google/web/bindery/autobean/vm/SplittableJreTest.java
new file mode 100644
index 0000000..ddb9126
--- /dev/null
+++ b/user/test/com/google/web/bindery/autobean/vm/SplittableJreTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.web.bindery.autobean.vm;
+
+import com.google.web.bindery.autobean.shared.SplittableTest;
+
+/**
+ * A JRE-only version of SplittableTest.
+ */
+public class SplittableJreTest extends SplittableTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryExceptionHandlerTest.gwt.xml b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryExceptionHandlerTest.gwt.xml
new file mode 100644
index 0000000..32645c8
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryExceptionHandlerTest.gwt.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.1/distro-source/core/src/gwt-module.dtd">
+<!--
+ Copyright 2010 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>
+ <inherits name='com.google.web.bindery.requestfactory.RequestFactory'/>
+ <!-- use this old inefficient JSON library just for the time being, replace soon -->
+ <inherits name='com.google.gwt.junit.JUnit'/>
+ <inherits name='com.google.gwt.json.JSON'/>
+ <servlet path='/gwtRequest'
+ class='com.google.web.bindery.requestfactory.server.RequestFactoryExceptionHandlerServlet' />
+</module>
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
new file mode 100644
index 0000000..c8c61d1
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt;
+
+import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModelTest;
+import com.google.web.bindery.requestfactory.server.BoxesAndPrimitivesJreTest;
+import com.google.web.bindery.requestfactory.server.ComplexKeysJreTest;
+import com.google.web.bindery.requestfactory.server.FindServiceJreTest;
+import com.google.web.bindery.requestfactory.server.LocatorJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryExceptionPropagationJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryUnicodeEscapingJreTest;
+import com.google.web.bindery.requestfactory.server.ServiceInheritanceJreTest;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleEntityProxyIdTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Suite of RequestFactory tests that require the JRE.
+ * <p>
+ * Note: these tests require gwt-user src on the classpath. To run in
+ * Eclipse, use Google Plugin for Eclipse to run as a GWT JUnit test
+ * or edit the Eclipse launch config and add the src folder to the classpath
+ * (click Classpath tab, User entries, Advanced..., Add folders)
+ */
+public class RequestFactoryGwtJreSuite {
+ public static Test suite() {
+ TestSuite suite = new TestSuite(
+ "requestfactory package tests that require the JRE");
+ suite.addTestSuite(BoxesAndPrimitivesJreTest.class);
+ suite.addTestSuite(ComplexKeysJreTest.class);
+ suite.addTestSuite(FindServiceJreTest.class);
+ suite.addTestSuite(LocatorJreTest.class);
+ suite.addTestSuite(RequestFactoryExceptionPropagationJreTest.class);
+ suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
+ suite.addTestSuite(RequestFactoryJreTest.class);
+ suite.addTestSuite(RequestFactoryModelTest.class);
+ suite.addTestSuite(RequestFactoryUnicodeEscapingJreTest.class);
+ suite.addTestSuite(ServiceInheritanceJreTest.class);
+ suite.addTestSuite(SimpleEntityProxyIdTest.class);
+
+ return suite;
+ }
+
+ /**
+ * Used to test the client package.
+ */
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.gwt.xml b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.gwt.xml
new file mode 100644
index 0000000..51a99f4
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.gwt.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.1/distro-source/core/src/gwt-module.dtd">
+<!--
+ Copyright 2010 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>
+ <inherits name='com.google.web.bindery.requestfactory.RequestFactory'/>
+ <!-- use this old inefficient JSON library just for the time being, replace soon -->
+ <inherits name='com.google.gwt.junit.JUnit'/>
+ <inherits name='com.google.gwt.json.JSON'/>
+ <servlet path='/gwtRequest'
+ class='com.google.web.bindery.requestfactory.server.RequestFactoryServlet' />
+</module>
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.java b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.java
new file mode 100644
index 0000000..5794598
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactorySuite.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+import com.google.web.bindery.requestfactory.gwt.client.FindServiceTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryExceptionHandlerTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryExceptionPropagationTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryPolymorphicTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryTest;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryUnicodeEscapingTest;
+import com.google.web.bindery.requestfactory.gwt.client.ui.EditorTest;
+import com.google.web.bindery.requestfactory.shared.BoxesAndPrimitivesTest;
+import com.google.web.bindery.requestfactory.shared.ComplexKeysTest;
+import com.google.web.bindery.requestfactory.shared.LocatorTest;
+import com.google.web.bindery.requestfactory.shared.ServiceInheritanceTest;
+
+import junit.framework.Test;
+
+/**
+ * Tests of RequestFactory that require GWT.
+ */
+public class RequestFactorySuite {
+ public static Test suite() {
+ GWTTestSuite suite = new GWTTestSuite(
+ "Test suite for requestfactory gwt code.");
+ suite.addTestSuite(BoxesAndPrimitivesTest.class);
+ suite.addTestSuite(ComplexKeysTest.class);
+ suite.addTestSuite(EditorTest.class);
+ suite.addTestSuite(FindServiceTest.class);
+ suite.addTestSuite(LocatorTest.class);
+ suite.addTestSuite(RequestFactoryTest.class);
+ suite.addTestSuite(RequestFactoryExceptionHandlerTest.class);
+ suite.addTestSuite(RequestFactoryExceptionPropagationTest.class);
+ suite.addTestSuite(RequestFactoryPolymorphicTest.class);
+ suite.addTestSuite(RequestFactoryUnicodeEscapingTest.class);
+ suite.addTestSuite(ServiceInheritanceTest.class);
+ return suite;
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/FindServiceTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/FindServiceTest.java
new file mode 100644
index 0000000..ca82e36
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/FindServiceTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.web.bindery.event.shared.HandlerRegistration;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleBarRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * Tests for {@link com.google.web.bindery.requestfactory.shared.RequestFactory}.
+ */
+public class FindServiceTest extends RequestFactoryTestBase {
+ /*
+ * DO NOT USE finishTest(). Instead, call finishTestAndReset();
+ */
+
+ private static final int TEST_DELAY = 5000;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ public void testFetchDeletedEntity() {
+ delayTestFinish(TEST_DELAY);
+ SimpleBarRequest context = req.simpleBarRequest();
+ SimpleBarProxy willDelete = context.create(SimpleBarProxy.class);
+ context.persistAndReturnSelf().using(willDelete).fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final EntityProxyId<SimpleBarProxy> id = response.stableId();
+
+ // Make the entity behave as though it's been deleted
+ SimpleBarRequest context = req.simpleBarRequest();
+ Request<Void> persist = context.persist().using(response);
+ context.edit(response).setFindFails(true);
+ persist.fire(new Receiver<Void>() {
+
+ @Override
+ public void onSuccess(Void response) {
+ // Now try fetching the deleted instance
+ req.find(id).fire(new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ assertNull(response);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ public void testFetchEntityWithLongId() {
+ final boolean relationsAbsent = false;
+ delayTestFinish(TEST_DELAY);
+ req.simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ checkReturnedProxy(response, relationsAbsent);
+
+ final EntityProxyId<SimpleFooProxy> stableId = response.stableId();
+ req.find(stableId).fire(new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy returnedProxy) {
+ assertEquals(stableId, returnedProxy.stableId());
+ checkReturnedProxy(returnedProxy, relationsAbsent);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ public void testFetchEntityWithRelation() {
+ final boolean relationsPresent = true;
+ delayTestFinish(TEST_DELAY);
+ req.simpleFooRequest().findSimpleFooById(999L).with("barField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ checkReturnedProxy(response, relationsPresent);
+
+ final EntityProxyId<SimpleFooProxy> stableId = response.stableId();
+ req.find(stableId).with("barField").fire(
+ new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy returnedProxy) {
+ assertEquals(stableId, returnedProxy.stableId());
+ checkReturnedProxy(returnedProxy, relationsPresent);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ public void testFetchEntityWithStringId() {
+ delayTestFinish(TEST_DELAY);
+ req.simpleBarRequest().findSimpleBarById("999L").fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final EntityProxyId<SimpleBarProxy> stableId = response.stableId();
+ req.find(stableId).fire(new Receiver<SimpleBarProxy>() {
+
+ @Override
+ public void onSuccess(SimpleBarProxy returnedProxy) {
+ assertEquals(stableId, returnedProxy.stableId());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ public void testFetchsAfterCreateDontUpdate() {
+ final int[] count = {0};
+ final HandlerRegistration registration = EntityProxyChange.registerForProxyType(
+ req.getEventBus(), SimpleFooProxy.class,
+ new EntityProxyChange.Handler<SimpleFooProxy>() {
+ public void onProxyChange(EntityProxyChange<SimpleFooProxy> event) {
+ count[0]++;
+ }
+ });
+ delayTestFinish(TEST_DELAY);
+ SimpleFooRequest context = req.simpleFooRequest();
+ SimpleFooProxy proxy = context.create(SimpleFooProxy.class);
+ context.persistAndReturnSelf().using(proxy).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ // Persist and Update events
+ assertEquals(2, count[0]);
+ req.find(response.stableId()).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ // No new events
+ assertEquals(2, count[0]);
+ registration.removeHandler();
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Demonstrates behavior when fetching an unpersisted id. The setup is
+ * analagous to saving a future id into a cookie and then trying to fetch it
+ * later.
+ */
+ public void testFetchUnpersistedFutureId() {
+ String historyToken;
+
+ // Here's the factory from the "previous invocation" of the client
+ {
+ SimpleRequestFactory oldFactory = createFactory();
+ EntityProxyId<SimpleBarProxy> id = oldFactory.simpleBarRequest().create(
+ SimpleBarProxy.class).stableId();
+ historyToken = oldFactory.getHistoryToken(id);
+ }
+
+ EntityProxyId<SimpleBarProxy> id = req.getProxyId(historyToken);
+ assertNotNull(id);
+ try {
+ req.find(id);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * A second fetch for an unchanged object should not result in any update
+ * event.
+ */
+ public void testMultipleFetchesDontUpdate() {
+ final int[] count = {0};
+ final HandlerRegistration registration = EntityProxyChange.registerForProxyType(
+ req.getEventBus(), SimpleFooProxy.class,
+ new EntityProxyChange.Handler<SimpleFooProxy>() {
+ public void onProxyChange(EntityProxyChange<SimpleFooProxy> event) {
+ count[0]++;
+ }
+ });
+ delayTestFinish(TEST_DELAY);
+ req.simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals(1, count[0]);
+
+ final EntityProxyId<SimpleFooProxy> stableId = response.stableId();
+ req.find(stableId).fire(new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy returnedProxy) {
+ assertEquals(1, count[0]);
+ registration.removeHandler();
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ private void checkReturnedProxy(SimpleFooProxy response,
+ boolean checkForRelations) {
+ assertEquals(42, (int) response.getIntId());
+ assertEquals("GWT", response.getUserName());
+ assertEquals(8L, (long) response.getLongField());
+ assertEquals(com.google.web.bindery.requestfactory.shared.SimpleEnum.FOO,
+ response.getEnumField());
+ if (checkForRelations) {
+ assertNotNull(response.getBarField());
+ } else {
+ assertEquals(null, response.getBarField());
+ }
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionHandlerTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionHandlerTest.java
new file mode 100644
index 0000000..16a7b96
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionHandlerTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+
+/**
+ * Tests that {@code RequestFactoryServlet} when using a custom
+ * ExceptionHandler.
+ */
+public class RequestFactoryExceptionHandlerTest extends RequestFactoryTest {
+
+ private static final int DELAY_TEST_FINISH = 5000;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactoryExceptionHandlerTest";
+ }
+
+ @Override
+ public void testServerFailureCheckedException() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest context = req.simpleFooRequest();
+ SimpleFooProxy rayFoo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> persistRay = context.persistAndReturnSelf().using(
+ rayFoo);
+ rayFoo = context.edit(rayFoo);
+ // 42 is the crash causing magic number
+ rayFoo.setPleaseCrash(42);
+ persistRay.fire(new FooReciever(rayFoo, persistRay,
+ "java.lang.UnsupportedOperationException"));
+ }
+
+ @Override
+ public void testServerFailureRuntimeException() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest context = req.simpleFooRequest();
+ SimpleFooProxy rayFoo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> persistRay = context.persistAndReturnSelf().using(
+ rayFoo);
+ rayFoo = context.edit(rayFoo);
+ // 43 is the crash causing magic number
+ rayFoo.setPleaseCrash(43);
+ persistRay.fire(new FooReciever(rayFoo, persistRay, "java.lang.Exception"));
+ }
+
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionPropagationTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionPropagationTest.java
new file mode 100644
index 0000000..48482c7
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryExceptionPropagationTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.web.bindery.event.shared.UmbrellaException;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+import java.util.Set;
+
+/**
+ * Tests that an exception thrown by a {@link Receiver} does not prevent other
+ * {@link Receiver}s from being called.
+ */
+public class RequestFactoryExceptionPropagationTest extends
+ RequestFactoryTestBase {
+ /*
+ * DO NOT USE finishTest(). Instead, call finishTestAndReset();
+ */
+
+ private class CountingReceiver extends Receiver<Object> {
+ int failureCallCount;
+ int successCallCount;
+ int violationCallCount;
+
+ public void assertCounts(int expectedFailureCallCount,
+ int expectedSuccessCallCount, int expectedViolationCallCount) {
+ assertEquals(expectedFailureCallCount, failureCallCount);
+ assertEquals(expectedSuccessCallCount, successCallCount);
+ assertEquals(expectedViolationCallCount, violationCallCount);
+ }
+
+ @Override
+ public void onFailure(ServerFailure error) {
+ failureCallCount++;
+ }
+
+ @Override
+ public void onSuccess(Object response) {
+ successCallCount++;
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ violationCallCount++;
+ }
+ }
+
+ private class ThrowingReceiver<T> extends Receiver<T> {
+ private final RuntimeException e;
+
+ public ThrowingReceiver(RuntimeException e) {
+ this.e = e;
+ }
+
+ @Override
+ public void onFailure(ServerFailure error) {
+ throw e;
+ }
+
+ @Override
+ public void onSuccess(T response) {
+ throw e;
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ throw e;
+ }
+ }
+
+ private static final int DELAY_TEST_FINISH = 10 * 1000;
+
+ GWT.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ @Override
+ public void gwtSetUp() {
+ super.gwtSetUp();
+
+ defaultUncaughtExceptionHandler = GWT.getUncaughtExceptionHandler();
+ }
+
+ /**
+ * Test mixed invocation failure and success. One receiver will throw from
+ * onSuccess and another from onFailure. Other receivers onSuccess and
+ * onFailure will corectly be called.
+ */
+ public void testMixedSuccessAndFailureThrow() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ final RuntimeException exception1 = new RuntimeException("first exception");
+ final RuntimeException exception2 = new RuntimeException("second exception");
+ final CountingReceiver count = new CountingReceiver();
+
+ SimpleFooRequest context = req.simpleFooRequest();
+ // 42 is the crash causing magic number for a runtime exception
+ context.pleaseCrash(42).to(count);
+ context.returnNullString().to(new ThrowingReceiver<String>(exception1));
+ context.returnNullString().to(count);
+
+ fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+ new GWT.UncaughtExceptionHandler() {
+ public void onUncaughtException(Throwable e) {
+ if (e instanceof UmbrellaException) {
+ count.assertCounts(1, 1, 0);
+
+ Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+ assertEquals(2, causes.size());
+ assertTrue(causes.contains(exception1));
+ assertTrue(causes.contains(exception2));
+
+ finishTestAndReset();
+ } else {
+ defaultUncaughtExceptionHandler.onUncaughtException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Test invocation failure. Other invocations' onSuccess should correctly be
+ * called.
+ */
+ public void testOnFailureThrow() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ final RuntimeException exception1 = new RuntimeException("first exception");
+ final RuntimeException exception2 = new RuntimeException("second exception");
+ final CountingReceiver count = new CountingReceiver();
+
+ SimpleFooRequest context = req.simpleFooRequest();
+ context.returnNullString().to(count);
+ // 42 is the crash causing magic number for a runtime exception
+ context.pleaseCrash(42).to(new ThrowingReceiver<Void>(exception1));
+ context.returnNullString().to(count);
+
+ fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+ new GWT.UncaughtExceptionHandler() {
+ public void onUncaughtException(Throwable e) {
+ if (e instanceof UmbrellaException) {
+ count.assertCounts(0, 2, 0);
+
+ Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+ assertEquals(2, causes.size());
+ assertTrue(causes.contains(exception1));
+ assertTrue(causes.contains(exception2));
+
+ finishTestAndReset();
+ } else {
+ defaultUncaughtExceptionHandler.onUncaughtException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Test global failure. All receivers will have their onFailure called, and
+ * some of them will throw.
+ */
+ public void testOnGlobalFailureThrow() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ final RuntimeException exception1 = new RuntimeException("first exception");
+ final RuntimeException exception2 = new RuntimeException("second exception");
+ final CountingReceiver count = new CountingReceiver();
+
+ SimpleFooRequest context = req.simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+
+ context.returnNullString().to(count);
+ context.persist().using(newFoo).to(new ThrowingReceiver<Void>(exception1));
+ context.returnNullString().to(count);
+
+ final SimpleFooProxy mutableFoo = context.edit(newFoo);
+ // 42 is the crash causing magic number for a runtime exception
+ mutableFoo.setPleaseCrash(42);
+
+ fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+ new GWT.UncaughtExceptionHandler() {
+ public void onUncaughtException(Throwable e) {
+ if (e instanceof UmbrellaException) {
+ count.assertCounts(2, 0, 0);
+
+ Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+ assertEquals(2, causes.size());
+ assertTrue(causes.contains(exception1));
+ assertTrue(causes.contains(exception2));
+
+ finishTestAndReset();
+ } else {
+ defaultUncaughtExceptionHandler.onUncaughtException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * All receivers will have their onSuccess called, and some of them will
+ * throw.
+ */
+ public void testOnSuccessThrow() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ final RuntimeException exception1 = new RuntimeException("first exception");
+ final RuntimeException exception2 = new RuntimeException("second exception");
+ final CountingReceiver count = new CountingReceiver();
+
+ SimpleFooRequest context = req.simpleFooRequest();
+ context.returnNullString().to(count);
+ context.returnNullString().to(new ThrowingReceiver<String>(exception1));
+ context.returnNullString().to(count);
+
+ fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+ new GWT.UncaughtExceptionHandler() {
+ public void onUncaughtException(Throwable e) {
+ if (e instanceof UmbrellaException) {
+ count.assertCounts(0, 2, 0);
+
+ Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+ assertEquals(2, causes.size());
+ assertTrue(causes.contains(exception1));
+ assertTrue(causes.contains(exception2));
+
+ finishTestAndReset();
+ } else {
+ defaultUncaughtExceptionHandler.onUncaughtException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Test violations. All receivers will have their onViolation called, and some
+ * of them will throw.
+ */
+ public void testOnViolationThrow() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ final RuntimeException exception1 = new RuntimeException("first exception");
+ final RuntimeException exception2 = new RuntimeException("second exception");
+ final CountingReceiver count = new CountingReceiver();
+
+ SimpleFooRequest context = req.simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+ newFoo.setUserName("a"); // too short
+
+ context.returnNullString().to(count);
+ context.persist().using(newFoo).to(new ThrowingReceiver<Void>(exception1));
+ context.returnNullString().to(count);
+
+ fireContextAndCatch(context, new ThrowingReceiver<Void>(exception2),
+ new GWT.UncaughtExceptionHandler() {
+ public void onUncaughtException(Throwable e) {
+ if (e instanceof UmbrellaException) {
+ count.assertCounts(0, 0, 2);
+
+ Set<Throwable> causes = ((UmbrellaException) e).getCauses();
+ assertEquals(2, causes.size());
+ assertTrue(causes.contains(exception1));
+ assertTrue(causes.contains(exception2));
+
+ finishTestAndReset();
+ } else {
+ defaultUncaughtExceptionHandler.onUncaughtException(e);
+ }
+ }
+ });
+ }
+
+ protected void fireContextAndCatch(RequestContext context,
+ Receiver<Void> receiver, GWT.UncaughtExceptionHandler exceptionHandler) {
+ GWT.setUncaughtExceptionHandler(exceptionHandler);
+
+ if (receiver == null) {
+ context.fire();
+ } else {
+ context.fire(receiver);
+ }
+ }
+
+ @Override
+ protected void gwtTearDown() {
+ GWT.setUncaughtExceptionHandler(defaultUncaughtExceptionHandler);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryPolymorphicTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryPolymorphicTest.java
new file mode 100644
index 0000000..83baef3
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryPolymorphicTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.TestRequestFactory;
+
+/**
+ * Just tests the
+ * {@link com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator} to see
+ * if polymorphic signatures are allowed.
+ */
+public class RequestFactoryPolymorphicTest extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ public void testGenerator() {
+ TestRequestFactory rf = GWT.create(TestRequestFactory.class);
+ EventBus eventBus = new SimpleEventBus();
+ rf.initialize(eventBus);
+ SimpleFooProxy simpleFoo = rf.testFooPolymorphicRequest().create(
+ SimpleFooProxy.class);
+ assertNull(simpleFoo.getUserName());
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java
new file mode 100644
index 0000000..6b6f4fd
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java
@@ -0,0 +1,2741 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.OnlyUsedByRequestContextMethod;
+import com.google.web.bindery.requestfactory.shared.OnlyUsedInListProxy;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleBarRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleEnum;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleValueContext;
+import com.google.web.bindery.requestfactory.shared.SimpleValueProxy;
+import com.google.web.bindery.requestfactory.shared.Violation;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleEntityProxyId;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for {@link com.google.web.bindery.requestfactory.shared.RequestFactory}.
+ */
+public class RequestFactoryTest extends RequestFactoryTestBase {
+ /*
+ * DO NOT USE finishTest(). Instead, call finishTestAndReset();
+ *
+ * When possible, pass any returned proxies to checkSerialization() and use
+ * the return value in the place of the returned object.
+ */
+
+ class FooReciever extends Receiver<SimpleFooProxy> {
+ private SimpleFooProxy mutableFoo;
+ private Request<SimpleFooProxy> persistRequest;
+ private String expectedException;
+
+ public FooReciever(SimpleFooProxy mutableFoo,
+ Request<SimpleFooProxy> persistRequest, String exception) {
+ this.mutableFoo = mutableFoo;
+ this.persistRequest = persistRequest;
+ this.expectedException = exception;
+ }
+
+ @Override
+ public void onFailure(ServerFailure error) {
+ assertEquals(expectedException, error.getExceptionType());
+ if (expectedException != null) {
+ assertFalse(error.getStackTraceString().length() == 0);
+ assertEquals("THIS EXCEPTION IS EXPECTED BY A TEST", error.getMessage());
+ } else {
+ assertEquals(null, error.getStackTraceString());
+ assertEquals("Server Error: THIS EXCEPTION IS EXPECTED BY A TEST",
+ error.getMessage());
+ }
+
+ // Now show that we can fix the error and try again with the same
+ // request
+
+ mutableFoo.setPleaseCrash(24); // Only 42 and 43 crash
+ persistRequest.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ fail("Failure expected but onSuccess() was called");
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ fail("Failure expected but onViolation() was called");
+ }
+ }
+
+ class NullReceiver extends Receiver<Object> {
+ @Override
+ public void onSuccess(Object response) {
+ assertNull(response);
+ finishTestAndReset();
+ }
+ }
+
+ private class FailFixAndRefire<T> extends Receiver<T> {
+
+ private final SimpleFooProxy proxy;
+ private final Request<T> request;
+ private boolean voidReturnExpected;
+
+ FailFixAndRefire(SimpleFooProxy proxy, RequestContext context,
+ Request<T> request) {
+ this.proxy = context.edit(proxy);
+ this.request = request;
+ }
+
+ @Override
+ public void onSuccess(T response) {
+ /*
+ * Make sure your class path includes:
+ *
+ * tools/lib/apache/log4j/log4j-1.2.16.jar
+ * tools/lib/hibernate/validator/hibernate-validator-4.1.0.Final.jar
+ * tools/lib/slf4j/slf4j-api/slf4j-api-1.6.1.jar
+ * tools/lib/slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar
+ */
+ fail("Violations expected (you might be missing some jars, "
+ + "see the comment above this line)");
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+
+ // size violation expected
+
+ assertEquals(1, errors.size());
+ Violation error = errors.iterator().next();
+ assertEquals("userName", error.getPath());
+ assertEquals("size must be between 3 and 30", error.getMessage());
+ assertEquals(proxy.stableId(), error.getProxyId());
+
+ // Now re-used the request to fix the edit
+
+ proxy.setUserName("long enough");
+ request.fire(new Receiver<T>() {
+ @Override
+ public void onSuccess(T response) {
+ if (voidReturnExpected) {
+ assertNull(response);
+ } else {
+ assertEquals(proxy.stableId(),
+ ((SimpleFooProxy) response).stableId());
+ }
+ finishTestAndReset();
+ }
+ });
+ }
+
+ void doTest() {
+ proxy.setUserName("a"); // too short
+ request.fire(this);
+ }
+
+ void doVoidTest() {
+ voidReturnExpected = true;
+ doTest();
+ }
+ }
+
+ private static final int DELAY_TEST_FINISH = 10 * 1000;
+
+ public <T extends EntityProxy> void assertContains(Collection<T> col, T value) {
+ EntityProxyId<?> lookFor = value.stableId();
+ for (T x : col) {
+ EntityProxyId<?> found = x.stableId();
+ if (found.equals(lookFor)) {
+ return;
+ }
+ }
+ assertTrue("Value " + value + " not found in collection " + col.toString(),
+ false);
+ }
+
+ public <T extends EntityProxy> void assertNotContains(Collection<T> col,
+ T value) {
+ for (T x : col) {
+ assertNotSame(x.stableId(), value.stableId());
+ }
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ /**
+ * Test that the same object, referenced twice, points to the same instance.
+ */
+ public void testAntiAliasing() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().fetchDoubleReference().with("fooField",
+ "selfOneToManyField").fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertNotNull(response.getFooField());
+ assertSame(response.getFooField(),
+ response.getSelfOneToManyField().get(0));
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Test that we can commit child objects.
+ */
+ public void testCascadingCommit() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest context = req.simpleFooRequest();
+ final SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+ final SimpleBarProxy bar0 = context.create(SimpleBarProxy.class);
+ final SimpleBarProxy bar1 = context.create(SimpleBarProxy.class);
+ List<SimpleBarProxy> bars = new ArrayList<SimpleBarProxy>();
+ bars.add(bar0);
+ bars.add(bar1);
+
+ final SimpleFooEventHandler<SimpleBarProxy> handler = new SimpleFooEventHandler<SimpleBarProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleBarProxy.class, handler);
+
+ Request<SimpleFooProxy> request = context.persistCascadingAndReturnSelf().using(
+ foo);
+ SimpleFooProxy editFoo = context.edit(foo);
+ editFoo.setOneToManyField(bars);
+ request.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertFalse(((SimpleEntityProxyId<SimpleFooProxy>) response.stableId()).isEphemeral());
+ assertEquals(2, handler.persistEventCount); // two bars persisted.
+ assertEquals(2, handler.updateEventCount); // two bars persisted.
+ assertEquals(4, handler.totalEventCount);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testChangedCreate() {
+ SimpleFooRequest context = simpleFooRequest();
+
+ // Creates don't cause a change
+ SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+ assertFalse(context.isChanged());
+
+ // Change
+ foo.setCharField('c');
+ assertTrue(context.isChanged());
+
+ // Undo the change
+ foo.setCharField(null);
+ assertFalse(context.isChanged());
+ }
+
+ public void testChangedCreateValueProxy() {
+ SimpleFooRequest context = simpleFooRequest();
+
+ // Creates don't cause a change
+ SimpleValueProxy foo = context.create(SimpleValueProxy.class);
+ assertFalse(context.isChanged());
+
+ // Change
+ foo.setString("foo");
+ assertTrue(context.isChanged());
+
+ // Undo the change
+ foo.setString(null);
+ assertFalse(context.isChanged());
+ }
+
+ public void testChangedEdit() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(1L).fire(
+ new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy foo) {
+ foo = checkSerialization(foo);
+ SimpleFooRequest context = simpleFooRequest();
+
+ // edit() doesn't cause a change
+ foo = context.edit(foo);
+ assertFalse(context.isChanged());
+
+ final String newName = "something else;";
+ String oldName = foo.getUserName();
+ assertFalse("Don't accidentally set the same name",
+ newName.equals(oldName));
+
+ // gets don't cause a change
+ assertFalse(context.isChanged());
+
+ // Change
+ foo.setUserName(newName);
+ assertTrue(context.isChanged());
+
+ // Undo the change
+ foo.setUserName(oldName);
+ assertFalse(context.isChanged());
+
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testChangedNothing() {
+ SimpleFooRequest context = simpleFooRequest();
+ assertFalse(context.isChanged());
+ }
+
+ public void testClassToken() {
+ String token = req.getHistoryToken(SimpleFooProxy.class);
+ assertEquals(SimpleFooProxy.class, req.getProxyClass(token));
+
+ SimpleFooProxy foo = simpleFooRequest().create(SimpleFooProxy.class);
+ assertEquals(SimpleFooProxy.class, foo.stableId().getProxyClass());
+ }
+
+ public void testCollectionSubProperties() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().getSimpleFooWithSubPropertyCollection().with(
+ "selfOneToManyField", "selfOneToManyField.fooField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(
+ "I'm here",
+ response.getSelfOneToManyField().get(0).getFooField().getUserName());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testDomainUpcast() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnSimpleFooSubclass().fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(42, response.getIntId().intValue());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testDummyCreate() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleFooProxy.class, handler);
+
+ SimpleFooRequest context = simpleFooRequest();
+ final SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+ final EntityProxyId<SimpleFooProxy> futureId = foo.stableId();
+ assertTrue(((SimpleEntityProxyId<?>) futureId).isEphemeral());
+ Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(foo);
+ fooReq.fire(new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy returned) {
+ returned = checkSerialization(returned);
+ EntityProxyId<SimpleFooProxy> returnedId = returned.stableId();
+ assertEquals(futureId, returnedId);
+ assertFalse((((SimpleEntityProxyId<?>) returnedId).isEphemeral()));
+ assertEquals(1, handler.persistEventCount);
+ assertEquals(1, handler.updateEventCount);
+ assertEquals(2, handler.totalEventCount);
+
+ checkStableIdEquals(foo, returned);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testDummyCreateBar() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleBarRequest context = simpleBarRequest();
+ final SimpleBarProxy foo = context.create(SimpleBarProxy.class);
+ assertTrue(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+ Request<SimpleBarProxy> fooReq = context.persistAndReturnSelf().using(foo);
+ fooReq.fire(new Receiver<SimpleBarProxy>() {
+
+ @Override
+ public void onSuccess(SimpleBarProxy returned) {
+ returned = checkSerialization(returned);
+ assertFalse(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+
+ checkStableIdEquals(foo, returned);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testDummyCreateList() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleBarRequest context = simpleBarRequest();
+ final SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+ assertTrue(((SimpleEntityProxyId<?>) bar.stableId()).isEphemeral());
+ Request<SimpleBarProxy> fooReq = context.returnFirst(Collections.singletonList(bar));
+ fooReq.fire(new Receiver<SimpleBarProxy>() {
+
+ @Override
+ public void onSuccess(SimpleBarProxy returned) {
+ returned = checkSerialization(returned);
+ assertFalse(((SimpleEntityProxyId<?>) bar.stableId()).isEphemeral());
+ assertFalse(((SimpleEntityProxyId<?>) returned.stableId()).isEphemeral());
+
+ checkStableIdEquals(bar, returned);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Tests behaviors relating to editing an object with one context and then
+ * using with another.
+ */
+ public void testEditAcrossContexts() {
+ SimpleFooRequest contextA = simpleFooRequest();
+ final SimpleFooRequest contextB = simpleFooRequest();
+
+ SimpleFooProxy fromA = contextA.create(SimpleFooProxy.class);
+
+ try {
+ contextB.edit(fromA);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ contextB.persistAndReturnSelf().using(fromA).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ fail();
+ }
+ });
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ delayTestFinish(DELAY_TEST_FINISH);
+ contextA.findSimpleFooById(999L).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ // The response shouldn't be associated with a RequestContext
+ contextB.edit(response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Tests that enum values used only as method parameters in a RequestContext
+ * are in the EnumMap. This test only applies to GWT-based clients.
+ */
+ public void testEnumOnlyUsedByRequestContext() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest ctx = simpleFooRequest();
+ ctx.receiveEnum(OnlyUsedByRequestContextMethod.FOO).fire(
+ new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * Check default value, a newly-set value, and a null value.
+ */
+ public void testEnumProperty() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals(SimpleEnum.FOO, response.getEnumField());
+ SimpleFooRequest ctx = simpleFooRequest();
+ response = ctx.edit(response);
+ response.setEnumField(SimpleEnum.BAR);
+ ctx.persistAndReturnSelf().using(response).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals(SimpleEnum.BAR, response.getEnumField());
+ SimpleFooRequest ctx = simpleFooRequest();
+ response = ctx.edit(response);
+ response.setEnumField(null);
+ ctx.persistAndReturnSelf().using(response).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertNull(response.getEnumField());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ public void testFetchEntity() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(42, (int) response.getIntId());
+ assertEquals("GWT", response.getUserName());
+ assertEquals(8L, (long) response.getLongField());
+ assertEquals(com.google.web.bindery.requestfactory.shared.SimpleEnum.FOO,
+ response.getEnumField());
+ assertEquals(null, response.getBarField());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testFetchEntityWithRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).with("barField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(42, (int) response.getIntId());
+ assertEquals("GWT", response.getUserName());
+ assertEquals(8L, (long) response.getLongField());
+ assertEquals(com.google.web.bindery.requestfactory.shared.SimpleEnum.FOO,
+ response.getEnumField());
+ assertNotNull(response.getBarField());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testFetchList() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findAll().fire(new Receiver<List<SimpleFooProxy>>() {
+ @Override
+ public void onSuccess(List<SimpleFooProxy> responseList) {
+ SimpleFooProxy response = checkSerialization(responseList.get(0));
+ assertEquals(42, (int) response.getIntId());
+ assertEquals("GWT", response.getUserName());
+ assertEquals(8L, (long) response.getLongField());
+ assertEquals(com.google.web.bindery.requestfactory.shared.SimpleEnum.FOO,
+ response.getEnumField());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testFetchSet() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleBarRequest().findAsSet().fire(new Receiver<Set<SimpleBarProxy>>() {
+ @Override
+ public void onSuccess(Set<SimpleBarProxy> response) {
+ assertEquals(2, response.size());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testFindFindEdit() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleFooProxy.class, handler);
+
+ req.simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy newFoo) {
+ newFoo = checkSerialization(newFoo);
+ assertEquals(1, handler.updateEventCount);
+ assertEquals(1, handler.totalEventCount);
+
+ req.simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy newFoo) {
+ newFoo = checkSerialization(newFoo);
+ // no events are fired second time.
+ assertEquals(1, handler.updateEventCount);
+ assertEquals(1, handler.totalEventCount);
+ SimpleFooRequest context = req.simpleFooRequest();
+ final Request<Void> mutateRequest = context.persist().using(
+ newFoo);
+ newFoo = context.edit(newFoo);
+ newFoo.setUserName("Ray");
+ mutateRequest.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ // events fired on updates.
+ assertEquals(2, handler.updateEventCount);
+ assertEquals(2, handler.totalEventCount);
+
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ public void testForwardReferenceDecode() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().getTripletReference().with(
+ "selfOneToManyField.selfOneToManyField.fooField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertNotNull(response.getSelfOneToManyField().get(0));
+ assertNotNull(response.getSelfOneToManyField().get(0).getSelfOneToManyField());
+ assertNotNull(response.getSelfOneToManyField().get(0).getSelfOneToManyField().get(
+ 0));
+ assertNotNull(response.getSelfOneToManyField().get(0).getSelfOneToManyField().get(
+ 0).getFooField());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testGetEventBus() {
+ assertEquals(eventBus, req.getEventBus());
+ }
+
+ public void testGetListLongId() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // Long ids
+ simpleFooRequest().findAll().with("barField.userName").fire(
+ new Receiver<List<SimpleFooProxy>>() {
+ @Override
+ public void onSuccess(List<SimpleFooProxy> response) {
+ assertEquals(2, response.size());
+ for (SimpleFooProxy foo : response) {
+ foo = checkSerialization(foo);
+ assertNotNull(foo.stableId());
+ assertEquals("FOO", foo.getBarField().getUserName());
+ }
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testGetListStringId() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // String ids
+ simpleBarRequest().findAll().fire(new Receiver<List<SimpleBarProxy>>() {
+ @Override
+ public void onSuccess(List<SimpleBarProxy> response) {
+ assertEquals(2, response.size());
+ for (SimpleBarProxy bar : response) {
+ bar = checkSerialization(bar);
+ assertNotNull(bar.stableId());
+ finishTestAndReset();
+ }
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testHistoryToken() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleBarRequest context = simpleBarRequest();
+ final SimpleBarProxy foo = context.create(SimpleBarProxy.class);
+ final EntityProxyId<SimpleBarProxy> futureId = foo.stableId();
+ final String futureToken = req.getHistoryToken(futureId);
+
+ // Check that a newly-created object's token can be found
+ assertEquals(futureId, req.getProxyId(futureToken));
+ assertEquals(futureId.getProxyClass(), req.getProxyClass(futureToken));
+
+ Request<SimpleBarProxy> fooReq = context.persistAndReturnSelf().using(foo);
+ fooReq.fire(new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy returned) {
+ returned = checkSerialization(returned);
+ EntityProxyId<SimpleBarProxy> persistedId = returned.stableId();
+ String persistedToken = req.getHistoryToken(returned.stableId());
+
+ // Expect variations after persist
+ assertFalse(futureToken.equals(persistedToken));
+
+ // Make sure the token is stable after persist using the future id
+ assertEquals(persistedToken, req.getHistoryToken(futureId));
+
+ // Check that the persisted object can be found with future token
+ assertEquals(futureId, req.getProxyId(futureToken));
+ assertEquals(futureId, req.getProxyId(persistedToken));
+ assertEquals(futureId.getProxyClass(),
+ req.getProxyClass(persistedToken));
+
+ assertEquals(persistedId, req.getProxyId(futureToken));
+ assertEquals(persistedId, req.getProxyId(persistedToken));
+ assertEquals(persistedId.getProxyClass(),
+ req.getProxyClass(futureToken));
+
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testInstanceServiceRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ req.instanceServiceRequest().add(5).fire(new Receiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertEquals(10, (int) response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testInstanceServiceRequestByName() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ req.instanceServiceRequestByName().add(5).fire(new Receiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertEquals(10, (int) response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Make sure our stock RF logging service keeps receiving.
+ */
+ public void testLoggingService() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ String logRecordJson = new StringBuilder("{") //
+ .append("\"level\": \"ALL\", ") //
+ .append("\"loggerName\": \"logger\", ") //
+ .append("\"msg\": \"Hi mom\", ") //
+ .append("\"timestamp\": \"1234567890\",") //
+ .append("\"thrown\": {}") //
+ .append("}") //
+ .toString();
+
+ req.loggingRequest().logMessage(logRecordJson).fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Test that removing a parent entity and implicitly removing the child sends
+ * an event to the client that the child was removed.
+ */
+ public void testMethodWithSideEffectDeleteChild() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // Handle changes to SimpleFooProxy.
+ final SimpleFooEventHandler<SimpleFooProxy> fooHandler = new SimpleFooEventHandler<SimpleFooProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleFooProxy.class, fooHandler);
+
+ // Handle changes to SimpleBarProxy.
+ final SimpleFooEventHandler<SimpleBarProxy> barHandler = new SimpleFooEventHandler<SimpleBarProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleBarProxy.class, barHandler);
+
+ // Persist bar.
+ SimpleBarRequest context = req.simpleBarRequest();
+ final SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+ context.persistAndReturnSelf().using(bar).fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy persistentBar) {
+ persistentBar = checkSerialization(persistentBar);
+ // Persist foo with bar as a child.
+ SimpleFooRequest context = req.simpleFooRequest();
+ SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> persistRequest = context.persistAndReturnSelf().using(
+ foo).with("barField");
+ foo = context.edit(foo);
+ foo.setUserName("John");
+ foo.setBarField(bar);
+ persistRequest.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy persistentFoo) {
+ persistentFoo = checkSerialization(persistentFoo);
+
+ // Delete bar.
+ SimpleFooRequest context = req.simpleFooRequest();
+ final Request<Void> deleteRequest = context.deleteBar().using(
+ persistentFoo);
+ deleteRequest.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ assertEquals(1, fooHandler.persistEventCount);
+ assertEquals(2, fooHandler.updateEventCount);
+ assertEquals(3, fooHandler.totalEventCount);
+
+ assertEquals(1, barHandler.persistEventCount);
+ assertEquals(1, barHandler.updateEventCount);
+ assertEquals(1, barHandler.deleteEventCount);
+ assertEquals(3, barHandler.totalEventCount);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * tests that (a) any method can have a side effect that is handled correctly.
+ * (b) instance methods are handled correctly and (c) a request cannot be
+ * reused after a successful response is received. (Yet?)
+ */
+ public void testMethodWithSideEffects() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleFooProxy.class, handler);
+
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy newFoo) {
+ newFoo = checkSerialization(newFoo);
+ assertEquals(1, handler.updateEventCount);
+ assertEquals(1, handler.totalEventCount);
+ SimpleFooRequest context = simpleFooRequest();
+ final Request<Long> mutateRequest = context.countSimpleFooWithUserNameSideEffect().using(
+ newFoo);
+ newFoo = context.edit(newFoo);
+ newFoo.setUserName("Ray");
+ mutateRequest.fire(new Receiver<Long>() {
+ @Override
+ public void onSuccess(Long response) {
+ assertCannotFire(mutateRequest);
+ assertEquals(new Long(2L), response);
+ assertEquals(2, handler.updateEventCount);
+ assertEquals(2, handler.totalEventCount);
+
+ // confirm that the instance method did have the desired
+ // sideEffect.
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy finalFoo) {
+ finalFoo = checkSerialization(finalFoo);
+ assertEquals("Ray", finalFoo.getUserName());
+ assertEquals(2, handler.updateEventCount);
+ assertEquals(2, handler.totalEventCount);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ });
+
+ try {
+ newFoo.setUserName("Barney");
+ fail();
+ } catch (IllegalStateException e) {
+ /* pass, cannot change a request that is in flight */
+ }
+ }
+ });
+ }
+
+ public void testMultipleEdits() {
+ RequestContext c1 = req.simpleFooRequest();
+ SimpleFooProxy proxy = c1.create(SimpleFooProxy.class);
+ // Re-editing is idempotent
+ assertSame(proxy, c1.edit(c1.edit(proxy)));
+
+ // Should not allow "crossing the steams"
+ RequestContext c2 = req.simpleFooRequest();
+ try {
+ c2.edit(proxy);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ /**
+ * Tests a no-op request.
+ */
+ public void testNoOpRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Ensures that a service method can respond with a null value.
+ */
+ public void testNullEntityProxyResult() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnNullSimpleFoo().fire(new NullReceiver());
+ }
+
+ /**
+ * Test that a null value can be sent in a request.
+ */
+ public void testNullListRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final Request<Void> fooReq = req.simpleFooRequest().receiveNullList(null);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void v) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Ensures that a service method can respond with a null value.
+ */
+ public void testNullListResult() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnNullList().fire(new NullReceiver());
+ }
+
+ /**
+ * Test that a null value can be sent in a request.
+ */
+ public void testNullSimpleFooRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final Request<Void> fooReq = req.simpleFooRequest().receiveNullSimpleFoo(
+ null);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void v) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Test that a null value can be sent to an instance method.
+ */
+ public void testNullStringInstanceRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // Get a valid proxy entity.
+ req.simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ Request<Void> fooReq = req.simpleFooRequest().receiveNull(null).using(
+ response);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void v) {
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Test that a null value can be sent in a request.
+ */
+ public void testNullStringRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final Request<Void> fooReq = req.simpleFooRequest().receiveNullString(null);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void v) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Ensures that a service method can respond with a null value.
+ */
+ public void testNullStringResult() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnNullString().fire(new NullReceiver());
+ }
+
+ /**
+ * Test that a null value can be sent within a list of entities.
+ */
+ public void testNullValueInEntityListRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // Get a valid proxy entity.
+ req.simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ List<SimpleFooProxy> list = new ArrayList<SimpleFooProxy>();
+ list.add(response); // non-null
+ list.add(null); // null
+ final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInEntityList(
+ list);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void v) {
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Test that a null value can be sent within a list of objects.
+ */
+ public void testNullValueInIntegerListRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ List<Integer> list = Arrays.asList(new Integer[] {1, 2, null});
+ final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInIntegerList(
+ list);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void v) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Test that a null value can be sent within a list of strings.
+ */
+ public void testNullValueInStringListRequest() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ List<String> list = Arrays.asList(new String[] {"nonnull", "null", null});
+ final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInStringList(
+ list);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void v) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Tests a message consisting only of operations, with no invocations.
+ */
+ public void testOperationOnlyMessage() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ RequestContext ctx = simpleFooRequest();
+ SimpleFooProxy proxy = ctx.create(SimpleFooProxy.class);
+ proxy.setUserName("GWT");
+ ctx.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPersistAllValueTypes() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest r = simpleFooRequest();
+ SimpleFooProxy f = r.create(SimpleFooProxy.class);
+
+ f.setUserName("user name");
+ f.setByteField((byte) 100);
+ f.setShortField((short) 12345);
+ f.setFloatField(1234.56f);
+ f.setDoubleField(1.2345);
+ f.setLongField(1234L);
+ f.setBoolField(false);
+ f.setOtherBoolField(true);
+ f.setCreated(new Date(397387389L));
+
+ r.persistAndReturnSelf().using(f).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy f) {
+ f = checkSerialization(f);
+ assertEquals("user name", f.getUserName());
+ assertEquals(Byte.valueOf((byte) 100), f.getByteField());
+ assertEquals(Short.valueOf((short) 12345), f.getShortField());
+ assertEquals(0, (int) Math.rint(123456f - 100 * f.getFloatField()));
+ assertEquals(Double.valueOf(1.2345), f.getDoubleField());
+ assertEquals(Long.valueOf(1234L), f.getLongField());
+ assertFalse(f.getBoolField());
+ assertTrue(f.getOtherBoolField());
+ assertEquals(new Date(397387389L), f.getCreated());
+
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Test that the server code will not allow a persisted entity to be returned
+ * if it has a null version property.
+ */
+ public void testPersistedEntityWithNullVersion() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().getSimpleFooWithNullVersion().fire(
+ new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onFailure(ServerFailure error) {
+ finishTestAndReset();
+ }
+
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ fail();
+ }
+ });
+ }
+
+ public void testPersistExistingEntityExistingRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // Retrieve a Bar
+ simpleBarRequest().findSimpleBarById("999L").fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final SimpleBarProxy barProxy = checkSerialization(response);
+ // Retrieve a Foo
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ fooProxy = context.edit(fooProxy);
+ // Make the Foo point to the Bar
+ fooProxy.setBarField(barProxy);
+ fooProxy.setUserName("Hello");
+ fooProxy.setByteField((byte) 55);
+ context.persistAndReturnSelf().using(fooProxy).with(
+ "barField").fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy received) {
+ received = checkSerialization(received);
+ // Check that Foo points to Bar
+ assertNotNull(received.getBarField());
+ assertEquals(barProxy.stableId(),
+ received.getBarField().stableId());
+ assertEquals("Hello", received.getUserName());
+ assertTrue(55 == received.getByteField());
+
+ // Unset the association
+ SimpleFooRequest context = simpleFooRequest();
+ received = context.edit(received);
+ received.setBarField(null);
+ received.setUserName(null);
+ received.setByteField(null);
+ context.persistAndReturnSelf().using(received).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertNull(response.getBarField());
+ assertNull(response.getUserName());
+ assertNull(response.getByteField());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * Find Entity Create Entity2 Relate Entity2 to Entity Persist Entity
+ */
+ public void testPersistExistingEntityNewRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ // Make a new bar
+ SimpleBarRequest context = simpleBarRequest();
+ SimpleBarProxy makeABar = context.create(SimpleBarProxy.class);
+ Request<SimpleBarProxy> persistRequest = context.persistAndReturnSelf().using(
+ makeABar);
+ makeABar = context.edit(makeABar);
+ makeABar.setUserName("Amit");
+
+ persistRequest.fire(new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final SimpleBarProxy persistedBar = checkSerialization(response);
+
+ // It was made, now find a foo to assign it to
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+
+ // Found the foo, edit it
+ SimpleFooRequest context = simpleFooRequest();
+ Request<Void> fooReq = context.persist().using(response);
+ response = context.edit(response);
+ response.setBarField(persistedBar);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+
+ // Foo was persisted, fetch it again check the goods
+ simpleFooRequest().findSimpleFooById(999L).with(
+ "barField.userName").fire(
+ new Receiver<SimpleFooProxy>() {
+
+ // Here it is
+ @Override
+ public void onSuccess(SimpleFooProxy finalFooProxy) {
+ finalFooProxy = checkSerialization(finalFooProxy);
+ assertEquals("Amit",
+ finalFooProxy.getBarField().getUserName());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+
+ /**
+ * Ensure that a relationship can be set up between two newly-created objects.
+ */
+ public void testPersistFutureToFuture() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+ final SimpleBarProxy newBar = context.create(SimpleBarProxy.class);
+
+ Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(
+ newFoo).with("barField");
+ newFoo = context.edit(newFoo);
+ newFoo.setBarField(newBar);
+
+ fooReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertNotNull(response.getBarField());
+ assertEquals(newBar.stableId(), response.getBarField().stableId());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /*
+ * Find Entity2 Create Entity, Persist Entity Relate Entity2 to Entity Persist
+ * Entity
+ */
+ public void testPersistNewEntityExistingRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy newFoo = context.edit(context.create(SimpleFooProxy.class));
+ final Request<Void> fooReq = context.persist().using(newFoo);
+
+ newFoo.setUserName("Ray");
+
+ final SimpleFooProxy finalFoo = newFoo;
+ simpleBarRequest().findSimpleBarById("999L").fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ response = checkSerialization(response);
+ finalFoo.setBarField(response);
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy finalFooProxy) {
+ finalFooProxy = checkSerialization(finalFooProxy);
+ // newFoo hasn't been persisted, so userName is the old
+ // value.
+ assertEquals("GWT", finalFooProxy.getUserName());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * Create Entity, Persist Entity Create Entity2, Perist Entity2 relate Entity2
+ * to Entity Persist
+ */
+ public void testPersistNewEntityNewRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+
+ final Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(
+ newFoo);
+ newFoo = context.edit(newFoo);
+ newFoo.setUserName("Ray");
+
+ SimpleBarRequest context2 = simpleBarRequest();
+ SimpleBarProxy newBar = context2.create(SimpleBarProxy.class);
+ final Request<SimpleBarProxy> barReq = context2.persistAndReturnSelf().using(
+ newBar);
+ newBar = context2.edit(newBar);
+ newBar.setUserName("Amit");
+
+ fooReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(final SimpleFooProxy response) {
+ final SimpleFooProxy persistedFoo = checkSerialization(response);
+ barReq.fire(new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final SimpleBarProxy persistedBar = checkSerialization(response);
+
+ assertEquals("Ray", persistedFoo.getUserName());
+ SimpleFooRequest context = simpleFooRequest();
+ final Request<Void> fooReq2 = context.persist().using(persistedFoo);
+ SimpleFooProxy editablePersistedFoo = context.edit(persistedFoo);
+ editablePersistedFoo.setBarField(persistedBar);
+ fooReq2.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ req.simpleFooRequest().findSimpleFooById(persistedFoo.getId()).with(
+ "barField.userName").fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy finalFooProxy) {
+ finalFooProxy = checkSerialization(finalFooProxy);
+ assertEquals("Amit",
+ finalFooProxy.getBarField().getUserName());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistOneToManyExistingEntityExistingRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ simpleBarRequest().findSimpleBarById("999L").fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final SimpleBarProxy barProxy = checkSerialization(response);
+ simpleFooRequest().findSimpleFooById(999L).with("oneToManyField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy).with("oneToManyField");
+ fooProxy = context.edit(fooProxy);
+
+ List<SimpleBarProxy> barProxyList = fooProxy.getOneToManyField();
+ final int listCount = barProxyList.size();
+ barProxyList.add(barProxy);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(response.getOneToManyField().size(),
+ listCount + 1);
+ assertContains(response.getOneToManyField(), barProxy);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ public void testPersistRecursiveRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy rayFoo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> persistRay = context.persistAndReturnSelf().using(
+ rayFoo);
+ rayFoo = context.edit(rayFoo);
+ rayFoo.setUserName("Ray");
+ rayFoo.setFooField(rayFoo);
+ persistRay.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPersistRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy rayFoo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> persistRay = context.persistAndReturnSelf().using(
+ rayFoo);
+ rayFoo = context.edit(rayFoo);
+ rayFoo.setUserName("Ray");
+
+ persistRay.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ final SimpleFooProxy persistedRay = checkSerialization(response);
+ SimpleBarRequest context = simpleBarRequest();
+ SimpleBarProxy amitBar = context.create(SimpleBarProxy.class);
+ final Request<SimpleBarProxy> persistAmit = context.persistAndReturnSelf().using(
+ amitBar);
+ amitBar = context.edit(amitBar);
+ amitBar.setUserName("Amit");
+
+ persistAmit.fire(new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ response = checkSerialization(response);
+
+ SimpleFooRequest context = simpleFooRequest();
+ final Request<SimpleFooProxy> persistRelationship = context.persistAndReturnSelf().using(
+ persistedRay).with("barField");
+ SimpleFooProxy newRec = context.edit(persistedRay);
+ newRec.setBarField(response);
+
+ persistRelationship.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals("Amit", response.getBarField().getUserName());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ public void testPersistSelfOneToManyExistingEntityExistingRelation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ simpleFooRequest().findSimpleFooById(999L).with("selfOneToManyField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy).with("selfOneToManyField");
+ fooProxy = context.edit(fooProxy);
+ List<SimpleFooProxy> fooProxyList = fooProxy.getSelfOneToManyField();
+ final int listCount = fooProxyList.size();
+ fooProxyList.add(fooProxy);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(response.getSelfOneToManyField().size(),
+ listCount + 1);
+ assertContains(response.getSelfOneToManyField(), response);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+
+ public void testPersistValueList() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy);
+ fooProxy = context.edit(fooProxy);
+ fooProxy.getNumberListField().add(100);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertTrue(response.getNumberListField().contains(100));
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistValueListNull() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy);
+ fooProxy = context.edit(fooProxy);
+
+ fooProxy.setNumberListField(null);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ List<Integer> list = response.getNumberListField();
+ assertNull(list);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistValueListRemove() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy);
+ fooProxy = context.edit(fooProxy);
+ final int oldValue = fooProxy.getNumberListField().remove(0);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertFalse(response.getNumberListField().contains(oldValue));
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistValueListReplace() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy);
+ fooProxy = context.edit(fooProxy);
+ final ArrayList<Integer> al = new ArrayList<Integer>();
+ al.add(5);
+ al.add(8);
+ al.add(13);
+ fooProxy.setNumberListField(al);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ List<Integer> list = response.getNumberListField();
+ assertEquals(5, (int) list.get(0));
+ assertEquals(8, (int) list.get(1));
+ assertEquals(13, (int) list.get(2));
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistValueListReverse() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy);
+ fooProxy = context.edit(fooProxy);
+ final ArrayList<Integer> al = new ArrayList<Integer>();
+ List<Integer> listField = fooProxy.getNumberListField();
+ al.addAll(listField);
+ Collections.reverse(listField);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ Collections.reverse(al);
+ assertTrue(response.getNumberListField().equals(al));
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistValueListSetIndex() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy);
+ fooProxy = context.edit(fooProxy);
+ fooProxy.getNumberListField().set(0, 10);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertTrue(response.getNumberListField().get(0) == 10);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistValueSetAddNew() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleBarRequest context = simpleBarRequest();
+ SimpleBarProxy newBar = context.create(SimpleBarProxy.class);
+
+ context.persistAndReturnSelf().using(newBar).fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final SimpleBarProxy barProxy = checkSerialization(response);
+ simpleFooRequest().findSimpleFooById(999L).with("oneToManySetField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy).with("oneToManySetField");
+ fooProxy = context.edit(fooProxy);
+
+ Set<SimpleBarProxy> setField = fooProxy.getOneToManySetField();
+ final int listCount = setField.size();
+ setField.add(barProxy);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(listCount + 1,
+ response.getOneToManySetField().size());
+ assertContains(response.getOneToManySetField(),
+ barProxy);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistValueSetAlreadyExists() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ simpleBarRequest().findSimpleBarById("1L").fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final SimpleBarProxy barProxy = checkSerialization(response);
+
+ simpleFooRequest().findSimpleFooById(999L).with("oneToManySetField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy).with("oneToManySetField");
+ fooProxy = context.edit(fooProxy);
+
+ Set<SimpleBarProxy> setField = fooProxy.getOneToManySetField();
+ final int listCount = setField.size();
+ assertContains(setField, barProxy);
+ setField.add(barProxy);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(response.getOneToManySetField().size(),
+ listCount);
+ assertContains(response.getOneToManySetField(),
+ barProxy);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+ public void testPersistValueSetRemove() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ simpleBarRequest().findSimpleBarById("1L").fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ final SimpleBarProxy barProxy = checkSerialization(response);
+ simpleFooRequest().findSimpleFooById(999L).with("oneToManySetField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy fooProxy) {
+ fooProxy = checkSerialization(fooProxy);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> updReq = context.persistAndReturnSelf().using(
+ fooProxy).with("oneToManySetField");
+ fooProxy = context.edit(fooProxy);
+
+ Set<SimpleBarProxy> setField = fooProxy.getOneToManySetField();
+ final int listCount = setField.size();
+ assertContains(setField, barProxy);
+ setField.remove(context.edit(barProxy));
+ assertNotContains(setField, barProxy);
+ updReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(listCount - 1,
+ response.getOneToManySetField().size());
+ assertNotContains(response.getOneToManySetField(),
+ barProxy);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ public void testPrimitiveList() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final Request<List<Integer>> fooReq = simpleFooRequest().getNumberList();
+ fooReq.fire(new Receiver<List<Integer>>() {
+ @Override
+ public void onSuccess(List<Integer> response) {
+ assertEquals(3, response.size());
+ assertEquals(1, (int) response.get(0));
+ assertEquals(2, (int) response.get(1));
+ assertEquals(3, (int) response.get(2));
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPrimitiveListAsParameter() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final Request<SimpleFooProxy> fooReq = simpleFooRequest().findSimpleFooById(
+ 999L);
+ fooReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ final Request<Integer> sumReq = simpleFooRequest().sum(
+ Arrays.asList(1, 2, 3)).using(response);
+ sumReq.fire(new Receiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertEquals(6, response.intValue());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ public void testPrimitiveListBigDecimalAsParameter() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // Keep these values in sync with SimpleFoo.processBigIntegerList
+ final List<BigDecimal> testList = new ArrayList<BigDecimal>();
+ testList.add(BigDecimal.TEN);
+ testList.add(new BigDecimal("12345.6789") {
+ // This is an anonymous subtype
+ });
+ simpleFooRequest().processBigDecimalList(testList).fire(
+ new Receiver<List<BigDecimal>>() {
+ @Override
+ public void onSuccess(List<BigDecimal> response) {
+ // Check upcasted values only
+ assertEquals(testList, response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPrimitiveListBigIntegerAsParameter() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // Keep these values in sync with SimpleFoo.processBigIntegerList
+ final List<BigInteger> testList = new ArrayList<BigInteger>();
+ testList.add(BigInteger.TEN);
+ testList.add(new BigInteger("12345") {
+ // This is an anonymous subtype
+ });
+ simpleFooRequest().processBigIntegerList(testList).fire(
+ new Receiver<List<BigInteger>>() {
+ @Override
+ public void onSuccess(List<BigInteger> response) {
+ // Check upcasted values only
+ assertEquals(testList, response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPrimitiveListBooleanAsParameter() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ Request<Boolean> procReq = simpleFooRequest().processBooleanList(
+ Arrays.asList(true, false));
+
+ procReq.fire(new Receiver<Boolean>() {
+ @Override
+ public void onSuccess(Boolean response) {
+ assertEquals(true, (boolean) response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ @SuppressWarnings("deprecation")
+ public void testPrimitiveListDateAsParameter() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ // Keep these values in sync with SimpleFoo.processDateList
+ Date date = new Date(90, 0, 1);
+ java.sql.Date sqlDate = new java.sql.Date(90, 0, 2);
+ Time sqlTime = new Time(1, 2, 3);
+ Timestamp sqlTimestamp = new Timestamp(12345L);
+ final List<Date> testList = Arrays.asList(date, sqlDate, sqlTime,
+ sqlTimestamp);
+ simpleFooRequest().processDateList(testList).fire(
+ new Receiver<List<Date>>() {
+ @Override
+ public void onSuccess(List<Date> response) {
+ // Check upcasted values only
+ assertEquals(testList.size(), response.size());
+ Iterator<Date> expected = testList.iterator();
+ Iterator<Date> actual = response.iterator();
+ while (expected.hasNext()) {
+ assertEquals(expected.next().getTime(), actual.next().getTime());
+ }
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPrimitiveListEnumAsParameter() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ Request<SimpleEnum> procReq = simpleFooRequest().processEnumList(
+ Arrays.asList(SimpleEnum.BAR));
+
+ procReq.fire(new Receiver<SimpleEnum>() {
+ @Override
+ public void onSuccess(SimpleEnum response) {
+ assertEquals(SimpleEnum.BAR, response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPrimitiveParameter() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().add(3, 5).fire(new Receiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertTrue(8 == response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPrimitiveSet() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final Request<Set<Integer>> fooReq = simpleFooRequest().getNumberSet();
+ fooReq.fire(new Receiver<Set<Integer>>() {
+ @Override
+ public void onSuccess(Set<Integer> response) {
+ assertEquals(3, response.size());
+ assertTrue(response.contains(1));
+ assertTrue(response.contains(2));
+ assertTrue(response.contains(3));
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testPrimitiveString() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final String testString = "test\"string\'with\nstring\u2060characters\t";
+ final Request<String> fooReq = simpleFooRequest().processString(testString);
+ fooReq.fire(new Receiver<String>() {
+ @Override
+ public void onSuccess(String response) {
+ assertEquals(testString, response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testProxyList() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final Request<SimpleFooProxy> fooReq = simpleFooRequest().findSimpleFooById(
+ 999L).with("oneToManyField");
+ fooReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(2, response.getOneToManyField().size());
+
+ // Check lists of proxies returned from a mutable object are mutable
+ response = simpleFooRequest().edit(response);
+ response.getOneToManyField().get(0).setUserName("canMutate");
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testProxyListAsParameter() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final Request<SimpleFooProxy> fooReq = simpleFooRequest().findSimpleFooById(
+ 999L).with("selfOneToManyField");
+ fooReq.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ final SimpleFooProxy fooProxy = checkSerialization(response);
+ final Request<String> procReq = simpleFooRequest().processList(
+ fooProxy.getSelfOneToManyField()).using(fooProxy);
+ procReq.fire(new Receiver<String>() {
+ @Override
+ public void onSuccess(String response) {
+ assertEquals(fooProxy.getUserName(), response);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ public void testProxysAsInstanceMethodParams() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().findSimpleFooById(999L).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+ Request<String> helloReq = context.hello(bar).using(response);
+ bar = context.edit(bar);
+ bar.setUserName("BAR");
+ helloReq.fire(new Receiver<String>() {
+ @Override
+ public void onSuccess(String response) {
+ assertEquals("Greetings BAR from GWT", response);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ public void testServerFailureCheckedException() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> persistRequest = context.persistAndReturnSelf().using(
+ newFoo);
+ final SimpleFooProxy mutableFoo = context.edit(newFoo);
+ // 43 is the crash causing magic number for a checked exception
+ mutableFoo.setPleaseCrash(43);
+ persistRequest.fire(new FooReciever(mutableFoo, persistRequest, null));
+ }
+
+ public void testServerFailureRuntimeException() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> persistRequest = context.persistAndReturnSelf().using(
+ newFoo);
+ final SimpleFooProxy mutableFoo = context.edit(newFoo);
+ // 42 is the crash causing magic number for a runtime exception
+ mutableFoo.setPleaseCrash(42);
+ persistRequest.fire(new FooReciever(mutableFoo, persistRequest, null));
+ }
+
+ /**
+ * Tests the behaviors of setters and their effects on getters.
+ */
+ public void testSetters() {
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+ SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+
+ // Assert that uninitalize references are null
+ assertNull(foo.getBarField());
+
+ // Assert that objects are mutable after creation
+ foo.setBarField(bar);
+
+ assertSame(foo, context.edit(foo));
+ foo.setBarField(bar);
+
+ // Assert that the set value is retained
+ SimpleBarProxy returnedBarField = foo.getBarField();
+ assertNotNull(returnedBarField);
+ assertEquals(bar.stableId(), returnedBarField.stableId());
+ assertEquals(returnedBarField, foo.getBarField());
+ assertSame(returnedBarField, foo.getBarField());
+
+ // Getters called on mutable objects are also mutable
+ returnedBarField.setUserName("userName");
+ assertEquals("userName", returnedBarField.getUserName());
+ }
+
+ /**
+ * There's plenty of special-case code for Collection properties, so they need
+ * to be tested as well.
+ */
+ public void testSettersWithCollections() {
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+ SimpleBarProxy bar = context.create(SimpleBarProxy.class);
+ List<SimpleBarProxy> originalList = Collections.singletonList(bar);
+
+ // Assert that uninitalize references are null
+ assertNull(foo.getOneToManyField());
+
+ // Assert that objects are mutable after creation
+ foo.setOneToManyField(null);
+
+ assertSame(foo, context.edit(foo));
+ foo.setOneToManyField(originalList);
+ // There's a "dummy" create case here; AbstractRequest, DVS is untestable
+
+ // Quick sanity check on the behavior
+ List<SimpleBarProxy> list = foo.getOneToManyField();
+ assertNotSame(originalList, list);
+ assertEquals(originalList, list);
+ assertEquals(1, list.size());
+ assertEquals(bar.stableId(), list.get(0).stableId());
+ assertEquals(list, foo.getOneToManyField());
+
+ // Assert that entities returned from editable list are mutable
+ list.get(0).setUserName("userName");
+ }
+
+ public void testSettersWithMutableObject() {
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+ foo = context.edit(foo);
+
+ SimpleBarProxy immutableBar = context.create(SimpleBarProxy.class);
+ SimpleBarProxy mutableBar = context.edit(immutableBar);
+ mutableBar.setUserName("userName");
+ foo.setBarField(mutableBar);
+
+ // Creating a new editable object in the same request should read through
+ context.edit(immutableBar).setUserName("Reset");
+ assertEquals("Reset", foo.getBarField().getUserName());
+ }
+
+ public void testStableId() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest context = simpleFooRequest();
+ final SimpleFooProxy foo = context.create(SimpleFooProxy.class);
+ final Object futureId = foo.getId();
+ assertTrue(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+ Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(foo);
+
+ final SimpleFooProxy newFoo = context.edit(foo);
+ assertEquals(futureId, foo.getId());
+ assertTrue(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+ assertEquals(futureId, newFoo.getId());
+ assertTrue(((SimpleEntityProxyId<?>) newFoo.stableId()).isEphemeral());
+
+ newFoo.setUserName("GWT basic user");
+ fooReq.fire(new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ final SimpleFooProxy returned = checkSerialization(response);
+ assertEquals(futureId, foo.getId());
+ assertFalse(((SimpleEntityProxyId<?>) foo.stableId()).isEphemeral());
+ assertEquals(futureId, newFoo.getId());
+ assertFalse(((SimpleEntityProxyId<?>) newFoo.stableId()).isEphemeral());
+
+ assertFalse(((SimpleEntityProxyId<?>) returned.stableId()).isEphemeral());
+
+ checkStableIdEquals(foo, returned);
+ checkStableIdEquals(newFoo, returned);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> editRequest = context.persistAndReturnSelf().using(
+ returned);
+ final SimpleFooProxy editableFoo = context.edit(returned);
+ editableFoo.setUserName("GWT power user");
+ editRequest.fire(new Receiver<SimpleFooProxy>() {
+
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(response.getId(), returned.getId());
+ assertEquals("GWT power user", response.getUserName());
+ checkStableIdEquals(editableFoo, response);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Test that a proxy only referenced via a parameterization is available.
+ */
+ public void testOnlyUsedInList() {
+ OnlyUsedInListProxy proxy = simpleFooRequest().create(
+ OnlyUsedInListProxy.class);
+ assertNotNull(proxy);
+ }
+
+ /**
+ * Check if a graph of unpersisted objects can be echoed.
+ */
+ public void testUnpersistedEchoComplexGraph() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleFooProxy.class, handler);
+ SimpleFooRequest context = req.simpleFooRequest();
+ final SimpleBarProxy simpleBar = context.create(SimpleBarProxy.class);
+ simpleBar.setUnpersisted(true);
+ final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
+ simpleFoo.setUnpersisted(true);
+ simpleFoo.setBarField(simpleBar);
+ context.echoComplex(simpleFoo, simpleBar).with("barField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ // The reconstituted object may not have the same stable id
+ checkStableIdEquals(simpleBar, response.getBarField());
+ response = checkSerialization(response);
+ assertEquals(0, handler.totalEventCount);
+ checkStableIdEquals(simpleFoo, response);
+ SimpleBarProxy responseBar = response.getBarField();
+ assertNotNull(responseBar);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Check if an unpersisted object can be echoed.
+ */
+ public void testUnpersistedEchoObject() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleFooProxy.class, handler);
+ SimpleFooRequest context = req.simpleFooRequest();
+ final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
+ simpleFoo.setUnpersisted(true);
+ context.echo(simpleFoo).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(0, handler.totalEventCount);
+ checkStableIdEquals(simpleFoo, response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Return an unpersisted object from a service method and echo it.
+ */
+ public void testUnpersistedObjectFetch() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ req.simpleFooRequest().getUnpersistedInstance().fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ final SimpleFooProxy created = checkSerialization(response);
+ assertNotNull(created);
+ assertTrue(created.getUnpersisted());
+ req.simpleFooRequest().echo(created).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertNotNull(response);
+ assertEquals(created.stableId(), response.stableId());
+ assertTrue(response.getUnpersisted());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * This is analagous to FindServiceTest.testFetchDeletedEntity() only we're
+ * trying to invoke a method on the deleted entity using a stale EntityProxy
+ * reference on the client.
+ */
+ public void testUseOfDeletedEntity() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleBarRequest context = simpleBarRequest();
+ SimpleBarProxy willDelete = context.create(SimpleBarProxy.class);
+ willDelete.setUserName("A");
+
+ // Persist the newly-created object
+ context.persistAndReturnSelf().using(willDelete).fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ response = checkSerialization(response);
+ assertEquals("A", response.getUserName());
+ // Mark the object as deleted
+ SimpleBarRequest context = simpleBarRequest();
+ response = context.edit(response);
+ response.setFindFails(true);
+ response.setUserName("B");
+ context.persistAndReturnSelf().using(response).fire(
+ new Receiver<SimpleBarProxy>() {
+
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ response = checkSerialization(response);
+ // The last-known state should be returned
+ assertNotNull(response);
+ assertEquals("B", response.getUserName());
+
+ SimpleBarRequest context = simpleBarRequest();
+ // Ensure attempts to mutate deleted objects don't blow up
+ response = context.edit(response);
+ response.setUserName("C");
+
+ // Attempting to use the now-deleted object should fail
+ context.persistAndReturnSelf().using(response).fire(
+ new Receiver<SimpleBarProxy>() {
+ @Override
+ public void onFailure(ServerFailure error) {
+ assertTrue(error.getMessage().contains(
+ "The requested entity is not available on"
+ + " the server"));
+ finishTestAndReset();
+ }
+
+ @Override
+ public void onSuccess(SimpleBarProxy response) {
+ response = checkSerialization(response);
+ fail();
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ public void testValueMethodInvocation() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleValueContext ctx = req.simpleValueContext();
+ SimpleValueProxy p = ctx.create(SimpleValueProxy.class);
+ p.setString("Hello World!");
+ ctx.getString().using(p).fire(new Receiver<String>() {
+ @Override
+ public void onSuccess(String response) {
+ assertEquals("Hello World!", response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testValueObjectCreateSetRetrieveUpdate() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest req = simpleFooRequest();
+ req.findSimpleFooById(1L).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ SimpleFooRequest req = simpleFooRequest();
+
+ // Create
+ final SimpleValueProxy created = req.create(SimpleValueProxy.class);
+ created.setNumber(42);
+ created.setString("Hello world!");
+ created.setSimpleFoo(response);
+ // Test cycles in value
+ created.setSimpleValue(Arrays.asList(created));
+
+ // Set
+ response = req.edit(response);
+ response.setSimpleValue(created);
+
+ // Retrieve
+ req.persistAndReturnSelf().using(response).with(
+ "simpleValue.simpleFoo", "simpleValue.simpleValue").to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ SimpleValueProxy value = response.getSimpleValue();
+ assertEquals(42, value.getNumber());
+ assertEquals("Hello world!", value.getString());
+ assertSame(response, value.getSimpleFoo());
+ assertSame(value, value.getSimpleValue().get(0));
+
+ try {
+ // Require owning object to be editable
+ response.getSimpleValue().setNumber(43);
+ fail("Should have thrown exception");
+ } catch (IllegalStateException expected) {
+ }
+
+ // Update
+ SimpleFooRequest req = simpleFooRequest();
+ response = req.edit(response);
+ response.getSimpleValue().setNumber(43);
+ req.persistAndReturnSelf().using(response).with("simpleValue").to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(43, response.getSimpleValue().getNumber());
+ finishTestAndReset();
+ }
+ }).fire();
+ }
+ }).fire();
+ }
+ });
+ }
+
+ public void testValueObjectCreateSetRetrieveUpdateViaList() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest req = simpleFooRequest();
+ req.findSimpleFooById(1L).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ SimpleFooRequest req = simpleFooRequest();
+
+ // Create
+ final SimpleValueProxy created = req.create(SimpleValueProxy.class);
+ created.setNumber(42);
+ created.setString("Hello world!");
+ created.setSimpleFoo(response);
+
+ // Set
+ response = req.edit(response);
+ response.setSimpleValues(Arrays.asList(created));
+
+ // Retrieve
+ req.persistAndReturnSelf().using(response).with("simpleValues").to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ SimpleValueProxy value = response.getSimpleValues().get(0);
+ assertEquals(42, value.getNumber());
+
+ try {
+ // Require owning object to be editable
+ response.getSimpleValues().get(0).setNumber(43);
+ fail("Should have thrown exception");
+ } catch (IllegalStateException expected) {
+ }
+
+ // Update
+ SimpleFooRequest req = simpleFooRequest();
+ response = req.edit(response);
+ response.getSimpleValues().get(0).setNumber(43);
+ req.persistAndReturnSelf().using(response).with("simpleValues").to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ response = checkSerialization(response);
+ assertEquals(43,
+ response.getSimpleValues().get(0).getNumber());
+ finishTestAndReset();
+ }
+ }).fire();
+ }
+ }).fire();
+ }
+ });
+ }
+
+ public void testValueObjectEquality() {
+ SimpleFooRequest req = simpleFooRequest();
+ SimpleValueProxy a = req.create(SimpleValueProxy.class);
+ SimpleValueProxy b = req.create(SimpleValueProxy.class);
+ checkEqualityAndHashcode(a, b);
+
+ a.setString("Hello");
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+
+ b.setString("Hello");
+ checkEqualityAndHashcode(a, b);
+
+ a.setSimpleValue(Collections.singletonList(req.create(SimpleValueProxy.class)));
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+ b.setSimpleValue(Collections.singletonList(req.create(SimpleValueProxy.class)));
+ checkEqualityAndHashcode(a, b);
+
+ a.getSimpleValue().get(0).setNumber(55);
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+ b.getSimpleValue().get(0).setNumber(55);
+ checkEqualityAndHashcode(a, b);
+ }
+
+ /**
+ * Since a ValueProxy cannot be passed into RequestContext edit, a proxy
+ * returned from a service method should be mutable by default.
+ */
+ public void testValueObjectReturnedFromRequestIsImmutable() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnValueProxy().fire(
+ new Receiver<SimpleValueProxy>() {
+ @Override
+ public void onSuccess(SimpleValueProxy a) {
+ a = checkSerialization(a);
+ try {
+ a.setNumber(77);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ // Ensure Dates comply with ValueProxy mutation behaviors
+ a.getDate().setTime(1);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ SimpleFooRequest ctx = simpleFooRequest();
+ final SimpleValueProxy toCheck = ctx.edit(a);
+ toCheck.setNumber(77);
+ toCheck.getDate().setTime(1);
+ ctx.returnValueProxy().fire(new Receiver<SimpleValueProxy>() {
+ @Override
+ public void onSuccess(SimpleValueProxy b) {
+ b = checkSerialization(b);
+ b = simpleFooRequest().edit(b);
+ // Now check that same value is equal across contexts
+ b.setNumber(77);
+ b.setDate(new Date(1));
+ checkEqualityAndHashcode(toCheck, b);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ public void testValueObjectViolationsOnCreate() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest req = simpleFooRequest();
+ final SimpleValueProxy value = req.create(SimpleValueProxy.class);
+ value.setShouldBeNull("Hello world");
+
+ SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+ foo.setSimpleValue(value);
+ req.echo(foo).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ fail();
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ assertEquals(1, errors.size());
+ Violation v = errors.iterator().next();
+ assertEquals(value, v.getInvalidProxy());
+ assertNull(v.getOriginalProxy());
+ assertEquals("shouldBeNull", v.getPath());
+ assertNull(v.getProxyId());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testValueObjectViolationsOnEdit() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnValueProxy().fire(
+ new Receiver<SimpleValueProxy>() {
+ @Override
+ public void onSuccess(SimpleValueProxy response) {
+ final SimpleValueProxy original = checkSerialization(response);
+ SimpleFooRequest req = simpleFooRequest();
+ final SimpleValueProxy value = req.edit(response);
+ value.setShouldBeNull("Hello world");
+ SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+ foo.setSimpleValue(value);
+ req.echo(foo).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ fail();
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ assertEquals(1, errors.size());
+ Violation v = errors.iterator().next();
+ assertEquals(value, v.getInvalidProxy());
+ assertEquals(original, v.getOriginalProxy());
+ assertEquals("shouldBeNull", v.getPath());
+ assertNull(v.getProxyId());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ public void testViolationAbsent() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+ final Request<Void> fooReq = context.persist().using(newFoo);
+
+ newFoo = context.edit(newFoo);
+ newFoo.setUserName("Amit"); // will not cause violation.
+
+ fooReq.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void ignore) {
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testViolationsOnCreate() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> create = context.persistAndReturnSelf().using(
+ newFoo);
+ new FailFixAndRefire<SimpleFooProxy>(newFoo, context, create).doTest();
+ }
+
+ public void testViolationsOnCreateVoidReturn() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy newFoo = context.create(SimpleFooProxy.class);
+ final Request<Void> create = context.persist().using(newFoo);
+ new FailFixAndRefire<Void>(newFoo, context, create).doVoidTest();
+ }
+
+ public void testViolationsOnEdit() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ fooCreationRequest().fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy returned) {
+ returned = checkSerialization(returned);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<SimpleFooProxy> editRequest = context.persistAndReturnSelf().using(
+ returned);
+ new FailFixAndRefire<SimpleFooProxy>(returned, context, editRequest).doTest();
+ }
+ });
+ }
+
+ public void testViolationsOnEditVoidReturn() {
+ delayTestFinish(DELAY_TEST_FINISH);
+
+ fooCreationRequest().fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy returned) {
+ returned = checkSerialization(returned);
+ SimpleFooRequest context = simpleFooRequest();
+ Request<Void> editRequest = context.persist().using(returned);
+ new FailFixAndRefire<Void>(returned, context, editRequest).doVoidTest();
+ }
+ });
+ }
+
+ protected SimpleBarRequest simpleBarRequest() {
+ return req.simpleBarRequest();
+ }
+
+ protected SimpleFooRequest simpleFooRequest() {
+ return req.simpleFooRequest();
+ }
+
+ private void assertCannotFire(final Request<Long> mutateRequest) {
+ try {
+ mutateRequest.fire(new Receiver<Long>() {
+ @Override
+ public void onSuccess(Long response) {
+ fail("Should not be called");
+ }
+ });
+ fail("Expected IllegalStateException");
+ } catch (IllegalStateException e) {
+ /* cannot reuse a successful request, mores the pity */
+ }
+ }
+
+ private Request<SimpleFooProxy> fooCreationRequest() {
+ SimpleFooRequest context = simpleFooRequest();
+ SimpleFooProxy originalFoo = context.create(SimpleFooProxy.class);
+ final Request<SimpleFooProxy> fooReq = context.persistAndReturnSelf().using(
+ originalFoo);
+ originalFoo = context.edit(originalFoo);
+ originalFoo.setUserName("GWT User");
+ return fooReq;
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java
new file mode 100644
index 0000000..cac062b
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.autobean.shared.AutoBean;
+import com.google.web.bindery.autobean.shared.AutoBeanUtils;
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.DefaultProxyStore;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
+import com.google.web.bindery.requestfactory.shared.ProxySerializer;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.web.bindery.requestfactory.shared.impl.Constants;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
+
+/**
+ * A base class for anything that makes use of the SimpleRequestFactory.
+ * Subclasses must always use {@link #finishTestAndReset()} in order to allow
+ * calls to the reset methods to complete before the next test starts.
+ *
+ */
+public abstract class RequestFactoryTestBase extends GWTTestCase {
+
+ /**
+ * Class for counting events.
+ */
+ protected class SimpleFooEventHandler<P extends EntityProxy> implements
+ EntityProxyChange.Handler<P> {
+ int persistEventCount = 0;
+ int deleteEventCount = 0;
+ int totalEventCount = 0;
+ int updateEventCount = 0;
+
+ public void onProxyChange(EntityProxyChange<P> event) {
+ totalEventCount++;
+ switch (event.getWriteOperation()) {
+ case PERSIST:
+ persistEventCount++;
+ break;
+ case DELETE:
+ deleteEventCount++;
+ break;
+ case UPDATE:
+ updateEventCount++;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ protected EventBus eventBus;
+ protected SimpleRequestFactory req;
+
+ @Override
+ public void gwtSetUp() {
+ req = createFactory();
+ eventBus = req.getEventBus();
+ }
+
+ protected void checkEqualityAndHashcode(Object a, Object b) {
+ assertNotNull(a);
+ assertNotNull(b);
+ assertEquals(a.hashCode(), b.hashCode());
+ assertEquals(a, b);
+ assertEquals(b, a);
+ }
+
+ /**
+ * Run the given proxy through a ProxySerializer and verify that the
+ * before-and-after values match.
+ */
+ protected <T extends BaseProxy> T checkSerialization(T proxy) {
+ AutoBean<T> originalBean = AutoBeanUtils.getAutoBean(proxy);
+ SimpleProxyId<T> id = BaseProxyCategory.stableId(originalBean);
+ DefaultProxyStore store = new DefaultProxyStore();
+ ProxySerializer s = req.getSerializer(store);
+
+ String key = s.serialize(proxy);
+ assertNotNull(key);
+
+ // Use a new instance
+ store = new DefaultProxyStore(store.encode());
+ s = req.getSerializer(store);
+ T restored = s.deserialize(id.getProxyClass(), key);
+ AutoBean<? extends BaseProxy> restoredBean = AutoBeanUtils.getAutoBean(restored);
+ assertNotSame(proxy, restored);
+ /*
+ * Performing a regular assertEquals() or even an AutoBeanUtils.diff() here
+ * is wrong. If any of the objects in the graph are unpersisted, it's
+ * expected that the stable ids would change. Instead, we do a value-based
+ * check.
+ */
+ assertTrue(AutoBeanUtils.deepEquals(originalBean, restoredBean));
+
+ if (proxy instanceof EntityProxy && !id.isEphemeral()) {
+ assertEquals(((EntityProxy) proxy).stableId(),
+ ((EntityProxy) restored).stableId());
+ }
+
+ // In deference to testing stable ids, copy the original id into the clone
+ restoredBean.setTag(Constants.STABLE_ID,
+ originalBean.getTag(Constants.STABLE_ID));
+ return restored;
+ }
+
+ protected void checkStableIdEquals(EntityProxy expected, EntityProxy actual) {
+ assertEquals(expected.stableId(), actual.stableId());
+ assertEquals(expected.stableId().hashCode(), actual.stableId().hashCode());
+ assertSame(expected.stableId(), actual.stableId());
+
+ // No assumptions about the proxy objects (being proxies and all)
+ assertNotSame(expected, actual);
+ assertFalse(expected.equals(actual));
+ }
+
+ /**
+ * Create and initialize a new {@link SimpleRequestFactory}.
+ */
+ protected SimpleRequestFactory createFactory() {
+ SimpleRequestFactory toReturn = GWT.create(SimpleRequestFactory.class);
+ toReturn.initialize(new SimpleEventBus());
+ return toReturn;
+ }
+
+ protected void finishTestAndReset() {
+ final boolean[] reallyDone = {false, false};
+ req.simpleFooRequest().reset().fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ reallyDone[0] = true;
+ if (reallyDone[0] && reallyDone[1]) {
+ finishTest();
+ }
+ }
+ });
+ req.simpleBarRequest().reset().fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ reallyDone[1] = true;
+ if (reallyDone[0] && reallyDone[1]) {
+ finishTest();
+ }
+ }
+ });
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryUnicodeEscapingTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryUnicodeEscapingTest.java
new file mode 100644
index 0000000..3a334b2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryUnicodeEscapingTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.gwt.user.client.rpc.UnicodeEscapingService.InvalidCharacterException;
+import com.google.gwt.user.client.rpc.UnicodeEscapingTest;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Runs through a portion of the Basic Multilingual Plane.
+ */
+public class RequestFactoryUnicodeEscapingTest extends RequestFactoryTestBase {
+ private static final int TEST_FINISH_DELAY_MS = 5000;
+ private final UnicodeEscapingTest test = new UnicodeEscapingTest() {
+
+ @Override
+ protected void clientToServerVerifyRange(int start, final int end,
+ final int size, final int step) throws InvalidCharacterException {
+ current = start;
+ int blockEnd = Math.min(end, current + size);
+ req.unicodeTestRequest().verifyStringContainingCharacterRange(current,
+ blockEnd, getStringContainingCharacterRange(start, blockEnd)).fire(
+ new Receiver<Void>() {
+ List<ServerFailure> fails = new ArrayList<ServerFailure>();
+
+ @Override
+ public void onFailure(ServerFailure error) {
+ fails.add(error);
+ onSuccess(null);
+ }
+
+ @Override
+ public void onSuccess(Void response) {
+ current += step;
+ if (current < end) {
+ delayTestFinish(TEST_FINISH_DELAY_MS);
+ int blockEnd = Math.min(end, current + size);
+ req.unicodeTestRequest().verifyStringContainingCharacterRange(
+ current, blockEnd,
+ getStringContainingCharacterRange(current, blockEnd)).fire(
+ this);
+ } else if (!fails.isEmpty()) {
+ StringBuilder msg = new StringBuilder();
+ for (ServerFailure error : fails) {
+ msg.append(error.getMessage()).append("\n");
+ }
+ throw new RuntimeException(msg.toString());
+ } else {
+ finishTest();
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void serverToClientVerify(int start, final int end,
+ final int size, final int step) {
+ current = start;
+ req.unicodeTestRequest().getStringContainingCharacterRange(start,
+ Math.min(end, current + size)).fire(new Receiver<String>() {
+ List<ServerFailure> fails = new ArrayList<ServerFailure>();
+
+ @Override
+ public void onFailure(ServerFailure error) {
+ fails.add(error);
+ nextBatch();
+ }
+
+ @Override
+ public void onSuccess(String response) {
+ try {
+ verifyStringContainingCharacterRange(current,
+ Math.min(end, current + size), response);
+ } catch (InvalidCharacterException e) {
+ fails.add(new ServerFailure(e.getMessage()));
+ }
+ nextBatch();
+ }
+
+ private void nextBatch() {
+ current += step;
+ if (current < end) {
+ delayTestFinish(TEST_FINISH_DELAY_MS);
+ req.unicodeTestRequest().getStringContainingCharacterRange(current,
+ Math.min(end, current + size)).fire(this);
+ } else if (!fails.isEmpty()) {
+ StringBuilder msg = new StringBuilder();
+ for (ServerFailure t : fails) {
+ msg.append(t.getMessage()).append("\n");
+ }
+ throw new RuntimeException(msg.toString());
+ } else {
+ finishTest();
+ }
+ }
+ });
+ }
+ };
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ public void testClientToServerBMPHigh() throws InvalidCharacterException {
+ test.testClientToServerBMPHigh();
+ }
+
+ public void testClientToServerBMPLow() throws InvalidCharacterException {
+ test.testClientToServerBMPLow();
+ }
+
+ public void testClientToServerNonBMP() throws InvalidCharacterException {
+ test.testClientToServerNonBMP();
+ }
+
+ public void testServerToClientBMP() {
+ test.testServerToClientBMP();
+ }
+
+ public void testServerToClientNonBMP() {
+ test.testServerToClientNonBMP();
+ }
+
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/SimpleRequestFactoryInstance.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/SimpleRequestFactoryInstance.java
new file mode 100644
index 0000000..da4c61e
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/SimpleRequestFactoryInstance.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * Easy access for RequestFactory tests.
+ */
+public class SimpleRequestFactoryInstance {
+ private static SimpleRequestFactory factory;
+
+ public static SimpleRequestFactory factory() {
+ if (factory == null) {
+ factory = GWT.create(SimpleRequestFactory.class);
+ factory.initialize(new SimpleEventBus());
+ }
+
+ return factory;
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/ui/EditorTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/ui/EditorTest.java
new file mode 100644
index 0000000..66b8cc2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/ui/EditorTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.HasEditorDelegate;
+import com.google.gwt.editor.client.HasEditorErrors;
+import com.google.gwt.editor.client.adapters.EditorSource;
+import com.google.gwt.editor.client.adapters.ListEditor;
+import com.google.gwt.editor.client.adapters.SimpleEditor;
+import com.google.web.bindery.requestfactory.gwt.client.HasRequestContext;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryTestBase;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
+import com.google.web.bindery.requestfactory.shared.Violation;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Integration test of the Editor framework. Only tests for
+ * RequestFactory-specific features belong here; all other tests should use the
+ * SimpleBeanEditorDriver to make the tests simpler.
+ */
+public class EditorTest extends RequestFactoryTestBase {
+ /*
+ * DO NOT USE finishTest(). Instead, call finishTestAndReset();
+ */
+
+ static class SimpleBarEditor implements Editor<SimpleBarProxy>,
+ HasRequestContext<SimpleBarProxy> {
+ protected final SimpleEditor<String> userName = SimpleEditor.of();
+ RequestContext ctx;
+
+ public void setRequestContext(RequestContext ctx) {
+ this.ctx = ctx;
+ }
+ }
+
+ static class SimpleFooBarNameOnlyEditor implements Editor<SimpleFooProxy> {
+ /**
+ * Test nested path access.
+ */
+ @Path("barField.userName")
+ final SimpleEditor<String> barName = SimpleEditor.of();
+ }
+
+ interface SimpleFooDriver extends
+ RequestFactoryEditorDriver<SimpleFooProxy, SimpleFooEditor> {
+ }
+
+ static class SimpleFooEditor implements HasEditorErrors<SimpleFooProxy> {
+ /**
+ * Test field-based access.
+ */
+ final SimpleEditor<String> userName = SimpleEditor.of();
+
+ /**
+ * Test nested path access.
+ */
+ @Path("barField.userName")
+ final SimpleEditor<String> barName = SimpleEditor.of();
+
+ final ListEditor<SimpleFooProxy, SimpleFooBarNameOnlyEditor> selfOneToManyField = ListEditor.of(new EditorSource<SimpleFooBarNameOnlyEditor>() {
+ @Override
+ public SimpleFooBarNameOnlyEditor create(int index) {
+ return new SimpleFooBarNameOnlyEditor();
+ }
+ });
+
+ private final SimpleBarEditor barEditor = new SimpleBarEditor();
+
+ List<EditorError> errors;
+
+ public void showErrors(List<EditorError> errors) {
+ this.errors = errors;
+ }
+
+ /**
+ * Test method-based access with path override.
+ */
+ @Path("barField")
+ SimpleBarEditor barEditor() {
+ return barEditor;
+ }
+ }
+
+ static class SimpleFooEditorWithDelegate extends SimpleFooEditor implements
+ HasEditorDelegate<SimpleFooProxy> {
+ EditorDelegate<SimpleFooProxy> delegate;
+
+ public void setDelegate(EditorDelegate<SimpleFooProxy> delegate) {
+ this.delegate = delegate;
+ }
+ }
+
+ private static final int TEST_TIMEOUT = 5000;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ public void test() {
+ delayTestFinish(TEST_TIMEOUT);
+ final SimpleFooEditor editor = new SimpleFooEditor();
+
+ final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+ driver.initialize(req, editor);
+ final String[] paths = driver.getPaths();
+ assertEquals(Arrays.asList("barField", "selfOneToManyField",
+ "selfOneToManyField.barField"), Arrays.asList(paths));
+
+ req.simpleFooRequest().findSimpleFooById(1L).with(paths).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+
+ SimpleFooRequest context = req.simpleFooRequest();
+ driver.edit(response, context);
+ assertSame(context, editor.barEditor().ctx);
+ context.persistAndReturnSelf().using(response).with(paths).to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals("EditorFooTest", response.getUserName());
+ assertEquals("EditorBarTest",
+ response.getBarField().getUserName());
+ finishTestAndReset();
+ }
+ });
+ assertEquals("GWT", editor.userName.getValue());
+ assertEquals("FOO", editor.barEditor().userName.getValue());
+ assertEquals("FOO", editor.barName.getValue());
+ editor.userName.setValue("EditorFooTest");
+ // When there are duplicate paths, last declared editor wins
+ editor.barEditor().userName.setValue("EditorBarTest");
+ editor.barName.setValue("ignored 1");
+ editor.selfOneToManyField.getEditors().get(0).barName.setValue(
+ "ignored 2");
+ driver.flush().fire();
+ }
+ });
+ }
+
+ public void testNoSubscription() {
+ final SimpleFooEditorWithDelegate editor = new SimpleFooEditorWithDelegate();
+
+ final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+ driver.initialize(req, editor);
+
+ /*
+ * Confirm that it's always safe to call subscribe. The editor's delegate
+ * isn't set until edit is called, so edit nothing.
+ */
+ driver.edit(null, null);
+ assertNull(editor.delegate.subscribe());
+ }
+
+ /**
+ * Tests the editor can be re-used while the initial context is locked and
+ * therefore its attached proxies are frozen..
+ *
+ * @see http://code.google.com/p/google-web-toolkit/issues/detail?id=5752
+ */
+ public void testReuse() {
+ delayTestFinish(TEST_TIMEOUT);
+ final SimpleFooEditor editor = new SimpleFooEditor();
+
+ final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+ driver.initialize(req, editor);
+
+ req.simpleFooRequest().findSimpleFooById(1L).with(driver.getPaths()).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+
+ SimpleFooRequest context = req.simpleFooRequest();
+ driver.edit(response, context);
+ editor.userName.setValue("One");
+ context.persistAndReturnSelf().using(response).with(
+ driver.getPaths()).to(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals("One", response.getUserName());
+ // just testing that it doesn't throw (see issue 5752)
+ driver.edit(response, req.simpleFooRequest());
+ editor.userName.setValue("Two");
+ driver.flush();
+ finishTestAndReset();
+ }
+ });
+ // The fire() will freeze the proxies and lock the context
+ driver.flush().fire();
+ }
+ });
+ }
+
+ public void testSubscription() {
+ delayTestFinish(TEST_TIMEOUT);
+ final SimpleFooEditorWithDelegate editor = new SimpleFooEditorWithDelegate();
+
+ final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+ driver.initialize(req, editor);
+
+ String[] paths = driver.getPaths();
+ assertEquals(Arrays.asList("barField", "selfOneToManyField",
+ "selfOneToManyField.barField"), Arrays.asList(paths));
+
+ req.simpleFooRequest().findSimpleFooById(1L).with(paths).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ // Set up driver in read-only mode
+ driver.edit(response, null);
+ assertNotNull(editor.delegate.subscribe());
+
+ // Simulate edits occurring elsewhere in the module
+ SimpleFooRequest context = req.simpleFooRequest();
+ Request<SimpleFooProxy> request = context.persistAndReturnSelf().using(
+ response);
+ SimpleBarProxy newBar = context.create(SimpleBarProxy.class);
+ newBar = context.edit(newBar);
+ newBar.setUserName("newBar");
+ response = context.edit(response);
+ response.setBarField(newBar);
+ response.setUserName("updated");
+
+ request.fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ // EventBus notifications occur after the onSuccess()
+ Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
+ public boolean execute() {
+ if ("updated".equals(editor.userName.getValue())) {
+ assertEquals("updated", editor.userName.getValue());
+ assertEquals("newBar",
+ editor.barEditor().userName.getValue());
+ finishTestAndReset();
+ return false;
+ }
+ return true;
+ }
+ }, 50);
+ }
+ });
+ }
+ });
+ }
+
+ public void testViolations() {
+ delayTestFinish(TEST_TIMEOUT);
+ final SimpleFooEditor editor = new SimpleFooEditor();
+
+ final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+ driver.initialize(req, editor);
+
+ req.simpleFooRequest().findSimpleFooById(1L).with(driver.getPaths()).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+
+ SimpleFooRequest context = req.simpleFooRequest();
+ driver.edit(response, context);
+ context.persistAndReturnSelf().using(response).with(
+ driver.getPaths()).to(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ fail("Expected errors. You may be missing jars, see "
+ + "the comment in RequestFactoryTest.ShouldNotSucceedReceiver.onSuccess");
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ assertEquals(1, errors.size());
+ Violation v = errors.iterator().next();
+
+ driver.setViolations(errors);
+ assertEquals(1, editor.errors.size());
+ EditorError error = editor.errors.get(0);
+ assertEquals("userName", error.getAbsolutePath());
+ assertSame(editor.userName, error.getEditor());
+ assertTrue(error.getMessage().length() > 0);
+ assertEquals("userName", error.getPath());
+ assertSame(v, error.getUserData());
+ assertNull(error.getValue());
+ finishTestAndReset();
+ }
+ });
+ // Set to an illegal value
+ editor.userName.setValue("");
+
+ driver.flush().fire();
+ }
+ });
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModelTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModelTest.java
new file mode 100644
index 0000000..14af62b
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/rebind/model/RequestFactoryModelTest.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.gwt.rebind.model;
+
+import com.google.web.bindery.autobean.shared.Splittable;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.impl.JavaResourceBase;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.UnitTestTreeLogger;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.web.bindery.requestfactory.server.TestContextImpl;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+import junit.framework.TestCase;
+
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+/**
+ * Test case for
+ * {@link com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModel} that
+ * uses mock CompilationStates.
+ */
+public class RequestFactoryModelTest extends TestCase {
+
+ /**
+ * Constructs an empty interface representation of a type.
+ */
+ private static class EmptyMockJavaResource extends MockJavaResource {
+
+ private final StringBuilder code = new StringBuilder();
+
+ public EmptyMockJavaResource(Class<?> clazz) {
+ super(clazz.getName());
+
+ code.append("package ").append(clazz.getPackage().getName()).append(";\n");
+ code.append("public interface ").append(clazz.getSimpleName());
+
+ int numParams = clazz.getTypeParameters().length;
+ if (numParams != 0) {
+ code.append("<");
+ for (int i = 0; i < numParams; i++) {
+ if (i != 0) {
+ code.append(",");
+ }
+ code.append("T").append(i);
+ }
+ code.append(">");
+ }
+
+ code.append("{}\n");
+ }
+
+ @Override
+ protected CharSequence getContent() {
+ return code;
+ }
+ }
+
+ /**
+ * Loads the actual source of a type. This should be used only for types
+ * directly tested by this test. Note that use of this class requires your
+ * source files to be on your classpath.
+ */
+ private static class RealJavaResource extends MockJavaResource {
+
+ public RealJavaResource(Class<?> clazz) {
+ super(clazz.getName());
+ }
+
+ @Override
+ protected CharSequence getContent() {
+ String resourceName = getTypeName().replace('.', '/') + ".java";
+ InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(
+ resourceName);
+ assertNotNull("Could not open " + resourceName, stream);
+ return Util.readStreamAsString(stream);
+ }
+ }
+
+ private static TreeLogger createCompileLogger() {
+ PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(
+ System.err, true));
+ logger.setMaxDetail(TreeLogger.ERROR);
+ return logger;
+ }
+
+ private TreeLogger logger;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ logger = createCompileLogger();
+ }
+
+ public void testBadCollectionType() {
+ testModelWithMethodDecl(
+ "Request<SortedSet<Integer>> badReturnType();",
+ "Requests that return collections may be declared with java.util.List or java.util.Set only");
+ }
+
+ public void testBadCollectionTypeNotParameterized() {
+ testModelWithMethodDecl("Request<SortedSet> badReturnType();",
+ "Requests that return collections of List or Set must be parameterized");
+ }
+
+ public void testBadReturnType() {
+ testModelWithMethodDecl("Request<Iterable> badReturnType();",
+ "Invalid Request parameterization java.lang.Iterable");
+ }
+
+ public void testDuplicateBooleanGetters() {
+ testModelWithMethodDecl("Request<t.ProxyWithRepeatedGetters> method();",
+ "Duplicate accessors for property foo: getFoo() and isFoo()");
+ }
+
+ public void testMissingProxyFor() {
+ testModelWithMethodDeclArgs("Request<TestProxy> okMethodProxy();",
+ TestContextImpl.class.getName(), null,
+ "The t.TestProxy type does not have a @ProxyFor, "
+ + "@ProxyForName, or @JsonRpcProxy annotation");
+ }
+
+ public void testMissingService() {
+ testModelWithMethodDeclArgs("Request<String> okMethod();", null,
+ TestContextImpl.class.getName(),
+ "RequestContext subtype t.TestContext is missing a "
+ + "@Service or @JsonRpcService annotation");
+ }
+
+ public void testModelWithMethodDecl(final String clientMethodDecls,
+ String... expected) {
+ testModelWithMethodDeclArgs(clientMethodDecls,
+ TestContextImpl.class.getName(), TestContextImpl.class.getName(),
+ expected);
+ }
+
+ public void testModelWithMethodDeclArgs(final String clientMethodDecls,
+ final String serviceClass, String proxyClass, String... expected) {
+ Set<Resource> javaResources = getJavaResources(proxyClass);
+ javaResources.add(new MockJavaResource("t.TestRequestFactory") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package t;\n");
+ code.append("import " + RequestFactory.class.getName() + ";\n");
+ code.append("interface TestRequestFactory extends RequestFactory {\n");
+ code.append("TestContext testContext();");
+ code.append("}");
+ return code;
+ }
+ });
+ javaResources.add(new MockJavaResource("t.TestContext") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package t;\n");
+ code.append("import " + Request.class.getName() + ";\n");
+ code.append("import " + InstanceRequest.class.getName() + ";\n");
+
+ code.append("import " + RequestContext.class.getName() + ";\n");
+ code.append("import " + SortedSet.class.getName() + ";\n");
+ code.append("import " + List.class.getName() + ";\n");
+ code.append("import " + Set.class.getName() + ";\n");
+ code.append("import " + Service.class.getName() + ";\n");
+ code.append("import " + TestContextImpl.class.getName() + ";\n");
+
+ if (serviceClass != null) {
+ code.append("@Service(" + serviceClass + ".class)");
+ }
+ code.append("interface TestContext extends RequestContext {\n");
+ code.append(clientMethodDecls);
+ code.append("}");
+ return code;
+ }
+ });
+
+ CompilationState state = CompilationStateBuilder.buildFrom(logger,
+ javaResources);
+
+ UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
+ builder.setLowestLogLevel(TreeLogger.ERROR);
+ for (String expectedMsg : expected) {
+ builder.expectError(expectedMsg, null);
+ }
+ builder.expectError(RequestFactoryModel.poisonedMessage(), null);
+ UnitTestTreeLogger testLogger = builder.createLogger();
+ try {
+ new RequestFactoryModel(testLogger, state.getTypeOracle().findType(
+ "t.TestRequestFactory"));
+ fail("Should have complained");
+ } catch (UnableToCompleteException e) {
+ }
+ testLogger.assertCorrectLogEntries();
+ }
+
+ private Set<Resource> getJavaResources(final String proxyClass) {
+ MockJavaResource[] javaFiles = {new MockJavaResource("t.AddressProxy") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package t;\n");
+ code.append("import " + ProxyFor.class.getName() + ";\n");
+ code.append("import " + EntityProxy.class.getName() + ";\n");
+ if (proxyClass != null) {
+ code.append("@ProxyFor(" + proxyClass + ".class)");
+ }
+ code.append("interface TestProxy extends EntityProxy {\n");
+ code.append("}");
+ System.out.println(code);
+ return code;
+ }
+ }, new MockJavaResource("t.ProxyWithRepeatedGetters") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package t;\n");
+ code.append("import " + ProxyFor.class.getName() + ";\n");
+ code.append("import " + EntityProxy.class.getName() + ";\n");
+ if (proxyClass != null) {
+ code.append("@ProxyFor(" + proxyClass + ".class)");
+ }
+ code.append("interface ProxyWithRepeatedGetters extends EntityProxy {\n");
+ code.append(" boolean getFoo();");
+ code.append(" boolean isFoo();");
+ code.append("}");
+ return code;
+ }
+ }, new MockJavaResource("java.util.List") {
+ // Tests a Driver interface that extends more than RFED
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package java.util;\n");
+ code.append("public interface List<T> extends Collection<T> {\n");
+ code.append("}");
+ return code;
+ }
+ }, new MockJavaResource("java.util.Set") {
+ // Tests a Driver interface that extends more than RFED
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package java.util;\n");
+ code.append("public interface Set<T> extends Collection<T> {\n");
+ code.append("}");
+ return code;
+ }
+ }, new MockJavaResource("java.util.SortedSet") {
+ // Tests a Driver interface that extends more than RFED
+ @Override
+ protected CharSequence getContent() {
+ StringBuilder code = new StringBuilder();
+ code.append("package java.util;\n");
+ code.append("public interface SortedSet<T> extends Set<T> {\n");
+ code.append("}");
+ return code;
+ }
+ }};
+
+ Set<Resource> toReturn = new HashSet<Resource>(Arrays.asList(javaFiles));
+
+ toReturn.addAll(Arrays.asList(new Resource[] {
+ new EmptyMockJavaResource(Iterable.class),
+ new EmptyMockJavaResource(EntityProxy.class),
+ new EmptyMockJavaResource(InstanceRequest.class),
+ new EmptyMockJavaResource(Locator.class),
+ new EmptyMockJavaResource(RequestFactory.class),
+ new EmptyMockJavaResource(Receiver.class),
+ new EmptyMockJavaResource(ServiceLocator.class),
+ new EmptyMockJavaResource(Splittable.class),
+ new EmptyMockJavaResource(ValueProxy.class),
+
+ new RealJavaResource(Request.class),
+ new RealJavaResource(Service.class),
+ new RealJavaResource(ProxyFor.class),
+ new EmptyMockJavaResource(RequestContext.class),}));
+ toReturn.addAll(Arrays.asList(JavaResourceBase.getStandardResources()));
+ return toReturn;
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
new file mode 100644
index 0000000..c38d6c5
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.dev.asm.Type;
+import com.google.gwt.dev.asm.commons.Method;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.VisibleErrorContext;
+import com.google.web.bindery.requestfactory.shared.BoxesAndPrimitivesTest;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+/**
+ * A JRE version of {@link BoxesAndPrimitivesTest} with additional validation
+ * tests.
+ */
+public class BoxesAndPrimitivesJreTest extends BoxesAndPrimitivesTest {
+
+ @Service(ServiceImpl.class)
+ interface ContextMismatchedParameterA extends RequestContext {
+ Request<Void> checkBoxed(int value);
+ }
+
+ @Service(ServiceImpl.class)
+ interface ContextMismatchedParameterB extends RequestContext {
+ Request<Void> checkPrimitive(Integer value);
+ }
+
+ @ProxyFor(Entity.class)
+ interface ProxyMismatchedGetterA extends EntityProxy {
+ int getBoxed();
+ }
+
+ @ProxyFor(Entity.class)
+ interface ProxyMismatchedGetterB extends EntityProxy {
+ Integer getPrimitive();
+ }
+
+ private VisibleErrorContext errors;
+ private RequestFactoryInterfaceValidator v;
+
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ public void test() {
+ RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(
+ Logger.getAnonymousLogger(),
+ new RequestFactoryInterfaceValidator.ClassLoaderLoader(
+ getClass().getClassLoader()));
+ v.validateRequestFactory(Factory.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
+ /**
+ * Tests that mismatched primitive verses boxed getters are correctly
+ * reported.
+ */
+ public void testMismatchedGetters() {
+ v.validateEntityProxy(ProxyMismatchedGetterA.class.getName());
+ v.validateEntityProxy(ProxyMismatchedGetterB.class.getName());
+ assertTrue(v.isPoisoned());
+
+ String getBoxedMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
+ Type.getType(Entity.class),
+ Arrays.asList(new Method("getBoxed", "()Ljava/lang/Integer;")));
+ String getPrimitiveMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
+ Type.getType(Entity.class),
+ Arrays.asList(new Method("getPrimitive", "()I")));
+ assertEquals(Arrays.asList(getBoxedMessage, getPrimitiveMessage),
+ errors.logs);
+ }
+
+ /**
+ * Tests that mismatched parameter types are correctly reported.
+ */
+ public void testMismatchedParameters() {
+ v.validateRequestContext(ContextMismatchedParameterA.class.getName());
+ v.validateRequestContext(ContextMismatchedParameterB.class.getName());
+
+ String checkBoxedMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
+ Type.getType(ServiceImpl.class),
+ Arrays.asList(new Method("checkBoxed", "(Ljava/lang/Integer;)V")));
+ String checkPrimitiveMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
+ Type.getType(ServiceImpl.class),
+ Arrays.asList(new Method("checkPrimitive", "(I)V")));
+ assertEquals(Arrays.asList(checkBoxedMessage, checkPrimitiveMessage),
+ errors.logs);
+ }
+
+ @Override
+ protected Factory createFactory() {
+ return RequestFactoryJreTest.createInProcess(Factory.class);
+ }
+
+ @Override
+ protected void gwtSetUp() {
+ super.gwtSetUp();
+ errors = new VisibleErrorContext(Logger.getAnonymousLogger());
+ v = new RequestFactoryInterfaceValidator(errors,
+ new RequestFactoryInterfaceValidator.ClassLoaderLoader(
+ getClass().getClassLoader()));
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/ComplexKeysJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/ComplexKeysJreTest.java
new file mode 100644
index 0000000..44454a2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/ComplexKeysJreTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ComplexKeysTest;
+
+/**
+ * JRE version of ComplexKeysTest.
+ */
+public class ComplexKeysJreTest extends ComplexKeysTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected Factory createFactory() {
+ return RequestFactoryJreTest.createInProcess(Factory.class);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/FindServiceJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/FindServiceJreTest.java
new file mode 100644
index 0000000..d8fa59c
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/FindServiceJreTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.gwt.client.FindServiceTest;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * Run the FindService tests in-process.
+ */
+public class FindServiceJreTest extends FindServiceTest {
+
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected SimpleRequestFactory createFactory() {
+ return RequestFactoryJreTest.createInProcess(SimpleRequestFactory.class);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/HasId.java b/user/test/com/google/web/bindery/requestfactory/server/HasId.java
new file mode 100644
index 0000000..23b4c53
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/HasId.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * Used to test covariant return types.
+ */
+public interface HasId {
+ Object getId();
+
+ Object persistAndReturnSelf();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/InstanceService.java b/user/test/com/google/web/bindery/requestfactory/server/InstanceService.java
new file mode 100644
index 0000000..26f5139
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/InstanceService.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * A service API that doesn't have static methods and that can't be
+ * default-instantiated.
+ */
+public interface InstanceService {
+ Integer add(int value);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceImpl.java b/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceImpl.java
new file mode 100644
index 0000000..cd509b3
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * A service API that doesn't have static methods and that can't be
+ * default-instantiated.
+ */
+public class InstanceServiceImpl implements InstanceService {
+ private final int base;
+
+ public InstanceServiceImpl(int base) {
+ this.base = base;
+ }
+
+ public Integer add(int value) {
+ return base + value;
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceLocator.java b/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceLocator.java
new file mode 100644
index 0000000..131da3a
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/InstanceServiceLocator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+/**
+ * Demonstrates instance-based service objects.
+ */
+public class InstanceServiceLocator implements ServiceLocator {
+
+ public Object getInstance(Class<?> clazz) {
+ assert InstanceService.class.equals(clazz);
+ return clazz.cast(new InstanceServiceImpl(5));
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/LocatorJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/LocatorJreTest.java
new file mode 100644
index 0000000..d500bf9
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/LocatorJreTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.LocatorTest;
+
+/**
+ * A JRE version of {@link LocatorTest}.
+ */
+public class LocatorJreTest extends LocatorTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected Factory createFactory() {
+ return RequestFactoryJreTest.createInProcess(Factory.class);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionHandlerServlet.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionHandlerServlet.java
new file mode 100644
index 0000000..a76d2b6
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionHandlerServlet.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServerFailure;
+
+/**
+ * A RequestFactoryServlet that forwards all exception information.
+ */
+public class RequestFactoryExceptionHandlerServlet
+ extends RequestFactoryServlet {
+ public RequestFactoryExceptionHandlerServlet() {
+ super(new ExceptionHandler() {
+ public ServerFailure createServerFailure(Throwable throwable) {
+ return new ServerFailure(throwable.getMessage(),
+ throwable.getClass().getName(), "my stack trace", true);
+ }
+ });
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java
new file mode 100644
index 0000000..90eede7
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryExceptionPropagationJreTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.core.client.GWT;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryExceptionPropagationTest;
+import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * JRE version of {@link RequestFactoryExceptionPropagationTest}.
+ */
+public class RequestFactoryExceptionPropagationJreTest extends
+ RequestFactoryExceptionPropagationTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected SimpleRequestFactory createFactory() {
+ return RequestFactoryJreTest.createInProcess(SimpleRequestFactory.class);
+ }
+
+ @Override
+ protected void fireContextAndCatch(RequestContext context,
+ Receiver<Void> receiver, GWT.UncaughtExceptionHandler exceptionHandler) {
+ try {
+ if (receiver == null) {
+ context.fire();
+ } else {
+ context.fire(receiver);
+ }
+ } catch (Throwable e) {
+ exceptionHandler.onUncaughtException(e);
+ }
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
new file mode 100644
index 0000000..698b2b4
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.Locator;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+import com.google.web.bindery.requestfactory.shared.impl.FindRequest;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * JRE tests for {@link RequestFactoryInterfaceValidator}.
+ */
+public class RequestFactoryInterfaceValidatorTest extends TestCase {
+ static class ClinitEntity {
+ static ClinitEntity findClinitEntity(String key) {
+ return null;
+ }
+
+ static ClinitEntity request() {
+ return null;
+ }
+
+ Object OBJECT = new Object();
+
+ String getId() {
+ return null;
+ }
+
+ int getVersion() {
+ return 0;
+ }
+ }
+
+ @ProxyFor(ClinitEntity.class)
+ interface ClinitEntityProxy extends EntityProxy {
+ Object OBJECT = new Object();
+ }
+
+ @Service(ClinitEntity.class)
+ interface ClinitRequestContext extends RequestContext {
+ Object OBJECT = new Object();
+
+ Request<ClinitEntityProxy> request();
+ }
+
+ interface ClinitRequestFactory extends RequestFactory {
+ Object OBJECT = new Object();
+
+ ClinitRequestContext context();
+ }
+
+ static class Domain {
+ static int fooStatic(int a) {
+ return 0;
+ }
+
+ int foo(int a) {
+ return 0;
+ }
+
+ java.sql.Date getSqlDate() {
+ return null;
+ }
+ }
+
+ @ProxyFor(Domain.class)
+ interface DomainProxy extends EntityProxy {
+ }
+
+ interface DomainProxyMissingAnnotation extends EntityProxy {
+ }
+
+ static class DomainWithOverloads {
+ void foo() {
+ }
+
+ void foo(int a) {
+ }
+
+ String getId() {
+ return null;
+ }
+
+ int getVersion() {
+ return 0;
+ }
+ }
+
+ @ProxyFor(DomainWithOverloads.class)
+ interface DomainWithOverloadsProxy extends EntityProxy {
+ void foo();
+ }
+
+ @ProxyFor(Domain.class)
+ interface DomainWithSqlDateProxy extends EntityProxy {
+ java.sql.Date getSqlDate();
+ }
+
+ class Foo {
+ }
+
+ @ProxyFor(HasListDomain.class)
+ interface HasList extends EntityProxy {
+ List<ReachableOnlyThroughReturnedList> getList();
+
+ void setList(List<ReachableOnlyThroughParamaterList> list);
+ }
+
+ static class HasListDomain extends Domain {
+ public String getId() {
+ return null;
+ }
+
+ public int getVersion() {
+ return 0;
+ }
+
+ List<Domain> getList() {
+ return null;
+ }
+
+ void setList(List<Domain> value) {
+ }
+ }
+
+ /**
+ * An entity type without the usual boilerplate.
+ */
+ class LocatorEntity {
+ }
+
+ class LocatorEntityLocator extends Locator<LocatorEntity, String> {
+ @Override
+ public LocatorEntity create(Class<? extends LocatorEntity> clazz) {
+ return null;
+ }
+
+ @Override
+ public LocatorEntity find(Class<? extends LocatorEntity> clazz, String id) {
+ return null;
+ }
+
+ @Override
+ public Class<LocatorEntity> getDomainType() {
+ return null;
+ }
+
+ @Override
+ public String getId(LocatorEntity domainObject) {
+ return null;
+ }
+
+ @Override
+ public Class<String> getIdType() {
+ return null;
+ }
+
+ @Override
+ public Object getVersion(LocatorEntity domainObject) {
+ return null;
+ }
+ }
+
+ @ProxyFor(value = LocatorEntity.class, locator = LocatorEntityLocator.class)
+ interface LocatorEntityProxy extends EntityProxy {
+ }
+
+ @ProxyForName(value = "com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.LocatorEntity", locator = "badLocator")
+ interface LocatorEntityProxyWithBadLocator extends EntityProxy {
+ }
+
+ @ProxyForName(value = "badDomainType", locator = "com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.LocatorEntityProxyWithBadServiceName")
+ interface LocatorEntityProxyWithBadServiceName extends EntityProxy {
+ }
+
+ @ProxyFor(value = Value.class)
+ interface MyValueProxy extends ValueProxy {
+ }
+
+ @ProxyFor(Domain.class)
+ interface ReachableOnlyThroughParamaterList extends EntityProxy {
+ }
+
+ @ProxyFor(Domain.class)
+ interface ReachableOnlyThroughReturnedList extends EntityProxy {
+ }
+
+ interface RequestContextMissingAnnotation extends RequestContext {
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMismatchedArity extends RequestContext {
+ InstanceRequest<DomainProxy, Integer> foo(int a, int b);
+
+ Request<Integer> fooStatic(int a, int b);
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMismatchedParam extends RequestContext {
+ Request<Integer> foo(long a);
+ }
+ @Service(Domain.class)
+ interface ServiceRequestMismatchedReturn extends RequestContext {
+ Request<Long> foo(int a);
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMismatchedStatic extends RequestContext {
+ Request<Integer> foo(int a);
+
+ InstanceRequest<DomainProxy, Integer> fooStatic(int a);
+ }
+
+ @Service(Domain.class)
+ interface ServiceRequestMissingMethod extends RequestContext {
+ Request<Integer> doesNotExist(int a);
+ }
+
+ @Service(Domain.class)
+ interface SkipValidationContext extends RequestContext {
+ @SkipInterfaceValidation
+ Request<Integer> doesNotExist(int a);
+
+ @SkipInterfaceValidation
+ Request<Long> foo(int a);
+ }
+
+ @Service(Domain.class)
+ interface SkipValidationProxy extends ValueProxy {
+ @SkipInterfaceValidation
+ boolean doesNotExist();
+ }
+
+ @Service(Domain.class)
+ interface SkipValidationChecksReferredProxies extends ValueProxy {
+ @SkipInterfaceValidation
+ // still validates other proxies
+ DomainProxyMissingAnnotation getDomainProxyMissingAnnotation();
+ }
+
+ @ProxyFor(Domain.class)
+ @ProxyForName("Domain")
+ @Service(Domain.class)
+ @ServiceName("Domain")
+ interface TooManyAnnotations extends RequestContext {
+ }
+
+ static class UnexpectedIdAndVersionDomain {
+ Random getId() {
+ return null;
+ }
+
+ Random getVersion() {
+ return null;
+ }
+ }
+
+ @ProxyFor(UnexpectedIdAndVersionDomain.class)
+ interface UnexpectedIdAndVersionProxy extends EntityProxy {
+ }
+
+ static class Value {
+ }
+
+ static class VisibleErrorContext extends
+ RequestFactoryInterfaceValidator.ErrorContext {
+ final List<String> logs;
+
+ public VisibleErrorContext(Logger logger) {
+ super(logger);
+ logs = new ArrayList<String>();
+ }
+
+ public VisibleErrorContext(VisibleErrorContext that) {
+ super(that);
+ this.logs = that.logs;
+ }
+
+ @Override
+ public void poison(String msg, Object... args) {
+ logs.add(String.format(msg, args));
+ super.poison(msg, args);
+ }
+
+ @Override
+ public void poison(String msg, Throwable t) {
+ logs.add(msg);
+ super.poison(msg, t);
+ }
+
+ @Override
+ protected VisibleErrorContext fork() {
+ return new VisibleErrorContext(this);
+ }
+ }
+
+ RequestFactoryInterfaceValidator v;;
+
+ private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");;
+
+ private VisibleErrorContext errors;
+
+ /**
+ * Ensure that calling {@link RequestFactoryInterfaceValidator#antidote()}
+ * doesn't cause information to be lost.
+ */
+ public void testAntidote() {
+ v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+ assertTrue(v.isPoisoned());
+ v.antidote();
+ assertFalse(v.isPoisoned());
+ v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testBadLocatorName() {
+ v.validateEntityProxy(LocatorEntityProxyWithBadLocator.class.getName());
+ assertTrue(v.isPoisoned());
+ assertTrue(errors.logs.contains("Cannot find locator named badLocator"));
+ }
+
+ public void testBadServiceName() {
+ v.validateEntityProxy(LocatorEntityProxyWithBadServiceName.class.getName());
+ assertTrue(v.isPoisoned());
+ assertTrue(errors.logs.contains("Cannot find domain type named badDomainType"));
+ }
+
+ /**
+ * Test that subclasses of {@code java.util.Date} are not transportable.
+ */
+ public void testDateSubclass() {
+ v.validateEntityProxy(DomainWithSqlDateProxy.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ /**
+ * Test the {@link FindRequest} context used to implement find().
+ */
+ public void testFindRequestContext() {
+ v.validateRequestContext(FindRequest.class.getName());
+ }
+
+ /**
+ * Make sure that proxy types referenced through type parameters of method
+ * return types and paramaters types are examined.
+ */
+ public void testFollowingTypeParameters() {
+ v.validateEntityProxy(HasList.class.getName());
+ assertNotNull(v.getEntityProxyTypeName(HasListDomain.class.getName(),
+ HasList.class.getName()));
+ assertNotNull(v.getEntityProxyTypeName(Domain.class.getName(),
+ ReachableOnlyThroughParamaterList.class.getName()));
+ assertNotNull(v.getEntityProxyTypeName(Domain.class.getName(),
+ ReachableOnlyThroughReturnedList.class.getName()));
+ }
+
+ /**
+ * Ensure that the <clinit> methods don't interfere with validation.
+ */
+ public void testIntecfacesWithClinits() {
+ v.validateRequestFactory(ClinitRequestFactory.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
+ public void testLocatorProxy() {
+ v.validateEntityProxy(LocatorEntityProxy.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
+ public void testMismatchedArity() {
+ v.validateRequestContext(ServiceRequestMismatchedArity.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMismatchedParamType() {
+ v.validateRequestContext(ServiceRequestMismatchedParam.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMismatchedReturnType() {
+ v.validateRequestContext(ServiceRequestMismatchedReturn.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMismatchedStatic() {
+ v.validateRequestContext(ServiceRequestMismatchedStatic.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMissingDomainAnnotations() {
+ v.validateEntityProxy(DomainProxyMissingAnnotation.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMissingIdAndVersion() {
+ v.validateEntityProxy(DomainProxy.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMissingMethod() {
+ v.validateRequestContext(ServiceRequestMissingMethod.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testMissingServiceAnnotations() {
+ v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testOverloadedMethod() {
+ v.validateEntityProxy(DomainWithOverloadsProxy.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testSkipValidationChecksReferredProxies() {
+ v.validateValueProxy(SkipValidationChecksReferredProxies.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testSkipValidationContext() {
+ v.validateRequestContext(SkipValidationContext.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
+ public void testSkipValidationProxy() {
+ v.validateValueProxy(SkipValidationProxy.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
+ /**
+ * Perform a full test of the RequestFactory used for most tests.
+ */
+ public void testTestCodeFactories() {
+ v.validateRequestFactory(SimpleRequestFactory.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
+ public void testTooManyAnnotations() {
+ v.validateRequestContext(TooManyAnnotations.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testUnexpectedIdAndVersion() {
+ v.validateEntityProxy(UnexpectedIdAndVersionProxy.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
+ public void testValueType() {
+ v.validateValueProxy(MyValueProxy.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ Logger logger = Logger.getLogger("");
+ logger.setLevel(DUMP_PAYLOAD ? Level.ALL : Level.OFF);
+ errors = new VisibleErrorContext(logger);
+ v = new RequestFactoryInterfaceValidator(errors, new ClassLoaderLoader(
+ Thread.currentThread().getContextClassLoader()));
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java
new file mode 100644
index 0000000..d17b608
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.event.shared.EventBus;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryTest;
+import com.google.web.bindery.requestfactory.server.testing.InProcessRequestTransport;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.vm.RequestFactorySource;
+
+/**
+ * Runs the RequestFactory tests in-process.
+ */
+public class RequestFactoryJreTest extends RequestFactoryTest {
+
+ public static <T extends RequestFactory> T createInProcess(Class<T> clazz) {
+ EventBus eventBus = new SimpleEventBus();
+ T req = RequestFactorySource.create(clazz);
+ ServiceLayer serviceLayer = ServiceLayer.create();
+ SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
+ req.initialize(eventBus, new InProcessRequestTransport(processor));
+ return req;
+ }
+
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected SimpleRequestFactory createFactory() {
+ return createInProcess(SimpleRequestFactory.class);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryUnicodeEscapingJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryUnicodeEscapingJreTest.java
new file mode 100644
index 0000000..16c4601
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryUnicodeEscapingJreTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryUnicodeEscapingTest;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+
+/**
+ * A JRE implementation of {@link RequestFactoryUnicodeEscapingTest}.
+ */
+public class RequestFactoryUnicodeEscapingJreTest extends
+ RequestFactoryUnicodeEscapingTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected SimpleRequestFactory createFactory() {
+ return RequestFactoryJreTest.createInProcess(SimpleRequestFactory.class);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/ServiceInheritanceJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/ServiceInheritanceJreTest.java
new file mode 100644
index 0000000..8f209de
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/ServiceInheritanceJreTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServiceInheritanceTest;
+
+/**
+ * A JRE version of {@link ServiceInheritanceTest}.
+ */
+public class ServiceInheritanceJreTest extends ServiceInheritanceTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected Factory createFactory() {
+ return RequestFactoryJreTest.createInProcess(Factory.class);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/ServiceLocatorTest.java b/user/test/com/google/web/bindery/requestfactory/server/ServiceLocatorTest.java
new file mode 100644
index 0000000..3fee0cd
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/ServiceLocatorTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.ServiceLocator;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests creating of ServiceLocators with custom ServiceLayerDecorators
+ */
+public class ServiceLocatorTest extends TestCase {
+
+ static class CustomLocatorLayer extends ServiceLayerDecorator {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends ServiceLocator> T createServiceLocator(Class<T> clazz) {
+ return (T) ServiceLocatorImpl.INSTANCE;
+ }
+ }
+
+ static class ServiceLocatorImpl implements ServiceLocator {
+ static final ServiceLocatorImpl INSTANCE = new ServiceLocatorImpl();
+
+ public Object getInstance(Class<?> clazz) {
+ return new Object();
+ }
+ }
+
+ public void testGetsServiceLocatorFromDecorator() {
+ ServiceLayer layer = ServiceLayer.create(new CustomLocatorLayer());
+ ServiceLocatorImpl locator = layer.createServiceLocator(ServiceLocatorImpl.class);
+ assertSame(ServiceLocatorImpl.INSTANCE, locator);
+ }
+
+ public void testInstantiatesServiceLocatorByDefault() {
+ ServiceLayer layer = ServiceLayer.create();
+ ServiceLocatorImpl locator = layer.createServiceLocator(ServiceLocatorImpl.class);
+ assertNotNull(locator);
+ assertNotSame(ServiceLocatorImpl.INSTANCE, locator);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java b/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java
new file mode 100644
index 0000000..5b8e40c
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.gwt.dev.util.collect.HashSet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Domain object for SimpleFooRequest.
+ */
+public class SimpleBar implements HasId {
+ /**
+ * DO NOT USE THIS UGLY HACK DIRECTLY! Call {@link #get} instead.
+ */
+ private static Map<String, SimpleBar> jreTestSingleton = new HashMap<String, SimpleBar>();
+
+ private static long nextId = 2L;
+
+ static {
+ try {
+ reset();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ public static Long countSimpleBar() {
+ return (long) get().size();
+ }
+
+ public static List<SimpleBar> findAll() {
+ return new ArrayList<SimpleBar>(get().values());
+ }
+
+ public static Set<SimpleBar> findAsSet() {
+ return new HashSet<SimpleBar>(get().values());
+ }
+
+ public static SimpleBar findSimpleBar(String id) {
+ return findSimpleBarById(id);
+ }
+
+ /**
+ * Returns <code>null</code> if {@link #findFails} is <code>true</code>.
+ */
+ public static SimpleBar findSimpleBarById(String id) {
+ SimpleBar toReturn = get().get(id);
+ return (toReturn == null || toReturn.findFails) ? null : toReturn;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized Map<String, SimpleBar> get() {
+ HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
+ if (req == null) {
+ // May be in a JRE test case, use the the singleton
+ return jreTestSingleton;
+ } else {
+ /*
+ * This will not behave entirely correctly unless we have a servlet filter
+ * that doesn't allow any requests to be processed unless they're
+ * associated with an existing session.
+ */
+ Map<String, SimpleBar> value = (Map<String, SimpleBar>) req.getSession().getAttribute(
+ SimpleBar.class.getCanonicalName());
+ if (value == null) {
+ value = resetImpl();
+ }
+ return value;
+ }
+ }
+
+ public static SimpleBar getSingleton() {
+ return findSimpleBar("1L");
+ }
+
+ public static void reset() {
+ resetImpl();
+ }
+
+ public static synchronized Map<String, SimpleBar> resetImpl() {
+ Map<String, SimpleBar> instance = new HashMap<String, SimpleBar>();
+ // fixtures
+ SimpleBar s1 = new SimpleBar();
+ s1.setId("1L");
+ s1.isNew = false;
+ instance.put(s1.getId(), s1);
+
+ SimpleBar s2 = new SimpleBar();
+ s2.setId("999L");
+ s2.isNew = false;
+ instance.put(s2.getId(), s2);
+
+ HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
+ if (req == null) {
+ jreTestSingleton = instance;
+ } else {
+ req.getSession().setAttribute(SimpleBar.class.getCanonicalName(),
+ instance);
+ }
+ return instance;
+ }
+
+ public static SimpleBar returnFirst(List<SimpleBar> list) {
+ SimpleBar toReturn = list.get(0);
+ return toReturn;
+ }
+
+ Integer version = 1;
+
+ private String id = "999L";
+ private boolean findFails;
+ private boolean isNew = true;
+ private boolean unpersisted;
+ private String userName;
+
+ public SimpleBar() {
+ version = 1;
+ userName = "FOO";
+ }
+
+ public void delete() {
+ get().remove(getId());
+ }
+
+ public Boolean getFindFails() {
+ return findFails;
+ }
+
+ public String getId() {
+ return unpersisted ? null : id;
+ }
+
+ public Boolean getUnpersisted() {
+ return unpersisted;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public Integer getVersion() {
+ return unpersisted ? null : version;
+ }
+
+ public void persist() {
+ if (isNew) {
+ setId(Long.toString(nextId++));
+ isNew = false;
+ get().put(getId(), this);
+ }
+ version++;
+ }
+
+ public SimpleBar persistAndReturnSelf() {
+ persist();
+ return this;
+ }
+
+ public void setFindFails(Boolean fails) {
+ this.findFails = fails;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setUnpersisted(Boolean unpersisted) {
+ this.unpersisted = unpersisted;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public void setVersion(Integer version) {
+ this.version = version;
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/SimpleFoo.java b/user/test/com/google/web/bindery/requestfactory/server/SimpleFoo.java
new file mode 100644
index 0000000..1f3e61e
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/SimpleFoo.java
@@ -0,0 +1,875 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import com.google.web.bindery.requestfactory.shared.OnlyUsedByRequestContextMethod;
+import com.google.web.bindery.requestfactory.shared.SimpleEnum;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.constraints.Size;
+
+/**
+ * Domain object for SimpleFooRequest.
+ */
+public class SimpleFoo {
+ /**
+ * DO NOT USE THIS UGLY HACK DIRECTLY! Call {@link #get} instead.
+ */
+ private static Map<Long, SimpleFoo> jreTestSingleton;
+
+ private static Long nextId = 1L;
+
+ public static Integer add(Integer a, int b) {
+ return a + b;
+ }
+
+ public static Long countSimpleFoo() {
+ return (long) get().size();
+ }
+
+ public static SimpleFoo echo(SimpleFoo simpleFoo) {
+ return simpleFoo;
+ }
+
+ public static SimpleFoo echoComplex(SimpleFoo simpleFoo, SimpleBar simpleBar) {
+ simpleFoo.setBarField(simpleBar);
+ return simpleFoo;
+ }
+
+ public static SimpleFoo fetchDoubleReference() {
+ SimpleFoo foo = new SimpleFoo();
+ SimpleFoo foo2 = new SimpleFoo();
+ foo.setFooField(foo2);
+ foo.setSelfOneToManyField(Arrays.asList(foo2));
+ foo.persist();
+ foo2.persist();
+ return foo;
+ }
+
+ public static List<SimpleFoo> findAll() {
+ return new ArrayList<SimpleFoo>(get().values());
+ }
+
+ public static SimpleFoo findSimpleFoo(Long id) {
+ return findSimpleFooById(id);
+ }
+
+ public static SimpleFoo findSimpleFooById(Long id) {
+ return get().get(id);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized Map<Long, SimpleFoo> get() {
+ HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
+ if (req == null) {
+ // May be in a JRE test case, use the singleton
+ if (jreTestSingleton == null) {
+ jreTestSingleton = resetImpl();
+ }
+ return jreTestSingleton;
+ } else {
+ /*
+ * This will not behave entirely correctly unless we have a servlet filter
+ * that doesn't allow any requests to be processed unless they're
+ * associated with an existing session.
+ */
+ Map<Long, SimpleFoo> value = (Map<Long, SimpleFoo>) req.getSession().getAttribute(
+ SimpleFoo.class.getCanonicalName());
+ if (value == null) {
+ value = resetImpl();
+ }
+ return value;
+ }
+ }
+
+ public static List<Integer> getNumberList() {
+ ArrayList<Integer> list = new ArrayList<Integer>();
+ list.add(1);
+ list.add(2);
+ list.add(3);
+ return list;
+ }
+
+ public static Set<Integer> getNumberSet() {
+ Set<Integer> list = new HashSet<Integer>();
+ list.add(1);
+ list.add(2);
+ list.add(3);
+ return list;
+ }
+
+ /**
+ * This tests that the server detects and disallows the use of persisted
+ * objects with a null version property.
+ */
+ public static SimpleFoo getSimpleFooWithNullVersion() {
+ System.err.println("The following exception about an entity with a null"
+ + " version is expected");
+ SimpleFoo foo = new SimpleFoo();
+ foo.setVersion(null);
+ return foo;
+ }
+
+ public static SimpleFoo getSimpleFooWithSubPropertyCollection() {
+ SimpleFoo foo = new SimpleFoo();
+ SimpleFoo subFoo = new SimpleFoo();
+ SimpleFoo subSubFoo = new SimpleFoo();
+ subFoo.setFooField(subSubFoo);
+ subSubFoo.setUserName("I'm here");
+ subSubFoo.persist();
+ subFoo.persist();
+ foo.persist();
+ foo.setSelfOneToManyField(Arrays.asList(subFoo));
+ return foo;
+ }
+
+ public static SimpleFoo getTripletReference() {
+ SimpleFoo foo1 = new SimpleFoo();
+ SimpleFoo foo2 = new SimpleFoo();
+ SimpleFoo foo3 = new SimpleFoo();
+ ArrayList<SimpleFoo> foos = new ArrayList<SimpleFoo>();
+ foos.add(foo2);
+ ArrayList<SimpleFoo> subFoos = new ArrayList<SimpleFoo>();
+ subFoos.add(foo3);
+ foo1.setSelfOneToManyField(foos);
+ foo2.setSelfOneToManyField(subFoos);
+ foo3.setFooField(foo2);
+ foo1.persist();
+ foo2.persist();
+ foo3.persist();
+ return foo1;
+ }
+
+ public static SimpleFoo getUnpersistedInstance() {
+ SimpleFoo foo = new SimpleFoo();
+ foo.setUnpersisted(true);
+ return foo;
+ }
+
+ public static void pleaseCrash(Integer crashIf42or43) throws Exception {
+ if (crashIf42or43 == 42) {
+ throw new UnsupportedOperationException(
+ "THIS EXCEPTION IS EXPECTED BY A TEST");
+ }
+ if (crashIf42or43 == 43) {
+ throw new Exception("THIS EXCEPTION IS EXPECTED BY A TEST");
+ }
+ }
+
+ /**
+ * Check client-side upcasting to BigDecimal and return a list of BigDecimals
+ * that should be upcast.
+ */
+ public static List<BigDecimal> processBigDecimalList(List<BigDecimal> values) {
+ List<BigDecimal> toReturn = new ArrayList<BigDecimal>();
+ toReturn.add(BigDecimal.TEN);
+ toReturn.add(new BigDecimal("12345.6789") {
+ // This is an anonymous subtype
+ });
+ if (!toReturn.equals(values)) {
+ throw new IllegalArgumentException(toReturn + " != " + values);
+ }
+ return toReturn;
+ }
+
+ /**
+ * Check client-side upcasting to BigInteger and return a list of BigIntegers
+ * that should be upcast.
+ */
+ public static List<BigInteger> processBigIntegerList(List<BigInteger> values) {
+ List<BigInteger> toReturn = new ArrayList<BigInteger>();
+ toReturn.add(BigInteger.TEN);
+ toReturn.add(new BigInteger("12345") {
+ // This is an anonymous subtype
+ });
+ if (!toReturn.equals(values)) {
+ throw new IllegalArgumentException(toReturn + " != " + values);
+ }
+ return toReturn;
+ }
+
+ public static Boolean processBooleanList(List<Boolean> values) {
+ return values.get(0);
+ }
+
+ /**
+ * Check client-side upcasting to Date and return a list of Dates that should
+ * be upcast.
+ */
+ @SuppressWarnings("deprecation")
+ public static List<Date> processDateList(List<Date> values) {
+ // Keep these values in sync with SimpleFoo.processDateList
+ Date date = new Date(90, 0, 1);
+ java.sql.Date sqlDate = new java.sql.Date(90, 0, 2);
+ Time sqlTime = new Time(1, 2, 3);
+ Timestamp sqlTimestamp = new Timestamp(12345L);
+ List<Date> toReturn = Arrays.asList(date, sqlDate, sqlTime, sqlTimestamp);
+
+ if (toReturn.size() != values.size()) {
+ throw new IllegalArgumentException("size");
+ }
+
+ Iterator<Date> expected = toReturn.iterator();
+ Iterator<Date> actual = values.iterator();
+ while (expected.hasNext()) {
+ Date expectedDate = expected.next();
+ long expectedTime = expectedDate.getTime();
+ long actualTime = actual.next().getTime();
+ if (expectedTime != actualTime) {
+ throw new IllegalArgumentException(expectedDate.getClass().getName()
+ + " " + expectedTime + " != " + actualTime);
+ }
+ }
+
+ return toReturn;
+ }
+
+ public static SimpleEnum processEnumList(List<SimpleEnum> values) {
+ return values.get(0);
+ }
+
+ public static String processString(String string) {
+ return string;
+ }
+
+ public static void receiveEnum(OnlyUsedByRequestContextMethod value) {
+ if (value != OnlyUsedByRequestContextMethod.FOO) {
+ throw new IllegalArgumentException("Expecting FOO, received " + value);
+ }
+ }
+
+ public static void receiveNullList(List<SimpleFoo> value) {
+ if (value != null) {
+ throw new IllegalArgumentException(
+ "Expected value to be null. Actual value: \"" + value + "\"");
+ }
+ }
+
+ public static void receiveNullSimpleFoo(SimpleFoo value) {
+ if (value != null) {
+ throw new IllegalArgumentException(
+ "Expected value to be null. Actual value: \"" + value + "\"");
+ }
+ }
+
+ public static void receiveNullString(String value) {
+ if (value != null) {
+ throw new IllegalArgumentException(
+ "Expected value to be null. Actual value: \"" + value + "\"");
+ }
+ }
+
+ public static void receiveNullValueInEntityList(List<SimpleFoo> list) {
+ if (list == null) {
+ throw new IllegalArgumentException("Expected list to be non null.");
+ } else if (list.size() != 2) {
+ throw new IllegalArgumentException("Expected list to contain two items.");
+ } else if (list.get(0) == null) {
+ throw new IllegalArgumentException(
+ "Expected list.get(0) to return non null.");
+ } else if (list.get(1) != null) {
+ throw new IllegalArgumentException(
+ "Expected list.get(1) to return null. Actual: " + list.get(1));
+ }
+ }
+
+ public static void receiveNullValueInIntegerList(List<Integer> list) {
+ if (list == null) {
+ throw new IllegalArgumentException("Expected list to be non null.");
+ } else if (list.size() != 3) {
+ throw new IllegalArgumentException(
+ "Expected list to contain three items.");
+ } else if (list.get(0) == null || list.get(1) == null) {
+ throw new IllegalArgumentException(
+ "Expected list.get(0)/get(1) to return non null.");
+ } else if (list.get(2) != null) {
+ throw new IllegalArgumentException(
+ "Expected list.get(2) to return null. Actual: \"" + list.get(2)
+ + "\"");
+ }
+ }
+
+ public static void receiveNullValueInStringList(List<String> list) {
+ if (list == null) {
+ throw new IllegalArgumentException("Expected list to be non null.");
+ } else if (list.size() != 3) {
+ throw new IllegalArgumentException(
+ "Expected list to contain three items.");
+ } else if (list.get(0) == null || list.get(1) == null) {
+ throw new IllegalArgumentException(
+ "Expected list.get(0)/get(1) to return non null.");
+ } else if (list.get(2) != null) {
+ throw new IllegalArgumentException(
+ "Expected list.get(2) to return null. Actual: \"" + list.get(2)
+ + "\"");
+ }
+ }
+
+ public static void reset() {
+ resetImpl();
+ }
+
+ public static synchronized Map<Long, SimpleFoo> resetImpl() {
+ Map<Long, SimpleFoo> instance = new HashMap<Long, SimpleFoo>();
+ // fixtures
+ SimpleFoo s1 = new SimpleFoo();
+ s1.setId(1L);
+ s1.isNew = false;
+ instance.put(s1.getId(), s1);
+
+ SimpleFoo s2 = new SimpleFoo();
+ s2.setId(999L);
+ s2.isNew = false;
+ instance.put(s2.getId(), s2);
+
+ HttpServletRequest req = RequestFactoryServlet.getThreadLocalRequest();
+ if (req == null) {
+ jreTestSingleton = instance;
+ } else {
+ req.getSession().setAttribute(SimpleFoo.class.getCanonicalName(),
+ instance);
+ }
+ return instance;
+ }
+
+ public static List<SimpleFoo> returnNullList() {
+ return null;
+ }
+
+ public static SimpleFoo returnNullSimpleFoo() {
+ return null;
+ }
+
+ public static String returnNullString() {
+ return null;
+ }
+
+ public static void returnOnlyUsedInParameterization(List<SimpleFoo> values) {
+ }
+
+ public static SimpleFoo returnSimpleFooSubclass() {
+ return new SimpleFoo() {
+ };
+ }
+
+ public static SimpleValue returnValueProxy() {
+ SimpleValue toReturn = new SimpleValue();
+ toReturn.setNumber(42);
+ toReturn.setString("Hello world!");
+ toReturn.setDate(new Date());
+ return toReturn;
+ }
+
+ @SuppressWarnings("unused")
+ private static Integer privateMethod() {
+ return 0;
+ }
+
+ Integer version = 1;
+
+ private Long id = 1L;
+ private boolean isNew = true;
+
+ @Size(min = 3, max = 30)
+ private String userName;
+ private String password;
+
+ private Character charField;
+ private Long longField;
+
+ private BigDecimal bigDecimalField;
+
+ private BigInteger bigIntField;
+ private Integer intId = -1;
+ private Short shortField;
+
+ private Byte byteField;
+
+ private Date created;
+ private Double doubleField;
+
+ private Float floatField;
+
+ private SimpleEnum enumField;
+ private Boolean boolField;
+
+ private Boolean otherBoolField;
+ private Integer pleaseCrash;
+
+ private SimpleBar barField;
+ private SimpleFoo fooField;
+
+ private String nullField;
+ private SimpleBar barNullField;
+
+ private List<SimpleBar> oneToManyField;
+ private List<SimpleFoo> selfOneToManyField;
+ private Set<SimpleBar> oneToManySetField;
+
+ private List<Integer> numberListField;
+
+ private SimpleValue simpleValueField;
+
+ /*
+ * isChanged is just a quick-and-dirty way to get version-ing for now.
+ * Currently, only set by setUserName and setIntId. TODO for later: Use a
+ * cleaner solution to figure out when to increment version numbers.
+ */
+ boolean isChanged;
+
+ private boolean unpersisted;
+
+ public SimpleFoo() {
+ intId = 42;
+ version = 1;
+ userName = "GWT";
+ longField = 8L;
+ enumField = SimpleEnum.FOO;
+ created = new Date();
+ barField = SimpleBar.getSingleton();
+ boolField = true;
+ oneToManyField = new ArrayList<SimpleBar>();
+ oneToManyField.add(barField);
+ oneToManyField.add(barField);
+ numberListField = new ArrayList<Integer>();
+ numberListField.add(42);
+ numberListField.add(99);
+ selfOneToManyField = new ArrayList<SimpleFoo>();
+ selfOneToManyField.add(this);
+ oneToManySetField = new HashSet<SimpleBar>();
+ oneToManySetField.add(barField);
+ nullField = null;
+ barNullField = null;
+ pleaseCrash = 0;
+ isChanged = false;
+ }
+
+ public Long countSimpleFooWithUserNameSideEffect() {
+ findSimpleFoo(1L).setUserName(userName);
+ version++;
+ return countSimpleFoo();
+ }
+
+ public void deleteBar() {
+ if (barField != null) {
+ isChanged = true;
+ barField.delete();
+ }
+ barField = null;
+ persist();
+ }
+
+ public SimpleBar getBarField() {
+ return barField;
+ }
+
+ public SimpleBar getBarNullField() {
+ return barNullField;
+ }
+
+ /**
+ * Returns the bigDecimalField.
+ */
+ public BigDecimal getBigDecimalField() {
+ return bigDecimalField;
+ }
+
+ /**
+ * Returns the bigIntegerField.
+ */
+ public BigInteger getBigIntField() {
+ return bigIntField;
+ }
+
+ public Boolean getBoolField() {
+ return boolField;
+ }
+
+ /**
+ * Returns the byteField.
+ */
+ public Byte getByteField() {
+ return byteField;
+ }
+
+ /**
+ * Returns the charField.
+ */
+ public Character getCharField() {
+ return charField;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ /**
+ * Returns the doubleField.
+ */
+ public Double getDoubleField() {
+ return doubleField;
+ }
+
+ public SimpleEnum getEnumField() {
+ return enumField;
+ }
+
+ /**
+ * Returns the floatField.
+ */
+ public Float getFloatField() {
+ return floatField;
+ }
+
+ public SimpleFoo getFooField() {
+ return fooField;
+ }
+
+ public Long getId() {
+ return unpersisted ? null : id;
+ }
+
+ public Integer getIntId() {
+ return intId;
+ }
+
+ public Long getLongField() {
+ return longField;
+ }
+
+ public String getNullField() {
+ return nullField;
+ }
+
+ public List<Integer> getNumberListField() {
+ return numberListField;
+ }
+
+ public List<SimpleBar> getOneToManyField() {
+ return oneToManyField;
+ }
+
+ public Set<SimpleBar> getOneToManySetField() {
+ return oneToManySetField;
+ }
+
+ /**
+ * Returns the otherBoolField.
+ */
+ public Boolean getOtherBoolField() {
+ return otherBoolField;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public Integer getPleaseCrash() {
+ return pleaseCrash;
+ }
+
+ public List<SimpleFoo> getSelfOneToManyField() {
+ return selfOneToManyField;
+ }
+
+ /**
+ * Returns the shortField.
+ */
+ public Short getShortField() {
+ return shortField;
+ }
+
+ public SimpleValue getSimpleValue() {
+ return simpleValueField;
+ }
+
+ public List<SimpleValue> getSimpleValues() {
+ return Arrays.asList(simpleValueField);
+ }
+
+ public boolean getUnpersisted() {
+ return unpersisted;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public Integer getVersion() {
+ return unpersisted ? null : version;
+ }
+
+ public String hello(SimpleBar bar) {
+ return "Greetings " + bar.getUserName() + " from " + getUserName();
+ }
+
+ public void persist() {
+ if (isNew) {
+ setId(nextId++);
+ isNew = false;
+ get().put(getId(), this);
+ }
+ if (isChanged) {
+ version++;
+ isChanged = false;
+ }
+ }
+
+ public SimpleFoo persistAndReturnSelf() {
+ persist();
+ return this;
+ }
+
+ public SimpleFoo persistCascadingAndReturnSelf() {
+ persistCascadingAndReturnSelfImpl(new HashSet<SimpleFoo>());
+ return this;
+ }
+
+ public String processList(List<SimpleFoo> values) {
+ String result = "";
+ for (SimpleFoo n : values) {
+ result += n.getUserName();
+ }
+ return result;
+ }
+
+ public void receiveNull(String value) {
+ if (value != null) {
+ throw new IllegalArgumentException(
+ "Expected value to be null. Actual value: \"" + value + "\"");
+ }
+ }
+
+ public void setBarField(SimpleBar barField) {
+ this.barField = barField;
+ }
+
+ public void setBarNullField(SimpleBar barNullField) {
+ this.barNullField = barNullField;
+ }
+
+ /**
+ * @param bigDecimalField the bigDecimalField to set
+ */
+ public void setBigDecimalField(BigDecimal bigDecimalField) {
+ this.bigDecimalField = bigDecimalField;
+ }
+
+ /**
+ * @param bigIntegerField the bigIntegerField to set
+ */
+ public void setBigIntField(BigInteger bigIntegerField) {
+ this.bigIntField = bigIntegerField;
+ }
+
+ public void setBoolField(Boolean bool) {
+ boolField = bool;
+ }
+
+ /**
+ * @param byteField the byteField to set
+ */
+ public void setByteField(Byte byteField) {
+ this.byteField = byteField;
+ }
+
+ /**
+ * @param charField the charField to set
+ */
+ public void setCharField(Character charField) {
+ this.charField = charField;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ /**
+ * @param doubleField the doubleField to set
+ */
+ public void setDoubleField(Double doubleField) {
+ this.doubleField = doubleField;
+ }
+
+ public void setEnumField(SimpleEnum enumField) {
+ this.enumField = enumField;
+ }
+
+ /**
+ * @param floatField the floatField to set
+ */
+ public void setFloatField(Float floatField) {
+ this.floatField = floatField;
+ }
+
+ public void setFooField(SimpleFoo fooField) {
+ this.fooField = fooField;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public void setIntId(Integer id) {
+ if (!this.intId.equals(id)) {
+ this.intId = id;
+ isChanged = true;
+ }
+ }
+
+ public void setLongField(Long longField) {
+ this.longField = longField;
+ }
+
+ public void setNullField(String nullField) {
+ this.nullField = nullField;
+ }
+
+ public void setNumberListField(List<Integer> numberListField) {
+ this.numberListField = numberListField;
+ }
+
+ public void setOneToManyField(List<SimpleBar> oneToManyField) {
+ this.oneToManyField = oneToManyField;
+ }
+
+ public void setOneToManySetField(Set<SimpleBar> oneToManySetField) {
+ this.oneToManySetField = oneToManySetField;
+ }
+
+ /**
+ * @param otherBoolField the otherBoolField to set
+ */
+ public void setOtherBoolField(Boolean otherBoolField) {
+ this.otherBoolField = otherBoolField;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public void setPleaseCrash(Integer crashIf42or43) throws Exception {
+ pleaseCrash(crashIf42or43);
+ pleaseCrash = crashIf42or43;
+ }
+
+ public void setSelfOneToManyField(List<SimpleFoo> selfOneToManyField) {
+ this.selfOneToManyField = selfOneToManyField;
+ }
+
+ /**
+ * @param shortField the shortField to set
+ */
+ public void setShortField(Short shortField) {
+ this.shortField = shortField;
+ }
+
+ public void setSimpleValue(SimpleValue simpleValueField) {
+ this.simpleValueField = simpleValueField;
+ }
+
+ public void setSimpleValues(List<SimpleValue> simpleValueField) {
+ this.simpleValueField = simpleValueField.get(0);
+ }
+
+ public void setUnpersisted(boolean unpersisted) {
+ this.unpersisted = unpersisted;
+ }
+
+ public void setUserName(String userName) {
+ if (!this.userName.equals(userName)) {
+ this.userName = userName;
+ isChanged = true;
+ }
+ }
+
+ public void setVersion(Integer version) {
+ this.version = version;
+ }
+
+ public Integer sum(List<Integer> values) {
+ int sum = 0;
+ for (int n : values) {
+ sum += n;
+ }
+ return sum;
+ }
+
+ /**
+ * Persist this entity and all child entities. This method can handle loops.
+ *
+ * @param processed the entities that have been processed
+ */
+ private void persistCascadingAndReturnSelfImpl(Set<SimpleFoo> processed) {
+ if (processed.contains(this)) {
+ return;
+ }
+
+ // Persist this entity.
+ processed.add(this);
+ persist();
+
+ // Persist SimpleBar children.
+ // We don't need to keep track of the processed SimpleBars because persist()
+ // is a no-op if the SimpleBar has already been persisted.
+ if (barField != null) {
+ barField.persist();
+ }
+ if (barNullField != null) {
+ barNullField.persist();
+ }
+ if (oneToManySetField != null) {
+ for (SimpleBar child : oneToManySetField) {
+ if (child != null) {
+ child.persist();
+ }
+ }
+ }
+ if (oneToManyField != null) {
+ for (SimpleBar child : oneToManyField) {
+ if (child != null) {
+ child.persist();
+ }
+ }
+ }
+
+ // Persist SimpleFoo children.
+ if (fooField != null) {
+ fooField.persistCascadingAndReturnSelfImpl(processed);
+ }
+ if (selfOneToManyField != null) {
+ for (SimpleFoo child : selfOneToManyField) {
+ if (child != null) {
+ child.persistCascadingAndReturnSelfImpl(processed);
+ }
+ }
+ }
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/SimpleValue.java b/user/test/com/google/web/bindery/requestfactory/server/SimpleValue.java
new file mode 100644
index 0000000..5565fec
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/SimpleValue.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.validation.constraints.Null;
+
+/**
+ * A domain object that is used to demonstrate value-object behaviors.
+ */
+public class SimpleValue {
+ private Date date;
+ private int number;
+ private SimpleFoo simpleFoo;
+ /**
+ * Constraint violation testing.
+ */
+ @Null
+ private String shouldBeNull;
+ private List<SimpleValue> simpleValue;
+ private String string;
+
+ public Date getDate() {
+ return date;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ public String getShouldBeNull() {
+ return shouldBeNull;
+ }
+
+ public SimpleFoo getSimpleFoo() {
+ return simpleFoo;
+ }
+
+ public List<SimpleValue> getSimpleValue() {
+ return simpleValue;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public void setShouldBeNull(String value) {
+ this.shouldBeNull = value;
+ }
+
+ public void setNumber(int number) {
+ this.number = number;
+ }
+
+ public void setSimpleFoo(SimpleFoo simpleFoo) {
+ this.simpleFoo = simpleFoo;
+ }
+
+ public void setSimpleValue(List<SimpleValue> simpleValue) {
+ this.simpleValue = simpleValue;
+ }
+
+ public void setString(String string) {
+ this.string = string;
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/TestContextImpl.java b/user/test/com/google/web/bindery/requestfactory/server/TestContextImpl.java
new file mode 100644
index 0000000..b918077
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/TestContextImpl.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * Bad service method declarations.
+ */
+public class TestContextImpl {
+
+ public static Class badReturnType() {
+ return null;
+ }
+
+ public static String mismatchedArityStatic(int x, int y, int z) {
+ return null;
+ }
+
+ public static String mismatchedParamType(String param) {
+ return null;
+ }
+
+ public static Integer mismatchedReturnType() {
+ return null;
+ }
+
+ public static String overloadedMethod() {
+ return null;
+ }
+
+ public static String overloadedMethod(String foo) {
+ return null;
+ }
+ public String getId() {
+ return null;
+ }
+
+ public String getVersion() {
+ return null;
+ }
+
+ public String mismatchedArityInstance(int x, int y) {
+ return null;
+ }
+
+ public String mismatchedStatic(String param) {
+ return null;
+ }
+
+ public static String okMethod() {
+ return null;
+ }
+
+ public static TestContextImpl okMethodProxy() {
+ return null;
+ }
+
+ public static String mismatchedNonStatic(String param) {
+ return null;
+ }
+}
+
diff --git a/user/test/com/google/web/bindery/requestfactory/server/TestContextNoIdImpl.java b/user/test/com/google/web/bindery/requestfactory/server/TestContextNoIdImpl.java
new file mode 100644
index 0000000..5c03704
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/TestContextNoIdImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * Bad proxy, no id.
+ */
+public class TestContextNoIdImpl {
+
+ public static TestContextNoIdImpl okMethodProxy() {
+ return null;
+ }
+
+ public String getVersion() {
+ return null;
+ }
+}
+
diff --git a/user/test/com/google/web/bindery/requestfactory/server/TestContextNoVersionImpl.java b/user/test/com/google/web/bindery/requestfactory/server/TestContextNoVersionImpl.java
new file mode 100644
index 0000000..9cc7a83
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/TestContextNoVersionImpl.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.server;
+
+/**
+ * Bad proxy, no version.
+ */
+public class TestContextNoVersionImpl {
+
+ public static TestContextNoVersionImpl okMethodProxy() {
+ return null;
+ }
+
+ public String getId() {
+ return null;
+ }
+}
+
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/BaseFooProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/BaseFooProxy.java
new file mode 100644
index 0000000..ca750ec
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/BaseFooProxy.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A simple proxy used for testing. Has an int field and date field. Add other
+ * data types as their support gets built in.
+ */
+public interface BaseFooProxy extends EntityProxy {
+
+ SimpleBarProxy getBarField();
+
+ SimpleBarProxy getBarNullField();
+
+ BigDecimal getBigDecimalField();
+
+ BigInteger getBigIntField();
+
+ Boolean getBoolField();
+
+ Byte getByteField();
+
+ Character getCharField();
+
+ Date getCreated();
+
+ Double getDoubleField();
+
+ SimpleEnum getEnumField();
+
+ Float getFloatField();
+
+ Integer getIntId();
+
+ Long getLongField();
+
+ String getNullField();
+
+ List<Integer> getNumberListField();
+
+ List<SimpleBarProxy> getOneToManyField();
+
+ Set<SimpleBarProxy> getOneToManySetField();
+
+ Boolean getOtherBoolField();
+
+ String getPassword();
+
+ Integer getPleaseCrash();
+
+ List<SimpleFooProxy> getSelfOneToManyField();
+
+ Short getShortField();
+
+ SimpleValueProxy getSimpleValue();
+
+ List<SimpleValueProxy> getSimpleValues();
+
+ boolean getUnpersisted();
+
+ String getUserName();
+
+ void setBarField(SimpleBarProxy barField);
+
+ void setBarNullField(SimpleBarProxy barNullField);
+
+ void setBigDecimalField(BigDecimal d);
+
+ void setBigIntField(BigInteger i);
+
+ void setBoolField(Boolean boolField);
+
+ void setByteField(Byte b);
+
+ void setCharField(Character c);
+
+ void setCreated(Date created);
+
+ void setDoubleField(Double d);
+
+ void setEnumField(SimpleEnum value);
+
+ void setFloatField(Float f);
+
+ void setIntId(Integer intId);
+
+ void setLongField(Long longField);
+
+ void setNullField(String nullField);
+
+ void setNumberListField(List<Integer> field);
+
+ void setOneToManyField(List<SimpleBarProxy> field);
+
+ void setOneToManySetField(Set<SimpleBarProxy> field);
+
+ void setOtherBoolField(Boolean boolField);
+
+ void setPassword(String password);
+
+ void setPleaseCrash(Integer dummy);
+
+ void setSelfOneToManyField(List<SimpleFooProxy> field);
+
+ void setShortField(Short s);
+
+ void setSimpleValue(SimpleValueProxy value);
+
+ void setSimpleValues(List<SimpleValueProxy> value);
+
+ void setUnpersisted(boolean unpersisted);
+
+ void setUserName(String userName);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/BasicRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/shared/BasicRequestFactory.java
new file mode 100644
index 0000000..46bb188
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/BasicRequestFactory.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * A base type for {@link SimpleRequestFactory} to ensure that extending
+ * RequestFactory interfaces works correctly.
+ */
+public interface BasicRequestFactory extends RequestFactory {
+ LoggingRequest loggingRequest();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/BoxesAndPrimitivesTest.java b/user/test/com/google/web/bindery/requestfactory/shared/BoxesAndPrimitivesTest.java
new file mode 100644
index 0000000..87f962e
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/BoxesAndPrimitivesTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+/**
+ * Contains a set of checks of how primitive and boxed method declarations
+ * interact.
+ */
+public class BoxesAndPrimitivesTest extends GWTTestCase {
+
+ /**
+ * The domain type.
+ */
+ protected static class Entity {
+ static final Entity SINGLETON = new Entity();
+
+ public static Entity findEntity(int id) {
+ return SINGLETON;
+ }
+
+ public Integer getBoxed() {
+ return EXPECTED_BOXED;
+ }
+
+ public int getId() {
+ return 0;
+ }
+
+ public int getPrimitive() {
+ return EXPECTED;
+ }
+
+ public int getVersion() {
+ return 0;
+ }
+
+ public boolean hasHas() {
+ return EXPECTED_BOOL;
+ }
+
+ public Boolean hasHasBoxed() {
+ return EXPECTED_BOOL_BOXED;
+ }
+
+ public boolean isIs() {
+ return EXPECTED_BOOL;
+ }
+
+ public Boolean isIsBoxed() {
+ return EXPECTED_BOOL_BOXED;
+ }
+
+ public void setBoxed(Integer value) {
+ assertEquals(EXPECTED_BOXED, value);
+ }
+
+ public void setPrimitive(int value) {
+ assertEquals(EXPECTED, value);
+ }
+ }
+
+ /**
+ * The RequestFactory.
+ */
+ protected interface Factory extends RequestFactory {
+ Context context();
+ }
+
+ /**
+ * The service method implementations.
+ */
+ protected static class ServiceImpl {
+ public static void checkBoxed(Integer value) {
+ assertEquals(EXPECTED_BOXED, value);
+ }
+
+ public static void checkPrimitive(int value) {
+ assertEquals(EXPECTED, value);
+ }
+
+ public static Integer getBoxed() {
+ return EXPECTED_BOXED;
+ }
+
+ public static Entity getEntity() {
+ return Entity.SINGLETON;
+ }
+
+ public static int getPrimitive() {
+ return EXPECTED;
+ }
+ }
+
+ @Service(ServiceImpl.class)
+ interface Context extends RequestContext {
+ Request<Void> checkBoxed(Integer value);
+
+ Request<Void> checkPrimitive(int value);
+
+ Request<Integer> getBoxed();
+
+ Request<Proxy> getEntity();
+
+ Request<Integer> getPrimitive();
+ }
+
+ @ProxyFor(Entity.class)
+ interface Proxy extends EntityProxy {
+ Integer getBoxed();
+
+ int getPrimitive();
+
+ boolean hasHas();
+
+ Boolean hasHasBoxed();
+
+ boolean isIs();
+
+ Boolean isIsBoxed();
+
+ void setBoxed(Integer value);
+
+ void setPrimitive(int value);
+ }
+
+ static abstract class TestReceiver<T> extends Receiver<T> {
+ @Override
+ public void onFailure(ServerFailure error) {
+ fail(error.getMessage());
+ }
+ }
+
+ private static final int EXPECTED = 42;
+ private static final Integer EXPECTED_BOXED = Integer.valueOf(EXPECTED);
+ private static final boolean EXPECTED_BOOL = true;
+ private static final Boolean EXPECTED_BOOL_BOXED = Boolean.TRUE;
+ private static final int TEST_DELAY = 5000;
+
+ private Factory factory;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ /**
+ * Tests that domain service methods that return a primitive type are upcast
+ * to the boxed type that the generic declaration requires. Also checks that
+ * primitive and boxed property types can be retrieved and that boxed and
+ * primitive method arguments work.
+ */
+ public void testReturnAndParamTypes() {
+ delayTestFinish(TEST_DELAY);
+ Context ctx = context();
+ // Boxed service method
+ ctx.getBoxed().to(new TestReceiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertEquals(EXPECTED_BOXED, response);
+ }
+ });
+ // Primitive service method
+ ctx.getPrimitive().to(new TestReceiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertEquals(EXPECTED_BOXED, response);
+ }
+ });
+ // Boxed and primitive properties
+ ctx.getEntity().to(new TestReceiver<Proxy>() {
+ @Override
+ public void onSuccess(Proxy response) {
+ assertEquals(EXPECTED_BOXED, response.getBoxed());
+ assertEquals(EXPECTED, response.getPrimitive());
+ assertEquals(EXPECTED_BOOL, response.isIs());
+ assertEquals(EXPECTED_BOOL_BOXED, response.isIsBoxed());
+ assertEquals(EXPECTED_BOOL, response.hasHas());
+ assertEquals(EXPECTED_BOOL_BOXED, response.hasHasBoxed());
+ }
+ });
+ // Boxed service argument
+ ctx.checkBoxed(EXPECTED_BOXED).to(new TestReceiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ // OK
+ }
+ });
+ // Primitive service argument
+ ctx.checkPrimitive(EXPECTED).to(new TestReceiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ // OK
+ }
+ });
+ ctx.fire(new TestReceiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTest();
+ }
+ });
+ }
+
+ protected Factory createFactory() {
+ Factory toReturn = GWT.create(Factory.class);
+ toReturn.initialize(new SimpleEventBus());
+ return toReturn;
+ }
+
+ @Override
+ protected void gwtSetUp() {
+ factory = createFactory();
+ }
+
+ private Context context() {
+ return factory.context();
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/ComplexKeysTest.java b/user/test/com/google/web/bindery/requestfactory/shared/ComplexKeysTest.java
new file mode 100644
index 0000000..8c43a16
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/ComplexKeysTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+/**
+ * Tests the use of non-trivial EntityProxy and ValueProxy key types.
+ */
+public class ComplexKeysTest extends GWTTestCase {
+
+ /**
+ * The factory being tested.
+ */
+ protected interface Factory extends RequestFactory {
+ Context context();
+ }
+
+ @Service(ContextImpl.class)
+ interface Context extends RequestContext {
+ Request<DomainWithEntityKeyProxy> createEntity(String key);
+
+ Request<DomainWithValueKeyProxy> createValue(String key);
+ }
+
+ static class ContextImpl {
+ public static DomainWithEntityKey createEntity(String key) {
+ return new DomainWithEntityKey(new EntityKey(key));
+ }
+
+ public static DomainWithValueKey createValue(String key) {
+ return new DomainWithValueKey(new ValueKey(key));
+ }
+ }
+
+ static class DomainWithEntityKey {
+
+ public static DomainWithEntityKey findDomainWithEntityKey(EntityKey key) {
+ return new DomainWithEntityKey(key);
+ }
+
+ private final EntityKey key;
+
+ public DomainWithEntityKey(EntityKey key) {
+ if (key == null) {
+ throw new IllegalArgumentException("Key key");
+ }
+ this.key = key;
+ }
+
+ public EntityKey getId() {
+ return key;
+ }
+
+ public Integer getVersion() {
+ return 0;
+ }
+ }
+
+ @ProxyFor(DomainWithEntityKey.class)
+ interface DomainWithEntityKeyProxy extends EntityProxy {
+ EntityKeyProxy getId();
+
+ EntityProxyId<DomainWithEntityKeyProxy> stableId();
+ }
+
+ static class DomainWithValueKey {
+ public static DomainWithValueKey create(String key) {
+ return new DomainWithValueKey(new ValueKey(key));
+ }
+
+ public static DomainWithValueKey findDomainWithValueKey(ValueKey key) {
+ return new DomainWithValueKey(key);
+ }
+
+ private final ValueKey key;
+
+ public DomainWithValueKey(ValueKey key) {
+ if (key == null) {
+ throw new IllegalArgumentException("Key key");
+ }
+ this.key = key;
+ }
+
+ public ValueKey getId() {
+ return key;
+ }
+
+ public Integer getVersion() {
+ return 0;
+ }
+ }
+
+ @ProxyFor(DomainWithValueKey.class)
+ interface DomainWithValueKeyProxy extends EntityProxy {
+ ValueKeyProxy getId();
+
+ EntityProxyId<DomainWithValueKeyProxy> stableId();
+ }
+
+ static class EntityKey {
+ public static EntityKey findEntityKey(String key) {
+ return new EntityKey(key);
+ }
+
+ private final String key;
+
+ public EntityKey(String key) {
+ assertEquals("key", key);
+ this.key = key;
+ }
+
+ public String getId() {
+ return key;
+ }
+
+ public Integer getVersion() {
+ return 0;
+ }
+ }
+
+ @ProxyFor(EntityKey.class)
+ interface EntityKeyProxy extends EntityProxy {
+ }
+
+ static class ValueKey {
+ private String key;
+
+ public ValueKey() {
+ }
+
+ public ValueKey(String key) {
+ setKey(key);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ assertEquals("key", key);
+ this.key = key;
+ }
+ }
+
+ @ProxyFor(ValueKey.class)
+ interface ValueKeyProxy extends ValueProxy {
+ String getKey();
+ }
+
+ private static final int TEST_DELAY = 5000;
+ private Factory factory;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ public void testEntityKey() {
+ delayTestFinish(TEST_DELAY);
+ context().createEntity("key").fire(
+ new Receiver<DomainWithEntityKeyProxy>() {
+ @Override
+ public void onSuccess(final DomainWithEntityKeyProxy response) {
+ factory.find(response.stableId()).fire(
+ new Receiver<DomainWithEntityKeyProxy>() {
+ @Override
+ public void onSuccess(DomainWithEntityKeyProxy found) {
+ assertEquals(response.stableId(), found.stableId());
+ finishTest();
+ }
+ });
+ }
+ });
+ }
+
+ public void testValueKey() {
+ delayTestFinish(TEST_DELAY);
+ context().createValue("key").fire(new Receiver<DomainWithValueKeyProxy>() {
+ @Override
+ public void onSuccess(final DomainWithValueKeyProxy response) {
+ factory.find(response.stableId()).fire(
+ new Receiver<DomainWithValueKeyProxy>() {
+ @Override
+ public void onSuccess(DomainWithValueKeyProxy found) {
+ assertEquals(response.stableId(), found.stableId());
+ finishTest();
+ }
+ });
+ }
+ });
+ }
+
+ protected Factory createFactory() {
+ Factory toReturn = GWT.create(Factory.class);
+ toReturn.initialize(new SimpleEventBus());
+ return toReturn;
+ }
+
+ @Override
+ protected void gwtSetUp() {
+ factory = createFactory();
+ }
+
+ private Context context() {
+ return factory.context();
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequest.java
new file mode 100644
index 0000000..376eeb5
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.InstanceService;
+import com.google.web.bindery.requestfactory.server.InstanceServiceLocator;
+
+/**
+ * Used to test the ServiceLocator extension hook.
+ */
+@Service(value = InstanceService.class, locator = InstanceServiceLocator.class)
+public interface InstanceServiceRequest extends RequestContext {
+ Request<Integer> add(int value);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequestByName.java b/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequestByName.java
new file mode 100644
index 0000000..648abee
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/InstanceServiceRequestByName.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Used to test the ServiceLocator extension hook.
+ */
+@ServiceName(value = "com.google.web.bindery.requestfactory.server.InstanceService",
+ locator = "com.google.web.bindery.requestfactory.server.InstanceServiceLocator")
+public interface InstanceServiceRequestByName extends RequestContext {
+ Request<Integer> add(int value);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/LocatorTest.java b/user/test/com/google/web/bindery/requestfactory/shared/LocatorTest.java
new file mode 100644
index 0000000..632ffb7
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/LocatorTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+/**
+ * Tests the use of Locator objects.
+ */
+public class LocatorTest extends GWTTestCase {
+ /**
+ * The locator being tested.
+ */
+ public static class DomainLocator extends Locator<Domain, String> {
+ @Override
+ public Domain create(Class<? extends Domain> clazz) {
+ assertEquals(Domain.class, clazz);
+ return new Domain();
+ }
+
+ @Override
+ public Domain find(Class<? extends Domain> clazz, String id) {
+ assertEquals(ID, id);
+ return Domain.INSTANCE;
+ }
+
+ @Override
+ public Class<Domain> getDomainType() {
+ return Domain.class;
+ }
+
+ @Override
+ public String getId(Domain domainObject) {
+ return ID;
+ }
+
+ @Override
+ public Class<String> getIdType() {
+ return String.class;
+ }
+
+ @Override
+ public Object getVersion(Domain domainObject) {
+ return 0;
+ }
+ }
+
+ /**
+ * The factory under test.
+ */
+ protected interface Factory extends RequestFactory {
+ Context context();
+ }
+
+ @Service(ContextImpl.class)
+ interface Context extends RequestContext {
+ Request<DomainProxy> getDomain();
+ }
+
+ static class ContextImpl {
+ public static Domain getDomain() {
+ return Domain.INSTANCE;
+ }
+ }
+
+ static class Domain {
+ static final Domain INSTANCE = new Domain();
+ }
+
+ @ProxyFor(value = Domain.class, locator = DomainLocator.class)
+ interface DomainProxy extends EntityProxy {
+ EntityProxyId<DomainProxy> stableId();
+ };
+
+ private static final String ID = "DomainId";
+ private static final int TEST_DELAY = 5000;
+
+ private Factory factory;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ public void testLocator() {
+ delayTestFinish(TEST_DELAY);
+ context().getDomain().fire(new Receiver<DomainProxy>() {
+ @Override
+ public void onSuccess(final DomainProxy response) {
+ factory.find(response.stableId()).fire(new Receiver<DomainProxy>() {
+ @Override
+ public void onSuccess(DomainProxy found) {
+ assertEquals(response.stableId(), found.stableId());
+ finishTest();
+ }
+ });
+ }
+ });
+ }
+
+ protected Factory createFactory() {
+ Factory toReturn = GWT.create(Factory.class);
+ toReturn.initialize(new SimpleEventBus());
+ return toReturn;
+ }
+
+ @Override
+ protected void gwtSetUp() {
+ factory = createFactory();
+ }
+
+ private Context context() {
+ return factory.context();
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedByRequestContextMethod.java b/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedByRequestContextMethod.java
new file mode 100644
index 0000000..8df9169
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedByRequestContextMethod.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared;
+
+/**
+ * Ensures that enums used only by a method in a RequestContext are property
+ * emitted into the EnumMap used to encode values.
+ */
+public enum OnlyUsedByRequestContextMethod {
+ FOO, BAR;
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedInListProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedInListProxy.java
new file mode 100644
index 0000000..73460ff
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/OnlyUsedInListProxy.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.SimpleFoo;
+
+/**
+ * This proxy type should only be used as the parameterization of a list type to
+ * ensure that proxy types reachable only through a parameterization can be
+ * created by a RequestContext.
+ */
+@ProxyFor(SimpleFoo.class)
+public interface OnlyUsedInListProxy extends ValueProxy {
+ String getUserName();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/ServiceInheritanceTest.java b/user/test/com/google/web/bindery/requestfactory/shared/ServiceInheritanceTest.java
new file mode 100644
index 0000000..ee604b9
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/ServiceInheritanceTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2011 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.web.bindery.requestfactory.shared;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.web.bindery.event.shared.SimpleEventBus;
+
+/**
+ * Tests the ability of instance services to inherit methods
+ * from a base class.
+ */
+public class ServiceInheritanceTest extends GWTTestCase {
+
+ /**
+ * ServiceLocator that returns the base class or subclass implementation
+ * specified in the @{@link Service} annotation.
+ */
+ public static class SumServiceLocator implements ServiceLocator {
+ public Object getInstance(Class<?> clazz) {
+ if (BaseImpl.class.equals(clazz)) {
+ return new BaseImpl();
+ } else if (SubclassImpl.class.equals(clazz)) {
+ return new SubclassImpl();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * The factory under test.
+ */
+ protected interface Factory extends RequestFactory {
+ SumServiceBase baseContext();
+ SumServiceSub subContext();
+ }
+
+ /**
+ * Specifies the base class implementation.
+ */
+ @Service(value = BaseImpl.class, locator = SumServiceLocator.class)
+ interface SumServiceBase extends RequestContext {
+ Request<Integer> add(int n);
+ Request<Integer> subtract(int n);
+ }
+
+ /**
+ * Specifies the subclass implementation.
+ */
+ @Service(value = SubclassImpl.class, locator = SumServiceLocator.class)
+ interface SumServiceSub extends RequestContext {
+ Request<Integer> add(int n);
+ Request<Integer> subtract(int n);
+ }
+
+ /**
+ * Base implementation of {@link SumServiceBase}.
+ */
+ static class BaseImpl {
+ protected int initialValue;
+
+ public BaseImpl() {
+ initialValue = 5;
+ }
+
+ public Integer add(int n) {
+ return initialValue + n;
+ }
+
+ public Integer subtract(int n) {
+ return initialValue - n;
+ }
+ }
+
+ /**
+ * Subclass implementation of {@link SumServiceSub}
+ * inherits the add() method.
+ */
+ static class SubclassImpl extends BaseImpl {
+ public SubclassImpl() {
+ /*
+ * Init with a different value to distinguish between base & subclass
+ * implementations in the tests
+ */
+ initialValue = 8;
+ }
+
+ @Override
+ public Integer subtract(int n) {
+ return 0;
+ }
+ }
+
+ private static final int TEST_DELAY = 5000;
+
+ private Factory factory;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
+ }
+
+ /**
+ * Call a method inherited from a base class.
+ */
+ public void testInvokeInheritedMethod() {
+ delayTestFinish(TEST_DELAY);
+ factory.subContext().add(13).fire(new Receiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertEquals((Integer) 21, response);
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * Call a method implemented in a base class.
+ */
+ public void testInvokeMethodOnBaseClass() {
+ delayTestFinish(TEST_DELAY);
+ factory.baseContext().add(13).fire(new Receiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertEquals((Integer) 18, response);
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * Call a method overridden in a subclass.
+ */
+ public void testInvokeOverriddenMethod() {
+ delayTestFinish(TEST_DELAY);
+ factory.subContext().subtract(3).fire(new Receiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ assertEquals((Integer) 0, response);
+ finishTest();
+ }
+ });
+ }
+
+ protected Factory createFactory() {
+ Factory toReturn = GWT.create(Factory.class);
+ toReturn.initialize(new SimpleEventBus());
+ return toReturn;
+ }
+
+ @Override
+ protected void gwtSetUp() {
+ factory = createFactory();
+ }
+
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarProxy.java
new file mode 100644
index 0000000..2995e06
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarProxy.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * A simple entity used for testing. Has an int field and date field. Add other
+ * data types as their support gets built in.
+ */
+@ProxyForName("com.google.web.bindery.requestfactory.server.SimpleBar")
+public interface SimpleBarProxy extends EntityProxy {
+ Boolean getFindFails();
+
+ Boolean getUnpersisted();
+
+ /*
+ * NB: The lack of a getId() here is intentional, to ensure that the system
+ * does not assume that the id property is available to the client.
+ */
+
+ String getUserName();
+
+ void setFindFails(Boolean fails);
+
+ void setUnpersisted(Boolean b);
+
+ void setUserName(String userName);
+
+ EntityProxyId<SimpleBarProxy> stableId();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarRequest.java
new file mode 100644
index 0000000..3d9bb6f
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleBarRequest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Do nothing test interface.
+ */
+@ServiceName("com.google.web.bindery.requestfactory.server.SimpleBar")
+public interface SimpleBarRequest extends RequestContext {
+
+ Request<Long> countSimpleBar();
+
+ Request<List<SimpleBarProxy>> findAll();
+
+ Request<Set<SimpleBarProxy>> findAsSet();
+
+ Request<SimpleBarProxy> findSimpleBarById(String id);
+
+ InstanceRequest<SimpleBarProxy, Void> persist();
+
+ InstanceRequest<SimpleBarProxy, SimpleBarProxy> persistAndReturnSelf();
+
+ Request<Void> reset();
+
+ Request<SimpleBarProxy> returnFirst(List<SimpleBarProxy> proxy);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleEnum.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleEnum.java
new file mode 100644
index 0000000..b338e5f
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleEnum.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Test enum class.
+ */
+public enum SimpleEnum {
+ FOO, BAR
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooProxy.java
new file mode 100644
index 0000000..9925dde
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooProxy.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.SimpleFoo;
+
+/**
+ * A simple extension of AbstractFooProxy with Long id.
+ */
+@ProxyFor(SimpleFoo.class)
+public interface SimpleFooProxy extends BaseFooProxy {
+ SimpleFooProxy getFooField();
+
+ Long getId();
+
+ void setFooField(SimpleFooProxy fooField);
+
+ EntityProxyId<SimpleFooProxy> stableId();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooRequest.java
new file mode 100644
index 0000000..ba305a2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleFooRequest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Do nothing test interface.
+ */
+@Service(com.google.web.bindery.requestfactory.server.SimpleFoo.class)
+public interface SimpleFooRequest extends RequestContext {
+ Request<Integer> add(Integer a, int b);
+
+ Request<Long> countSimpleFoo();
+
+ InstanceRequest<SimpleFooProxy, Long> countSimpleFooWithUserNameSideEffect();
+
+ InstanceRequest<SimpleFooProxy, Void> deleteBar();
+
+ Request<SimpleFooProxy> echo(SimpleFooProxy proxy);
+
+ Request<SimpleFooProxy> echoComplex(SimpleFooProxy fooProxy,
+ SimpleBarProxy barProxy);
+
+ Request<SimpleFooProxy> fetchDoubleReference();
+
+ Request<List<SimpleFooProxy>> findAll();
+
+ Request<SimpleFooProxy> findSimpleFooById(Long id);
+
+ Request<List<Integer>> getNumberList();
+
+ Request<Set<Integer>> getNumberSet();
+
+ Request<SimpleFooProxy> getSimpleFooWithNullVersion();
+
+ Request<SimpleFooProxy> getSimpleFooWithSubPropertyCollection();
+
+ Request<SimpleFooProxy> getTripletReference();
+
+ Request<SimpleFooProxy> getUnpersistedInstance();
+
+ InstanceRequest<SimpleFooProxy, String> hello(SimpleBarProxy proxy);
+
+ InstanceRequest<SimpleFooProxy, Void> persist();
+
+ InstanceRequest<SimpleFooProxy, SimpleFooProxy> persistAndReturnSelf();
+
+ InstanceRequest<SimpleFooProxy, SimpleFooProxy> persistCascadingAndReturnSelf();
+
+ Request<Void> pleaseCrash(Integer crashIf42or43);
+
+ Request<List<BigDecimal>> processBigDecimalList(List<BigDecimal> values);
+
+ Request<List<BigInteger>> processBigIntegerList(List<BigInteger> values);
+
+ Request<Boolean> processBooleanList(List<Boolean> values);
+
+ Request<List<Date>> processDateList(List<Date> values);
+
+ Request<SimpleEnum> processEnumList(List<SimpleEnum> values);
+
+ InstanceRequest<SimpleFooProxy, String> processList(
+ List<SimpleFooProxy> values);
+
+ Request<String> processString(String value);
+
+ Request<Void> receiveEnum(OnlyUsedByRequestContextMethod value);
+
+ InstanceRequest<SimpleFooProxy, Void> receiveNull(String value);
+
+ Request<Void> receiveNullList(List<SimpleFooProxy> value);
+
+ Request<Void> receiveNullSimpleFoo(SimpleFooProxy value);
+
+ Request<Void> receiveNullString(String value);
+
+ Request<Void> receiveNullValueInEntityList(List<SimpleFooProxy> value);
+
+ Request<Void> receiveNullValueInIntegerList(List<Integer> value);
+
+ Request<Void> receiveNullValueInStringList(List<String> value);
+
+ Request<Void> reset();
+
+ Request<List<SimpleFooProxy>> returnNullList();
+
+ Request<SimpleFooProxy> returnNullSimpleFoo();
+
+ Request<String> returnNullString();
+
+ Request<Void> returnOnlyUsedInParameterization(
+ List<OnlyUsedInListProxy> values);
+
+ Request<SimpleFooProxy> returnSimpleFooSubclass();
+
+ Request<SimpleValueProxy> returnValueProxy();
+
+ InstanceRequest<SimpleFooProxy, Integer> sum(List<Integer> values);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleRequestFactory.java
new file mode 100644
index 0000000..63944e2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleRequestFactory.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Simple RequestFactory interface with two domain objects, and our standard
+ * UserInformation and Logging services.
+ */
+public interface SimpleRequestFactory extends BasicRequestFactory {
+
+ InstanceServiceRequest instanceServiceRequest();
+
+ InstanceServiceRequestByName instanceServiceRequestByName();
+
+ SimpleBarRequest simpleBarRequest();
+
+ SimpleFooRequest simpleFooRequest();
+
+ SimpleValueContext simpleValueContext();
+
+ UnicodeTestRequest unicodeTestRequest();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueContext.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueContext.java
new file mode 100644
index 0000000..9fdbaf2
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueContext.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.SimpleValue;
+
+/**
+ * Tests instance method invocations on value objects.
+ */
+@Service(SimpleValue.class)
+public interface SimpleValueContext extends RequestContext {
+ InstanceRequest<SimpleValueProxy, String> getString();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueProxy.java b/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueProxy.java
new file mode 100644
index 0000000..7ff4a09
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/SimpleValueProxy.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.web.bindery.requestfactory.server.SimpleValue;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A proxy for a non-addressable value object.
+ */
+@ProxyFor(value = SimpleValue.class)
+public interface SimpleValueProxy extends ValueProxy {
+ Date getDate();
+
+ int getNumber();
+
+ String getShouldBeNull();
+
+ SimpleFooProxy getSimpleFoo();
+
+ List<SimpleValueProxy> getSimpleValue();
+
+ String getString();
+
+ void setDate(Date value);
+
+ void setNumber(int value);
+
+ void setShouldBeNull(String value);
+
+ void setSimpleFoo(SimpleFooProxy value);
+
+ void setSimpleValue(List<SimpleValueProxy> value);
+
+ void setString(String value);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
new file mode 100644
index 0000000..5662c40
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Just to test the
+ * {@link com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator} code.
+ */
+@Service(com.google.web.bindery.requestfactory.server.SimpleFoo.class)
+public interface TestFooPolymorphicRequest extends RequestContext {
+ <P extends SimpleFooProxy> Request<P> echo(P proxy);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java
new file mode 100644
index 0000000..6ced0f6
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+/**
+ * Creates TestFooPolymorphicRequest.
+ */
+public interface TestRequestFactory extends RequestFactory {
+ TestFooPolymorphicRequest testFooPolymorphicRequest();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/UnicodeTestRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/UnicodeTestRequest.java
new file mode 100644
index 0000000..10fe3c9
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/UnicodeTestRequest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared;
+
+import com.google.gwt.user.client.rpc.UnicodeEscapingTest;
+
+/**
+ * Provides access to the static test methods in {@link UnicodeEscapingTes}.
+ */
+@Service(UnicodeEscapingTest.class)
+public interface UnicodeTestRequest extends RequestContext {
+ Request<String> getStringContainingCharacterRange(int start, int end);
+
+ Request<Void> verifyStringContainingCharacterRange(int start, int end,
+ String str);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyIdTest.java b/user/test/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyIdTest.java
new file mode 100644
index 0000000..249c658
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/shared/impl/SimpleEntityProxyIdTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.shared.impl;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
+import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link SimpleEntityProxyId}.
+ */
+public class SimpleEntityProxyIdTest extends TestCase {
+
+ public void testEquality() {
+ SimpleEntityProxyId<EntityProxy> client1 = id(EntityProxy.class, 1);
+ // equal to self
+ assertTrue(isStable(client1, client1));
+ // equal to identical client id
+ assertTrue(isStable(client1, id(EntityProxy.class, 1)));
+
+ // Persist and check again
+ client1.setServerId("server1");
+ // equal to self
+ assertTrue(isStable(client1, client1));
+ // equal to identical client id
+ assertTrue(isStable(client1, id(EntityProxy.class, 1)));
+
+ SimpleEntityProxyId<EntityProxy> server1 = id(EntityProxy.class, "server1");
+ assertTrue(isStable(server1, id(EntityProxy.class, "server1")));
+
+ /*
+ * Compare a server-only id the persisted client id, this should be false
+ * since the hashcodes would vary.
+ */
+ assertFalse(isStable(client1, server1));
+ }
+
+ public void testInequality() {
+ assertFalse(isStable(id(EntityProxy.class, 1), id(EntityProxy.class, 2)));
+
+ assertFalse(isStable(id(EntityProxy.class, "server1"),
+ id(EntityProxy.class, "server2")));
+
+ // Same client-side id, but different types
+ assertFalse(isStable(id(SimpleFooProxy.class, 1),
+ id(SimpleBarProxy.class, 1)));
+
+ // Same server id, but different types
+ assertFalse(isStable(id(SimpleFooProxy.class, "server1"),
+ id(SimpleBarProxy.class, "server1")));
+ }
+
+ private <T extends EntityProxy> SimpleEntityProxyId<T> id(Class<T> clazz,
+ int clientId) {
+ return new SimpleEntityProxyId<T>(clazz, clientId);
+ }
+
+ private <T extends EntityProxy> SimpleEntityProxyId<T> id(Class<T> clazz,
+ String serverId) {
+ return new SimpleEntityProxyId<T>(clazz, serverId);
+ }
+
+ /**
+ * Assert that the id behaves with the stable sematics that are desired for
+ * client code.
+ */
+ private boolean isStable(SimpleEntityProxyId<?> a, SimpleEntityProxyId<?> b) {
+ return a.equals(b) && b.equals(a) && a.hashCode() == b.hashCode();
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java b/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
new file mode 100644
index 0000000..5b7d5ed
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 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.web.bindery.requestfactory.vm;
+
+import com.google.web.bindery.requestfactory.server.BoxesAndPrimitivesJreTest;
+import com.google.web.bindery.requestfactory.server.ComplexKeysJreTest;
+import com.google.web.bindery.requestfactory.server.FindServiceJreTest;
+import com.google.web.bindery.requestfactory.server.LocatorJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryExceptionPropagationJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryJreTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryUnicodeEscapingJreTest;
+import com.google.web.bindery.requestfactory.server.ServiceInheritanceJreTest;
+import com.google.web.bindery.requestfactory.server.ServiceLocatorTest;
+import com.google.web.bindery.requestfactory.shared.impl.SimpleEntityProxyIdTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Suite of RequestFactory tests that require the JRE (without GWT).
+ * <p>
+ * Note: these tests require gwt-user src on the classpath. To run in
+ * Eclipse, use Google Plugin for Eclipse to run as a GWT JUnit test
+ * or edit the Eclipse launch config and add the src folder to the classpath
+ * (click Classpath tab, User entries, Advanced..., Add folders)
+ */
+public class RequestFactoryJreSuite {
+ public static Test suite() {
+ TestSuite suite = new TestSuite(
+ "requestfactory package tests that require the JRE");
+ suite.addTestSuite(BoxesAndPrimitivesJreTest.class);
+ suite.addTestSuite(ComplexKeysJreTest.class);
+ suite.addTestSuite(FindServiceJreTest.class);
+ suite.addTestSuite(LocatorJreTest.class);
+ suite.addTestSuite(RequestFactoryExceptionPropagationJreTest.class);
+ suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
+ suite.addTestSuite(RequestFactoryJreTest.class);
+ suite.addTestSuite(RequestFactoryUnicodeEscapingJreTest.class);
+ suite.addTestSuite(ServiceInheritanceJreTest.class);
+ suite.addTestSuite(ServiceLocatorTest.class);
+ suite.addTestSuite(SimpleEntityProxyIdTest.class);
+
+ return suite;
+ }
+
+ /**
+ * Used to test the JVM-only client package.
+ */
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+}