Add compile-time tests for APT-based RequestFactory interface validator.
Move all validator message formatting to a single class.
Patch by: bobv
Review by: keertip,pquitslund, t.broyer
Suggested by: t.broyer
Review at http://gwt-code-reviews.appspot.com/1473801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10426 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/ClientToDomainMapper.java b/user/src/com/google/web/bindery/requestfactory/apt/ClientToDomainMapper.java
index f4cf816..c7332dc 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/ClientToDomainMapper.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ClientToDomainMapper.java
@@ -15,8 +15,6 @@
*/
package com.google.web.bindery.requestfactory.apt;
-import com.google.web.bindery.autobean.shared.ValueCodex;
-
import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -30,14 +28,13 @@
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
-import javax.lang.model.util.SimpleTypeVisitor6;
/**
* Uses information in a State object to convert client types to their domain
* equivalents. This types assumes that any incoming type has already been
* determined to be a transportable type.
*/
-class ClientToDomainMapper extends SimpleTypeVisitor6<TypeMirror, State> {
+class ClientToDomainMapper extends TypeVisitorBase<TypeMirror> {
public static class UnmappedTypeException extends RuntimeException {
private final TypeMirror clientType;
@@ -83,12 +80,8 @@
// InstanceRequest<FooProxy, X> -> FooDomain
return convertSingleParamType(x, state.instanceRequestType, 1, state);
}
- for (Class<?> clazz : ValueCodex.getAllValueTypes()) {
- if (clazz.isPrimitive()) {
- continue;
- }
-
- if (state.types.isAssignable(x, state.findType(clazz))) {
+ for (DeclaredType valueType : getValueTypes(state)) {
+ if (state.types.isAssignable(x, valueType)) {
// Value types map straight through
return x;
}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/DomainChecker.java b/user/src/com/google/web/bindery/requestfactory/apt/DomainChecker.java
index 7c53177..dba4293 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/DomainChecker.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/DomainChecker.java
@@ -48,14 +48,16 @@
static class MethodFinder extends ScannerBase<ExecutableElement> {
private TypeElement domainType;
private ExecutableElement found;
+ private final boolean boxReturnType;
private final CharSequence name;
private final TypeMirror returnType;
private final List<TypeMirror> params;
public MethodFinder(CharSequence name, TypeMirror returnType, List<TypeMirror> params,
- State state) {
+ boolean boxReturnType, State state) {
+ this.boxReturnType = boxReturnType;
this.name = name;
- this.returnType = TypeSimplifier.simplify(returnType, true, state);
+ this.returnType = TypeSimplifier.simplify(returnType, boxReturnType, state);
List<TypeMirror> temp = new ArrayList<TypeMirror>(params.size());
for (TypeMirror param : params) {
temp.add(TypeSimplifier.simplify(param, false, state));
@@ -80,11 +82,9 @@
returnTypeMatches = true;
} else {
TypeMirror domainReturn =
- TypeSimplifier.simplify(domainMethod.getReturnType(), true, state);
+ TypeSimplifier.simplify(domainMethod.getReturnType(), boxReturnType, state);
// The isSameType handles the NONE case.
- returnTypeMatches =
- state.types.isSameType(domainReturn, returnType)
- || state.types.isAssignable(domainReturn, returnType);
+ returnTypeMatches = state.types.isSubtype(domainReturn, returnType);
}
if (returnTypeMatches) {
boolean paramsMatch = true;
@@ -94,8 +94,7 @@
assert domainParam.hasNext();
TypeMirror requestedType = lookFor.next();
TypeMirror paramType = TypeSimplifier.simplify(domainParam.next(), false, state);
- if (!state.types.isSameType(requestedType, paramType)
- && !state.types.isAssignable(requestedType, paramType)) {
+ if (!state.types.isSubtype(requestedType, paramType)) {
paramsMatch = false;
}
}
@@ -134,11 +133,11 @@
* This is used as the target for errors since generic methods show up as
* synthetic elements that don't correspond to any source.
*/
- private TypeElement checkedType;
+ private TypeElement checkedElement;
private boolean currentTypeIsProxy;
+ private TypeElement domainElement;
private boolean requireInstanceDomainMethods;
private boolean requireStaticDomainMethods;
- private TypeElement domainType;
@Override
public Void visitExecutable(ExecutableElement clientMethodElement, State state) {
@@ -152,7 +151,7 @@
return null;
}
- ExecutableType clientMethod = viewIn(checkedType, clientMethodElement, state);
+ ExecutableType clientMethod = viewIn(checkedElement, clientMethodElement, state);
List<TypeMirror> lookFor = new ArrayList<TypeMirror>();
// Convert client method signature to domain types
TypeMirror returnType;
@@ -173,16 +172,23 @@
if (currentTypeIsProxy && isSetter(clientMethodElement, state)) {
// Look for void setFoo(...)
domainMethod =
- new MethodFinder(name, state.types.getNoType(TypeKind.VOID), lookFor, state).scan(
- domainType, state);
+ new MethodFinder(name, state.types.getNoType(TypeKind.VOID), lookFor, false, state).scan(
+ domainElement, state);
if (domainMethod == null) {
// Try a builder style
domainMethod =
- new MethodFinder(name, domainType.asType(), lookFor, state).scan(domainType, state);
+ new MethodFinder(name, domainElement.asType(), lookFor, false, state).scan(
+ domainElement, state);
}
} else {
- // The usual case for getters and all service methods
- domainMethod = new MethodFinder(name, returnType, lookFor, state).scan(domainType, state);
+ /*
+ * The usual case for getters and all service methods. Only box return
+ * types when matching context methods since there's a significant
+ * semantic difference between a null Integer and 0.
+ */
+ domainMethod =
+ new MethodFinder(name, returnType, lookFor, !currentTypeIsProxy, state).scan(
+ domainElement, state);
}
if (domainMethod == null) {
@@ -194,7 +200,7 @@
}
sb.append(")");
- state.poison(clientMethodElement, "Could not find domain method similar to %s", sb);
+ state.poison(clientMethodElement, Messages.domainMissingMethod(sb));
return null;
}
@@ -203,21 +209,19 @@
* InstanceRequests assume instance methods on the domain type.
*/
boolean isInstanceRequest =
- state.types.isAssignable(clientMethod.getReturnType(), state.instanceRequestType);
+ state.types.isSubtype(clientMethod.getReturnType(), state.instanceRequestType);
if ((isInstanceRequest || requireInstanceDomainMethods)
&& domainMethod.getModifiers().contains(Modifier.STATIC)) {
- state.poison(checkedType, "Found static domain method %s when instance method required",
- domainMethod.getSimpleName());
+ state.poison(checkedElement, Messages.domainMethodWrongModifier(false, domainMethod
+ .getSimpleName()));
}
if (!isInstanceRequest && requireStaticDomainMethods
&& !domainMethod.getModifiers().contains(Modifier.STATIC)) {
- state.poison(checkedType, "Found instance domain method %s when static method required",
- domainMethod.getSimpleName());
+ state.poison(checkedElement, Messages.domainMethodWrongModifier(true, domainMethod
+ .getSimpleName()));
}
- if (state.verbose) {
- state.warn(clientMethodElement, "Found domain method %s", domainMethod.toString());
- }
+ state.debug(clientMethodElement, "Found domain method %s", domainMethod.toString());
return null;
}
@@ -225,12 +229,11 @@
@Override
public Void visitType(TypeElement clientTypeElement, State state) {
TypeMirror clientType = clientTypeElement.asType();
- checkedType = clientTypeElement;
- boolean isEntityProxy = state.types.isAssignable(clientType, state.entityProxyType);
- currentTypeIsProxy =
- isEntityProxy || state.types.isAssignable(clientType, state.valueProxyType);
- domainType = state.getClientToDomainMap().get(clientTypeElement);
- if (domainType == null) {
+ checkedElement = clientTypeElement;
+ boolean isEntityProxy = state.types.isSubtype(clientType, state.entityProxyType);
+ currentTypeIsProxy = isEntityProxy || state.types.isSubtype(clientType, state.valueProxyType);
+ domainElement = state.getClientToDomainMap().get(clientTypeElement);
+ if (domainElement == null) {
// A proxy with an unresolved domain type (e.g. ProxyForName(""))
return null;
}
@@ -243,11 +246,10 @@
requireInstanceDomainMethods = true;
if (!hasProxyLocator(clientTypeElement, state)) {
// Domain types without a Locator should have a no-arg constructor
- if (!hasNoArgConstructor(domainType)) {
- state.warn(clientTypeElement, "The domain type %s has no default constructor."
- + " Calling %s.create(%s.class) will cause a server error.", domainType,
- state.requestContextType.asElement().getSimpleName(), clientTypeElement
- .getSimpleName());
+ if (!hasNoArgConstructor(domainElement)) {
+ state.warn(clientTypeElement, Messages.domainNoDefaultConstructor(domainElement
+ .getSimpleName(), clientTypeElement.getSimpleName(), state.requestContextType
+ .asElement().getSimpleName()));
}
/*
@@ -279,40 +281,35 @@
*/
private void checkDomainEntityMethods(State state) {
ExecutableElement getId =
- new MethodFinder("getId", null, Collections.<TypeMirror> emptyList(), state).scan(
- domainType, state);
+ new MethodFinder("getId", null, Collections.<TypeMirror> emptyList(), false, state).scan(
+ domainElement, state);
if (getId == null) {
- state.poison(checkedType, "Domain type %s does not have a getId() method", domainType
- .asType());
+ state.poison(checkedElement, Messages.domainNoGetId(domainElement.asType()));
} else {
if (getId.getModifiers().contains(Modifier.STATIC)) {
-
- state.poison(checkedType, "The domain type's getId() method must not be static");
+ state.poison(checkedElement, Messages.domainGetIdStatic());
}
// Can only check findFoo() if we have a getId
ExecutableElement find =
- new MethodFinder("find" + domainType.getSimpleName(), domainType.asType(), Collections
- .singletonList(getId.getReturnType()), state).scan(domainType, state);
+ new MethodFinder("find" + domainElement.getSimpleName(), domainElement.asType(),
+ Collections.singletonList(getId.getReturnType()), false, state).scan(domainElement,
+ state);
if (find == null) {
- state.warn(checkedType, "The domain type %s has no %s find%s(%s) method. "
- + "Attempting to send a %s to the server will result in a server error.", domainType
- .asType(), domainType.getSimpleName(), domainType.getSimpleName(), getId
- .getReturnType(), checkedType.getSimpleName());
+ state.warn(checkedElement, Messages.domainMissingFind(domainElement.asType(), domainElement
+ .getSimpleName(), getId.getReturnType(), checkedElement.getSimpleName()));
} else if (!find.getModifiers().contains(Modifier.STATIC)) {
- state.poison(checkedType, "The domain object's find%s() method is not static", domainType
- .getSimpleName());
+ state.poison(checkedElement, Messages.domainFindNotStatic(domainElement.getSimpleName()));
}
}
ExecutableElement getVersion =
- new MethodFinder("getVersion", null, Collections.<TypeMirror> emptyList(), state).scan(
- domainType, state);
+ new MethodFinder("getVersion", null, Collections.<TypeMirror> emptyList(), false, state)
+ .scan(domainElement, state);
if (getVersion == null) {
- state.poison(checkedType, "Domain type %s does not have a getVersion() method", domainType
- .asType());
+ state.poison(checkedElement, Messages.domainNoGetVersion(domainElement.asType()));
} else if (getVersion.getModifiers().contains(Modifier.STATIC)) {
- state.poison(checkedType, "The domain type's getVersion() method must not be static");
+ state.poison(checkedElement, Messages.domainGetVersionStatic());
}
}
@@ -338,8 +335,7 @@
} catch (UnmappedTypeException e) {
error = true;
returnType = null;
- state.warn(warnTo, "Cannot validate this method because the domain mapping for the"
- + " return type (%s) could not be resolved to a domain type", e.getClientType());
+ state.warn(warnTo, Messages.methodNoDomainPeer(e.getClientType(), false));
}
for (TypeMirror param : clientMethod.getParameterTypes()) {
try {
@@ -347,8 +343,7 @@
} catch (UnmappedTypeException e) {
parameterAccumulator.add(null);
error = true;
- state.warn(warnTo, "Cannot validate this method because the domain mapping for a"
- + " parameter of type (%s) could not be resolved to a domain type", e.getClientType());
+ state.warn(warnTo, Messages.methodNoDomainPeer(e.getClientType(), true));
}
}
if (error) {
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/Messages.java b/user/src/com/google/web/bindery/requestfactory/apt/Messages.java
new file mode 100644
index 0000000..4f5cbd9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/Messages.java
@@ -0,0 +1,146 @@
+/*
+ * 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.apt;
+
+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.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+
+/**
+ * Contains string-formatting methods to produce error messages. This class
+ * exists to avoid the need to duplicate error messages in test code. All method
+ * parameters in this class accept {@code Object} so that the production code
+ * can pass {@code javax.lang.model} types and the test code can pass Strings.
+ */
+class Messages {
+ /*
+ * Note to maintainers: When new messages are added to this class, the
+ * RfValidatorTest.testErrorsAndWarnings() method should be updated to test
+ * the new message.
+ */
+
+ public static String contextMissingDomainType(Object domainTypeName) {
+ return String.format("Cannot fully validate context since domain type %s is not available",
+ domainTypeName);
+ }
+
+ public static String contextMustBeAnnotated(Object requestContextName) {
+ return String.format("A %s must be annotated with %s, %s, or %s", requestContextName,
+ Service.class.getSimpleName(), ServiceName.class.getSimpleName(), JsonRpcService.class
+ .getSimpleName());
+ }
+
+ public static String contextRequiredReturnTypes(Object requestName, Object instanceRequestName) {
+ return String.format("The return type must be a %s or %s", requestName, instanceRequestName);
+ }
+
+ public static String domainFindNotStatic(Object domainTypeName) {
+ return String.format("The domain object's find%s() method is not static", domainTypeName);
+ }
+
+ public static String domainGetIdStatic() {
+ return "The domain type's getId() method must not be static";
+ }
+
+ public static String domainGetVersionStatic() {
+ return "The domain type's getVersion() method must not be static";
+ }
+
+ public static String domainMethodWrongModifier(boolean expectStatic, Object domainMethodName) {
+ return String.format("Found %s domain method %s when %s method required", expectStatic
+ ? "instance" : "static", domainMethodName, expectStatic ? "static" : "instance");
+ }
+
+ public static String domainMissingFind(Object domainType, Object simpleName,
+ Object getIdReturnType, Object checkedTypeName) {
+ return String.format("The domain type %s has no %s find%s(%s) method. "
+ + "Attempting to send a %s to the server will result in a server error.", domainType,
+ simpleName, simpleName, getIdReturnType, checkedTypeName);
+ }
+
+ public static String domainMissingMethod(Object description) {
+ return String.format("Could not find domain method similar to %s", description);
+ }
+
+ public static String domainNoDefaultConstructor(Object domainName, Object proxyName,
+ Object requestContextName) {
+ return String.format("The domain type %s has no default constructor."
+ + " Calling %s.create(%s.class) will cause a server error.", domainName,
+ requestContextName, proxyName);
+ }
+
+ public static String domainNoGetId(Object domainType) {
+ return String.format("Domain type %s does not have a getId() method", domainType);
+ }
+
+ public static String domainNoGetVersion(Object domainType) {
+ return String.format("Domain type %s does not have a getVersion() method", domainType);
+ }
+
+ public static String factoryMustBeAssignable(Object assignableTo) {
+ return String.format("The return type of this method must return a %s", assignableTo);
+ }
+
+ public static String factoryMustReturnInterface(Object returnType) {
+ return String.format("The return type %s must be an interface", returnType);
+ }
+
+ public static String factoryNoMethodParameters() {
+ return "This method must have no parameters";
+ }
+
+ public static String methodNoDomainPeer(Object proxyTypeName, boolean isParameter) {
+ return String.format("Cannot validate this method because the domain mapping for "
+ + " %s type (%s) could not be resolved to a domain type", isParameter ? "a parameter of"
+ : "the return", proxyTypeName);
+ }
+
+ public static String proxyMissingDomainType(Object missingDomainName) {
+ return String.format("Cannot fully validate proxy since type %s is not available",
+ missingDomainName);
+ }
+
+ public static String proxyMustBeAnnotated() {
+ return String.format("A proxy must be annotated with %s, %s, or %s", ProxyFor.class
+ .getSimpleName(), ProxyForName.class.getSimpleName(), JsonRpcProxy.class.getSimpleName());
+ }
+
+ public static String proxyOnlyGettersSetters() {
+ return "Only getters and setters allowed";
+ }
+
+ public static String rawType() {
+ return "A raw type may not be used here";
+ }
+
+ public static String redundantAnnotation(Object annotationName) {
+ return String.format("Redundant annotation: %s", annotationName);
+ }
+
+ public static String untransportableType(Object returnType) {
+ return String.format("The type %s cannot be used here", returnType);
+ }
+
+ public static String warnSuffix() {
+ return "\n\nAdd @SuppressWarnings(\"requestfactory\") to dismiss.";
+ }
+
+ private Messages() {
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java
index f3c85c7..2b72edf 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java
@@ -41,10 +41,10 @@
if (isGetter(x, state)) {
TypeMirror returnType = x.getReturnType();
if (!state.isTransportableType(returnType)) {
- state.poison(x, "The return type %s cannot be used here", returnType.toString());
+ state.poison(x, Messages.untransportableType(returnType));
}
} else if (!isSetter(x, state)) {
- state.poison(x, "Only getters and setters allowed");
+ state.poison(x, Messages.proxyOnlyGettersSetters());
}
// Parameters checked by visitVariable
return super.visitExecutable(x, state);
@@ -72,8 +72,7 @@
TypeElement domain =
state.elements.getTypeElement(BinaryName.toSourceName(proxyForName.value()));
if (domain == null) {
- state.warn(x, "Cannot fully validate proxy since type %s is not available", proxyForName
- .value());
+ state.warn(x, Messages.proxyMissingDomainType(proxyForName.value()));
}
state.addMapping(x, domain);
}
@@ -86,7 +85,7 @@
@Override
public Void visitVariable(VariableElement x, State state) {
if (!state.isTransportableType(x.asType())) {
- state.poison(x, "The type %s cannot be used here", x.asType().toString());
+ state.poison(x, Messages.untransportableType(x.asType()));
}
return super.visitVariable(x, state);
}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java
index 3fe796a..2fecf20 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java
@@ -44,11 +44,11 @@
// Extract Request<Foo> type
DeclaredType asRequest = (DeclaredType) State.viewAs(state.requestType, returnType, state);
if (asRequest.getTypeArguments().isEmpty()) {
- state.poison(x, "A raw return type may not be used here");
+ state.poison(x, Messages.rawType());
} else {
TypeMirror requestReturn = asRequest.getTypeArguments().get(0);
if (!state.isTransportableType(requestReturn)) {
- state.poison(x, "The type %s cannot be used as a return value", requestReturn.toString());
+ state.poison(x, Messages.untransportableType(requestReturn));
}
}
} else if (state.types.isAssignable(returnType, state.instanceRequestType)) {
@@ -56,23 +56,20 @@
DeclaredType asInstanceRequest =
(DeclaredType) State.viewAs(state.instanceRequestType, returnType, state);
if (asInstanceRequest.getTypeArguments().isEmpty()) {
- state.poison(x, "A raw return type may not be used here");
+ state.poison(x, Messages.rawType());
} else {
TypeMirror instanceType = asInstanceRequest.getTypeArguments().get(0);
- if (!state.isTransportableType(instanceType)) {
- state.poison(x, "The type %s cannot be used as an invocation target", instanceType
- .toString());
- }
+ state.maybeScanProxy((TypeElement) state.types.asElement(instanceType));
TypeMirror requestReturn = asInstanceRequest.getTypeArguments().get(1);
if (!state.isTransportableType(requestReturn)) {
- state.poison(x, "The type %s cannot be used as a return value", requestReturn.toString());
+ state.poison(x, Messages.untransportableType(requestReturn));
}
}
} else if (isSetter(x, state)) {
// Parameter checked in visitVariable
} else {
- state.poison(x, "The return type must be a %s or %s", state.requestType.asElement()
- .getSimpleName(), state.instanceRequestType.asElement().getSimpleName());
+ state.poison(x, Messages.contextRequiredReturnTypes(state.requestType.asElement()
+ .getSimpleName(), state.instanceRequestType.asElement().getSimpleName()));
}
return super.visitExecutable(x, state);
}
@@ -83,9 +80,8 @@
ServiceName serviceName = x.getAnnotation(ServiceName.class);
JsonRpcService jsonRpcService = x.getAnnotation(JsonRpcService.class);
if (service == null && serviceName == null && jsonRpcService == null) {
- state.poison(x, "A %s must be annotated with %s, %s, or %s", state.types.asElement(
- state.requestContextType).getSimpleName(), Service.class.getSimpleName(),
- ServiceName.class.getSimpleName(), JsonRpcService.class.getSimpleName());
+ state.poison(x, Messages.contextMustBeAnnotated(state.requestContextType.asElement()
+ .getSimpleName()));
}
if (service != null) {
poisonIfAnnotationPresent(state, x, serviceName, jsonRpcService);
@@ -104,8 +100,7 @@
TypeElement domain =
state.elements.getTypeElement(BinaryName.toSourceName(serviceName.value()));
if (domain == null) {
- state.warn(x, "Cannot fully validate context since type %s is not available", serviceName
- .value());
+ state.warn(x, Messages.contextMissingDomainType(serviceName.value()));
} else {
state.addMapping(x, domain);
}
@@ -120,7 +115,7 @@
public Void visitTypeParameter(TypeParameterElement x, State state) {
for (TypeMirror bound : x.getBounds()) {
if (!state.isTransportableType(bound)) {
- state.poison(x, "The type %s cannot be used here", bound);
+ state.poison(x, Messages.untransportableType(bound));
}
}
return super.visitTypeParameter(x, state);
@@ -129,7 +124,7 @@
@Override
public Void visitVariable(VariableElement x, State state) {
if (!state.isTransportableType(x.asType())) {
- state.poison(x, "The type %s cannot be used here", x.asType());
+ state.poison(x, Messages.untransportableType(x.asType()));
}
return super.visitVariable(x, state);
}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RequestFactoryScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/RequestFactoryScanner.java
index e9cea1c..d36e38c 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/RequestFactoryScanner.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RequestFactoryScanner.java
@@ -33,18 +33,19 @@
return null;
}
if (!x.getParameters().isEmpty()) {
- state.poison(x, "This method must have no parameters");
+ state.poison(x, Messages.factoryNoMethodParameters());
}
TypeMirror returnType = x.getReturnType();
if (state.types.isAssignable(returnType, state.requestContextType)) {
Element returnTypeElement = state.types.asElement(returnType);
if (!returnTypeElement.getKind().equals(ElementKind.INTERFACE)) {
- state.poison(x, "The return type %s must be an interface", returnType.toString());
+ state.poison(x, Messages.factoryMustReturnInterface(returnTypeElement.getSimpleName()));
} else {
state.maybeScanContext((TypeElement) returnTypeElement);
}
} else {
- state.poison(x, "This method must return a %s", state.requestContextType);
+ state.poison(x, Messages.factoryMustBeAssignable(state.requestContextType.asElement()
+ .getSimpleName()));
}
return null;
}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RfValidator.java b/user/src/com/google/web/bindery/requestfactory/apt/RfValidator.java
index 332c6d9..1d1eec8 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/RfValidator.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RfValidator.java
@@ -34,12 +34,13 @@
@SupportedOptions({"suppressErrors", "suppressWarnings", "verbose"})
public class RfValidator extends AbstractProcessor {
+ private boolean forceErrors;
private State state;
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (state == null) {
- state = new State(processingEnv);
+ state = forceErrors ? new State.ForTesting(processingEnv) : new State(processingEnv);
}
try {
@@ -57,4 +58,8 @@
}
return false;
}
+
+ void setForceErrors(boolean forceErrors) {
+ this.forceErrors = forceErrors;
+ }
}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/ScannerBase.java b/user/src/com/google/web/bindery/requestfactory/apt/ScannerBase.java
index 49268da..85ceea6 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/ScannerBase.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ScannerBase.java
@@ -42,7 +42,7 @@
Annotation... annotations) {
for (Annotation a : annotations) {
if (a != null) {
- state.poison(x, "Redundant annotation: %s", a.annotationType().getSimpleName());
+ state.poison(x, Messages.redundantAnnotation(a.annotationType().getSimpleName()));
}
}
}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/State.java b/user/src/com/google/web/bindery/requestfactory/apt/State.java
index 52c6ef3..6285eef 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/State.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/State.java
@@ -15,9 +15,6 @@
*/
package com.google.web.bindery.requestfactory.apt;
-import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
-import com.google.web.bindery.requestfactory.shared.ProxyFor;
-import com.google.web.bindery.requestfactory.shared.ProxyForName;
import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
import java.io.PrintWriter;
@@ -48,6 +45,20 @@
import javax.tools.Diagnostic.Kind;
class State {
+ /**
+ * Slightly tweaked implementation used when running tests.
+ */
+ static class ForTesting extends State {
+ public ForTesting(ProcessingEnvironment processingEnv) {
+ super(processingEnv);
+ }
+
+ @Override
+ boolean respectAnnotations() {
+ return false;
+ }
+ }
+
private static class Job {
public final TypeElement element;
public final ScannerBase<?> scanner;
@@ -103,19 +114,20 @@
final DeclaredType requestType;
final DeclaredType serviceLocatorType;
final Set<TypeElement> seen;
- final boolean suppressErrors;
- final boolean suppressWarnings;
final Types types;
final DeclaredType valueProxyType;
- final boolean verbose;
private final Map<TypeElement, TypeElement> clientToDomainMain;
private final List<Job> jobs = new LinkedList<Job>();
private final Messager messager;
private boolean poisoned;
+ private final boolean suppressErrors;
+ private final boolean suppressWarnings;
+ private final boolean verbose;
/**
* Prevents duplicate messages from being emitted.
*/
private final Map<Element, Set<String>> previousMessages = new HashMap<Element, Set<String>>();
+
private final Set<TypeElement> proxiesRequiringMapping = new LinkedHashSet<TypeElement>();
public State(ProcessingEnvironment processingEnv) {
@@ -123,9 +135,9 @@
elements = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
types = processingEnv.getTypeUtils();
- verbose = Boolean.parseBoolean(processingEnv.getOptions().get("verbose"));
suppressErrors = Boolean.parseBoolean(processingEnv.getOptions().get("suppressErrors"));
suppressWarnings = Boolean.parseBoolean(processingEnv.getOptions().get("suppressWarnings"));
+ verbose = Boolean.parseBoolean(processingEnv.getOptions().get("verbose"));
entityProxyType = findType("EntityProxy");
entityProxyIdType = findType("EntityProxyId");
@@ -197,13 +209,21 @@
}).scan(x, this);
}
+ /**
+ * Print a warning message if verbose mode is enabled. A warning is used to
+ * ensure that the message shows up in Eclipse's editor (a note only makes it
+ * into the error console).
+ */
+ public void debug(Element elt, String message, Object... args) {
+ if (verbose) {
+ messager.printMessage(Kind.WARNING, String.format(message, args), elt);
+ }
+ }
+
public void executeJobs() {
while (!jobs.isEmpty()) {
Job job = jobs.remove(0);
- if (verbose) {
- messager.printMessage(Kind.NOTE, String.format("Scanning %s", elements
- .getBinaryName(job.element)));
- }
+ debug(job.element, "Scanning");
try {
job.scanner.scan(job.element, this);
} catch (HaltException ignored) {
@@ -216,9 +236,7 @@
}
for (TypeElement proxyElement : proxiesRequiringMapping) {
if (!getClientToDomainMap().containsKey(proxyElement)) {
- poison(proxyElement, "A proxy must be annotated with %s, %s, or %s", ProxyFor.class
- .getSimpleName(), ProxyForName.class.getSimpleName(), JsonRpcProxy.class
- .getSimpleName());
+ poison(proxyElement, Messages.proxyMustBeAnnotated());
}
}
}
@@ -287,28 +305,29 @@
* eclosing type is annotated with {@link SkipInterfaceValidation} the message
* will be dropped.
*/
- public void poison(Element elt, String message, Object... args) {
+ public void poison(Element elt, String message) {
if (suppressErrors) {
return;
}
- String formatted = String.format(message, args);
- if (squelchMessage(elt, formatted)) {
+ if (squelchMessage(elt, message)) {
return;
}
- Element check = elt;
- while (check != null) {
- if (check.getAnnotation(SkipInterfaceValidation.class) != null) {
- return;
+ if (respectAnnotations()) {
+ Element check = elt;
+ while (check != null) {
+ if (check.getAnnotation(SkipInterfaceValidation.class) != null) {
+ return;
+ }
+ check = check.getEnclosingElement();
}
- check = check.getEnclosingElement();
}
if (elt == null) {
- messager.printMessage(Kind.ERROR, formatted);
+ messager.printMessage(Kind.ERROR, message);
} else {
- messager.printMessage(Kind.ERROR, formatted, elt);
+ messager.printMessage(Kind.ERROR, message, elt);
}
}
@@ -320,30 +339,41 @@
* Emits a warning message, unless the element or an enclosing element are
* annotated with a {@code @SuppressWarnings("requestfactory")}.
*/
- public void warn(Element elt, String message, Object... args) {
+ public void warn(Element elt, String message) {
if (suppressWarnings) {
return;
}
- String formatted = String.format(message, args);
- if (squelchMessage(elt, formatted)) {
+ if (squelchMessage(elt, message)) {
return;
}
- SuppressWarnings suppress;
- Element check = elt;
- while (check != null) {
- suppress = check.getAnnotation(SuppressWarnings.class);
- if (suppress != null) {
- if (Arrays.asList(suppress.value()).contains("requestfactory")) {
+ if (respectAnnotations()) {
+ SuppressWarnings suppress;
+ Element check = elt;
+ while (check != null) {
+ if (check.getAnnotation(SkipInterfaceValidation.class) != null) {
return;
}
+ suppress = check.getAnnotation(SuppressWarnings.class);
+ if (suppress != null) {
+ if (Arrays.asList(suppress.value()).contains("requestfactory")) {
+ return;
+ }
+ }
+ check = check.getEnclosingElement();
}
- check = check.getEnclosingElement();
}
- messager.printMessage(Kind.WARNING, formatted
- + "\n\nAdd @SuppressWarnings(\"requestfactory\") to dismiss.", elt);
+ messager.printMessage(Kind.WARNING, message + Messages.warnSuffix(), elt);
+ }
+
+ /**
+ * This switch allows the RfValidatorTest code to be worked on in the IDE
+ * without causing compilation failures.
+ */
+ boolean respectAnnotations() {
+ return true;
}
private boolean fastFail(TypeElement element) {
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/TransportableTypeVisitor.java b/user/src/com/google/web/bindery/requestfactory/apt/TransportableTypeVisitor.java
index e6d106d..e9e5d08 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/TransportableTypeVisitor.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/TransportableTypeVisitor.java
@@ -15,8 +15,6 @@
*/
package com.google.web.bindery.requestfactory.apt;
-import com.google.web.bindery.autobean.shared.ValueCodex;
-
import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -28,12 +26,11 @@
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
-import javax.lang.model.util.SimpleTypeVisitor6;
/**
* Scans a TypeMirror to determine if it can be transported by RequestFactory.
*/
-class TransportableTypeVisitor extends SimpleTypeVisitor6<Boolean, State> {
+class TransportableTypeVisitor extends TypeVisitorBase<Boolean> {
/**
* AutoBeans supports arbitrary parameterizations, but there's work that needs
@@ -62,12 +59,8 @@
}
return asId.getTypeArguments().get(0).accept(this, state);
}
- for (Class<?> clazz : ValueCodex.getAllValueTypes()) {
- if (clazz.isPrimitive()) {
- continue;
- }
-
- if (state.types.isAssignable(t, state.findType(clazz))) {
+ for (DeclaredType valueType : getValueTypes(state)) {
+ if (state.types.isAssignable(t, valueType)) {
return true;
}
}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/TypeVisitorBase.java b/user/src/com/google/web/bindery/requestfactory/apt/TypeVisitorBase.java
new file mode 100644
index 0000000..5468981
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/TypeVisitorBase.java
@@ -0,0 +1,63 @@
+/*
+ * 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.apt;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.SimpleTypeVisitor6;
+
+/**
+ * Provides utility functions for type visitors.
+ *
+ * @param <T> the return type for the visitor
+ */
+class TypeVisitorBase<T> extends SimpleTypeVisitor6<T, State> {
+ /**
+ * This method should be kept in sync with
+ * {@code ValueCodex.getAllValueTypes()}. It doesn't use
+ * {@code getAllValueTypes()} because a dependency on {@code ValueCodex} would
+ * pull in a large number of dependencies into the minimal
+ * {@code requestfactory-apt.jar}.
+ */
+ protected List<DeclaredType> getValueTypes(State state) {
+ List<DeclaredType> types = new ArrayList<DeclaredType>();
+ for (TypeKind kind : TypeKind.values()) {
+ if (kind.isPrimitive()) {
+ PrimitiveType primitiveType = state.types.getPrimitiveType(kind);
+ TypeElement boxedClass = state.types.boxedClass(primitiveType);
+ types.add((DeclaredType) boxedClass.asType());
+ }
+ }
+ types.add(state.findType(BigDecimal.class));
+ types.add(state.findType(BigInteger.class));
+ types.add(state.findType(Date.class));
+ types.add(state.findType(String.class));
+ types.add(state.findType(Void.class));
+ // Avoids compile-dependency bloat
+ types.add(state.types.getDeclaredType(state.elements
+ .getTypeElement("com.google.web.bindery.autobean.shared.Splittable")));
+ return Collections.unmodifiableList(types);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
index c2b69c1..3bc051d 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
@@ -30,6 +30,6 @@
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
+@Target({ElementType.METHOD, ElementType.PACKAGE, ElementType.TYPE})
public @interface SkipInterfaceValidation {
}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/DiagnosticComparator.java b/user/test/com/google/web/bindery/requestfactory/apt/DiagnosticComparator.java
new file mode 100644
index 0000000..80b1938
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/DiagnosticComparator.java
@@ -0,0 +1,43 @@
+/*
+ * 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.apt;
+
+import java.util.Comparator;
+
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+/**
+ * Orders Diagnostic objects by filename, position, and message.
+ */
+class DiagnosticComparator implements Comparator<Diagnostic<? extends JavaFileObject>> {
+ @Override
+ public int compare(Diagnostic<? extends JavaFileObject> o1,
+ Diagnostic<? extends JavaFileObject> o2) {
+ int c;
+ if (o1.getSource() != null && o2.getSource() != null) {
+ c = o1.getSource().toUri().toString().compareTo(o2.getSource().toUri().toString());
+ if (c != 0) {
+ return c;
+ }
+ }
+ long p = o1.getPosition() - o2.getPosition();
+ if (p != 0) {
+ return Long.signum(p);
+ }
+ return o1.getMessage(null).compareTo(o2.getMessage(null));
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyCheckDomainMapping.java b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyCheckDomainMapping.java
new file mode 100644
index 0000000..6a1ee98
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyCheckDomainMapping.java
@@ -0,0 +1,70 @@
+/*
+ * 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.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+
+@Expected({
+ @Expect(method = "domainGetIdStatic"),
+ @Expect(method = "domainGetVersionStatic"),
+ @Expect(method = "domainFindNotStatic", args = "Domain"),
+ @Expect(method = "domainMethodWrongModifier", args = {"false", "getFoo"}),
+ @Expect(method = "domainNoDefaultConstructor", args = {
+ "Domain", "EntityProxyCheckDomainMapping", "RequestContext"}, warning = true)})
+@ProxyFor(EntityProxyCheckDomainMapping.Domain.class)
+@SuppressWarnings("requestfactory")
+interface EntityProxyCheckDomainMapping extends EntityProxy {
+ public static class Domain {
+ public static String getFoo() {
+ return null;
+ }
+
+ public static String getId() {
+ return null;
+ }
+
+ public static String getVersion() {
+ return null;
+ }
+
+ public Domain(@SuppressWarnings("unused") boolean ignored) {
+ }
+
+ public Domain findDomain(@SuppressWarnings("unused") String id) {
+ return null;
+ }
+ }
+
+ String getFoo();
+
+ @Expect(method = "domainMissingMethod", args = "java.lang.String getMissingProperty()")
+ String getMissingProperty();
+
+ @Expected({
+ @Expect(method = "methodNoDomainPeer", args = {"java.lang.Object", "false"}, warning = true),
+ @Expect(method = "untransportableType", args = "java.lang.Object")})
+ Object getUntransportable();
+
+ @Expect(method = "methodNoDomainPeer", args = {"java.lang.Object", "true"}, warning = true)
+ void setUntransportable(
+ @Expect(method = "untransportableType", args = "java.lang.Object") Object obj);
+
+ @Expected({
+ @Expect(method = "proxyOnlyGettersSetters"),
+ @Expect(method = "domainMissingMethod", args = "java.lang.String notAProperty()")})
+ String notAProperty();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMismatchedFind.java b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMismatchedFind.java
new file mode 100644
index 0000000..ad943b8
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMismatchedFind.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.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+
+@Expect(method = "domainMissingFind", args = {
+ "com.google.web.bindery.requestfactory.apt.EntityProxyMismatchedFind.Domain", "Domain",
+ "java.lang.String", "EntityProxyMismatchedFind"}, warning = true)
+@ProxyFor(EntityProxyMismatchedFind.Domain.class)
+@SuppressWarnings("requestfactory")
+interface EntityProxyMismatchedFind extends EntityProxy {
+ public static class Domain {
+ public static Domain findDomain(@SuppressWarnings("unused") Integer id) {
+ return null;
+ }
+
+ public String getId() {
+ return null;
+ }
+
+ public String getVersion() {
+ return null;
+ }
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainLocatorMethods.java b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainLocatorMethods.java
new file mode 100644
index 0000000..a222e01
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainLocatorMethods.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.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+
+@Expected({
+ @Expect(method = "domainNoGetId", args = "com.google.web.bindery.requestfactory.apt.EntityProxyMissingDomainLocatorMethods.Domain"),
+ @Expect(method = "domainNoGetVersion", args = "com.google.web.bindery.requestfactory.apt.EntityProxyMissingDomainLocatorMethods.Domain")})
+@ProxyFor(EntityProxyMissingDomainLocatorMethods.Domain.class)
+interface EntityProxyMissingDomainLocatorMethods extends EntityProxy {
+ public static class Domain {
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainType.java b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainType.java
new file mode 100644
index 0000000..2282bc8
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainType.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.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+
+@Expect(method = "proxyMissingDomainType", args = "does.not.exist", warning = true)
+@ProxyForName("does.not.exist")
+interface EntityProxyMissingDomainType extends EntityProxy {
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/Expect.java b/user/test/com/google/web/bindery/requestfactory/apt/Expect.java
new file mode 100644
index 0000000..953b2b5
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/Expect.java
@@ -0,0 +1,43 @@
+/*
+ * 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.apt;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This annotation is applied to any element that is expected to be the target
+ * of an error or a warning from the validator.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@interface Expect {
+ /**
+ * The arguments to be passed to {@code method}.
+ */
+ String[] args() default {};
+
+ /**
+ * The name of a method defined in {@link Messages}.
+ */
+ String method();
+
+ /**
+ * Specifies whether the diagnostic will be a warning or an error.
+ */
+ boolean warning() default false;
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/ExpectCollector.java b/user/test/com/google/web/bindery/requestfactory/apt/ExpectCollector.java
new file mode 100644
index 0000000..31f7cde
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/ExpectCollector.java
@@ -0,0 +1,115 @@
+/*
+ * 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.apt;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementScanner6;
+import javax.tools.Diagnostic.Kind;
+
+/**
+ * This is a trivial scanner that collects {@code @Expect} declarations on a
+ * type. It does not perform any type-chasing or supertype traversal. The named
+ * method on the {@link Messages} class is invoked with the provided arguments
+ * and the resulting message is emitted to the {@link Messager}.
+ */
+@SupportedAnnotationTypes({
+ "com.google.web.bindery.requestfactory.apt.Expect",
+ "com.google.web.bindery.requestfactory.apt.Expected"})
+@SupportedSourceVersion(SourceVersion.RELEASE_6)
+class ExpectCollector extends AbstractProcessor {
+ class Scanner extends ElementScanner6<Void, Void> {
+
+ private final Messager messager;
+
+ public Scanner(Messager messager) {
+ this.messager = messager;
+ }
+
+ @Override
+ public Void scan(Element e, Void p) {
+ Expect expect = e.getAnnotation(Expect.class);
+ if (expect != null) {
+ addExpect(expect, e);
+ }
+ Expected expected = e.getAnnotation(Expected.class);
+ if (expected != null) {
+ for (Expect v : expected.value()) {
+ addExpect(v, e);
+ }
+ }
+ return super.scan(e, p);
+ }
+
+ private void addExpect(Expect expect, Element accumulator) {
+ Method toInvoke = null;
+ for (Method m : Messages.class.getDeclaredMethods()) {
+ if (m.getName().equals(expect.method())) {
+ toInvoke = m;
+ break;
+ }
+ }
+ if (toInvoke == null) {
+ throw new RuntimeException("No method named " + expect.method());
+ }
+ String[] originalArgs = expect.args();
+ Object[] args = new Object[originalArgs.length];
+ for (int i = 0, j = args.length; i < j; i++) {
+ // Special case for domainMethodWrongModifier
+ if (boolean.class.equals(toInvoke.getParameterTypes()[i])) {
+ args[i] = Boolean.valueOf(originalArgs[i]);
+ } else {
+ args[i] = originalArgs[i];
+ }
+ }
+ String message;
+ Throwable ex;
+ try {
+ message = (String) toInvoke.invoke(null, args);
+ if (expect.warning()) {
+ message += Messages.warnSuffix();
+ }
+ messager.printMessage(expect.warning() ? Kind.WARNING : Kind.ERROR, message, accumulator);
+ return;
+ } catch (IllegalArgumentException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ } catch (InvocationTargetException e) {
+ ex = e.getCause();
+ }
+ throw new RuntimeException("Could not get test message", ex);
+ }
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ Scanner scanner = new Scanner(processingEnv.getMessager());
+ scanner.scan(roundEnv.getElementsAnnotatedWith(Expect.class), null);
+ scanner.scan(roundEnv.getElementsAnnotatedWith(Expected.class), null);
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/Expected.java b/user/test/com/google/web/bindery/requestfactory/apt/Expected.java
new file mode 100644
index 0000000..5e73483
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/Expected.java
@@ -0,0 +1,27 @@
+/*
+ * 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.apt;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Allows several {@link Expect} annotations to be applied to an element.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@interface Expected {
+ Expect[] value();
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/MyRequestContext.java b/user/test/com/google/web/bindery/requestfactory/apt/MyRequestContext.java
new file mode 100644
index 0000000..9e118f9
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/MyRequestContext.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.requestfactory.apt;
+
+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.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;
+
+@Expect(method = "contextMustBeAnnotated", args = "RequestContext")
+interface MyRequestContext extends RequestContext {
+ @Expect(method = "proxyMustBeAnnotated")
+ interface ProxyMissingAnnotation extends EntityProxy {
+ }
+
+ @Expected({
+ @Expect(method = "proxyMissingDomainType", args = "bad", warning = true),
+ @Expect(method = "redundantAnnotation", args = "ProxyForName"),
+ @Expect(method = "redundantAnnotation", args = "JsonRpcProxy")})
+ @ProxyFor(ProxyWithRedundantAnnotations.Domain.class)
+ @ProxyForName("bad")
+ @JsonRpcProxy
+ interface ProxyWithRedundantAnnotations extends EntityProxy {
+ static class Domain {
+ }
+ }
+
+ /**
+ * Because this is not referenced from the context, it shouldn't generate an
+ * error.
+ */
+ interface UnusedProxyBase extends EntityProxy {
+ }
+
+ @Expect(method = "untransportableType", args = "java.lang.Object")
+ InstanceRequest<ProxyWithRedundantAnnotations, Object> badInstanceReturn();
+
+ @Expect(method = "contextRequiredReturnTypes", args = {"Request", "InstanceRequest"})
+ String badMethod();
+
+ Request<Void> badParam(@Expect(method = "untransportableType", args = "java.lang.Object") Object o);
+
+ @Expect(method = "untransportableType", args = "java.lang.Object")
+ Request<Object> badReturn();
+
+ Request<ProxyMissingAnnotation> forceAnnotation();
+
+ @Expect(method = "rawType")
+ @SuppressWarnings("rawtypes")
+ InstanceRequest rawInstance();
+
+ @Expect(method = "rawType")
+ @SuppressWarnings("rawtypes")
+ Request rawRequest();
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/MyRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/apt/MyRequestFactory.java
new file mode 100644
index 0000000..2abe076
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/MyRequestFactory.java
@@ -0,0 +1,29 @@
+/*
+ * 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.apt;
+
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
+
+interface MyRequestFactory extends RequestFactory {
+ @Expect(method = "factoryMustReturnInterface", args = "AbstractRequestContext")
+ AbstractRequestContext concreteContext();
+
+ @Expected({
+ @Expect(method = "factoryNoMethodParameters"),
+ @Expect(method = "factoryMustBeAssignable", args = "RequestContext")})
+ String stringMethod(String bad);
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RequestContextMissingDomainType.java b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextMissingDomainType.java
new file mode 100644
index 0000000..57de96a
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextMissingDomainType.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.apt;
+
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+
+@Expect(method = "contextMissingDomainType", args = "does.not.exist", warning = true)
+@ServiceName("does.not.exist")
+interface RequestContextMissingDomainType extends RequestContext {
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RequestContextUsingUnmappedProxy.java b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextUsingUnmappedProxy.java
new file mode 100644
index 0000000..ddb0efd
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextUsingUnmappedProxy.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.apt;
+
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+
+@Service(RequestContextUsingUnmappedProxy.Domain.class)
+interface RequestContextUsingUnmappedProxy extends RequestContext {
+ public static class Domain {
+ }
+
+ @Expected({
+ @Expect(method = "methodNoDomainPeer", args = {
+ "com.google.web.bindery.requestfactory.apt.EntityProxyMissingDomainType", "false"}, warning = true),
+ @Expect(method = "methodNoDomainPeer", args = {
+ "com.google.web.bindery.requestfactory.apt.EntityProxyMissingDomainType", "true"}, warning = true)})
+ Request<EntityProxyMissingDomainType> cannotVerify(EntityProxyMissingDomainType proxy);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedBoxes.java b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedBoxes.java
new file mode 100644
index 0000000..ee854aa
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedBoxes.java
@@ -0,0 +1,60 @@
+/*
+ * 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.apt;
+
+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 com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+/**
+ * Checks that proxy property accessors match exactly and that context method
+ * arguments must match exactly.
+ */
+@Service(RequestContextWithMismatchedBoxes.Domain.class)
+interface RequestContextWithMismatchedBoxes extends RequestContext {
+ @Expect(method = "domainMissingMethod", args = "java.lang.Void checkBoxed(int)")
+ Request<Void> checkBoxed(int value);
+
+ @Expect(method = "domainMissingMethod", args = "java.lang.Void checkPrimitive(java.lang.Integer)")
+ Request<Void> checkPrimitive(Integer value);
+
+ @ProxyFor(Domain.class)
+ interface ProxyMismatchedGetterA extends ValueProxy {
+ @Expect(method = "domainMissingMethod", args = "int getBoxed()")
+ int getBoxed();
+
+ @Expect(method = "domainMissingMethod", args = "java.lang.Integer getPrimitive()")
+ Integer getPrimitive();
+ }
+
+ static class Domain {
+ static void checkBoxed(@SuppressWarnings("unused") Integer value) {
+ }
+
+ static void checkPrimitive(@SuppressWarnings("unused") int value) {
+ }
+
+ Integer getBoxed() {
+ return null;
+ }
+
+ int getPrimitive() {
+ return 0;
+ }
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedInstance.java b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedInstance.java
new file mode 100644
index 0000000..26c428d
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedInstance.java
@@ -0,0 +1,61 @@
+/*
+ * 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.apt;
+
+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.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+
+/**
+ * Tests Request and InstanceRequest methods bound to methods with the wrong
+ * static modifier.
+ */
+@Expected({
+ @Expect(method = "domainMethodWrongModifier", args = {"true", "instanceMethod"}),
+ @Expect(method = "domainMethodWrongModifier", args = {"false", "staticMethod"})})
+@Service(RequestContextWithMismatchedInstance.Domain.class)
+interface RequestContextWithMismatchedInstance extends RequestContext {
+ static class Domain {
+ public static Domain findDomain(@SuppressWarnings("unused") String id) {
+ return null;
+ }
+
+ public static void staticMethod() {
+ }
+
+ public String getId() {
+ return null;
+ }
+
+ public String getVersion() {
+ return null;
+ }
+
+ public void instanceMethod() {
+ }
+ }
+
+ @ProxyFor(Domain.class)
+ interface Proxy extends EntityProxy {
+ }
+
+ Request<Void> instanceMethod();
+
+ InstanceRequest<Proxy, Void> staticMethod();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RfValidatorTest.java b/user/test/com/google/web/bindery/requestfactory/apt/RfValidatorTest.java
new file mode 100644
index 0000000..c57ae33
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RfValidatorTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.apt;
+
+import com.google.gwt.dev.util.Util;
+import com.google.web.bindery.requestfactory.shared.LoggingRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.shared.TestRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.FindRequest;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.TreeSet;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+/**
+ * Integration test of {@link RfValidator} using the Java6 tools API to invoke
+ * the Java compiler. This test requires that the gwt-user source is available
+ * on the classpath.
+ */
+public class RfValidatorTest extends TestCase {
+ /**
+ * Provides access to a source file from the classpath.
+ */
+ private static class UriJavaFileObject extends SimpleJavaFileObject {
+ public static UriJavaFileObject create(Class<?> toLoad) {
+ try {
+ String path = toLoad.getName().replace('.', '/') + ".java";
+ /*
+ * SimpleJavaFileObject does not like the URI's created from jar URLs
+ * since their path does not start with a leading forward-slash. The
+ * choice of "classpath" as the scheme is arbitrary and meaningless.
+ */
+ URI fakeLocation = new URI("classpath:/" + path);
+ return new UriJavaFileObject(fakeLocation, Thread.currentThread().getContextClassLoader()
+ .getResource(path));
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final URL contents;
+
+ public UriJavaFileObject(URI fakeLocation, URL contents) throws URISyntaxException {
+ super(fakeLocation, JavaFileObject.Kind.SOURCE);
+ this.contents = contents;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignored) throws IOException {
+ return Util.readStreamAsString(contents.openStream());
+ }
+ }
+
+ /**
+ * Smoke test to ensure that appropriate errors and warnings are emitted.
+ */
+ public void testErrorsAndWarnings() {
+ testGeneratedMessages(EntityProxyCheckDomainMapping.class);
+ testGeneratedMessages(EntityProxyMismatchedFind.class);
+ testGeneratedMessages(EntityProxyMissingDomainLocatorMethods.class);
+ testGeneratedMessages(EntityProxyMissingDomainType.class);
+ testGeneratedMessages(MyRequestContext.class);
+ testGeneratedMessages(MyRequestFactory.class);
+ testGeneratedMessages(RequestContextMissingDomainType.class);
+ testGeneratedMessages(RequestContextUsingUnmappedProxy.class,
+ EntityProxyMissingDomainType.class);
+ testGeneratedMessages(RequestContextWithMismatchedBoxes.class);
+ testGeneratedMessages(RequestContextWithMismatchedInstance.class);
+ }
+
+ /**
+ * The target classes for this method don't contain any {@code @Expect}
+ * annotations, so this test will verify that they are error- and
+ * warning-free.
+ */
+ public void testTestClasses() {
+ testGeneratedMessages(FindRequest.class);
+ testGeneratedMessages(LoggingRequest.class);
+ testGeneratedMessages(SimpleRequestFactory.class);
+ testGeneratedMessages(TestRequestFactory.class);
+ }
+
+ private void assertEquals(TreeSet<Diagnostic<? extends JavaFileObject>> expected,
+ TreeSet<Diagnostic<? extends JavaFileObject>> actual) {
+ List<Diagnostic<?>> unmatched = new ArrayList<Diagnostic<?>>();
+
+ // Remove actual elements from expect, saving any unexpected elements
+ for (Diagnostic<? extends JavaFileObject> d : actual) {
+ if (!expected.remove(d)) {
+ unmatched.add(d);
+ }
+ }
+
+ assertTrue("Did not see expected errors: " + expected + "\n\nLeftovers :" + unmatched, expected
+ .isEmpty());
+ assertTrue("Unexpected errors: " + unmatched, unmatched.isEmpty());
+ }
+
+ /**
+ * Run the annotation processor over one or more classes and verify that the
+ * appropriate messages are generated.
+ */
+ private void testGeneratedMessages(Class<?>... classes) {
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+
+ List<JavaFileObject> files = new ArrayList<JavaFileObject>(classes.length);
+ for (Class<?> clazz : classes) {
+ JavaFileObject obj = UriJavaFileObject.create(clazz);
+ files.add(obj);
+ }
+ StringWriter errorWriter = new StringWriter();
+ RfValidator rfValidator = new RfValidator();
+ rfValidator.setForceErrors(true);
+
+ DiagnosticCollector<JavaFileObject> expectedCollector =
+ new DiagnosticCollector<JavaFileObject>();
+ CompilationTask expectedTask =
+ compiler.getTask(errorWriter, null, expectedCollector, Arrays.asList("-proc:only"), null,
+ files);
+ expectedTask.setProcessors(Arrays.asList(new ExpectCollector()));
+ expectedTask.call();
+
+ DiagnosticCollector<JavaFileObject> actualCollector = new DiagnosticCollector<JavaFileObject>();
+ CompilationTask actualTask =
+ compiler.getTask(errorWriter, null, actualCollector, Arrays.asList("-proc:only"), null,
+ files);
+ actualTask.setProcessors(Arrays.asList(rfValidator));
+ actualTask.call();
+
+ TreeSet<Diagnostic<? extends JavaFileObject>> expected =
+ new TreeSet<Diagnostic<? extends JavaFileObject>>(new DiagnosticComparator());
+ expected.addAll(expectedCollector.getDiagnostics());
+ TreeSet<Diagnostic<? extends JavaFileObject>> actual =
+ new TreeSet<Diagnostic<? extends JavaFileObject>>(new DiagnosticComparator());
+ actual.addAll(actualCollector.getDiagnostics());
+ assertEquals(expected, actual);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/package-info.java b/user/test/com/google/web/bindery/requestfactory/apt/package-info.java
new file mode 100644
index 0000000..d2eab38
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/**
+ * By disabling the following annotation, a manual check can be made against the
+ * various {@link Expect} annotations and the IDE's behavior.
+ */
+@SkipInterfaceValidation
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
+
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
index 5a2f008..2505961 100644
--- a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
@@ -15,6 +15,7 @@
*/
package com.google.web.bindery.requestfactory.gwt;
+import com.google.web.bindery.requestfactory.apt.RfValidatorTest;
import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModelTest;
import junit.framework.Test;
@@ -33,6 +34,7 @@
TestSuite suite = new TestSuite(
"requestfactory package tests that require the JRE and gwt-user");
suite.addTestSuite(RequestFactoryModelTest.class);
+ suite.addTestSuite(RfValidatorTest.class);
return suite;
}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
index c38d6c5..bddcdd0 100644
--- a/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
+++ b/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
@@ -24,6 +24,7 @@
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.SkipInterfaceValidation;
import java.util.Arrays;
import java.util.logging.Logger;
@@ -32,7 +33,13 @@
* A JRE version of {@link BoxesAndPrimitivesTest} with additional validation
* tests.
*/
+@SkipInterfaceValidation
public class BoxesAndPrimitivesJreTest extends BoxesAndPrimitivesTest {
+ /*
+ * The SkipInterfaceValidation annotation is to prevent RfValidator from
+ * blowing up. This annotation, the inner interfaces, and the tests here can
+ * be removed when RequestFactoryInterfaceValidator is retired.
+ */
@Service(ServiceImpl.class)
interface ContextMismatchedParameterA extends RequestContext {
diff --git a/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java b/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
index 821f20a..350b8c9 100644
--- a/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
+++ b/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
@@ -22,5 +22,5 @@
*/
@Service(com.google.web.bindery.requestfactory.server.SimpleFoo.class)
public interface TestFooPolymorphicRequest extends RequestContext {
- <P extends SimpleFooProxy> Request<P> echo(SimpleFooProxy proxy);
+ <P extends SimpleFooProxy> Request<P> echo(P proxy);
}