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); }