Add RequestFactory validator implemented as an annotation processor.
This will eventually replace the RequestFactoryInterfaceValidator and it's
classfile-based approach.
Patch by: bobv
Review by: t.broyer, pquitslund
Review at http://gwt-code-reviews.appspot.com/1467804
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10417 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/eclipse/samples/DynaTableRf/.classpath b/eclipse/samples/DynaTableRf/.classpath
index f2c5ce7..3bdab67 100644
--- a/eclipse/samples/DynaTableRf/.classpath
+++ b/eclipse/samples/DynaTableRf/.classpath
@@ -7,5 +7,10 @@
<classpathentry kind="var" path="GWT_TOOLS/lib/javax/validation/validation-api-1.0.0.GA.jar" sourcepath="/GWT_TOOLS/lib/javax/validation/validation-api-1.0.0.GA-sources.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/lib/javax/validation/validation-api-1.0.0.GA-sources.jar"/>
<classpathentry kind="var" path="GWT_TOOLS/redist/json/r2_20080312/json-1.5.jar"/>
+ <classpathentry kind="src" path=".apt_generated">
+ <attributes>
+ <attribute name="optional" value="true"/>
+ </attributes>
+ </classpathentry>
<classpathentry kind="output" path="war/WEB-INF/classes"/>
</classpath>
diff --git a/eclipse/samples/DynaTableRf/.factorypath b/eclipse/samples/DynaTableRf/.factorypath
new file mode 100644
index 0000000..3602e92
--- /dev/null
+++ b/eclipse/samples/DynaTableRf/.factorypath
@@ -0,0 +1,3 @@
+<factorypath>
+ <factorypathentry kind="VARJAR" id="GWT_TOOLS/lib/requestfactory/requestfactory-apt.jar" enabled="true" runInBatchMode="false"/>
+</factorypath>
diff --git a/user/build.xml b/user/build.xml
index 5b39170..c37fbeb 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -151,7 +151,9 @@
<target name="precompile.modules" depends="compile">
<outofdate>
<sourcefiles>
- <fileset dir="${gwt.root}/user/src" />
+ <fileset dir="${gwt.root}/user/src" >
+ <exclude name="com/google/web/bindery/requestfactory/apt/**"/>
+ </fileset>
<fileset dir="${gwt.root}/user/super" />
<fileset dir="${gwt.root}/dev/core/src" />
<fileset dir="${gwt.root}/dev/core/super" />
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/ClientToDomainMapper.java b/user/src/com/google/web/bindery/requestfactory/apt/ClientToDomainMapper.java
new file mode 100644
index 0000000..f4cf816
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ClientToDomainMapper.java
@@ -0,0 +1,157 @@
+/*
+ * 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.autobean.shared.ValueCodex;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+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> {
+ public static class UnmappedTypeException extends RuntimeException {
+ private final TypeMirror clientType;
+
+ public UnmappedTypeException() {
+ super();
+ clientType = null;
+ }
+
+ public UnmappedTypeException(TypeMirror clientType) {
+ super("No domain type resolved for " + clientType.toString());
+ this.clientType = clientType;
+ }
+
+ public TypeMirror getClientType() {
+ return clientType;
+ }
+ }
+
+ @Override
+ public TypeMirror visitDeclared(DeclaredType x, State state) {
+ if (x.asElement().getKind().equals(ElementKind.ENUM)) {
+ // Enums map to enums
+ return x;
+ }
+ if (state.types.isAssignable(x, state.entityProxyType)
+ || state.types.isAssignable(x, state.valueProxyType)) {
+ // FooProxy -> FooDomain
+ TypeElement domainType = state.getClientToDomainMap().get(state.types.asElement(x));
+ if (domainType == null) {
+ return defaultAction(x, state);
+ }
+ return domainType.asType();
+ }
+ if (state.types.isAssignable(x, state.entityProxyIdType)) {
+ // EntityProxyId<FooProxy> -> FooDomain
+ return convertSingleParamType(x, state.entityProxyIdType, 0, state);
+ }
+ if (state.types.isAssignable(x, state.requestType)) {
+ // Request<FooProxy> -> FooDomain
+ return convertSingleParamType(x, state.requestType, 0, state);
+ }
+ if (state.types.isAssignable(x, state.instanceRequestType)) {
+ // 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))) {
+ // Value types map straight through
+ return x;
+ }
+ }
+ if (state.types.isAssignable(x, state.findType(List.class))
+ || state.types.isAssignable(x, state.findType(Set.class))) {
+ // Convert Set,List<FooProxy> to Set,List<FooDomain>
+ TypeMirror param = convertSingleParamType(x, state.findType(Collection.class), 0, state);
+ return state.types.getDeclaredType((TypeElement) state.types.asElement(x), param);
+ }
+ return defaultAction(x, state);
+ }
+
+ @Override
+ public TypeMirror visitNoType(NoType x, State state) {
+ if (x.getKind().equals(TypeKind.VOID)) {
+ // Pass void through
+ return x;
+ }
+ // Here, x would be NONE or PACKAGE, neither of which make sense
+ return defaultAction(x, state);
+ }
+
+ @Override
+ public TypeMirror visitPrimitive(PrimitiveType x, State state) {
+ // Primitives pass through
+ return x;
+ }
+
+ @Override
+ public TypeMirror visitTypeVariable(TypeVariable x, State state) {
+ // Convert <T extends FooProxy> to FooDomain
+ return x.getUpperBound().accept(this, state);
+ }
+
+ @Override
+ public TypeMirror visitWildcard(WildcardType x, State state) {
+ // Convert <? extends FooProxy> to FooDomain
+ if (x.getExtendsBound() != null) {
+ return x.getExtendsBound().accept(this, state);
+ }
+ return defaultAction(x, state);
+ }
+
+ /**
+ * Utility method to convert a {@code Foo<BarProxy> -> BarDomain}. The
+ * {@code param} parameter specifies the index of the type paramater to
+ * extract.
+ */
+ protected TypeMirror convertSingleParamType(DeclaredType x, DeclaredType convertTo, int param,
+ State state) {
+ DeclaredType converted = (DeclaredType) State.viewAs(convertTo, x, state);
+ if (converted == null) {
+ return state.types.getNoType(TypeKind.NONE);
+ }
+ if (converted.getTypeArguments().isEmpty()) {
+ return defaultAction(x, state);
+ }
+ return converted.getTypeArguments().get(param).accept(this, state);
+ }
+
+ @Override
+ protected TypeMirror defaultAction(TypeMirror x, State state) {
+ throw new UnmappedTypeException(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
new file mode 100644
index 0000000..7c53177
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/DomainChecker.java
@@ -0,0 +1,408 @@
+/*
+ * 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.apt.ClientToDomainMapper.UnmappedTypeException;
+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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.MirroredTypeException;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+
+/**
+ * Checks client to domain mappings.
+ */
+class DomainChecker extends ScannerBase<Void> {
+
+ /**
+ * Attempt to find the most specific method that conforms to a given
+ * signature.
+ */
+ static class MethodFinder extends ScannerBase<ExecutableElement> {
+ private TypeElement domainType;
+ private ExecutableElement found;
+ private final CharSequence name;
+ private final TypeMirror returnType;
+ private final List<TypeMirror> params;
+
+ public MethodFinder(CharSequence name, TypeMirror returnType, List<TypeMirror> params,
+ State state) {
+ this.name = name;
+ this.returnType = TypeSimplifier.simplify(returnType, true, state);
+ List<TypeMirror> temp = new ArrayList<TypeMirror>(params.size());
+ for (TypeMirror param : params) {
+ temp.add(TypeSimplifier.simplify(param, false, state));
+ }
+ this.params = Collections.unmodifiableList(temp);
+ }
+
+ @Override
+ public ExecutableElement visitExecutable(ExecutableElement domainMethodElement, State state) {
+ // Quick check for name, paramer count, and return type assignability
+ if (domainMethodElement.getSimpleName().contentEquals(name)
+ && domainMethodElement.getParameters().size() == params.size()) {
+ // Pick up parameterizations in domain type
+ ExecutableType domainMethod = viewIn(domainType, domainMethodElement, state);
+
+ boolean returnTypeMatches;
+ if (returnType == null) {
+ /*
+ * This condition is for methods that we don't really care about the
+ * domain return types (for getId(), getVersion()).
+ */
+ returnTypeMatches = true;
+ } else {
+ TypeMirror domainReturn =
+ TypeSimplifier.simplify(domainMethod.getReturnType(), true, state);
+ // The isSameType handles the NONE case.
+ returnTypeMatches =
+ state.types.isSameType(domainReturn, returnType)
+ || state.types.isAssignable(domainReturn, returnType);
+ }
+ if (returnTypeMatches) {
+ boolean paramsMatch = true;
+ Iterator<TypeMirror> lookFor = params.iterator();
+ Iterator<? extends TypeMirror> domainParam = domainMethod.getParameterTypes().iterator();
+ while (lookFor.hasNext()) {
+ 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)) {
+ paramsMatch = false;
+ }
+ }
+
+ if (paramsMatch) {
+ // Keep most-specific method signature
+ if (found == null
+ || state.types.isSubsignature(domainMethod, (ExecutableType) found.asType())) {
+ found = domainMethodElement;
+ }
+ }
+ }
+ }
+
+ return found;
+ }
+
+ @Override
+ public ExecutableElement visitType(TypeElement domainType, State state) {
+ this.domainType = domainType;
+ return scanAllInheritedMethods(domainType, state);
+ }
+ }
+
+ private static ExecutableType viewIn(TypeElement lookIn, ExecutableElement methodElement,
+ State state) {
+ try {
+ return (ExecutableType) state.types.asMemberOf(state.types.getDeclaredType(lookIn),
+ methodElement);
+ } catch (IllegalArgumentException e) {
+ return (ExecutableType) methodElement.asType();
+ }
+ }
+
+ /**
+ * 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 boolean currentTypeIsProxy;
+ private boolean requireInstanceDomainMethods;
+ private boolean requireStaticDomainMethods;
+ private TypeElement domainType;
+
+ @Override
+ public Void visitExecutable(ExecutableElement clientMethodElement, State state) {
+ if (shouldIgnore(clientMethodElement, state)) {
+ return null;
+ }
+ // Ignore overrides of stableId() in proxies
+ Name name = clientMethodElement.getSimpleName();
+ if (currentTypeIsProxy && name.contentEquals("stableId")
+ && clientMethodElement.getParameters().isEmpty()) {
+ return null;
+ }
+
+ ExecutableType clientMethod = viewIn(checkedType, clientMethodElement, state);
+ List<TypeMirror> lookFor = new ArrayList<TypeMirror>();
+ // Convert client method signature to domain types
+ TypeMirror returnType;
+ try {
+ returnType = convertToDomainTypes(clientMethod, lookFor, clientMethodElement, state);
+ } catch (UnmappedTypeException e) {
+ /*
+ * Unusual: this would happen if a RequestContext for which we have a
+ * resolved domain service method uses unresolved proxy types. For
+ * example, the RequestContext uses a @Service annotation, while one or
+ * more proxy types use @ProxyForName("") and specify a domain type not
+ * available to the compiler.
+ */
+ return null;
+ }
+
+ ExecutableElement domainMethod;
+ if (currentTypeIsProxy && isSetter(clientMethodElement, state)) {
+ // Look for void setFoo(...)
+ domainMethod =
+ new MethodFinder(name, state.types.getNoType(TypeKind.VOID), lookFor, state).scan(
+ domainType, state);
+ if (domainMethod == null) {
+ // Try a builder style
+ domainMethod =
+ new MethodFinder(name, domainType.asType(), lookFor, state).scan(domainType, state);
+ }
+ } else {
+ // The usual case for getters and all service methods
+ domainMethod = new MethodFinder(name, returnType, lookFor, state).scan(domainType, state);
+ }
+
+ if (domainMethod == null) {
+ // Did not find a service method
+ StringBuilder sb = new StringBuilder();
+ sb.append(returnType).append(" ").append(name).append("(");
+ for (TypeMirror param : lookFor) {
+ sb.append(param);
+ }
+ sb.append(")");
+
+ state.poison(clientMethodElement, "Could not find domain method similar to %s", sb);
+ return null;
+ }
+
+ /*
+ * Check the domain method for any requirements for it to be static.
+ * InstanceRequests assume instance methods on the domain type.
+ */
+ boolean isInstanceRequest =
+ state.types.isAssignable(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());
+ }
+ if (!isInstanceRequest && requireStaticDomainMethods
+ && !domainMethod.getModifiers().contains(Modifier.STATIC)) {
+ state.poison(checkedType, "Found instance domain method %s when static method required",
+ domainMethod.getSimpleName());
+ }
+ if (state.verbose) {
+ state.warn(clientMethodElement, "Found domain method %s", domainMethod.toString());
+ }
+
+ return null;
+ }
+
+ @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) {
+ // A proxy with an unresolved domain type (e.g. ProxyForName(""))
+ return null;
+ }
+
+ requireInstanceDomainMethods = false;
+ requireStaticDomainMethods = false;
+
+ if (currentTypeIsProxy) {
+ // Require domain property methods to be instance methods
+ 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());
+ }
+
+ /*
+ * Check for getId(), getVersion(), and findFoo() for any type that
+ * extends EntityProxy, but not on EntityProxy itself, since EntityProxy
+ * is mapped to java.lang.Object.
+ */
+ if (isEntityProxy && !state.types.isSameType(clientType, state.entityProxyType)) {
+ checkDomainEntityMethods(state);
+ }
+ }
+ } else if (!hasServiceLocator(clientTypeElement, state)) {
+ /*
+ * Otherwise, we're looking at a RequestContext. If it doesn't have a
+ * ServiceLocator, all methods must be static.
+ */
+ requireStaticDomainMethods = true;
+ }
+
+ scanAllInheritedMethods(clientTypeElement, state);
+ return null;
+ }
+
+ /**
+ * Check that {@code getId()} and {@code getVersion()} exist and that they are
+ * non-static. Check that {@code findFoo()} exists, is static, returns an
+ * appropriate type, and its parameter is assignable from the return value
+ * from {@code getId()}.
+ */
+ private void checkDomainEntityMethods(State state) {
+ ExecutableElement getId =
+ new MethodFinder("getId", null, Collections.<TypeMirror> emptyList(), state).scan(
+ domainType, state);
+ if (getId == null) {
+ state.poison(checkedType, "Domain type %s does not have a getId() method", domainType
+ .asType());
+ } else {
+ if (getId.getModifiers().contains(Modifier.STATIC)) {
+
+ state.poison(checkedType, "The domain type's getId() method must not be static");
+ }
+
+ // 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);
+ 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());
+ } else if (!find.getModifiers().contains(Modifier.STATIC)) {
+ state.poison(checkedType, "The domain object's find%s() method is not static", domainType
+ .getSimpleName());
+ }
+ }
+
+ ExecutableElement getVersion =
+ new MethodFinder("getVersion", null, Collections.<TypeMirror> emptyList(), state).scan(
+ domainType, state);
+ if (getVersion == null) {
+ state.poison(checkedType, "Domain type %s does not have a getVersion() method", domainType
+ .asType());
+ } else if (getVersion.getModifiers().contains(Modifier.STATIC)) {
+ state.poison(checkedType, "The domain type's getVersion() method must not be static");
+ }
+ }
+
+ /**
+ * Converts a client method's types to their domain counterparts.
+ *
+ * @param clientMethod the RequestContext method to validate
+ * @param parameterAccumulator an out parameter that will be populated with
+ * the converted paramater types
+ * @param warnTo The element to which warnings should be posted if one or more
+ * client types cannot be converted to domain types for validation
+ * @param state the State object
+ * @throws UnmappedTypeException if one or more types used in
+ * {@code clientMethod} cannot be resolved to domain types
+ */
+ private TypeMirror convertToDomainTypes(ExecutableType clientMethod,
+ List<TypeMirror> parameterAccumulator, ExecutableElement warnTo, State state)
+ throws UnmappedTypeException {
+ boolean error = false;
+ TypeMirror returnType;
+ try {
+ returnType = clientMethod.getReturnType().accept(new ClientToDomainMapper(), state);
+ } 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());
+ }
+ for (TypeMirror param : clientMethod.getParameterTypes()) {
+ try {
+ parameterAccumulator.add(param.accept(new ClientToDomainMapper(), state));
+ } 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());
+ }
+ }
+ if (error) {
+ throw new UnmappedTypeException();
+ }
+ return returnType;
+ }
+
+ /**
+ * Looks for a no-arg constructor or no constructors at all. Instance
+ * initializers are ignored.
+ */
+ private boolean hasNoArgConstructor(TypeElement x) {
+ List<ExecutableElement> constructors = ElementFilter.constructorsIn(x.getEnclosedElements());
+ if (constructors.isEmpty()) {
+ return true;
+ }
+ for (ExecutableElement constructor : constructors) {
+ if (constructor.getParameters().isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasProxyLocator(TypeElement x, State state) {
+ ProxyFor proxyFor = x.getAnnotation(ProxyFor.class);
+ if (proxyFor != null) {
+ // See javadoc on getAnnotation
+ try {
+ proxyFor.locator();
+ throw new RuntimeException("Should not reach here");
+ } catch (MirroredTypeException expected) {
+ TypeMirror locatorType = expected.getTypeMirror();
+ return !state.types.asElement(locatorType).equals(state.locatorType.asElement());
+ }
+ }
+ ProxyForName proxyForName = x.getAnnotation(ProxyForName.class);
+ return proxyForName != null && !proxyForName.locator().isEmpty();
+ }
+
+ private boolean hasServiceLocator(TypeElement x, State state) {
+ Service service = x.getAnnotation(Service.class);
+ if (service != null) {
+ // See javadoc on getAnnotation
+ try {
+ service.locator();
+ throw new RuntimeException("Should not reach here");
+ } catch (MirroredTypeException expected) {
+ TypeMirror locatorType = expected.getTypeMirror();
+ return !state.types.asElement(locatorType).equals(state.serviceLocatorType.asElement());
+ }
+ }
+ ServiceName serviceName = x.getAnnotation(ServiceName.class);
+ return serviceName != null && !serviceName.locator().isEmpty();
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/Finder.java b/user/src/com/google/web/bindery/requestfactory/apt/Finder.java
new file mode 100644
index 0000000..0d5d79a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/Finder.java
@@ -0,0 +1,44 @@
+/*
+ * 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 javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+
+/**
+ * Looks for all types assignable to {@code RequestFactory} and adds them to the
+ * output state. This is necessary to support factory types declared as inner
+ * classes.
+ */
+class Finder extends ScannerBase<Void> {
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ // Ignore anything other than interfaces
+ if (x.getKind().equals(ElementKind.INTERFACE)) {
+ if (state.types.isAssignable(x.asType(), state.requestFactoryType)) {
+ state.maybeScanFactory(x);
+ }
+ if (state.types.isAssignable(x.asType(), state.requestContextType)) {
+ state.maybeScanContext(x);
+ }
+ if (state.types.isAssignable(x.asType(), state.entityProxyType)
+ || state.types.isAssignable(x.asType(), state.valueProxyType)) {
+ state.maybeScanProxy(x);
+ }
+ }
+ return super.visitType(x, state);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/HaltException.java b/user/src/com/google/web/bindery/requestfactory/apt/HaltException.java
new file mode 100644
index 0000000..98b0f46
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/HaltException.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/**
+ * An un-logged RuntimeException used to abort processing.
+ */
+class HaltException extends RuntimeException {
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java
new file mode 100644
index 0000000..f3c85c7
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java
@@ -0,0 +1,102 @@
+/*
+ * 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.Name.BinaryName;
+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 javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.MirroredTypeException;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Examines the methods declared in a proxy interface. Also records the client
+ * to domain mapping for the proxy type.
+ */
+class ProxyScanner extends ScannerBase<Void> {
+
+ @Override
+ public Void visitExecutable(ExecutableElement x, State state) {
+ if (shouldIgnore(x, state)) {
+ return null;
+ }
+
+ 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());
+ }
+ } else if (!isSetter(x, state)) {
+ state.poison(x, "Only getters and setters allowed");
+ }
+ // Parameters checked by visitVariable
+ return super.visitExecutable(x, state);
+ }
+
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ ProxyFor proxyFor = x.getAnnotation(ProxyFor.class);
+ ProxyForName proxyForName = x.getAnnotation(ProxyForName.class);
+ JsonRpcProxy jsonRpcProxy = x.getAnnotation(JsonRpcProxy.class);
+ if (proxyFor != null) {
+ poisonIfAnnotationPresent(state, x, proxyForName, jsonRpcProxy);
+
+ // See javadoc on Element.getAnnotation() for why it works this way
+ try {
+ proxyFor.value();
+ throw new RuntimeException("Should not reach here");
+ } catch (MirroredTypeException expected) {
+ TypeMirror type = expected.getTypeMirror();
+ state.addMapping(x, (TypeElement) state.types.asElement(type));
+ }
+ }
+ if (proxyForName != null) {
+ poisonIfAnnotationPresent(state, x, jsonRpcProxy);
+ 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.addMapping(x, domain);
+ }
+
+ scanAllInheritedMethods(x, state);
+ state.checkExtraTypes(x);
+ return null;
+ }
+
+ @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());
+ }
+ return super.visitVariable(x, state);
+ }
+
+ @Override
+ protected boolean shouldIgnore(ExecutableElement x, State state) {
+ // Ignore overrides of stableId()
+ if (x.getSimpleName().contentEquals("stableId") && x.getParameters().isEmpty()) {
+ return true;
+ }
+ return super.shouldIgnore(x, state);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java
new file mode 100644
index 0000000..3fe796a
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java
@@ -0,0 +1,136 @@
+/*
+ * 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.Name.BinaryName;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.MirroredTypeException;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Scans a RequestContext declaration. This visitor will call out to the State
+ * object to validate the types that it encounters.
+ */
+class RequestContextScanner extends ScannerBase<Void> {
+
+ @Override
+ public Void visitExecutable(ExecutableElement x, State state) {
+ if (shouldIgnore(x, state)) {
+ return null;
+ }
+ TypeMirror returnType = x.getReturnType();
+ if (state.types.isAssignable(returnType, state.requestType)) {
+ // 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");
+ } 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());
+ }
+ }
+ } else if (state.types.isAssignable(returnType, state.instanceRequestType)) {
+ // Extract InstanceRequest<FooProxy, String>
+ 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");
+ } 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());
+ }
+ 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());
+ }
+ }
+ } 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());
+ }
+ return super.visitExecutable(x, state);
+ }
+
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ Service service = x.getAnnotation(Service.class);
+ 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());
+ }
+ if (service != null) {
+ poisonIfAnnotationPresent(state, x, serviceName, jsonRpcService);
+
+ // See javadoc on Element.getAnnotation() for why it works this way
+ try {
+ service.value();
+ throw new RuntimeException("Should not reach here");
+ } catch (MirroredTypeException expected) {
+ TypeMirror type = expected.getTypeMirror();
+ state.addMapping(x, (TypeElement) state.types.asElement(type));
+ }
+ }
+ if (serviceName != null) {
+ poisonIfAnnotationPresent(state, x, jsonRpcService);
+ 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());
+ } else {
+ state.addMapping(x, domain);
+ }
+ }
+
+ scanAllInheritedMethods(x, state);
+ state.checkExtraTypes(x);
+ return null;
+ }
+
+ @Override
+ 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);
+ }
+ }
+ return super.visitTypeParameter(x, state);
+ }
+
+ @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());
+ }
+ return super.visitVariable(x, state);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RequestFactoryScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/RequestFactoryScanner.java
new file mode 100644
index 0000000..e9cea1c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RequestFactoryScanner.java
@@ -0,0 +1,64 @@
+/*
+ * 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 javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Scans a RequestFactory declaration for errors. This visitor will call out to
+ * the State object to validate the types that it encounters.
+ */
+class RequestFactoryScanner extends ScannerBase<Void> {
+ @Override
+ public Void visitExecutable(ExecutableElement x, State state) {
+ if (shouldIgnore(x, state)) {
+ // Ignore initializers and methods from Object and RequestFactory
+ return null;
+ }
+ if (!x.getParameters().isEmpty()) {
+ state.poison(x, "This method must have no parameters");
+ }
+ 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());
+ } else {
+ state.maybeScanContext((TypeElement) returnTypeElement);
+ }
+ } else {
+ state.poison(x, "This method must return a %s", state.requestContextType);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ // Ignore RequestFactory itself
+ if (state.types.isSameType(state.requestFactoryType, x.asType())) {
+ return null;
+ }
+
+ scanAllInheritedMethods(x, state);
+ state.checkExtraTypes(x);
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java b/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java
index 2afd346..66e60d4 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java
@@ -113,7 +113,39 @@
// Extract data
new Finder().scan(ElementFilter.typesIn(roundEnv.getRootElements()), null);
- // On the last round, write out accumulated data
+ /*
+ * Getting the TOKEN_MANIFEST resource into an Android APK generated by the
+ * Android Eclipse plugin is non-trivial. (Users of ant and apkbuilder can
+ * just use -rf to include the relevant file). To support the common
+ * use-case, we'll generate a subclass of TypeTokenResolver.Builder that has
+ * all of the data already baked into it. This synthetic subtype is looked
+ * for first by TypeTokenResolver and any manifests that are on the
+ * classpath will be added on top.
+ */
+ try {
+ String packageName = TypeTokenResolver.class.getPackage().getName();
+ String simpleName = TypeTokenResolver.class.getSimpleName() + "BuilderImpl";
+ JavaFileObject classfile = filer.createSourceFile(packageName + "." + simpleName);
+ PrintWriter pw = new PrintWriter(classfile.openWriter());
+ pw.println("package " + packageName + ";");
+ pw.println("public class " + simpleName + " extends "
+ + TypeTokenResolver.Builder.class.getCanonicalName() + " {");
+ pw.println("public " + simpleName + "() {");
+ for (Map.Entry<String, String> entry : builder.peek().getAllTypeTokens().entrySet()) {
+ if (elements.getTypeElement(entry.getValue()) != null) {
+ pw.println("addTypeToken(\"" + entry.getKey() + "\", \"" + entry.getValue() + "\");");
+ }
+ }
+ pw.println("}");
+ pw.println("}");
+ pw.close();
+ } catch (FilerException e) {
+ log("Ignoring exception: %s", e.getMessage());
+ } catch (IOException e) {
+ error("Could not write BuilderImpl: " + e.getMessage());
+ }
+
+ // Write out accumulated data
if (roundEnv.processingOver()) {
TypeTokenResolver d = builder.build();
builder = null;
@@ -123,41 +155,8 @@
} catch (IOException e) {
error("Could not write output: " + e.getMessage());
}
-
- /*
- * Getting the TOKEN_MANIFEST resource into an Android APK generated by
- * the Android Eclipse plugin is non-trivial. (Users of ant and apkbuilder
- * can just use -rf to include the relevant file). To support the common
- * use-case, we'll generate a subclass of TypeTokenResolver.Builder that
- * has all of the data already baked into it. This synthetic subtype is
- * looked for first by TypeTokenResolver and any manifests that are on the
- * classpath will be added on top.
- */
- try {
- String packageName = TypeTokenResolver.class.getPackage().getName();
- String simpleName = TypeTokenResolver.class.getSimpleName() + "BuilderImpl";
- JavaFileObject classfile = filer.createSourceFile(packageName + "." + simpleName);
- PrintWriter pw = new PrintWriter(classfile.openWriter());
- pw.println("package " + packageName + ";");
- pw.println("public class " + simpleName + " extends "
- + TypeTokenResolver.Builder.class.getCanonicalName() + " {");
- pw.println("public " + simpleName + "() {");
- for (Map.Entry<String, String> entry : d.getAllTypeTokens().entrySet()) {
- if (elements.getTypeElement(entry.getValue()) != null) {
- pw.println("addTypeToken(\"" + entry.getKey() + "\", " + entry.getValue()
- + ".class.getName());");
- }
- }
- pw.println("}");
- pw.println("}");
- pw.close();
- } catch (FilerException e) {
- log("Ignoring exception: %s", e.getMessage());
- } catch (IOException e) {
- error("Could not write BuilderImpl: " + e.getMessage());
- }
- log("Finished!");
}
+ log("Finished!");
return false;
}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RfValidator.java b/user/src/com/google/web/bindery/requestfactory/apt/RfValidator.java
new file mode 100644
index 0000000..332c6d9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RfValidator.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 java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementFilter;
+
+/**
+ * The entry point for annotation validation.
+ */
+@SupportedAnnotationTypes("*")
+@SupportedSourceVersion(SourceVersion.RELEASE_6)
+@SupportedOptions({"suppressErrors", "suppressWarnings", "verbose"})
+public class RfValidator extends AbstractProcessor {
+
+ private State state;
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (state == null) {
+ state = new State(processingEnv);
+ }
+
+ try {
+ // Bootstrap the State's work queue
+ new Finder().scan(ElementFilter.typesIn(roundEnv.getRootElements()), state);
+ // Execute the work items
+ state.executeJobs();
+ if (roundEnv.processingOver()) {
+ // Verify mappings
+ new DomainChecker().scan(state.getClientToDomainMap().keySet(), state);
+ state = null;
+ }
+ } catch (HaltException ignored) {
+ // Already logged. Let any unhandled RuntimeExceptions fall out.
+ }
+ return false;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/ScannerBase.java b/user/src/com/google/web/bindery/requestfactory/apt/ScannerBase.java
new file mode 100644
index 0000000..49268da
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ScannerBase.java
@@ -0,0 +1,134 @@
+/*
+ * 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.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.ElementScanner6;
+
+/**
+ * Contains utility methods for traversing RequestFactory declarations.
+ */
+class ScannerBase<R> extends ElementScanner6<R, State> {
+
+ /**
+ * Poisons the given type if one or more of the annotation values are
+ * non-null.
+ */
+ protected static void poisonIfAnnotationPresent(State state, TypeElement x,
+ Annotation... annotations) {
+ for (Annotation a : annotations) {
+ if (a != null) {
+ state.poison(x, "Redundant annotation: %s", a.annotationType().getSimpleName());
+ }
+ }
+ }
+
+ @Override
+ public final R scan(Element x, State state) {
+ try {
+ return super.scan(x, state);
+ } catch (HaltException e) {
+ throw e;
+ } catch (Throwable e) {
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw));
+ state.poison(x, sw.toString());
+ throw new HaltException();
+ }
+ }
+
+ /**
+ * No parameters, name stars with "get" or is a boolean / Boolean isFoo hasFoo
+ * method.
+ */
+ protected boolean isGetter(ExecutableElement x, State state) {
+ String name = x.getSimpleName().toString();
+ TypeMirror returnType = x.getReturnType();
+ if (!x.getParameters().isEmpty()) {
+ return false;
+ }
+ if (name.startsWith("get")) {
+ return true;
+ }
+ if (name.startsWith("is") || name.startsWith("has")) {
+ TypeMirror javaLangBoolean =
+ state.types.boxedClass(state.types.getPrimitiveType(TypeKind.BOOLEAN)).asType();
+ if (returnType.getKind().equals(TypeKind.BOOLEAN)
+ || state.types.isSameType(returnType, javaLangBoolean)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Name starts with set, has one parameter, returns either null or something
+ * assignable from the element's enclosing type.
+ */
+ protected boolean isSetter(ExecutableElement x, State state) {
+ String name = x.getSimpleName().toString();
+ TypeMirror returnType = x.getReturnType();
+
+ if (x.getParameters().size() != 1) {
+ return false;
+ }
+ if (!name.startsWith("set")) {
+ return false;
+ }
+ if (returnType.getKind().equals(TypeKind.VOID)) {
+ return true;
+ }
+ if (x.getEnclosingElement() != null
+ && state.types.isAssignable(x.getEnclosingElement().asType(), returnType)) {
+ return true;
+ }
+ return false;
+ }
+
+ protected R scanAllInheritedMethods(TypeElement x, State state) {
+ R toReturn = DEFAULT_VALUE;
+ List<ExecutableElement> methods = ElementFilter.methodsIn(state.elements.getAllMembers(x));
+ for (ExecutableElement method : methods) {
+ toReturn = scan(method, state);
+ }
+ return toReturn;
+ }
+
+ /**
+ * Ignore all static initializers and methods defined in the base
+ * RequestFactory interfaces
+ */
+ protected boolean shouldIgnore(ExecutableElement x, State state) {
+ TypeMirror enclosingType = x.getEnclosingElement().asType();
+ return x.getKind().equals(ElementKind.STATIC_INIT)
+ || state.types.isSameType(state.objectType, enclosingType)
+ || state.types.isSameType(state.requestFactoryType, enclosingType)
+ || state.types.isSameType(state.requestContextType, enclosingType)
+ || state.types.isSameType(state.entityProxyType, enclosingType)
+ || state.types.isSameType(state.valueProxyType, enclosingType);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/State.java b/user/src/com/google/web/bindery/requestfactory/apt/State.java
new file mode 100644
index 0000000..52c6ef3
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/State.java
@@ -0,0 +1,365 @@
+/*
+ * 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.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic.Kind;
+
+class State {
+ private static class Job {
+ public final TypeElement element;
+ public final ScannerBase<?> scanner;
+
+ public Job(TypeElement element, ScannerBase<?> scanner) {
+ this.element = element;
+ this.scanner = scanner;
+ }
+ }
+
+ /**
+ * Used to take a {@code FooRequest extends Request<Foo>} and find the
+ * {@code Request<Foo>} type.
+ */
+ static TypeMirror viewAs(DeclaredType desiredType, TypeMirror searchFrom, State state) {
+ if (!desiredType.getTypeArguments().isEmpty()) {
+ throw new IllegalArgumentException("Expecting raw type, received " + desiredType.toString());
+ }
+ Element searchElement = state.types.asElement(searchFrom);
+ switch (searchElement.getKind()) {
+ case CLASS:
+ case INTERFACE:
+ case ENUM: {
+ TypeMirror rawSearchFrom = state.types.getDeclaredType((TypeElement) searchElement);
+ if (state.types.isSameType(desiredType, rawSearchFrom)) {
+ return searchFrom;
+ }
+ for (TypeMirror s : state.types.directSupertypes(searchFrom)) {
+ TypeMirror maybe = viewAs(desiredType, s, state);
+ if (maybe != null) {
+ return maybe;
+ }
+ }
+ break;
+ }
+ case TYPE_PARAMETER: {
+ // Search <T extends Foo> as Foo
+ return viewAs(desiredType, ((TypeVariable) searchElement).getUpperBound(), state);
+ }
+ }
+ return null;
+ }
+
+ final Elements elements;
+ final DeclaredType entityProxyIdType;
+ final DeclaredType entityProxyType;
+ final DeclaredType extraTypesAnnotation;
+ final DeclaredType instanceRequestType;
+ final DeclaredType locatorType;
+ final DeclaredType objectType;
+ final DeclaredType requestContextType;
+ final DeclaredType requestFactoryType;
+ 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;
+ /**
+ * 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) {
+ clientToDomainMain = new HashMap<TypeElement, TypeElement>();
+ 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"));
+
+ entityProxyType = findType("EntityProxy");
+ entityProxyIdType = findType("EntityProxyId");
+ extraTypesAnnotation = findType("ExtraTypes");
+ instanceRequestType = findType("InstanceRequest");
+ locatorType = findType("Locator");
+ objectType = findType(Object.class);
+ requestType = findType("Request");
+ requestContextType = findType("RequestContext");
+ requestFactoryType = findType("RequestFactory");
+ seen = new HashSet<TypeElement>();
+ serviceLocatorType = findType("ServiceLocator");
+ valueProxyType = findType("ValueProxy");
+ }
+
+ /**
+ * Add a mapping from a client type to a domain type.
+ */
+ public void addMapping(TypeElement clientType, TypeElement domainType) {
+ clientToDomainMain.put(clientType, domainType);
+ }
+
+ /**
+ * Check an element, and, for types, its supertype hierarchy, for an
+ * {@code ExtraTypes} annotation.
+ */
+ public void checkExtraTypes(Element x) {
+ (new ScannerBase<Void>() {
+ @Override
+ public Void visitExecutable(ExecutableElement x, State state) {
+ // Check method declaration
+ checkForAnnotation(x);
+ return null;
+ }
+
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ // Check type's declaration
+ checkForAnnotation(x);
+ // Look at superclass, if it exists
+ if (!x.getSuperclass().getKind().equals(TypeKind.NONE)) {
+ scan(state.types.asElement(x.getSuperclass()), state);
+ }
+ // Check super-interfaces
+ for (TypeMirror intf : x.getInterfaces()) {
+ scan(state.types.asElement(intf), state);
+ }
+ return null;
+ }
+
+ private void checkForAnnotation(Element x) {
+ // Bug similar to Eclipse 261969 makes ExtraTypes.value() unreliable.
+ for (AnnotationMirror mirror : x.getAnnotationMirrors()) {
+ if (!types.isSameType(mirror.getAnnotationType(), extraTypesAnnotation)) {
+ continue;
+ }
+ // The return of the Class[] value() method
+ AnnotationValue value = mirror.getElementValues().values().iterator().next();
+ // which is represented by a list
+ @SuppressWarnings("unchecked")
+ List<? extends AnnotationValue> valueList =
+ (List<? extends AnnotationValue>) value.getValue();
+ for (AnnotationValue clazz : valueList) {
+ TypeMirror type = (TypeMirror) clazz.getValue();
+ maybeScanProxy((TypeElement) types.asElement(type));
+ }
+ }
+ }
+ }).scan(x, this);
+ }
+
+ 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)));
+ }
+ try {
+ job.scanner.scan(job.element, this);
+ } catch (HaltException ignored) {
+ // Already reported
+ } catch (Throwable e) {
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw));
+ poison(job.element, sw.toString());
+ }
+ }
+ 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());
+ }
+ }
+ }
+
+ /**
+ * Utility method to look up raw types from class literals.
+ */
+ public DeclaredType findType(Class<?> clazz) {
+ return types.getDeclaredType(elements.getTypeElement(clazz.getCanonicalName()));
+ }
+
+ /**
+ * Utility method to look up raw types from the requestfactory.shared package.
+ * This method is used instead of class literals in order to minimize the
+ * number of dependencies that get packed into {@code requestfactoy-apt.jar}.
+ */
+ public DeclaredType findType(String simpleName) {
+ return types.getDeclaredType(elements
+ .getTypeElement("com.google.web.bindery.requestfactory.shared." + simpleName));
+ }
+
+ /**
+ * Returns a map of client proxy elements to their domain counterparts.
+ */
+ public Map<TypeElement, TypeElement> getClientToDomainMap() {
+ return Collections.unmodifiableMap(clientToDomainMain);
+ }
+
+ public boolean isPoisoned() {
+ return poisoned;
+ }
+
+ /**
+ * Verifies that the given type may be used with RequestFactory.
+ *
+ * @see TransportableTypeVisitor
+ */
+ public boolean isTransportableType(TypeMirror asType) {
+ return asType.accept(new TransportableTypeVisitor(), this);
+ }
+
+ public void maybeScanContext(TypeElement requestContext) {
+ // Also ignore RequestContext itself
+ if (fastFail(requestContext) || types.isSameType(requestContextType, requestContext.asType())) {
+ return;
+ }
+ jobs.add(new Job(requestContext, new RequestContextScanner()));
+ }
+
+ public void maybeScanFactory(TypeElement factoryType) {
+ if (fastFail(factoryType) || types.isSameType(requestFactoryType, factoryType.asType())) {
+ return;
+ }
+ jobs.add(new Job(factoryType, new RequestFactoryScanner()));
+ }
+
+ public void maybeScanProxy(TypeElement proxyType) {
+ if (fastFail(proxyType)) {
+ return;
+ }
+ jobs.add(new Job(proxyType, new ProxyScanner()));
+ }
+
+ /**
+ * Emits a fatal error message attached to an element. If the element or an
+ * eclosing type is annotated with {@link SkipInterfaceValidation} the message
+ * will be dropped.
+ */
+ public void poison(Element elt, String message, Object... args) {
+ if (suppressErrors) {
+ return;
+ }
+
+ String formatted = String.format(message, args);
+ if (squelchMessage(elt, formatted)) {
+ return;
+ }
+
+ Element check = elt;
+ while (check != null) {
+ if (check.getAnnotation(SkipInterfaceValidation.class) != null) {
+ return;
+ }
+ check = check.getEnclosingElement();
+ }
+
+ if (elt == null) {
+ messager.printMessage(Kind.ERROR, formatted);
+ } else {
+ messager.printMessage(Kind.ERROR, formatted, elt);
+ }
+ }
+
+ public void requireMapping(TypeElement proxyElement) {
+ proxiesRequiringMapping.add(proxyElement);
+ }
+
+ /**
+ * 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) {
+ if (suppressWarnings) {
+ return;
+ }
+
+ String formatted = String.format(message, args);
+ if (squelchMessage(elt, formatted)) {
+ return;
+ }
+
+ SuppressWarnings suppress;
+ Element check = elt;
+ while (check != null) {
+ suppress = check.getAnnotation(SuppressWarnings.class);
+ if (suppress != null) {
+ if (Arrays.asList(suppress.value()).contains("requestfactory")) {
+ return;
+ }
+ }
+ check = check.getEnclosingElement();
+ }
+
+ messager.printMessage(Kind.WARNING, formatted
+ + "\n\nAdd @SuppressWarnings(\"requestfactory\") to dismiss.", elt);
+ }
+
+ private boolean fastFail(TypeElement element) {
+ return !seen.add(element);
+ }
+
+ /**
+ * Prevents duplicate messages from being emitted.
+ */
+ private boolean squelchMessage(Element elt, String message) {
+ Set<String> set = previousMessages.get(elt);
+ if (set == null) {
+ set = new HashSet<String>();
+ // HashMap allows the null key
+ previousMessages.put(elt, set);
+ }
+ return !set.add(message);
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/TransportableTypeVisitor.java b/user/src/com/google/web/bindery/requestfactory/apt/TransportableTypeVisitor.java
new file mode 100644
index 0000000..e6d106d
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/TransportableTypeVisitor.java
@@ -0,0 +1,123 @@
+/*
+ * 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.autobean.shared.ValueCodex;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+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> {
+
+ /**
+ * AutoBeans supports arbitrary parameterizations, but there's work that needs
+ * to be done on the Request serialization code to support arbitrarily-complex
+ * parameterizations. For the moment, we'll disallow anything other than a
+ * one-level parameterization.
+ */
+ private boolean allowNestedParameterization = true;
+
+ @Override
+ public Boolean visitDeclared(DeclaredType t, State state) {
+ if (t.asElement().getKind().equals(ElementKind.ENUM)) {
+ return true;
+ }
+ if (state.types.isAssignable(t, state.entityProxyType)
+ || state.types.isAssignable(t, state.valueProxyType)) {
+ TypeElement proxyElement = (TypeElement) t.asElement();
+ state.maybeScanProxy(proxyElement);
+ state.requireMapping(proxyElement);
+ return true;
+ }
+ if (state.types.isAssignable(t, state.entityProxyIdType)) {
+ DeclaredType asId = (DeclaredType) State.viewAs(state.entityProxyIdType, t, state);
+ if (asId.getTypeArguments().isEmpty()) {
+ return false;
+ }
+ 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))) {
+ return true;
+ }
+ }
+ if (state.types.isAssignable(t, state.findType(List.class))
+ || state.types.isAssignable(t, state.findType(Set.class))) {
+ if (!allowNestedParameterization) {
+ return false;
+ }
+ allowNestedParameterization = false;
+ DeclaredType asCollection =
+ (DeclaredType) State.viewAs(state.findType(Collection.class), t, state);
+ if (asCollection.getTypeArguments().isEmpty()) {
+ return false;
+ }
+ return t.getTypeArguments().get(0).accept(this, state);
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean visitPrimitive(PrimitiveType x, State state) {
+ return true;
+ }
+
+ @Override
+ public Boolean visitTypeVariable(TypeVariable t, State state) {
+ if (t.equals(t.getUpperBound())) {
+ /*
+ * Weird case seen in Eclipse with self-parameterized type variables such
+ * as <T extends Enum<T>>.
+ *
+ * TODO(bobv): Should intersection types be allowed at all? They don't
+ * seem to make much sense in the most-derived interface types, since the
+ * RF Generator won't know how to implement them.
+ */
+ return state.types.erasure(t).accept(this, state);
+ }
+ // Allow <T extends FooProxy>
+ return t.getUpperBound().accept(this, state);
+ }
+
+ @Override
+ public Boolean visitWildcard(WildcardType t, State state) {
+ // Allow List<? extends FooProxy>
+ return t.getExtendsBound() != null && t.getExtendsBound().accept(this, state);
+ }
+
+ @Override
+ protected Boolean defaultAction(TypeMirror arg0, State arg1) {
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/TypeSimplifier.java b/user/src/com/google/web/bindery/requestfactory/apt/TypeSimplifier.java
new file mode 100644
index 0000000..f073734
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/TypeSimplifier.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.util.ArrayList;
+import java.util.List;
+
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.SimpleTypeVisitor6;
+
+/**
+ * Utility type for reducing complex type declarations to ones suitable for
+ * determining assignability based on RequestFactory's type-mapping semantics.
+ * <p>
+ * Rules:
+ * <ul>
+ * <li>primitive type {@code ->} boxed type (optional)</li>
+ * <li>{@code void -> Void} (optional)</li>
+ * <li>{@code <T extends Foo> -> Foo}</li>
+ * <li>{@code ? extends Foo -> Foo}</li>
+ * <li>{@code Foo<complex type> -> Foo<simplified type>}</li>
+ * </ul>
+ */
+public class TypeSimplifier extends SimpleTypeVisitor6<TypeMirror, State> {
+
+ public static TypeMirror simplify(TypeMirror toBox, boolean boxPrimitives, State state) {
+ if (toBox == null) {
+ return null;
+ }
+ return toBox.accept(new TypeSimplifier(boxPrimitives), state);
+ }
+
+ private final boolean boxPrimitives;
+
+ private TypeSimplifier(boolean boxPrimitives) {
+ this.boxPrimitives = boxPrimitives;
+ }
+
+ @Override
+ public TypeMirror visitDeclared(DeclaredType x, State state) {
+ if (x.getTypeArguments().isEmpty()) {
+ return x;
+ }
+ List<TypeMirror> newArgs = new ArrayList<TypeMirror>(x.getTypeArguments().size());
+ for (TypeMirror original : x.getTypeArguments()) {
+ // Are we looking at a self-parameterized type like Foo<T extends Foo<T>>?
+ if (original.getKind().equals(TypeKind.TYPEVAR) && state.types.isAssignable(original, x)) {
+ // If so, return a raw type
+ return state.types.getDeclaredType((TypeElement) x.asElement());
+ } else {
+ newArgs.add(original.accept(this, state));
+ }
+ }
+ return state.types.getDeclaredType((TypeElement) x.asElement(), newArgs
+ .toArray(new TypeMirror[newArgs.size()]));
+ }
+
+ @Override
+ public TypeMirror visitNoType(NoType x, State state) {
+ if (boxPrimitives) {
+ return state.findType(Void.class);
+ }
+ return x;
+ }
+
+ @Override
+ public TypeMirror visitPrimitive(PrimitiveType x, State state) {
+ if (boxPrimitives) {
+ return state.types.boxedClass(x).asType();
+ }
+ return x;
+ }
+
+ @Override
+ public TypeMirror visitTypeVariable(TypeVariable x, State state) {
+ if (x.equals(x.getUpperBound())) {
+ // See comment in TransportableTypeVisitor
+ return state.types.erasure(x);
+ }
+ return x.getUpperBound().accept(this, state);
+ }
+
+ @Override
+ public TypeMirror visitWildcard(WildcardType x, State state) {
+ if (x.getExtendsBound() != null) {
+ return x.getExtendsBound().accept(this, state);
+ }
+ return state.objectType;
+ }
+
+ @Override
+ protected TypeMirror defaultAction(TypeMirror x, State state) {
+ return state.types.getNoType(TypeKind.NONE);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
index c4c82c2..49eda05 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
@@ -32,6 +32,7 @@
import com.google.gwt.dev.util.Util;
import com.google.web.bindery.event.shared.SimpleEventBus;
import com.google.web.bindery.requestfactory.apt.RfApt;
+import com.google.web.bindery.requestfactory.apt.RfValidator;
import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ErrorContext;
import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.Loader;
@@ -40,6 +41,7 @@
import com.google.web.bindery.requestfactory.shared.EntityProxy;
import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.ExtraTypes;
import com.google.web.bindery.requestfactory.shared.InstanceRequest;
import com.google.web.bindery.requestfactory.shared.JsonRpcContent;
import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
@@ -631,13 +633,13 @@
@SuppressWarnings("deprecation")
private static final Class<?>[] SHARED_CLASSES = {
BaseProxy.class, DefaultProxyStore.class, EntityProxy.class, EntityProxyChange.class,
- EntityProxyId.class, InstanceRequest.class, JsonRpcContent.class, JsonRpcProxy.class,
- JsonRpcService.class, JsonRpcWireName.class, Locator.class, ProxyFor.class,
- ProxyForName.class, ProxySerializer.class, ProxyStore.class, Receiver.class, Request.class,
- RequestContext.class, RequestFactory.class, RequestTransport.class, ServerFailure.class,
- Service.class, ServiceLocator.class, ServiceName.class, ValueProxy.class,
- com.google.web.bindery.requestfactory.shared.Violation.class, WriteOperation.class,
- RequestFactorySource.class, SimpleEventBus.class};
+ EntityProxyId.class, ExtraTypes.class, InstanceRequest.class, JsonRpcContent.class,
+ JsonRpcProxy.class, JsonRpcService.class, JsonRpcWireName.class, Locator.class,
+ ProxyFor.class, ProxyForName.class, ProxySerializer.class, ProxyStore.class, Receiver.class,
+ Request.class, RequestContext.class, RequestFactory.class, RequestTransport.class,
+ ServerFailure.class, Service.class, ServiceLocator.class, ServiceName.class,
+ ValueProxy.class, com.google.web.bindery.requestfactory.shared.Violation.class,
+ WriteOperation.class, RequestFactorySource.class, SimpleEventBus.class};
/**
* Maximum number of threads to use to run the Extractor.
@@ -645,18 +647,20 @@
private static final int MAX_THREADS = 4;
static {
+ List<Class<?>> aptClasses =
+ Collections.unmodifiableList(Arrays.<Class<?>> asList(RfApt.class, RfValidator.class));
List<Class<?>> sharedClasses = Arrays.<Class<?>> asList(SHARED_CLASSES);
List<Class<?>> clientClasses = new ArrayList<Class<?>>();
clientClasses.addAll(sharedClasses);
- clientClasses.add(RfApt.class);
+ clientClasses.addAll(aptClasses);
clientClasses.add(UrlRequestTransport.class);
List<Class<?>> serverClasses = new ArrayList<Class<?>>();
serverClasses.addAll(Arrays.<Class<?>> asList(SERVER_CLASSES));
serverClasses.addAll(sharedClasses);
- SEEDS.put("apt", Collections.unmodifiableList(Arrays.<Class<?>> asList(RfApt.class)));
+ SEEDS.put("apt", aptClasses);
SEEDS.put("client", Collections.unmodifiableList(clientClasses));
SEEDS.put("server", Collections.unmodifiableList(serverClasses));
@@ -719,7 +723,8 @@
// Add the annotation processor manifest
resources =
Collections.singletonMap("META-INF/services/" + Processor.class.getCanonicalName(),
- RfApt.class.getCanonicalName().getBytes("UTF-8"));
+ (RfApt.class.getCanonicalName() + "\n" + RfValidator.class.getCanonicalName())
+ .getBytes("UTF-8"));
} else if (("test" + CODE_AND_SOURCE).equals(target)) {
// Combine all type token maps to run tests
ByteArrayOutputStream out = new ByteArrayOutputStream();
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 ad6575f..c2b69c1 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)
+@Target({ElementType.METHOD, ElementType.TYPE})
public @interface SkipInterfaceValidation {
}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java
index 5061b7f..1fcb1c4 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/FindRequest.java
@@ -31,5 +31,5 @@
/**
* Use the implicit lookup in passing EntityProxy types to service methods.
*/
- Request<EntityProxy> find(EntityProxyId<?> proxy);
+ Request<EntityProxy> find(EntityProxyId<? extends EntityProxy> proxy);
}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
index f13942b..2b523cf 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/ProxySerializerImpl.java
@@ -39,8 +39,7 @@
/**
* The default implementation of ProxySerializer.
*/
-class ProxySerializerImpl extends AbstractRequestContext implements
- ProxySerializer {
+class ProxySerializerImpl extends AbstractRequestContext implements ProxySerializer {
/**
* Used internally to unwind the stack if data cannot be found in the backing
@@ -55,13 +54,15 @@
* ValueProxy), we'll assign a synthetic id that is local to the store being
* used.
*/
- private final Map<SimpleProxyId<?>, SimpleProxyId<?>> syntheticIds = new HashMap<SimpleProxyId<?>, SimpleProxyId<?>>();
+ private final Map<SimpleProxyId<?>, SimpleProxyId<?>> syntheticIds =
+ new HashMap<SimpleProxyId<?>, SimpleProxyId<?>>();
/**
* The ids of proxies whose content has been reloaded.
*/
private final Set<SimpleProxyId<?>> restored = new HashSet<SimpleProxyId<?>>();
- private final Map<SimpleProxyId<?>, AutoBean<?>> serialized = new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+ private final Map<SimpleProxyId<?>, AutoBean<?>> serialized =
+ new HashMap<SimpleProxyId<?>, AutoBean<?>>();
public ProxySerializerImpl(AbstractRequestFactory factory, ProxyStore store) {
super(factory, Dialect.STANDARD);
@@ -73,7 +74,7 @@
if (store.get(key) == null) {
return null;
}
- OperationMessage op = getOperation(proxyType, key);
+ OperationMessage op = getOperation(key);
@SuppressWarnings("unchecked")
SimpleProxyId<T> id = (SimpleProxyId<T>) getId(op);
return doDeserialize(id);
@@ -123,13 +124,12 @@
}
@Override
- public void endVisitCollectionProperty(String propertyName,
- AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+ public void endVisitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
+ CollectionPropertyContext ctx) {
if (value == null) {
return;
}
- if (isEntityType(ctx.getElementType())
- || isValueType(ctx.getElementType())) {
+ if (isEntityType(ctx.getElementType()) || isValueType(ctx.getElementType())) {
for (Object o : value.as()) {
serialize((BaseProxy) o);
}
@@ -150,23 +150,20 @@
SimpleProxyId<BaseProxy> getId(IdMessage op) {
if (Strength.SYNTHETIC.equals(op.getStrength())) {
return getRequestFactory().allocateSyntheticId(
- getRequestFactory().getTypeFromToken(op.getTypeToken()),
- op.getSyntheticId());
+ getRequestFactory().getTypeFromToken(op.getTypeToken()), op.getSyntheticId());
}
return super.getId(op);
}
@Override
- <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
- SimpleProxyId<Q> id) {
+ <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(SimpleProxyId<Q> id) {
AutoBean<Q> toReturn = super.getProxyForReturnPayloadGraph(id);
if (restored.add(id)) {
/*
* If we haven't seen the id before, use the data in the OperationMessage
* to repopulate the properties of the canonical bean for this id.
*/
- OperationMessage op = getOperation(id.getProxyClass(),
- getRequestFactory().getHistoryToken(id));
+ OperationMessage op = getOperation(getRequestFactory().getHistoryToken(id));
this.processReturnOperation(id, op);
toReturn.setTag(Constants.STABLE_ID, super.getId(op));
}
@@ -196,32 +193,29 @@
* Load the OperationMessage containing the object state from the backing
* store.
*/
- private <T> OperationMessage getOperation(Class<T> proxyType, String key) {
+ private OperationMessage getOperation(String key) {
Splittable data = store.get(key);
if (data == null) {
throw new NoDataException();
}
- OperationMessage op = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
- OperationMessage.class, data).as();
+ OperationMessage op =
+ AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, OperationMessage.class, data).as();
return op;
}
/**
* Convert any non-persistent ids into store-local synthetic ids.
*/
- private <T extends BaseProxy> SimpleProxyId<T> serializedId(
- SimpleProxyId<T> stableId) {
+ private <T extends BaseProxy> SimpleProxyId<T> serializedId(SimpleProxyId<T> stableId) {
assert !stableId.isSynthetic();
if (stableId.isEphemeral()) {
@SuppressWarnings("unchecked")
SimpleProxyId<T> syntheticId = (SimpleProxyId<T>) syntheticIds.get(stableId);
if (syntheticId == null) {
int nextId = store.nextId();
- assert nextId >= 0 : "ProxyStore.nextId() returned a negative number "
- + nextId;
- syntheticId = getRequestFactory().allocateSyntheticId(
- stableId.getProxyClass(), nextId + 1);
+ assert nextId >= 0 : "ProxyStore.nextId() returned a negative number " + nextId;
+ syntheticId = getRequestFactory().allocateSyntheticId(stableId.getProxyClass(), nextId + 1);
syntheticIds.put(stableId, syntheticId);
}
return syntheticId;
@@ -231,10 +225,9 @@
private void serializeOneProxy(SimpleProxyId<?> idForSerialization,
AutoBean<? extends BaseProxy> bean) {
- AutoBean<OperationMessage> op = makeOperationMessage(
- serializedId(BaseProxyCategory.stableId(bean)), bean, false);
+ AutoBean<OperationMessage> op =
+ makeOperationMessage(serializedId(BaseProxyCategory.stableId(bean)), bean, false);
- store.put(getRequestFactory().getHistoryToken(idForSerialization),
- AutoBeanCodex.encode(op));
+ store.put(getRequestFactory().getHistoryToken(idForSerialization), AutoBeanCodex.encode(op));
}
}
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 5699c9a..aedcb27 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
@@ -68,6 +68,10 @@
}
in.close();
}
+
+ public TypeTokenResolver peek() {
+ return d;
+ }
}
public static final String TOKEN_MANIFEST = "META-INF/requestFactory/typeTokens";
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index 5fb2655..049935a 100644
--- a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -42,7 +42,12 @@
/**
* JRE tests for {@link RequestFactoryInterfaceValidator}.
*/
+@SkipInterfaceValidation
public class RequestFactoryInterfaceValidatorTest extends TestCase {
+ /*
+ * The annotation is only used by the APT-based validator and is temporary
+ * until RFIV and this test are removed.
+ */
static class ClinitEntity {
static ClinitEntity findClinitEntity(@SuppressWarnings("unused") String key) {
return null;
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 5662c40..821f20a 100644
--- a/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
+++ b/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
@@ -17,9 +17,10 @@
/**
* Just to test the
- * {@link com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator} code.
+ * {@link com.google.web.bindery.requestfactory.gwt.rebind.RequestFactoryGenerator}
+ * code.
*/
@Service(com.google.web.bindery.requestfactory.server.SimpleFoo.class)
public interface TestFooPolymorphicRequest extends RequestContext {
- <P extends SimpleFooProxy> Request<P> echo(P proxy);
+ <P extends SimpleFooProxy> Request<P> echo(SimpleFooProxy proxy);
}