Suport polymorphic return and parameter values in RequestFactory.
Issue 5367.
Review at http://gwt-code-reviews.appspot.com/1453811
Patch by: bobv
Review by: rjrjr
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10317 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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
index 718eaed..7bbd30a 100644
--- a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/RequestFactoryGenerator.java
@@ -43,6 +43,7 @@
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.EntityProxyModel.Type;
+import com.google.web.bindery.requestfactory.gwt.rebind.model.HasExtraTypes;
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;
@@ -84,6 +85,24 @@
}
@Override
+ public boolean visit(ContextMethod x) {
+ visitExtraTypes(x);
+ return true;
+ }
+
+ @Override
+ public boolean visit(EntityProxyModel x) {
+ visitExtraTypes(x);
+ return true;
+ }
+
+ @Override
+ public boolean visit(RequestFactoryModel x) {
+ visitExtraTypes(x);
+ return true;
+ }
+
+ @Override
void examineTypeOnce(JClassType type) {
// Need this to handle List<Foo>, Map<Foo>
JParameterizedType parameterized = type.isParameterized();
@@ -99,6 +118,14 @@
}
peer.accept(this);
}
+
+ void visitExtraTypes(HasExtraTypes x) {
+ if (x.getExtraTypes() != null) {
+ for (EntityProxyModel extra : x.getExtraTypes()) {
+ extra.accept(this);
+ }
+ }
+ }
}
/**
@@ -235,6 +262,7 @@
@Override
public void endVisit(EntityProxyModel x) {
models.add(x);
+ models.addAll(x.getSuperProxyTypes());
}
});
return models;
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
index 4de5093..efa6b4c 100644
--- 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
@@ -26,7 +26,7 @@
/**
* Represents a service endpoint.
*/
-public class ContextMethod implements AcceptsModelVisitor {
+public class ContextMethod implements AcceptsModelVisitor, HasExtraTypes {
/**
* Builds a {@link ContextMethod}.
@@ -42,23 +42,31 @@
}
}
- public void setDeclaredMethod(JMethod method) {
+ public Builder 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;
+ toReturn.simpleSourceName = returnClass.getName().replace('.', '_') + "Impl";
+ toReturn.dialect =
+ returnClass.isAnnotationPresent(JsonRpcService.class) ? Dialect.JSON_RPC
+ : Dialect.STANDARD;
+ return this;
}
- public void setRequestMethods(List<RequestMethod> requestMethods) {
+ public Builder setExtraTypes(List<EntityProxyModel> extraTypes) {
+ toReturn.extraTypes = extraTypes;
+ return this;
+ }
+
+ public Builder setRequestMethods(List<RequestMethod> requestMethods) {
toReturn.requestMethods = requestMethods;
+ return this;
}
}
private Dialect dialect;
+ private List<EntityProxyModel> extraTypes;
private String interfaceName;
private String methodName;
private String packageName;
@@ -81,6 +89,10 @@
return dialect;
}
+ public List<EntityProxyModel> getExtraTypes() {
+ return Collections.unmodifiableList(extraTypes);
+ }
+
/**
* The qualified source name of the RequestContext sub-interface (i.e., the
* return type of the method declaration).
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
index 87c2eac..76756a4 100644
--- 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
@@ -21,7 +21,7 @@
/**
* Represents an EntityProxy subtype.
*/
-public class EntityProxyModel implements AcceptsModelVisitor {
+public class EntityProxyModel implements AcceptsModelVisitor, HasExtraTypes {
/**
* Builds {@link EntityProxyModel}.
*/
@@ -29,6 +29,9 @@
private EntityProxyModel toReturn = new EntityProxyModel();
public EntityProxyModel build() {
+ if (toReturn.superProxyTypes == null) {
+ toReturn.superProxyTypes = Collections.emptyList();
+ }
try {
return toReturn;
} finally {
@@ -44,21 +47,35 @@
return toReturn;
}
- public void setQualifiedBinaryName(String qualifiedBinaryName) {
- toReturn.qualifiedBinaryName = qualifiedBinaryName;
+ public Builder setExtraTypes(List<EntityProxyModel> extraTypes) {
+ toReturn.extraTypes = extraTypes;
+ return this;
}
- public void setQualifiedSourceName(String name) {
+ public Builder setQualifiedBinaryName(String qualifiedBinaryName) {
+ toReturn.qualifiedBinaryName = qualifiedBinaryName;
+ return this;
+ }
+
+ public Builder setQualifiedSourceName(String name) {
assert !name.contains(" ");
toReturn.qualifiedSourceName = name;
+ return this;
}
- public void setRequestMethods(List<RequestMethod> requestMethods) {
+ public Builder setRequestMethods(List<RequestMethod> requestMethods) {
toReturn.requestMethods = requestMethods;
+ return this;
}
- public void setType(Type type) {
+ public Builder setSuperProxyTypes(List<EntityProxyModel> superTypes) {
+ toReturn.superProxyTypes = superTypes;
+ return this;
+ }
+
+ public Builder setType(Type type) {
toReturn.type = type;
+ return this;
}
}
@@ -70,9 +87,11 @@
ENTITY, VALUE
}
+ private List<EntityProxyModel> extraTypes;
private String qualifiedBinaryName;
private String qualifiedSourceName;
private List<RequestMethod> requestMethods;
+ private List<EntityProxyModel> superProxyTypes;
private Type type;
private EntityProxyModel() {
@@ -87,6 +106,10 @@
visitor.endVisit(this);
}
+ public List<EntityProxyModel> getExtraTypes() {
+ return Collections.unmodifiableList(extraTypes);
+ }
+
public String getQualifiedBinaryName() {
return qualifiedBinaryName;
}
@@ -99,6 +122,13 @@
return Collections.unmodifiableList(requestMethods);
}
+ /**
+ * Returns the proxy types to which the EntityProxyModel is assignable.
+ */
+ public List<EntityProxyModel> getSuperProxyTypes() {
+ return superProxyTypes;
+ }
+
public Type getType() {
return type;
}
diff --git a/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/HasExtraTypes.java b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/HasExtraTypes.java
new file mode 100644
index 0000000..6cd8a68
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/gwt/rebind/model/HasExtraTypes.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.requestfactory.gwt.rebind.model;
+
+import java.util.List;
+
+/**
+ * Capability interface for types that can be annotated with
+ * {@link com.google.web.bindery.requestfactory.shared.ExtraTypes}.
+ */
+public interface HasExtraTypes {
+ List<EntityProxyModel> getExtraTypes();
+}
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
index dc9a23c..3f08de3 100644
--- 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
@@ -15,8 +15,6 @@
*/
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;
@@ -26,9 +24,13 @@
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.autobean.gwt.rebind.model.JBeanMethod;
+import com.google.web.bindery.autobean.shared.Splittable;
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.BaseProxy;
import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ExtraTypes;
import com.google.web.bindery.requestfactory.shared.InstanceRequest;
import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
import com.google.web.bindery.requestfactory.shared.JsonRpcService;
@@ -46,6 +48,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,18 +56,16 @@
/**
* Represents a RequestFactory interface declaration.
*/
-public class RequestFactoryModel implements AcceptsModelVisitor {
+public class RequestFactoryModel implements AcceptsModelVisitor, HasExtraTypes {
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 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) {
@@ -74,6 +75,7 @@
private final JClassType collectionInterface;
private final List<ContextMethod> contextMethods = new ArrayList<ContextMethod>();
private final JClassType entityProxyInterface;
+ private final List<EntityProxyModel> extraTypes;
private final JClassType factoryType;
private final JClassType instanceRequestInterface;
private final JClassType listInterface;
@@ -83,11 +85,13 @@
/**
* This map prevents cyclic type dependencies from overflowing the stack.
*/
- private final Map<JClassType, EntityProxyModel.Builder> peerBuilders = new HashMap<JClassType, EntityProxyModel.Builder>();
+ 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 final Map<JClassType, EntityProxyModel> peers =
+ new LinkedHashMap<JClassType, EntityProxyModel>();
private boolean poisoned;
private final JClassType requestContextInterface;
private final JClassType requestFactoryInterface;
@@ -114,6 +118,7 @@
splittableType = oracle.findType(Splittable.class.getCanonicalName());
valueProxyInterface = oracle.findType(ValueProxy.class.getCanonicalName());
+ extraTypes = checkExtraTypes(factoryType, false);
for (JMethod method : factoryType.getOverridableMethods()) {
if (method.getEnclosingType().equals(requestFactoryInterface)) {
// Ignore methods defined an RequestFactory itself
@@ -126,10 +131,8 @@
}
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",
+ 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;
@@ -162,6 +165,14 @@
return Collections.unmodifiableCollection(peers.values());
}
+ /**
+ * These extra types will have already been added to the extra types for each
+ * {@link ContextMethod} in the model.
+ */
+ public List<EntityProxyModel> getExtraTypes() {
+ return Collections.unmodifiableList(extraTypes);
+ }
+
public JClassType getFactoryType() {
return factoryType;
}
@@ -185,16 +196,15 @@
/**
* Examine a RequestContext subtype to populate a ContextMethod.
*/
- private void buildContextMethod(ContextMethod.Builder contextBuilder,
- JClassType contextType) throws UnableToCompleteException {
+ 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());
+ 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;
}
@@ -208,15 +218,48 @@
RequestMethod.Builder methodBuilder = new RequestMethod.Builder();
methodBuilder.setDeclarationMethod(contextType, method);
- if (!validateContextMethodAndSetDataType(methodBuilder, method,
- jsonRpcAnnotation != null)) {
+ if (!validateContextMethodAndSetDataType(methodBuilder, method, jsonRpcAnnotation != null)) {
continue;
}
requestMethods.add(methodBuilder.build());
}
- contextBuilder.setRequestMethods(requestMethods);
+ contextBuilder.setExtraTypes(checkExtraTypes(contextType, true)).setRequestMethods(
+ requestMethods);
+ }
+
+ /**
+ * Checks type and its supertypes for {@link ExtraTypes} annotations.
+ *
+ * @param type the type to examine
+ * @param addModelExtraTypes if {@code true} the contents of the
+ * {@link #extraTypes} field will be added to the returned list.
+ */
+ private List<EntityProxyModel> checkExtraTypes(JClassType type, boolean addModelExtraTypes)
+ throws UnableToCompleteException {
+ Set<EntityProxyModel> toReturn = new LinkedHashSet<EntityProxyModel>();
+ if (addModelExtraTypes && extraTypes != null) {
+ toReturn.addAll(extraTypes);
+ }
+ for (JClassType toExamine : type.getFlattenedSupertypeHierarchy()) {
+ ExtraTypes proxyExtraTypes = toExamine.getAnnotation(ExtraTypes.class);
+ if (proxyExtraTypes != null) {
+ for (Class<? extends BaseProxy> clazz : proxyExtraTypes.value()) {
+ JClassType proxy = oracle.findType(clazz.getCanonicalName());
+ if (proxy == null) {
+ poison("Unknown class %s in @%s", clazz.getCanonicalName(), ExtraTypes.class
+ .getSimpleName());
+ } else {
+ toReturn.add(getEntityProxyType(proxy));
+ }
+ }
+ }
+ }
+ if (toReturn.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return new ArrayList<EntityProxyModel>(toReturn);
}
private void die(String message) throws UnableToCompleteException {
@@ -238,6 +281,15 @@
EntityProxyModel.Builder builder = new EntityProxyModel.Builder();
peerBuilders.put(entityProxyType, builder);
+ // Validate possible super-proxy types first
+ for (JClassType supertype : entityProxyType.getFlattenedSupertypeHierarchy()) {
+ List<EntityProxyModel> superTypes = new ArrayList<EntityProxyModel>();
+ if (supertype != entityProxyType && shouldAttemptProxyValidation(supertype)) {
+ superTypes.add(getEntityProxyType(supertype));
+ }
+ builder.setSuperProxyTypes(superTypes);
+ }
+
builder.setQualifiedBinaryName(ModelUtils.getQualifiedBaseBinaryName(entityProxyType));
builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseSourceName(entityProxyType));
if (entityProxyInterface.isAssignableFrom(entityProxyType)) {
@@ -245,9 +297,8 @@
} 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());
+ 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());
}
@@ -257,10 +308,9 @@
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());
+ 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
@@ -283,28 +333,26 @@
if (previouslySeen == null) {
duplicatePropertyGetters.put(propertyName, method);
} else {
- poison("Duplicate accessors for property %s: %s() and %s()",
- propertyName, previouslySeen.getName(), method.getName());
+ 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)) {
+ } 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) {
+ } 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());
+ 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);
+ builder.setExtraTypes(checkExtraTypes(entityProxyType, false)).setRequestMethods(
+ requestMethods);
toReturn = builder.build();
peers.put(entityProxyType, toReturn);
@@ -319,36 +367,50 @@
}
/**
+ * Returns {@code true} if the type is assignable to EntityProxy or ValueProxy
+ * and has a mapping to a domain type.
+ *
+ * @see com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator#shouldAttemptProxyValidation()
+ */
+ private boolean shouldAttemptProxyValidation(JClassType maybeProxy) {
+ if (!entityProxyInterface.isAssignableFrom(maybeProxy)
+ && !valueProxyInterface.isAssignableFrom(maybeProxy)) {
+ return false;
+ }
+ if (maybeProxy.getAnnotation(ProxyFor.class) == null
+ && maybeProxy.getAnnotation(ProxyForName.class) == null) {
+ return false;
+ }
+ return 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 {
+ 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));
+ poison(badContextReturnType(method, requestInterface, instanceRequestInterface));
return false;
}
if (instanceRequestInterface.isAssignableFrom(requestReturnType)) {
// Instance method invocation
- JClassType[] params = ModelUtils.findParameterizationOf(
- instanceRequestInterface, requestReturnType);
+ 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);
+ JClassType[] params = ModelUtils.findParameterizationOf(requestInterface, requestReturnType);
invocationReturnType = params[0];
} else {
// Unhandled return type, must be something random
- poison(badContextReturnType(method, requestInterface,
- instanceRequestInterface));
+ poison(badContextReturnType(method, requestInterface, instanceRequestInterface));
return false;
}
@@ -357,15 +419,14 @@
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;
+ 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 (JBeanMethod.SET.matches(maybeSetter) || JBeanMethod.SET_BUILDER.matches(maybeSetter)) {
if (allowSetters) {
methodBuilder.addExtraSetter(maybeSetter);
} else {
@@ -379,14 +440,13 @@
/**
* Examines a type to see if it can be transported.
*/
- private boolean validateTransportableType(
- RequestMethod.Builder methodBuilder, JType type, boolean requireObject)
- throws UnableToCompleteException {
+ 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());
+ poison("The type %s cannot be transported by RequestFactory as" + " a return type", type
+ .getQualifiedSourceName());
return false;
} else {
// Primitives always ok
@@ -394,8 +454,7 @@
}
}
- if (ModelUtils.isValueType(oracle, transportedClass)
- || splittableType.equals(transportedClass)) {
+ if (ModelUtils.isValueType(oracle, transportedClass) || splittableType.equals(transportedClass)) {
// Simple values, like Integer and String
methodBuilder.setValueType(true);
} else if (entityProxyInterface.isAssignableFrom(transportedClass)
@@ -414,14 +473,13 @@
} 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());
+ 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];
+ JClassType elementType =
+ ModelUtils.findParameterizationOf(collectionInterface, transportedClass)[0];
methodBuilder.setCollectionElementType(elementType);
validateTransportableType(methodBuilder, elementType, requireObject);
} else if (mapInterface.isAssignableFrom(transportedClass)) {
@@ -433,13 +491,12 @@
if (mapInterface.equals(parameterized.getBaseType())) {
methodBuilder.setCollectionType(CollectionType.MAP);
} else {
- poison("Requests that return maps may be declared with" + " %s only",
- mapInterface.getQualifiedSourceName());
+ 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[] params = ModelUtils.findParameterizationOf(mapInterface, transportedClass);
JClassType keyType = params[0];
JClassType valueType = params[1];
methodBuilder.setMapKeyType(keyType);
@@ -448,8 +505,7 @@
validateTransportableType(methodBuilder, valueType, requireObject);
} else {
// Unknown type, fail
- poison("Invalid Request parameterization %s",
- transportedClass.getQualifiedSourceName());
+ poison("Invalid Request parameterization %s", transportedClass.getQualifiedSourceName());
return false;
}
methodBuilder.setDataType(transportedClass);
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Deobfuscator.java b/user/src/com/google/web/bindery/requestfactory/server/Deobfuscator.java
index 683c6a6..448ded7 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/Deobfuscator.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/Deobfuscator.java
@@ -97,25 +97,30 @@
* Returns a method descriptor that should be invoked on the service object.
*/
public String getDomainMethodDescriptor(String operation) {
- return operationData.get(new OperationKey(operation)).getDomainMethodDescriptor();
+ OperationData data = operationData.get(new OperationKey(operation));
+ return data == null ? null : data.getDomainMethodDescriptor();
}
public String getRequestContext(String operation) {
- return operationData.get(new OperationKey(operation)).getRequestContext();
+ OperationData data = operationData.get(new OperationKey(operation));
+ return data == null ? null : data.getRequestContext();
}
public String getRequestContextMethodDescriptor(String operation) {
- return operationData.get(new OperationKey(operation)).getClientMethodDescriptor();
+ OperationData data = operationData.get(new OperationKey(operation));
+ return data == null ? null : data.getClientMethodDescriptor();
}
public String getRequestContextMethodName(String operation) {
- return operationData.get(new OperationKey(operation)).getMethodName();
+ OperationData data = operationData.get(new OperationKey(operation));
+ return data == null ? null : data.getMethodName();
}
/**
* Returns a type's binary name based on an obfuscated token.
*/
public String getTypeFromToken(String token) {
- return typeTokens.get(token).getClassName();
+ Type type = typeTokens.get(token);
+ return type == null ? null : type.getClassName();
}
}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
index b0933bc..9d19c99 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -31,6 +31,7 @@
import com.google.web.bindery.autobean.shared.ValueCodex;
import com.google.web.bindery.requestfactory.shared.BaseProxy;
import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ExtraTypes;
import com.google.web.bindery.requestfactory.shared.InstanceRequest;
import com.google.web.bindery.requestfactory.shared.ProxyFor;
import com.google.web.bindery.requestfactory.shared.ProxyForName;
@@ -48,6 +49,7 @@
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -55,6 +57,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -358,6 +362,47 @@
}
}
+ private class ExtraTypesCollector extends EmptyVisitor {
+ private final ErrorContext logger;
+ private final Set<Type> collected = new HashSet<Type>();
+
+ public ExtraTypesCollector(ErrorContext logger) {
+ this.logger = logger;
+ logger.spam("Collecting extra types");
+ }
+
+ public Type[] exec(Type type) {
+ for (Type toExamine : getSupertypes(logger, type)) {
+ RequestFactoryInterfaceValidator.this.visit(logger, toExamine.getInternalName(), this);
+ }
+ return collected.toArray(new Type[collected.size()]);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (!desc.equals(Type.getDescriptor(ExtraTypes.class))) {
+ return null;
+ }
+
+ return new EmptyVisitor() {
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if (!"value".equals(name)) {
+ return null;
+ }
+ return new EmptyVisitor() {
+ @Override
+ public void visit(String name, Object value) {
+ collected.add((Type) value);
+ }
+ };
+ }
+
+ };
+ }
+ }
+
/**
* Collects information about domain objects. This visitor is intended to be
* iteratively applied to collect all methods in a type hierarchy.
@@ -626,7 +671,8 @@
* 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>>();
+ private final Map<Type, SortedSet<Type>> domainToClientType =
+ new HashMap<Type, SortedSet<Type>>();
/**
* The type {@link EntityProxy}.
*/
@@ -650,6 +696,20 @@
*/
private final Map<Type, Set<RFMethod>> methodsInHierarchy = new HashMap<Type, Set<RFMethod>>();
/**
+ * Not static because it depends on {@link #parentLogger}.
+ */
+ private final Comparator<Type> typeNameComparator = new Comparator<Type>() {
+ @Override
+ public int compare(Type a, Type b) {
+ if (isAssignable(parentLogger, a, b)) {
+ return 1;
+ } else if (isAssignable(parentLogger, b, a)) {
+ return -1;
+ }
+ return a.getInternalName().compareTo(b.getInternalName());
+ }
+ };
+ /**
* Used to resolve obfuscated type tokens.
*/
private final Map<String, Type> typeTokens = new HashMap<String, Type>();
@@ -831,7 +891,7 @@
return;
}
- Type domainServiceType = getDomainType(logger, requestContextType);
+ Type domainServiceType = getDomainType(logger, requestContextType, false);
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
@@ -860,6 +920,7 @@
maybeCheckReferredProxies(logger, method);
}
+ maybeCheckExtraTypes(logger, requestContextType);
checkUnresolvedKeyTypes(logger);
}
@@ -903,6 +964,8 @@
validateRequestContext(returnType.getClassName());
}
}
+
+ maybeCheckExtraTypes(logger, requestFactoryType);
}
/**
@@ -942,7 +1005,7 @@
*/
String getEntityProxyTypeName(String domainTypeBinaryName, String clientTypeBinaryName) {
Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeBinaryName));
- List<Type> found = domainToClientType.get(key);
+ SortedSet<Type> found = domainToClientType.get(key);
/*
* If nothing was found look for proxyable supertypes the domain object can
@@ -970,7 +1033,7 @@
// Common case
if (found.size() == 1) {
- typeToReturn = found.get(0);
+ typeToReturn = found.first();
} else {
// Search for the first assignable type
Type assignableTo = Type.getObjectType(BinaryName.toInternalName(clientTypeBinaryName));
@@ -993,10 +1056,9 @@
clientToDomainType.put(clientType, domainType);
if (isAssignable(logger, baseProxyIntf, clientType)) {
- maybeCheckProxyType(logger, clientType);
- List<Type> list = domainToClientType.get(domainType);
+ SortedSet<Type> list = domainToClientType.get(domainType);
if (list == null) {
- list = new ArrayList<Type>();
+ list = new TreeSet<Type>(typeNameComparator);
domainToClientType.put(domainType, list);
}
list.add(clientType);
@@ -1127,9 +1189,9 @@
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]);
+ args[i] = getDomainType(logger, args[i], true);
}
- Type returnType = getDomainType(logger, clientMethod.getReturnType());
+ Type returnType = getDomainType(logger, clientMethod.getReturnType(), true);
return new Method(clientMethod.getName(), returnType, args);
}
@@ -1280,7 +1342,7 @@
* domain object type. RequestContext types will be resolved to their service
* object.
*/
- private Type getDomainType(ErrorContext logger, Type clientType) {
+ private Type getDomainType(ErrorContext logger, Type clientType, boolean requireMapping) {
Type domainType = clientToDomainType.get(clientType);
if (domainType != null) {
return domainType;
@@ -1294,8 +1356,10 @@
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());
+ if (requireMapping) {
+ 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());
@@ -1306,7 +1370,9 @@
}
}
addToDomainMap(logger, domainType, clientType);
- maybeCheckProxyType(logger, clientType);
+ if (domainType != errorType) {
+ maybeCheckProxyType(logger, clientType);
+ }
return domainType;
}
@@ -1457,6 +1523,14 @@
}
/**
+ * Examines a type for an {@link ExtraTypes} annotation and processes the
+ * referred types.
+ */
+ private void maybeCheckExtraTypes(ErrorContext logger, Type type) {
+ maybeCheckProxyType(logger, new ExtraTypesCollector(logger.setType(type)).exec(type));
+ }
+
+ /**
* Examine an array of Types and call {@link #validateEntityProxy(String)} or
* {@link #validateValueProxy(String)} if the type is a proxy.
*/
@@ -1494,6 +1568,23 @@
}
}
+ /**
+ * Returns {@code true} if the type is assignable to EntityProxy or ValueProxy
+ * and has a mapping to a domain type.
+ *
+ * @see com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModel#shouldAttemptProxyValidation()
+ */
+ private boolean shouldAttemptProxyValidation(ErrorContext logger, Type type) {
+ logger = logger.setType(type);
+ if (!isAssignable(logger, entityProxyIntf, type) && !isAssignable(logger, valueProxyIntf, type)) {
+ return false;
+ }
+ if (getDomainType(logger, type, false) == errorType) {
+ return false;
+ }
+ return true;
+ }
+
private void validateProxy(String binaryName, Type expectedType, boolean requireId) {
if (fastFail(binaryName)) {
return;
@@ -1509,8 +1600,15 @@
return;
}
+ // Check supertypes first
+ for (Type supertype : getSupertypes(logger, proxyType)) {
+ if (shouldAttemptProxyValidation(logger, supertype)) {
+ maybeCheckProxyType(logger, supertype);
+ }
+ }
+
// Find the domain type
- Type domainType = getDomainType(logger, proxyType);
+ Type domainType = getDomainType(logger, proxyType, false);
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
@@ -1539,6 +1637,7 @@
checkPropertyMethod(logger, clientPropertyMethod, domainType);
maybeCheckReferredProxies(logger, clientPropertyMethod);
}
+ maybeCheckExtraTypes(logger, proxyType);
}
/**
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Resolver.java b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
index 7b8c5a6..38e5a97 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/Resolver.java
@@ -404,7 +404,7 @@
boolean isProxy = BaseProxy.class.isAssignableFrom(returnClass);
boolean isId = EntityProxyId.class.isAssignableFrom(returnClass);
if (isProxy || isId) {
- Class<? extends BaseProxy> proxyClass = assignableTo.asSubclass(BaseProxy.class);
+ Class<? extends BaseProxy> proxyClass = returnClass.asSubclass(BaseProxy.class);
BaseProxy entity = resolveClientProxy(domainValue, proxyClass, propertyRefs, key, prefix);
if (isId) {
return assignableTo.cast(((EntityProxy) entity).stableId());
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java
index eaab4bc..8d93655 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/EntityProxy.java
@@ -18,6 +18,7 @@
/**
* A proxy for a server-side domain object.
*/
+@ProxyFor(Object.class)
public interface EntityProxy extends BaseProxy {
/**
* Returns the {@link EntityProxyId} that identifies a particular instance of
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ExtraTypes.java b/user/src/com/google/web/bindery/requestfactory/shared/ExtraTypes.java
new file mode 100644
index 0000000..0273c97
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ExtraTypes.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.Target;
+
+/**
+ * This annotation can be applied to {@link EntityProxy}, {@link ValueProxy},
+ * {@link RequestContext}, and {@link RequestFactory} type declarations to
+ * include additional polymorphic proxy types that are not explicitly
+ * referenced.
+ */
+@Target(ElementType.TYPE)
+public @interface ExtraTypes {
+ Class<? extends BaseProxy>[] value();
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java b/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java
index bbd7760..14e9aa2 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/ValueProxy.java
@@ -19,5 +19,6 @@
* An analog to EntityProxy for domain types that do not have an identity
* concept.
*/
+@ProxyFor(Object.class)
public interface ValueProxy extends BaseProxy {
}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
index 111e77f..ae23ac2 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
@@ -15,10 +15,12 @@
*/
package com.google.web.bindery.requestfactory.vm;
+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.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.JsonRpcContent;
import com.google.web.bindery.requestfactory.shared.JsonRpcWireName;
@@ -26,9 +28,10 @@
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 com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
+import com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
@@ -182,11 +185,13 @@
static final Object[] NO_ARGS = new Object[0];
private final Class<? extends RequestContext> context;
private final Dialect dialect;
+ private final TypeTokenResolver tokenResolver;
- protected InProcessRequestContext(AbstractRequestFactory factory, Dialect dialect,
+ protected InProcessRequestContext(InProcessRequestFactory factory, Dialect dialect,
Class<? extends RequestContext> context) {
super(factory, dialect);
this.context = context;
+ this.tokenResolver = factory.getTypeTokenResolver();
this.dialect = dialect;
}
@@ -198,6 +203,14 @@
}
@Override
+ public <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz, SimpleProxyId<T> id) {
+ if (tokenResolver.isReferencedType(clazz.getName())) {
+ return super.createProxy(clazz, id);
+ }
+ throw new IllegalArgumentException("Unknown proxy type " + clazz.getName());
+ }
+
+ @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
index da49296..fc3802b 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
@@ -131,4 +131,8 @@
protected String getTypeToken(Class<? extends BaseProxy> clazz) {
return isEntityType(clazz) || isValueType(clazz) ? OperationKey.hash(clazz.getName()) : null;
}
+
+ TypeTokenResolver getTypeTokenResolver() {
+ return deobfuscator;
+ }
}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java b/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java
index 96caf85..4ed5112 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java
@@ -22,8 +22,10 @@
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
+import java.util.Set;
/**
* Resolves payload type tokens to binary class names.
@@ -46,6 +48,8 @@
public TypeTokenResolver build() {
TypeTokenResolver toReturn = d;
toReturn.typeTokens = Collections.unmodifiableMap(toReturn.typeTokens);
+ toReturn.referencedTypes =
+ Collections.unmodifiableSet(new HashSet<String>(toReturn.typeTokens.values()));
d = null;
return toReturn;
}
@@ -86,6 +90,11 @@
}
/**
+ * The values of {@link #typeTokens}.
+ */
+ private Set<String> referencedTypes;
+
+ /**
* Map of obfuscated ids to binary class names.
*/
private Map<String, String> typeTokens = new HashMap<String, String>();
@@ -94,6 +103,10 @@
return typeTokens.get(typeToken);
}
+ public boolean isReferencedType(String binaryName) {
+ return referencedTypes.contains(binaryName);
+ }
+
/**
* Closes the OutputStream.
*/
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
index 83baef3..ffd546d 100644
--- a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryPolymorphicTest.java
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryPolymorphicTest.java
@@ -19,27 +19,751 @@
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.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ExtraTypes;
+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.SimpleFooProxy;
import com.google.web.bindery.requestfactory.shared.TestRequestFactory;
+import java.util.Arrays;
+import java.util.List;
+
/**
- * Just tests the
- * {@link com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator} to see
- * if polymorphic signatures are allowed.
+ * Tests behavior of RequestFactory when using polymorphic Request return types
+ * and other non-trivial type hierarchies.
*/
public class RequestFactoryPolymorphicTest extends GWTTestCase {
+ /**
+ * Mandatory javadoc.
+ */
+ public static class A {
+ private static int idCount = 0;
+
+ public static A findA(int id) {
+ A toReturn = new A();
+ toReturn.id = id;
+ return toReturn;
+ }
+
+ private String a = "a";
+
+ protected int id = idCount++;
+
+ public String getA() {
+ return a;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public int getVersion() {
+ return 0;
+ }
+
+ public void setA(String value) {
+ a = value;
+ }
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(A.class)
+ public interface AProxy extends EntityProxy, HasA {
+ // Mix in getA() from HasA
+ void setA(String value);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public static class ASub extends A {
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public static class B extends A {
+ public static B findB(int id) {
+ B toReturn = new B();
+ toReturn.id = id;
+ return toReturn;
+ }
+
+ private String b = "b";
+
+ public String getB() {
+ return b;
+ }
+
+ public void setB(String value) {
+ b = value;
+ }
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(B.class)
+ public interface B1Proxy extends AProxy {
+ String getB();
+
+ void setB(String value);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(B.class)
+ public interface B2Proxy extends AProxy {
+ String getB();
+
+ void setB(String value);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public static class BSub extends B {
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public static class C extends B {
+ public static C findC(int id) {
+ C toReturn = new C();
+ toReturn.id = id;
+ return toReturn;
+ }
+
+ private String c = "c";
+
+ public String getC() {
+ return c;
+ }
+
+ public void setC(String value) {
+ c = value;
+ }
+ }
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(C.class)
+ public interface C1Proxy extends B1Proxy {
+ String getC();
+
+ void setC(String value);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(C.class)
+ public interface C2Proxy extends B2Proxy {
+ String getC();
+
+ void setC(String value);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public static class CSub extends C {
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public static class D extends A {
+ public static D findD(int id) {
+ D toReturn = new D();
+ toReturn.id = id;
+ return toReturn;
+ }
+
+ private String d = "d";
+
+ public String getD() {
+ return d;
+ }
+
+ public void setD(String value) {
+ d = value;
+ }
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ExtraTypes(D2Proxy.class)
+ @ProxyFor(D.class)
+ public interface D1Proxy extends AProxy {
+ String getD();
+
+ void setD(String value);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(D.class)
+ public interface D2Proxy extends EntityProxy {
+ String getD();
+
+ void setD(String value);
+ }
+
+ /**
+ * This class should not be referenced except as a superclass of
+ * {@link MoreDerivedProxy}.
+ */
+ @ProxyFor(D.class)
+ public interface D3Proxy extends EntityProxy {
+ String getD();
+
+ void setD(String value);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public static class DSub extends D {
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public interface HasA {
+ String getA();
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ public static class Impl {
+ public static A AasA() {
+ return new A();
+ }
+
+ public static A BasA() {
+ return new B();
+ }
+
+ public static B BasB() {
+ return new B();
+ }
+
+ public static A CasA() {
+ return new C();
+ }
+
+ public static B CasB() {
+ return new C();
+ }
+
+ public static String checkA(Object obj) {
+ return A.class.equals(obj.getClass()) && "A".equals(((A) obj).getA()) ? "" : "checkA";
+ }
+
+ public static String checkB(Object obj) {
+ return B.class.equals(obj.getClass()) && "B".equals(((B) obj).getB()) ? "" : "checkB";
+ }
+
+ public static String checkC(Object obj) {
+ return C.class.equals(obj.getClass()) && "C".equals(((C) obj).getC()) ? "" : "checkC";
+ }
+
+ public static String checkD(Object obj) {
+ return D.class.equals(obj.getClass()) && "D".equals(((D) obj).getD()) ? "" : "checkD";
+ }
+
+ public static String checkList(List<Object> list) {
+ if (list.size() != 4) {
+ return "size";
+ }
+ String temp;
+ temp = checkA(list.get(0));
+ if (!temp.isEmpty()) {
+ return temp;
+ }
+ temp = checkB(list.get(1));
+ if (!temp.isEmpty()) {
+ return temp;
+ }
+ temp = checkC(list.get(2));
+ if (!temp.isEmpty()) {
+ return temp;
+ }
+ temp = checkD(list.get(3));
+ if (!temp.isEmpty()) {
+ return temp;
+ }
+ return "";
+ }
+
+ public static String checkW(Object obj) {
+ return W.class.equals(obj.getClass()) && "W".equals(((W) obj).getW()) ? "" : "checkW";
+ }
+
+ public static String checkZ(Object obj) {
+ return Z.class.equals(obj.getClass()) && "Z".equals(((Z) obj).getZ()) ? "" : "checkZ";
+ }
+
+ public static A DasA() {
+ return new D();
+ }
+
+ public static D DasD() {
+ return new D();
+ }
+
+ public static List<A> testCollection() {
+ return Arrays.asList(new A(), new B(), new C(), new D());
+ }
+
+ public static List<A> testCollectionSub() {
+ return Arrays.asList(new ASub(), new BSub(), new CSub(), new DSub());
+ }
+
+ public static W W() {
+ return new W();
+ }
+
+ public static W W2() {
+ return new W();
+ }
+
+ public static Z Z() {
+ return new Z();
+ }
+
+ public static Z Z2() {
+ return new Z();
+ }
+ }
+
+ /**
+ * Check diamond inheritance.
+ */
+ @ProxyFor(D.class)
+ public interface MoreDerivedProxy extends AProxy, D1Proxy, D2Proxy, D3Proxy {
+ }
+
+ /**
+ * The W and Z types are used with the WZProxy and ZWProxy to demonstrate
+ * proxy interface inheritance even when their proxy-for types aren't related.
+ */
+ public static class W {
+ private static int idCount;
+
+ public static W findW(int id) {
+ W toReturn = new W();
+ toReturn.id = id;
+ return toReturn;
+ }
+
+ private int id = idCount++;
+
+ private String w = "w";
+
+ private String z = "z";
+
+ public int getId() {
+ return id;
+ }
+
+ public int getVersion() {
+ return 0;
+ }
+
+ public String getW() {
+ return w;
+ }
+
+ public String getZ() {
+ return z;
+ }
+
+ public void setW(String w) {
+ this.w = w;
+ }
+
+ public void setZ(String z) {
+ this.z = z;
+ }
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(W.class)
+ public interface WProxy extends EntityProxy {
+ String getW();
+
+ void setW(String w);
+ }
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(W.class)
+ public interface WZProxy extends ZProxy {
+ String getW();
+
+ void setW(String w);
+ }
+ /**
+ * @see W
+ */
+ public static class Z extends A {
+ private static int idCount;
+
+ public static Z findZ(int id) {
+ Z toReturn = new Z();
+ toReturn.id = id;
+ return toReturn;
+ }
+
+ private int id = idCount++;
+
+ private String w = "w";
+
+ private String z = "z";
+
+ public int getId() {
+ return id;
+ }
+
+ public int getVersion() {
+ return 0;
+ }
+
+ public String getW() {
+ return w;
+ }
+
+ public String getZ() {
+ return z;
+ }
+
+ public void setW(String w) {
+ this.w = w;
+ }
+
+ public void setZ(String z) {
+ this.z = z;
+ }
+ }
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(Z.class)
+ public interface ZProxy extends EntityProxy {
+ String getZ();
+
+ void setZ(String z);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ProxyFor(Z.class)
+ public interface ZWProxy extends WProxy {
+ String getZ();
+
+ void setZ(String z);
+ }
+
+ /**
+ * Mandatory javadoc.
+ */
+ @ExtraTypes(B2Proxy.class)
+ protected interface Factory extends RequestFactory {
+ Context ctx();
+ }
+
+ /**
+ * Verifies that the received proxy's proxy type is exactly {@code clazz} and
+ * checks the values of the properties.
+ */
+ static class CastAndCheckReceiver extends Receiver<EntityProxy> {
+ public static CastAndCheckReceiver of(Class<?> clazz) {
+ return new CastAndCheckReceiver(clazz);
+ }
+
+ private final Class<?> clazz;
+
+ public CastAndCheckReceiver(Class<?> clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public void onSuccess(EntityProxy response) {
+ assertNotNull(response);
+ assertEquals(clazz, response.stableId().getProxyClass());
+ if (response instanceof HasA) {
+ assertEquals("a", ((HasA) response).getA());
+ }
+ if (response instanceof B1Proxy) {
+ assertEquals("b", ((B1Proxy) response).getB());
+ }
+ if (response instanceof B2Proxy) {
+ assertEquals("b", ((B2Proxy) response).getB());
+ }
+ if (response instanceof C1Proxy) {
+ assertEquals("c", ((C1Proxy) response).getC());
+ }
+ if (response instanceof C2Proxy) {
+ assertEquals("c", ((C2Proxy) response).getC());
+ }
+ if (response instanceof D1Proxy) {
+ assertEquals("d", ((D1Proxy) response).getD());
+ }
+ if (response instanceof D2Proxy) {
+ assertEquals("d", ((D2Proxy) response).getD());
+ }
+ }
+ }
+
+ @ExtraTypes({C1Proxy.class, C2Proxy.class, MoreDerivedProxy.class})
+ @Service(Impl.class)
+ interface Context extends RequestContext {
+ Request<AProxy> AasA();
+
+ Request<AProxy> BasA();
+
+ Request<B1Proxy> BasB();
+
+ Request<AProxy> CasA();
+
+ Request<B1Proxy> CasB();
+
+ Request<String> checkA(EntityProxy proxy);
+
+ Request<String> checkB(EntityProxy proxy);
+
+ Request<String> checkC(EntityProxy proxy);
+
+ Request<String> checkD(EntityProxy proxy);
+
+ Request<String> checkList(List<EntityProxy> list);
+
+ Request<String> checkW(EntityProxy proxy);
+
+ Request<String> checkZ(EntityProxy proxy);
+
+ Request<AProxy> DasA();
+
+ Request<D1Proxy> DasD();
+
+ Request<List<AProxy>> testCollection();
+
+ Request<List<AProxy>> testCollectionSub();
+
+ Request<WProxy> W();
+
+ Request<WZProxy> W2();
+
+ Request<ZProxy> Z();
+
+ Request<ZWProxy> Z2();
+ }
+
+ /**
+ * Checks that the incoming list is {@code [ A, B, C, D ]}.
+ */
+ static class ListChecker extends Receiver<List<AProxy>> {
+ @Override
+ public void onSuccess(List<AProxy> response) {
+ new CastAndCheckReceiver(AProxy.class).onSuccess(response.get(0));
+ new CastAndCheckReceiver(B1Proxy.class).onSuccess(response.get(1));
+ new CastAndCheckReceiver(C1Proxy.class).onSuccess(response.get(2));
+ new CastAndCheckReceiver(MoreDerivedProxy.class).onSuccess(response.get(3));
+ }
+ }
+
+ private final Receiver<String> checkReceiver = new Receiver<String>() {
+
+ @Override
+ public void onSuccess(String response) {
+ assertEquals("", response);
+ }
+ };
+
+ private static final int TEST_DELAY = 5000;
+
+ protected Factory factory;
+
@Override
public String getModuleName() {
return "com.google.web.bindery.requestfactory.gwt.RequestFactorySuite";
}
- public void testGenerator() {
+ public void testCreation() {
+ delayTestFinish(TEST_DELAY);
+ Context ctx = factory.ctx();
+ checkA(ctx, AProxy.class);
+ checkB(ctx, B1Proxy.class);
+ checkB(ctx, B2Proxy.class);
+ checkC(ctx, C1Proxy.class);
+ checkC(ctx, C2Proxy.class);
+ checkD(ctx, D1Proxy.class);
+ checkD(ctx, D2Proxy.class);
+ // D3Proxy is a proxy supertype, assignable to BaseProxy and has @ProxyFor
+ checkD(ctx, D3Proxy.class);
+ checkD(ctx, MoreDerivedProxy.class);
+ checkW(ctx, WProxy.class);
+ checkW(ctx, WZProxy.class);
+ checkZ(ctx, ZProxy.class);
+ checkZ(ctx, ZWProxy.class);
+ ctx.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * Ensure heterogeneous collections work.
+ */
+ public void testCreationList() {
+ Context ctx = factory.ctx();
+ ctx.checkList(
+ Arrays.asList(create(ctx, AProxy.class), create(ctx, B2Proxy.class), create(ctx,
+ C2Proxy.class), create(ctx, D2Proxy.class))).to(checkReceiver);
+ ctx.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTest();
+ }
+ });
+ }
+
+ public void testGenericRequest() {
TestRequestFactory rf = GWT.create(TestRequestFactory.class);
EventBus eventBus = new SimpleEventBus();
rf.initialize(eventBus);
- SimpleFooProxy simpleFoo = rf.testFooPolymorphicRequest().create(
- SimpleFooProxy.class);
+ SimpleFooProxy simpleFoo = rf.testGenericRequest().create(SimpleFooProxy.class);
assertNull(simpleFoo.getUserName());
}
+
+ public void testRetrieval() {
+ delayTestFinish(TEST_DELAY);
+ Context ctx = factory.ctx();
+ ctx.AasA().to(CastAndCheckReceiver.of(AProxy.class));
+ ctx.BasA().to(CastAndCheckReceiver.of(B1Proxy.class));
+ ctx.BasB().to(CastAndCheckReceiver.of(B1Proxy.class));
+ ctx.CasA().to(CastAndCheckReceiver.of(C1Proxy.class));
+ ctx.CasB().to(CastAndCheckReceiver.of(C1Proxy.class));
+ ctx.DasA().to(CastAndCheckReceiver.of(MoreDerivedProxy.class));
+ ctx.DasD().to(CastAndCheckReceiver.of(MoreDerivedProxy.class));
+ ctx.W().to(CastAndCheckReceiver.of(WProxy.class));
+ ctx.W2().to(CastAndCheckReceiver.of(WZProxy.class));
+ ctx.Z().to(CastAndCheckReceiver.of(ZProxy.class));
+ ctx.Z2().to(CastAndCheckReceiver.of(ZWProxy.class));
+ ctx.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTest();
+ }
+ });
+ }
+
+ public void testRetrievalCollection() {
+ delayTestFinish(TEST_DELAY);
+ Context ctx = factory.ctx();
+ ctx.testCollection().to(new ListChecker());
+ ctx.testCollectionSub().to(new ListChecker());
+ ctx.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ finishTest();
+ }
+ });
+ }
+
+ protected Factory createFactory() {
+ Factory f = GWT.create(Factory.class);
+ f.initialize(new SimpleEventBus());
+ return f;
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ factory = createFactory();
+ }
+
+ private void checkA(Context ctx, Class<? extends AProxy> clazz) {
+ ctx.checkA(create(ctx, clazz)).to(checkReceiver);
+ }
+
+ private void checkB(Context ctx, Class<? extends EntityProxy> clazz) {
+ ctx.checkB(create(ctx, clazz)).to(checkReceiver);
+ }
+
+ private void checkC(Context ctx, Class<? extends EntityProxy> clazz) {
+ ctx.checkC(create(ctx, clazz)).to(checkReceiver);
+ }
+
+ private void checkD(Context ctx, Class<? extends EntityProxy> clazz) {
+ ctx.checkD(create(ctx, clazz)).to(checkReceiver);
+ }
+
+ private void checkW(Context ctx, Class<? extends EntityProxy> clazz) {
+ ctx.checkW(create(ctx, clazz)).to(checkReceiver);
+ }
+
+ private void checkZ(Context ctx, Class<? extends EntityProxy> clazz) {
+ ctx.checkZ(create(ctx, clazz)).to(checkReceiver);
+ }
+
+ private <T extends EntityProxy> T create(Context ctx, Class<T> clazz) {
+ T obj = ctx.create(clazz);
+ if (obj instanceof AProxy) {
+ ((AProxy) obj).setA("A");
+ }
+ if (obj instanceof B1Proxy) {
+ ((B1Proxy) obj).setB("B");
+ }
+ if (obj instanceof B2Proxy) {
+ ((B2Proxy) obj).setB("B");
+ }
+ if (obj instanceof C1Proxy) {
+ ((C1Proxy) obj).setC("C");
+ }
+ if (obj instanceof C2Proxy) {
+ ((C2Proxy) obj).setC("C");
+ }
+ if (obj instanceof D1Proxy) {
+ ((D1Proxy) obj).setD("D");
+ }
+ if (obj instanceof D2Proxy) {
+ ((D2Proxy) obj).setD("D");
+ }
+ if (obj instanceof D3Proxy) {
+ ((D3Proxy) obj).setD("D");
+ }
+ if (obj instanceof WProxy) {
+ ((WProxy) obj).setW("W");
+ }
+ if (obj instanceof ZProxy) {
+ ((ZProxy) obj).setZ("Z");
+ }
+ if (obj instanceof WZProxy) {
+ ((WZProxy) obj).setW("W");
+ }
+ if (obj instanceof ZWProxy) {
+ ((ZWProxy) obj).setZ("Z");
+ }
+ return obj;
+ }
}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryPolymorphicJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryPolymorphicJreTest.java
new file mode 100644
index 0000000..6dc30c5
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryPolymorphicJreTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.gwt.client.RequestFactoryPolymorphicTest;
+import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
+import com.google.web.bindery.requestfactory.shared.BaseProxy;
+
+import java.util.logging.Logger;
+
+/**
+ * A JRE version of {@link RequestFactoryPolymorphicTest} that includes
+ * additional tests for the RequestFactoryInterfaceValidator that are specific
+ * to type-hierarchy mapping.
+ */
+public class RequestFactoryPolymorphicJreTest extends RequestFactoryPolymorphicTest {
+ RequestFactoryInterfaceValidator v;
+
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ /**
+ * Disabling, since this is a test of the code Generator.
+ */
+ @Override
+ public void testGenericRequest() {
+ }
+
+ /**
+ * Check related proxy types with unrelated domain types.
+ * */
+ public void testUnrelatedDomainTypes() {
+ // Simple mappings
+ check(v, W.class, WProxy.class, WProxy.class);
+ check(v, W.class, WZProxy.class, WZProxy.class);
+ check(v, Z.class, ZProxy.class, ZProxy.class);
+ check(v, Z.class, ZWProxy.class, ZWProxy.class);
+
+ // Look for derived proxy types that map to the domain type
+ check(v, Z.class, WProxy.class, ZWProxy.class);
+ check(v, W.class, ZProxy.class, WZProxy.class);
+
+ /*
+ * This test is included to verify that the validator will fail gracefully
+ * when asked for a nonsensical assignment. For these two tests, the
+ * requested proxy type isn't mapped to the domain type, nor are there any
+ * *sub*-types of the requested proxy class that are assignable to the
+ * domaintype. The requested proxy type's supertype is assignable to the
+ * domain type, however the supertype isn't assignable to its subtype, so
+ * it's not a valid choice. Normally, the RequestFactoryInterfaceValidator
+ * would detect the mismatch between the RequestContext and the service
+ * method return types, so this shouldn't be a problem in practice. It would
+ * only ever crop up when the SkipInterfaceValidation annotation has been
+ * used.
+ */
+ check(v, Z.class, WZProxy.class, null);
+ check(v, W.class, ZWProxy.class, null);
+ }
+
+ /**
+ * Tests that the RequestFactoryInterfaceValidator is producing the correct
+ * mappings from domain types back to client types.
+ */
+ public void testValidator() {
+ /*
+ * Check explicit mappings. Not all of the types are directly referenced in
+ * the method declarations, so this also tests the ExtraTypes annotation.
+ */
+ check(v, A.class, AProxy.class, AProxy.class);
+ check(v, B.class, B1Proxy.class, B1Proxy.class);
+ check(v, B.class, B2Proxy.class, B2Proxy.class);
+ check(v, C.class, C1Proxy.class, C1Proxy.class);
+ check(v, C.class, C2Proxy.class, C2Proxy.class);
+
+ // Check types without explicit mappings.
+ check(v, ASub.class, AProxy.class, AProxy.class);
+ check(v, BSub.class, B1Proxy.class, B1Proxy.class);
+ check(v, BSub.class, B2Proxy.class, B2Proxy.class);
+ check(v, CSub.class, C1Proxy.class, C1Proxy.class);
+ check(v, CSub.class, C2Proxy.class, C2Proxy.class);
+
+ // Check assignments with proxies extending proxies
+ check(v, C.class, B1Proxy.class, C1Proxy.class);
+ check(v, C.class, B2Proxy.class, C2Proxy.class);
+
+ // Should prefer more-derived interfaces when possible
+ check(v, D.class, AProxy.class, MoreDerivedProxy.class);
+ check(v, D.class, D1Proxy.class, MoreDerivedProxy.class);
+ check(v, D.class, D2Proxy.class, MoreDerivedProxy.class);
+ check(v, D.class, D3Proxy.class, MoreDerivedProxy.class);
+ check(v, D.class, MoreDerivedProxy.class, MoreDerivedProxy.class);
+ check(v, DSub.class, AProxy.class, MoreDerivedProxy.class);
+ check(v, DSub.class, D1Proxy.class, MoreDerivedProxy.class);
+ check(v, DSub.class, D2Proxy.class, MoreDerivedProxy.class);
+ check(v, DSub.class, D3Proxy.class, MoreDerivedProxy.class);
+ check(v, DSub.class, MoreDerivedProxy.class, MoreDerivedProxy.class);
+ }
+
+ @Override
+ protected Factory createFactory() {
+ return RequestFactoryJreTest.createInProcess(Factory.class);
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ super.gwtSetUp();
+ Logger logger = Logger.getLogger(getClass().getName());
+ ClassLoaderLoader loader = new ClassLoaderLoader(getClass().getClassLoader());
+ v = new RequestFactoryInterfaceValidator(logger, loader);
+ v.validateRequestFactory(Factory.class.getName());
+ }
+
+ private void check(RequestFactoryInterfaceValidator v, Class<?> domainType,
+ Class<? extends BaseProxy> declaredReturnType, Class<? extends BaseProxy> expectedClientType) {
+ String type = v.getEntityProxyTypeName(domainType.getName(), declaredReturnType.getName());
+ if (expectedClientType == null) {
+ assertNull(type, type);
+ } else {
+ assertEquals(expectedClientType.getName(), type);
+ }
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java
index 6ced0f6..8e4aefd 100644
--- a/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java
+++ b/user/test/com/google/web/bindery/requestfactory/shared/TestRequestFactory.java
@@ -16,8 +16,10 @@
package com.google.web.bindery.requestfactory.shared;
/**
- * Creates TestFooPolymorphicRequest.
+ * Creates TestFooPolymorphicRequest which verifies that the
+ * RequestFactoryGenerator can handle interface declarations that use generic
+ * type parameters.
*/
public interface TestRequestFactory extends RequestFactory {
- TestFooPolymorphicRequest testFooPolymorphicRequest();
+ TestFooPolymorphicRequest testGenericRequest();
}
diff --git a/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java b/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
index d714c79..f1193a7 100644
--- a/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
+++ b/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
@@ -23,6 +23,7 @@
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.RequestFactoryPolymorphicJreTest;
import com.google.web.bindery.requestfactory.server.RequestFactoryUnicodeEscapingJreTest;
import com.google.web.bindery.requestfactory.server.ServiceInheritanceJreTest;
import com.google.web.bindery.requestfactory.server.ServiceLocatorTest;
@@ -47,6 +48,7 @@
suite.addTestSuite(RequestFactoryExceptionPropagationJreTest.class);
suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
suite.addTestSuite(RequestFactoryJreTest.class);
+ suite.addTestSuite(RequestFactoryPolymorphicJreTest.class);
suite.addTestSuite(RequestFactoryUnicodeEscapingJreTest.class);
suite.addTestSuite(ServiceInheritanceJreTest.class);
suite.addTestSuite(ServiceLocatorTest.class);