Tag 2.4.0 release
git-svn-id: https://google-web-toolkit.googlecode.com/svn/tags/2.4.0@10590 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/requestfactory/build.xml b/requestfactory/build.xml
index 8b3ed84..28b786e 100755
--- a/requestfactory/build.xml
+++ b/requestfactory/build.xml
@@ -12,13 +12,15 @@
<delete file="${gwt.build.lib}/requestfactory-server.jar" />
<delete file="${gwt.build.lib}/requestfactory-server-src.jar" />
<delete file="${gwt.build.lib}/requestfactory-server+src.jar" />
- <delete file="${gwt.build.lib}/requestfactory-test+src.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-test.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-test-src.jar" />
+ <delete file="${gwt.build.lib}/requestfactory-test-validated.jar" />
</target>
<!-- Build a jar file containing a subset of requestfactory -->
<macrodef name="requestfactory-jar">
<!--
- "target" should be one of {client,server,all}[(+|-)src] or test+src.
+ "target" should be one of {client,server,all}[(+|-)src] or test[-src].
-src includes .java files only, +src includes .java and .class files
-->
<attribute name="target" default="client"/>
@@ -45,6 +47,11 @@
<requestfactory-jar target="apt"/>
</target>
+ <!-- Useful for Android testing -->
+ <target name="requestfactory-all" description="Build RequestFactory all jar">
+ <requestfactory-jar target="all"/>
+ </target>
+
<target name="requestfactory-client" description="Build RequestFactory client jar">
<requestfactory-jar target="client"/>
</target>
@@ -70,8 +77,9 @@
</target>
<!-- This target requires classes from ../build/{dev,user}/bin-test -->
- <target name="requestfactory-test+src" description="Build RequestFactory test source/class jar">
- <requestfactory-jar target="test+src" />
+ <target name="requestfactory-test" description="Build RequestFactory test source/class jar">
+ <requestfactory-jar target="test" />
+ <requestfactory-jar target="test-src" />
</target>
<!-- Build all client jars -->
@@ -88,7 +96,28 @@
<!-- Run RequestFactoryJreSuite from the requestfactory-test+src jar.
Assumes test classes have been built in the trunk directory
-->
- <target name="test" depends="requestfactory-test+src" description="Run RequestFactoryJreSuite">
+ <target name="test" depends="requestfactory-test" description="Run RequestFactoryJreSuite">
+ <!-- There's no direct dependency on the Deobfuscator builders, so this gives us an opportunity
+ to test the post-compilation ValidationTool to generate the metadata from the precompiled
+ class files. If a new RequestFactory types is added without the argument list below being
+ updated, a runtime error will occur giving the name of the RequestFactory that needs to
+ be validated.
+ -->
+ <java failonerror="true" fork="true"
+ classname="com.google.web.bindery.requestfactory.apt.ValidationTool" >
+ <classpath>
+ <fileset dir="${gwt.build.lib}" includes="requestfactory-apt.jar" />
+ <fileset dir="${gwt.build.lib}" includes="requestfactory-client.jar" />
+ <fileset dir="${gwt.build.lib}" includes="requestfactory-test.jar" />
+ </classpath>
+ <arg path="${gwt.build.lib}/requestfactory-test-validated.jar" />
+ <arg value="com.google.web.bindery.requestfactory.gwt.client.RequestFactoryPolymorphicTest.Factory" />
+ <arg value="com.google.web.bindery.requestfactory.shared.BoxesAndPrimitivesTest.Factory" />
+ <arg value="com.google.web.bindery.requestfactory.shared.ComplexKeysTest.Factory" />
+ <arg value="com.google.web.bindery.requestfactory.shared.LocatorTest.Factory" />
+ <arg value="com.google.web.bindery.requestfactory.shared.ServiceInheritanceTest$Factory" />
+ <arg value="com.google.web.bindery.requestfactory.shared.SimpleRequestFactory" />
+ </java>
<java failonerror="true" fork="true"
classname="com.google.web.bindery.requestfactory.vm.RequestFactoryJreSuite">
<jvmarg value="-Xss8m" />
@@ -100,9 +129,9 @@
<fileset dir="${gwt.tools.lib}" includes="hibernate/validator/hibernate-validator-4.1.0.Final.jar" />
<fileset dir="${gwt.tools.lib}" includes="javax/validation/validation-api-1.0.0.GA.jar" />
<fileset dir="${gwt.tools.lib}" includes="javax/xml/bind/jaxb-api-2.1.jar" />
- <fileset dir="${gwt.build.lib}" includes="requestfactory-apt.jar" />
- <fileset dir="${gwt.build.lib}" includes="requestfactory-test+src.jar" />
-
+ <fileset dir="${gwt.build.lib}" includes="requestfactory-test.jar" />
+ <fileset dir="${gwt.build.lib}" includes="requestfactory-test-src.jar" />
+ <fileset dir="${gwt.build.lib}" includes="requestfactory-test-validated.jar" />
</classpath>
</java>
</target>
diff --git a/samples/dynatablerf/build.xml b/samples/dynatablerf/build.xml
index 25d3a2d..9ffc6b7 100755
--- a/samples/dynatablerf/build.xml
+++ b/samples/dynatablerf/build.xml
@@ -5,6 +5,10 @@
<import file="../common.ant.xml" />
<!-- these are after the common.ant.xml so they have gwt.tools etc -->
<path id="sample.extraclasspath">
+ <!-- Run the RequestFactory annotation processor when compiling the classes.
+ See http://code.google.com/p/google-web-toolkit/wiki/RequestFactoryInterfaceValidation
+ -->
+ <pathelement location="${gwt.build.lib}/requestfactory-apt.jar" />
<pathelement location="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA.jar" />
<pathelement location="${gwt.tools.lib}/javax/validation/validation-api-1.0.0.GA-sources.jar" />
</path>
diff --git a/samples/mobilewebapp/pom.xml b/samples/mobilewebapp/pom.xml
index 773988b..616b5b8 100644
--- a/samples/mobilewebapp/pom.xml
+++ b/samples/mobilewebapp/pom.xml
@@ -65,9 +65,7 @@
of the GWT compiler.
This dependency has a scope of "provided" so that it only gets used as a
- compiler dependecy. The Maven GWT Plugin does not seem to honor this scoping,
- though. For that reason we explicitly remove gwt-dev-*.jar from the produced
- artifacts later on.
+ compiler dependecy.
-->
<dependency>
<groupId>com.google.gwt</groupId>
@@ -190,8 +188,8 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
- <version>2.2.0</version>
- <dependencies>
+ <version>2.3.0-1</version>
+ <dependencies>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
@@ -207,7 +205,7 @@
<artifactId>gwt-servlet</artifactId>
<version>${gwtVersion}</version>
</dependency>
- </dependencies>
+ </dependencies>
<!-- JS is only needed in the package phase, this speeds up testing -->
<executions>
<execution>
@@ -226,7 +224,7 @@
<hostedWebapp>target/www</hostedWebapp>
<!-- Ask GWT to create the Story of Your Compile (SOYC) (gwt:compile) -->
<compileReport>true</compileReport>
- <module>com.google.gwt.sample.mobilewebapp.MobileWebApp</module>
+ <module>com.google.gwt.sample.mobilewebapp.MobileWebApp</module>
<appEngineVersion>${gae.version}</appEngineVersion>
<appEngineHome>${gae.home}</appEngineHome>
@@ -258,7 +256,7 @@
<!-- Add source folders to test classpath in order to run gwt-tests as normal junit-tests -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
- <version>2.5</version>
+ <version>2.5</version>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>${project.build.sourceDirectory}</additionalClasspathElement>
@@ -276,7 +274,33 @@
</systemProperties>
</configuration>
</plugin>
-
+
+ <!-- Run the RequestFactory ValidationTool -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.2</version>
+ <executions>
+ <execution>
+ <phase>process-classes</phase>
+ <configuration>
+ <id>VerifyRequestFactoryInterfaces</id>
+ <executable>java</executable>
+ <arguments>
+ <argument>-cp</argument>
+ <classpath />
+ <argument>com.google.web.bindery.requestfactory.apt.ValidationTool</argument>
+ <argument>${project.build.outputDirectory}</argument>
+ <argument>com.google.gwt.sample.mobilewebapp.shared.MobileWebAppRequestFactory</argument>
+ </arguments>
+ </configuration>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
<!-- Copy static web files before executing gwt:run -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
@@ -300,9 +324,9 @@
</plugin>
<plugin>
- <artifactId>maven-eclipse-plugin</artifactId>
- <version>2.8</version>
- <configuration>
+ <artifactId>maven-eclipse-plugin</artifactId>
+ <version>2.8</version>
+ <configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>false</downloadJavadocs>
<wtpversion>2.0</wtpversion>
@@ -315,45 +339,51 @@
<projectnature>com.google.gwt.eclipse.core.gwtNature</projectnature>
<projectnature>com.google.appengine.eclipse.core.gaeNature</projectnature>
</additionalProjectnatures>
- </configuration>
- </plugin>
-
- <plugin>
- <!-- Don't deploy gwt-user nor gwt-dev jars to GAE.
- These jars are needed to compile the
- project and for DevMode, but not in AppEngine.
- -->
- <artifactId>maven-clean-plugin</artifactId>
- <version>2.3</version>
- <executions>
- <execution>
- <id>default-clean</id>
- <phase>clean</phase>
- <goals>
- <goal>clean</goal>
- </goals>
- </execution>
- <execution>
- <id>remove-gwt-dev-jar</id>
- <phase>process-classes</phase>
- <goals>
- <goal>clean</goal>
- </goals>
- <configuration>
- <excludeDefaultDirectories>true</excludeDefaultDirectories>
- <filesets>
- <fileset>
- <directory>${project.build.directory}/${project.build.finalName}/WEB-INF/lib</directory>
- <includes>
- <include>gwt-dev*jar</include>
- <include>gwt-user*jar</include>
- </includes>
- </fileset>
- </filesets>
- </configuration>
- </execution>
- </executions>
+ </configuration>
</plugin>
</plugins>
+
+ <pluginManagement>
+ <plugins>
+ <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.datanucleus</groupId>
+ <artifactId>maven-datanucleus-plugin</artifactId>
+ <versionRange>[1.1.4,)</versionRange>
+ <goals>
+ <goal>enhance</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>net.kindleit</groupId>
+ <artifactId>maven-gae-plugin</artifactId>
+ <versionRange>[0.7.3,)</versionRange>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <execute />
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
</build>
</project>
diff --git a/samples/mobilewebapp/src/main/webapp/WEB-INF/web.xml b/samples/mobilewebapp/src/main/webapp/WEB-INF/web.xml
index a8d039f..7490828 100644
--- a/samples/mobilewebapp/src/main/webapp/WEB-INF/web.xml
+++ b/samples/mobilewebapp/src/main/webapp/WEB-INF/web.xml
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE web-app
- PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd">
-
-<web-app>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+ http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ version="2.5"
+ xmlns="http://java.sun.com/xml/ns/javaee">
<!--
Require login. We only require login for the MobileWebApp.html page (instead of *.html) because
@@ -36,10 +36,10 @@
<filter>
<filter-name>GaeAuthFilter</filter-name>
- <description>
+ <!--
This filter demonstrates making GAE authentication
services visible to a RequestFactory client.
- </description>
+ -->
<filter-class>com.google.gwt.sample.gaerequest.server.GaeAuthFilter</filter-class>
</filter>
<filter-mapping>
diff --git a/user/build.xml b/user/build.xml
index 63a0eaa..69cfaaa 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/gwt/user/tools/templates/sample/_warFolder_/WEB-INF/web.xmlsrc b/user/src/com/google/gwt/user/tools/templates/sample/_warFolder_/WEB-INF/web.xmlsrc
index fa54208..d35c414 100644
--- a/user/src/com/google/gwt/user/tools/templates/sample/_warFolder_/WEB-INF/web.xmlsrc
+++ b/user/src/com/google/gwt/user/tools/templates/sample/_warFolder_/WEB-INF/web.xmlsrc
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE web-app
- PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd">
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+ http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ version="2.5"
+ xmlns="http://java.sun.com/xml/ns/javaee">
-<web-app>
-
<!-- Servlets -->
<servlet>
<servlet-name>greetServlet</servlet-name>
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..dd1f28c
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ClientToDomainMapper.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+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;
+
+/**
+ * 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 TypeVisitorBase<TypeMirror> {
+ 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
+ /*
+ * TODO(bobv): This if statement should be widened to baseProxy to support
+ * heterogenous collections of any proxy type. The BaseProxy interface
+ * would also need to be annotated with an @ProxyFor mapping. This can be
+ * done once RFIV is removed, since it only allows homogenous collections.
+ */
+ TypeElement domainType =
+ (TypeElement) 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 (DeclaredType valueType : getValueTypes(state)) {
+ if (state.types.isAssignable(x, valueType)) {
+ // 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
+ return state.types.erasure(x).accept(this, 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/DeobfuscatorBuilder.java b/user/src/com/google/web/bindery/requestfactory/apt/DeobfuscatorBuilder.java
new file mode 100644
index 0000000..2b8f2b1
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/DeobfuscatorBuilder.java
@@ -0,0 +1,237 @@
+/*
+ * 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.vm.impl.Deobfuscator;
+import com.google.web.bindery.requestfactory.vm.impl.OperationData;
+import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.tools.JavaFileObject;
+
+/**
+ * Visits a RequestFactory to create its associated DeobfuscatorBuilder, a
+ * self-configuring subtype of
+ * {@link com.google.web.bindery.requestfactory.vm.impl.Deobfuscator.Builder}
+ * which provides the ServiceLayer with type- and method-mapping information.
+ */
+class DeobfuscatorBuilder extends ScannerBase<Void> {
+ private TypeElement requestFactoryElement;
+ private final StringBuilder sb = new StringBuilder();
+
+ public String toString() {
+ return sb.toString();
+ }
+
+ /**
+ * Examine a method defined within a RequestFactory.
+ */
+ @Override
+ public Void visitExecutable(ExecutableElement x, State state) {
+ if (shouldIgnore(x, state)) {
+ return null;
+ }
+ final TypeElement requestContextElement =
+ (TypeElement) state.types.asElement(x.getReturnType());
+ new ScannerBase<Void>() {
+
+ /**
+ * Scan a method within a RequestContext.
+ */
+ @Override
+ public Void visitExecutable(ExecutableElement x, State state) {
+ if (shouldIgnore(x, state)) {
+ return null;
+ }
+ String requestContextBinaryName =
+ state.elements.getBinaryName(requestContextElement).toString();
+ String clientMethodDescriptor = x.asType().accept(new DescriptorBuilder(), state);
+ ExecutableElement domainElement = (ExecutableElement) state.getClientToDomainMap().get(x);
+ if (domainElement == null) {
+ /*
+ * No mapping from the client to domain type, probably because of an
+ * unresolved ServiceName annotation. This can be fixed when building
+ * the server by running ValidationTool.
+ */
+ if (state.mustResolveAllAnnotations()) {
+ state.poison(requestContextElement, Messages
+ .deobfuscatorMissingContext(requestContextElement.getSimpleName()));
+ }
+ return super.visitExecutable(x, state);
+ }
+ String domainMethodDescriptor =
+ domainElement.asType().accept(new DescriptorBuilder(), state);
+ String methodName = x.getSimpleName().toString();
+
+ OperationKey key =
+ new OperationKey(requestContextBinaryName, methodName, clientMethodDescriptor);
+ println("withOperation(new OperationKey(\"%s\"),", key.get());
+ println(" new OperationData.Builder()");
+ println(" .withClientMethodDescriptor(\"%s\")", clientMethodDescriptor);
+ println(" .withDomainMethodDescriptor(\"%s\")", domainMethodDescriptor);
+ println(" .withMethodName(\"%s\")", methodName);
+ println(" .withRequestContext(\"%s\")", requestContextBinaryName);
+ println(" .build());");
+ return super.visitExecutable(x, state);
+ }
+
+ /**
+ * Scan a RequestContext.
+ */
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ scanAllInheritedMethods(x, state);
+ return null;
+ }
+ }.scan(requestContextElement, state);
+ return null;
+ }
+
+ /**
+ * Scan a RequestFactory type.
+ */
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ requestFactoryElement = x;
+ String simpleName = computeSimpleName(x, state);
+ String packageName = state.elements.getPackageOf(x).getQualifiedName().toString();
+
+ println("// Automatically Generated -- DO NOT EDIT");
+ println("// %s", state.elements.getBinaryName(x));
+ println("package %s;", packageName);
+ println("import %s;", Arrays.class.getCanonicalName());
+ println("import %s;", OperationData.class.getCanonicalName());
+ println("import %s;", OperationKey.class.getCanonicalName());
+ println("public final class %s extends %s {", simpleName, Deobfuscator.Builder.class
+ .getCanonicalName());
+ println("{");
+ scanAllInheritedMethods(x, state);
+ writeTypeAndTokenMap(state);
+ println("}}");
+
+ // Don't write the deobfuscator if something has gone wrong.
+ if (state.isPoisoned()) {
+ return null;
+ }
+
+ try {
+ JavaFileObject obj = state.filer.createSourceFile(packageName + "." + simpleName, x);
+ Writer w = obj.openWriter();
+ w.write(sb.toString());
+ w.close();
+ } catch (Exception e) {
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw));
+ state.poison(x, sw.toString());
+ }
+ return null;
+ }
+
+ private String computeSimpleName(TypeElement x, State state) {
+ // See constants in Deobfuscator
+ String simpleName = state.elements.getBinaryName(x).toString() + "DeobfuscatorBuilder";
+ if (state.isClientOnly()) {
+ simpleName += "Lite";
+ }
+ simpleName = simpleName.substring(simpleName.lastIndexOf('.') + 1);
+ return simpleName;
+ }
+
+ private void println(String line, Object... args) {
+ sb.append(String.format(line, args)).append("\n");
+ }
+
+ /**
+ * Write calls to {@code withRawTypeToken} and
+ * {@code withClientToDomainMappings}.
+ */
+ private void writeTypeAndTokenMap(State state) {
+ /*
+ * A map and its comparator to build up the mapping between domain types and
+ * the client type(s) that it is mapped to.
+ */
+ TypeComparator comparator = new TypeComparator(state);
+ Map<TypeElement, SortedSet<TypeElement>> domainToClientMappings =
+ new TreeMap<TypeElement, SortedSet<TypeElement>>(comparator);
+ // Map accumulated by previous visitors
+ Map<Element, Element> clientToDomainMap = state.getClientToDomainMap();
+ // Get all types used by the RequestFactory
+ Set<TypeElement> referredTypes = ReferredTypesCollector.collect(requestFactoryElement, state);
+
+ for (TypeElement clientType : referredTypes) {
+ // Ignare non-proxy types
+ if (!state.types.isAssignable(clientType.asType(), state.baseProxyType)) {
+ continue;
+ }
+ String binaryName = state.elements.getBinaryName(clientType).toString();
+ // withRawTypeToken("1234ABC", "com.example.FooProxy");
+ println("withRawTypeToken(\"%s\", \"%s\");", OperationKey.hash(binaryName), binaryName);
+
+ TypeElement domainType = (TypeElement) clientToDomainMap.get(clientType);
+ if (domainType == null) {
+ /*
+ * Missing proxy mapping, probably due to an unresolved ProxyForName. If
+ * we're running as part of a build tool or an IDE, the
+ * mustResolveAllAnnotations() call below will return false. The
+ * isMappingRequired() check avoid false-positives on proxy supertypes
+ * that extend BaseProxy but that don't declare a ProxyFor annotation.
+ */
+ if (state.mustResolveAllAnnotations() && state.isMappingRequired(domainType)) {
+ state.poison(clientType, Messages.deobfuscatorMissingProxy(clientType.getSimpleName()));
+ }
+ continue;
+ }
+ // Generic get-and-add code
+ SortedSet<TypeElement> clientTypes = domainToClientMappings.get(domainType);
+ if (clientTypes == null) {
+ clientTypes = new TreeSet<TypeElement>(comparator);
+ domainToClientMappings.put(domainType, clientTypes);
+ }
+ clientTypes.add(clientType);
+ }
+
+ for (Map.Entry<TypeElement, SortedSet<TypeElement>> entry : domainToClientMappings.entrySet()) {
+ // Arrays.asList("com.example.FooView1Proxy", "com.example.FooView2Proxy")
+ StringBuilder list = new StringBuilder("Arrays.asList(");
+ boolean needsComma = false;
+ for (TypeElement elt : entry.getValue()) {
+ if (needsComma) {
+ list.append(", ");
+ } else {
+ needsComma = true;
+ }
+ list.append('"').append(state.elements.getBinaryName(elt)).append('"');
+ }
+ list.append(")");
+
+ // withClientToDomainMappings("com.example.Domain", Arrays.asList(...))
+ println("withClientToDomainMappings(\"%s\", %s);", state.elements.getBinaryName(entry
+ .getKey()), list);
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/DescriptorBuilder.java b/user/src/com/google/web/bindery/requestfactory/apt/DescriptorBuilder.java
new file mode 100644
index 0000000..0b2ac40
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/DescriptorBuilder.java
@@ -0,0 +1,117 @@
+/*
+ * 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.core.ext.typeinfo.JniConstants;
+import com.google.gwt.dev.util.Name.BinaryName;
+
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+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;
+
+/**
+ * Builds descriptors from TypeMirrors for both simple types and methods. Used
+ * by {@link DeobfuscatorBuilder} to construct client-to-server method mappings.
+ */
+class DescriptorBuilder extends SimpleTypeVisitor6<String, State> {
+
+ /**
+ * Arrays aren't actually used anywhere in RequestFactory, but it's trivial to
+ * implement and might be useful later on.
+ */
+ @Override
+ public String visitArray(ArrayType x, State state) {
+ return "[" + x.getComponentType().accept(this, state);
+ }
+
+ @Override
+ public String visitDeclared(DeclaredType x, State state) {
+ return "L"
+ + BinaryName.toInternalName(state.elements.getBinaryName((TypeElement) x.asElement())
+ .toString()) + ";";
+ }
+
+ /**
+ * Only generates the method descriptor, which does not include the method's
+ * name.
+ */
+ @Override
+ public String visitExecutable(ExecutableType x, State state) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ for (TypeMirror param : x.getParameterTypes()) {
+ sb.append(param.accept(this, state));
+ }
+ sb.append(")");
+ sb.append(x.getReturnType().accept(this, state));
+ return sb.toString();
+ }
+
+ @Override
+ public String visitNoType(NoType x, State state) {
+ if (x.getKind().equals(TypeKind.VOID)) {
+ return "V";
+ }
+ // The mythical NONE or PACKAGE type
+ return super.visitNoType(x, state);
+ }
+
+ @Override
+ public String visitPrimitive(PrimitiveType x, State state) {
+ switch (x.getKind()) {
+ case BOOLEAN:
+ return String.valueOf(JniConstants.DESC_BOOLEAN);
+ case BYTE:
+ return String.valueOf(JniConstants.DESC_BYTE);
+ case CHAR:
+ return String.valueOf(JniConstants.DESC_CHAR);
+ case DOUBLE:
+ return String.valueOf(JniConstants.DESC_DOUBLE);
+ case FLOAT:
+ return String.valueOf(JniConstants.DESC_FLOAT);
+ case INT:
+ return String.valueOf(JniConstants.DESC_INT);
+ case LONG:
+ return String.valueOf(JniConstants.DESC_LONG);
+ case SHORT:
+ return String.valueOf(JniConstants.DESC_SHORT);
+ }
+ return super.visitPrimitive(x, state);
+ }
+
+ @Override
+ public String visitTypeVariable(TypeVariable x, State state) {
+ return state.types.erasure(x).accept(this, state);
+ }
+
+ @Override
+ public String visitWildcard(WildcardType x, State state) {
+ return state.types.erasure(x).accept(this, state);
+ }
+
+ @Override
+ protected String defaultAction(TypeMirror x, State state) {
+ throw new RuntimeException("Unhandled type: " + x.toString());
+ }
+}
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..5f5839f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/DomainChecker.java
@@ -0,0 +1,394 @@
+/*
+ * 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 boolean boxReturnType;
+ private final CharSequence name;
+ private final TypeMirror returnType;
+ private final List<TypeMirror> params;
+
+ public MethodFinder(CharSequence name, TypeMirror returnType, List<TypeMirror> params,
+ boolean boxReturnType, State state) {
+ this.boxReturnType = boxReturnType;
+ this.name = name;
+ this.returnType = TypeSimplifier.simplify(returnType, boxReturnType, state);
+ List<TypeMirror> temp = new ArrayList<TypeMirror>(params.size());
+ for (TypeMirror param : params) {
+ temp.add(TypeSimplifier.simplify(param, false, state));
+ }
+ 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(), boxReturnType, state);
+ // The isSameType handles the NONE case.
+ returnTypeMatches = state.types.isSubtype(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.isSubtype(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);
+ }
+ }
+
+ /**
+ * 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 checkedElement;
+ private boolean currentTypeIsProxy;
+ private TypeElement domainElement;
+ private boolean requireInstanceDomainMethods;
+ private boolean requireStaticDomainMethods;
+
+ @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(checkedElement, 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, false, state).scan(
+ domainElement, state);
+ if (domainMethod == null) {
+ // Try a builder style
+ domainMethod =
+ new MethodFinder(name, domainElement.asType(), lookFor, false, state).scan(
+ domainElement, state);
+ }
+ } else {
+ /*
+ * The usual case for getters and all service methods. Only box return
+ * types when matching context methods since there's a significant
+ * semantic difference between a null Integer and 0.
+ */
+ domainMethod =
+ new MethodFinder(name, returnType, lookFor, !currentTypeIsProxy, state).scan(
+ domainElement, state);
+ }
+
+ if (domainMethod == null) {
+ // 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, Messages.domainMissingMethod(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.isSubtype(clientMethod.getReturnType(), state.instanceRequestType);
+
+ if ((isInstanceRequest || requireInstanceDomainMethods)
+ && domainMethod.getModifiers().contains(Modifier.STATIC)) {
+ state.poison(checkedElement, Messages.domainMethodWrongModifier(false, domainMethod
+ .getSimpleName()));
+ }
+ if (!isInstanceRequest && requireStaticDomainMethods
+ && !domainMethod.getModifiers().contains(Modifier.STATIC)) {
+ state.poison(checkedElement, Messages.domainMethodWrongModifier(true, domainMethod
+ .getSimpleName()));
+ }
+
+ // Record the mapping
+ state.addMapping(clientMethodElement, domainMethod);
+ return null;
+ }
+
+ @Override
+ public Void visitType(TypeElement clientTypeElement, State state) {
+ TypeMirror clientType = clientTypeElement.asType();
+ checkedElement = clientTypeElement;
+ boolean isEntityProxy = state.types.isSubtype(clientType, state.entityProxyType);
+ currentTypeIsProxy = isEntityProxy || state.types.isSubtype(clientType, state.valueProxyType);
+ domainElement = (TypeElement) state.getClientToDomainMap().get(clientTypeElement);
+ if (domainElement == 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(domainElement)) {
+ state.warn(clientTypeElement, Messages.domainNoDefaultConstructor(domainElement
+ .getSimpleName(), clientTypeElement.getSimpleName(), state.requestContextType
+ .asElement().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(), false, state).scan(
+ domainElement, state);
+ if (getId == null) {
+ state.poison(checkedElement, Messages.domainNoGetId(domainElement.asType()));
+ } else {
+ if (getId.getModifiers().contains(Modifier.STATIC)) {
+ state.poison(checkedElement, Messages.domainGetIdStatic());
+ }
+
+ // Can only check findFoo() if we have a getId
+ ExecutableElement find =
+ new MethodFinder("find" + domainElement.getSimpleName(), domainElement.asType(),
+ Collections.singletonList(getId.getReturnType()), false, state).scan(domainElement,
+ state);
+ if (find == null) {
+ state.warn(checkedElement, Messages.domainMissingFind(domainElement.asType(), domainElement
+ .getSimpleName(), getId.getReturnType(), checkedElement.getSimpleName()));
+ } else if (!find.getModifiers().contains(Modifier.STATIC)) {
+ state.poison(checkedElement, Messages.domainFindNotStatic(domainElement.getSimpleName()));
+ }
+ }
+
+ ExecutableElement getVersion =
+ new MethodFinder("getVersion", null, Collections.<TypeMirror> emptyList(), false, state)
+ .scan(domainElement, state);
+ if (getVersion == null) {
+ state.poison(checkedElement, Messages.domainNoGetVersion(domainElement.asType()));
+ } else if (getVersion.getModifiers().contains(Modifier.STATIC)) {
+ state.poison(checkedElement, Messages.domainGetVersionStatic());
+ }
+ }
+
+ /**
+ * 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, Messages.methodNoDomainPeer(e.getClientType(), false));
+ }
+ for (TypeMirror param : clientMethod.getParameterTypes()) {
+ try {
+ parameterAccumulator.add(param.accept(new ClientToDomainMapper(), state));
+ } catch (UnmappedTypeException e) {
+ parameterAccumulator.add(null);
+ error = true;
+ state.warn(warnTo, Messages.methodNoDomainPeer(e.getClientType(), true));
+ }
+ }
+ 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/ExtraTypesScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/ExtraTypesScanner.java
new file mode 100644
index 0000000..a1d09f8
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ExtraTypesScanner.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import java.util.List;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Looks for {@code ExtraTypes} annotations and calls
+ * {@link #scanExtraType(TypeElement)}.
+ */
+abstract class ExtraTypesScanner<T> extends ScannerBase<T> {
+ /**
+ * Check an element for a declaration.
+ */
+ protected void checkForAnnotation(Element x, State state) {
+ // Bug similar to Eclipse 261969 makes ExtraTypes.value() unreliable.
+ for (AnnotationMirror mirror : x.getAnnotationMirrors()) {
+ if (!state.types.isSameType(mirror.getAnnotationType(), state.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();
+ scanExtraType((TypeElement) state.types.asElement(type));
+ }
+ }
+ }
+
+ /**
+ * Check a type and all of its supertypes for the annotation.
+ */
+ protected void checkForAnnotation(TypeElement x, State state) {
+ // Check type's declaration
+ checkForAnnotation((Element) x, state);
+ // Look at superclass, if it exists
+ if (!x.getSuperclass().getKind().equals(TypeKind.NONE)) {
+ checkForAnnotation((TypeElement) state.types.asElement(x.getSuperclass()), state);
+ }
+ // Check super-interfaces
+ for (TypeMirror intf : x.getInterfaces()) {
+ checkForAnnotation((TypeElement) state.types.asElement(intf), state);
+ }
+ }
+
+ protected abstract void scanExtraType(TypeElement extraType);
+}
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/Messages.java b/user/src/com/google/web/bindery/requestfactory/apt/Messages.java
new file mode 100644
index 0000000..1379596
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/Messages.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
+import com.google.web.bindery.requestfactory.shared.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+
+/**
+ * Contains string-formatting methods to produce error messages. This class
+ * exists to avoid the need to duplicate error messages in test code. All method
+ * parameters in this class accept {@code Object} so that the production code
+ * can pass {@code javax.lang.model} types and the test code can pass Strings.
+ */
+class Messages {
+ /*
+ * Note to maintainers: When new messages are added to this class, the
+ * RfValidatorTest.testErrorsAndWarnings() method should be updated to test
+ * the new message.
+ */
+
+ public static String contextMissingDomainType(Object domainTypeName) {
+ return String.format("Cannot fully validate context since domain type %s is not available.\n"
+ + "You must run the ValidationTool as part of your server build process.", domainTypeName);
+ }
+
+ public static String contextMustBeAnnotated(Object requestContextName) {
+ return String.format("The type %s must be annotated with %s, %s, or %s", requestContextName,
+ Service.class.getSimpleName(), ServiceName.class.getSimpleName(), JsonRpcService.class
+ .getSimpleName());
+ }
+
+ public static String contextRequiredReturnTypes(Object requestName, Object instanceRequestName) {
+ return String.format("The return type must be a %s or %s", requestName, instanceRequestName);
+ }
+
+ public static String deobfuscatorMissingContext(Object contextName) {
+ return String.format("Could not load domain mapping for context %s.\n"
+ + "Check that both the shared interfaces and server domain types are on the classpath.",
+ contextName);
+ }
+
+ public static String deobfuscatorMissingProxy(Object proxyName) {
+ return String.format("Could not load domain mapping for proxy %s.\n"
+ + "Check that both the shared interfaces and server domain types are on the classpath.",
+ proxyName);
+ }
+
+ public static String domainFindNotStatic(Object domainTypeName) {
+ return String.format("The domain object's find%s() method is not static", domainTypeName);
+ }
+
+ public static String domainGetIdStatic() {
+ return "The domain type's getId() method must not be static";
+ }
+
+ public static String domainGetVersionStatic() {
+ return "The domain type's getVersion() method must not be static";
+ }
+
+ public static String domainMethodWrongModifier(boolean expectStatic, Object domainMethodName) {
+ return String.format("Found %s domain method %s when %s method required", expectStatic
+ ? "instance" : "static", domainMethodName, expectStatic ? "static" : "instance");
+ }
+
+ public static String domainMissingFind(Object domainType, Object simpleName,
+ Object getIdReturnType, Object checkedTypeName) {
+ return String.format("The domain type %s has no %s find%s(%s) method. "
+ + "Attempting to send a %s to the server will result in a server error.", domainType,
+ simpleName, simpleName, getIdReturnType, checkedTypeName);
+ }
+
+ public static String domainMissingMethod(Object description) {
+ return String.format("Could not find domain method similar to %s", description);
+ }
+
+ public static String domainNoDefaultConstructor(Object domainName, Object proxyName,
+ Object requestContextName) {
+ return String.format("The domain type %s has no default constructor."
+ + " Calling %s.create(%s.class) will cause a server error.", domainName,
+ requestContextName, proxyName);
+ }
+
+ public static String domainNoGetId(Object domainType) {
+ return String.format("Domain type %s does not have a getId() method", domainType);
+ }
+
+ public static String domainNoGetVersion(Object domainType) {
+ return String.format("Domain type %s does not have a getVersion() method", domainType);
+ }
+
+ public static String factoryMustBeAssignable(Object assignableTo) {
+ return String.format("The return type of this method must return a %s", assignableTo);
+ }
+
+ public static String factoryMustReturnInterface(Object returnType) {
+ return String.format("The return type %s must be an interface", returnType);
+ }
+
+ public static String factoryNoMethodParameters() {
+ return "This method must have no parameters";
+ }
+
+ public static String methodNoDomainPeer(Object proxyTypeName, boolean isParameter) {
+ return String.format("Cannot validate this method because the domain mapping for "
+ + " %s type (%s) could not be resolved to a domain type", isParameter ? "a parameter of"
+ : "the return", proxyTypeName);
+ }
+
+ public static String noSuchType(String binaryTypeName) {
+ return String.format("Could not find root type %s", binaryTypeName);
+ }
+
+ public static String proxyMissingDomainType(Object missingDomainName) {
+ return String.format("Cannot fully validate proxy since type %s is not available",
+ missingDomainName);
+ }
+
+ public static String proxyMustBeAnnotated(Object typeName) {
+ return String.format("The proxy type %s must be annotated with %s, %s, or %s", typeName,
+ ProxyFor.class.getSimpleName(), ProxyForName.class.getSimpleName(), JsonRpcProxy.class
+ .getSimpleName());
+ }
+
+ public static String proxyOnlyGettersSetters() {
+ return "Only getters and setters allowed";
+ }
+
+ public static String rawType() {
+ return "A raw type may not be used here";
+ }
+
+ public static String redundantAnnotation(Object annotationName) {
+ return String.format("Redundant annotation: %s", annotationName);
+ }
+
+ public static String untransportableType(Object returnType) {
+ return String.format("The type %s cannot be used here", returnType);
+ }
+
+ public static String warnSuffix() {
+ return "\n\nAdd @SuppressWarnings(\"requestfactory\") to dismiss.";
+ }
+
+ private Messages() {
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java
new file mode 100644
index 0000000..2b72edf
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ProxyScanner.java
@@ -0,0 +1,101 @@
+/*
+ * 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, Messages.untransportableType(returnType));
+ }
+ } else if (!isSetter(x, state)) {
+ state.poison(x, Messages.proxyOnlyGettersSetters());
+ }
+ // 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, Messages.proxyMissingDomainType(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, Messages.untransportableType(x.asType()));
+ }
+ 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/ReferredTypesCollector.java b/user/src/com/google/web/bindery/requestfactory/apt/ReferredTypesCollector.java
new file mode 100644
index 0000000..a8eb5e1
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ReferredTypesCollector.java
@@ -0,0 +1,145 @@
+/*
+ * 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 java.util.SortedSet;
+import java.util.Stack;
+import java.util.TreeSet;
+
+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.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+
+/**
+ * Given a RequestFactory interface, return all RequestContext and proxy types
+ * transitively referenced.
+ */
+class ReferredTypesCollector extends ExtraTypesScanner<Void> {
+
+ /**
+ * Finds TypeElements that we care about from TypeMirror API. This is used to
+ * handle generic type signatures and supertypes.
+ */
+ private class ElementFinder extends TypeVisitorBase<Void> {
+ @Override
+ public Void visitDeclared(DeclaredType x, State state) {
+ // Some generic types don't have elements
+ Element elt = state.types.asElement(x);
+ if (elt != null) {
+ ReferredTypesCollector.this.scan(elt, state);
+ }
+ // Capture List<FooProxy>
+ for (TypeMirror a : x.getTypeArguments()) {
+ a.accept(this, state);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitExecutable(ExecutableType x, State state) {
+ x.getReturnType().accept(this, state);
+ for (TypeMirror p : x.getParameterTypes()) {
+ p.accept(this, state);
+ }
+ for (TypeMirror t : x.getTypeVariables()) {
+ t.accept(this, state);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitTypeVariable(TypeVariable x, State state) {
+ return state.types.erasure(x).accept(this, state);
+ }
+
+ @Override
+ public Void visitWildcard(WildcardType x, State state) {
+ return state.types.erasure(x).accept(this, state);
+ }
+ }
+
+ /**
+ * Collect all RequestContext and proxy types reachable from the given
+ * RequestFactory.
+ */
+ public static Set<TypeElement> collect(TypeElement requestFactory, State state) {
+ ReferredTypesCollector c = new ReferredTypesCollector(state);
+ c.scan(requestFactory, state);
+ return c.seen;
+ }
+
+ private final Stack<TypeElement> currentType = new Stack<TypeElement>();
+ private final SortedSet<TypeElement> seen;
+ private final State state;
+
+ private ReferredTypesCollector(State state) {
+ seen = new TreeSet<TypeElement>(new TypeComparator(state));
+ this.state = state;
+ }
+
+ @Override
+ public Void visitExecutable(ExecutableElement x, State state) {
+ if (shouldIgnore(x, state)) {
+ return null;
+ }
+ ExecutableType xType = viewIn(currentType.peek(), x, state);
+ xType.accept(new ElementFinder(), state);
+ checkForAnnotation(x, state);
+ return null;
+ }
+
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ // Only look at proxies and contexts
+ boolean isContext = state.types.isAssignable(x.asType(), state.requestContextType);
+ boolean isFactory = state.types.isAssignable(x.asType(), state.requestFactoryType);
+ boolean isProxy = state.types.isAssignable(x.asType(), state.baseProxyType);
+ if (isProxy || isFactory || isContext) {
+ currentType.push(x);
+ try {
+ // Ignore previously-seen types and factories
+ if ((isContext || isProxy) && !seen.add(x)) {
+ return null;
+ }
+ // Visit a proxy's supertypes
+ if (isProxy) {
+ x.getSuperclass().accept(new ElementFinder(), state);
+ for (TypeMirror intf : x.getInterfaces()) {
+ intf.accept(new ElementFinder(), state);
+ }
+ }
+ // Visit methods
+ scanAllInheritedMethods(x, state);
+ // Scan for extra types
+ checkForAnnotation(x, state);
+ } finally {
+ currentType.pop();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void scanExtraType(TypeElement extraType) {
+ scan(extraType, state);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java b/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java
new file mode 100644
index 0000000..8a07085
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RequestContextScanner.java
@@ -0,0 +1,126 @@
+/*
+ * 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, Messages.rawType());
+ } else {
+ TypeMirror requestReturn = asRequest.getTypeArguments().get(0);
+ if (!state.isTransportableType(requestReturn)) {
+ state.poison(x, Messages.untransportableType(requestReturn));
+ }
+ }
+ } 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, Messages.rawType());
+ } else {
+ TypeMirror instanceType = asInstanceRequest.getTypeArguments().get(0);
+ state.maybeScanProxy((TypeElement) state.types.asElement(instanceType));
+ TypeMirror requestReturn = asInstanceRequest.getTypeArguments().get(1);
+ if (!state.isTransportableType(requestReturn)) {
+ state.poison(x, Messages.untransportableType(requestReturn));
+ }
+ }
+ } else if (isSetter(x, state)) {
+ // Parameter checked in visitVariable
+ } else {
+ state.poison(x, Messages.contextRequiredReturnTypes(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) {
+ 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, Messages.contextMissingDomainType(serviceName.value()));
+ }
+ 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, Messages.untransportableType(bound));
+ }
+ }
+ return super.visitTypeParameter(x, state);
+ }
+
+ @Override
+ public Void visitVariable(VariableElement x, State state) {
+ if (!state.isTransportableType(x.asType())) {
+ state.poison(x, Messages.untransportableType(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..3f881f7
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RequestFactoryScanner.java
@@ -0,0 +1,67 @@
+/*
+ * 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, Messages.factoryNoMethodParameters());
+ }
+ TypeMirror returnType = x.getReturnType();
+ if (state.types.isAssignable(returnType, state.requestContextType)) {
+ Element returnTypeElement = state.types.asElement(returnType);
+ if (!returnTypeElement.getKind().equals(ElementKind.INTERFACE)) {
+ state.poison(x, Messages.factoryMustReturnInterface(returnTypeElement.getSimpleName()));
+ } else {
+ TypeElement contextElement = (TypeElement) returnTypeElement;
+ state.maybeScanContext(contextElement);
+ state.requireMapping(contextElement);
+ }
+ } else {
+ state.poison(x, Messages.factoryMustBeAssignable(state.requestContextType.asElement()
+ .getSimpleName()));
+ }
+ 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
deleted file mode 100644
index 2afd346..0000000
--- a/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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 static com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver.TOKEN_MANIFEST;
-
-import com.google.web.bindery.requestfactory.shared.BaseProxy;
-import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
-import com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.Set;
-
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.Filer;
-import javax.annotation.processing.FilerException;
-import javax.annotation.processing.ProcessingEnvironment;
-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.Element;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.ElementFilter;
-import javax.lang.model.util.ElementScanner6;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
-import javax.tools.Diagnostic.Kind;
-import javax.tools.FileObject;
-import javax.tools.JavaFileObject;
-import javax.tools.StandardLocation;
-
-/**
- * An annotation processor that creates an obfuscated type manifest that is used
- * by {@link com.google.web.bindery.requestfactory.vm.RequestFactorySource} and
- * related implementation classes.
- */
-@SupportedAnnotationTypes("com.google.web.bindery.requestfactory.*")
-@SupportedSourceVersion(SourceVersion.RELEASE_6)
-@SupportedOptions("verbose")
-public class RfApt extends AbstractProcessor {
-
- /**
- * Looks for all types assignable to {@link BaseProxy} and adds them to the
- * output state.
- */
- private class Finder extends ElementScanner6<Void, Void> {
- // Only valid for a single round
- TypeMirror baseProxyType = elements.getTypeElement(BaseProxy.class.getCanonicalName()).asType();
-
- @Override
- public Void visitType(TypeElement elt, Void arg1) {
- String binaryName = elements.getBinaryName(elt).toString();
- if (types.isSubtype(elt.asType(), baseProxyType)) {
- String hash = OperationKey.hash(binaryName);
- builder.addTypeToken(hash, binaryName);
- log(elt, "Processed proxy %s %s", binaryName, hash);
- }
- return super.visitType(elt, arg1);
- }
- }
-
- private TypeTokenResolver.Builder builder;
- private Elements elements;
- private Filer filer;
- private boolean verbose;
- private Types types;
-
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- log("RfApt init");
- elements = processingEnv.getElementUtils();
- filer = processingEnv.getFiler();
- types = processingEnv.getTypeUtils();
- verbose = Boolean.parseBoolean(processingEnv.getOptions().get("verbose"));
- }
-
- @Override
- public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- log("RequestFactory processing a round");
-
- if (builder == null) {
- builder = new TypeTokenResolver.Builder();
- // Try not to obliterate existing data
- try {
- FileObject resource = filer.getResource(StandardLocation.CLASS_OUTPUT, "", TOKEN_MANIFEST);
- builder.load(resource.openInputStream());
- log("Reusing old data");
- } catch (IOException e) {
- // Likely because the file does not exist
- log("Not reusing existing manifest file: " + e.getMessage());
- }
- }
-
- // Extract data
- new Finder().scan(ElementFilter.typesIn(roundEnv.getRootElements()), null);
-
- // On the last round, write out accumulated data
- if (roundEnv.processingOver()) {
- TypeTokenResolver d = builder.build();
- builder = null;
- try {
- FileObject res = filer.createResource(StandardLocation.CLASS_OUTPUT, "", TOKEN_MANIFEST);
- d.store(res.openOutputStream());
- } 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!");
- }
- return false;
- }
-
- private void error(String message, Object... args) {
- processingEnv.getMessager().printMessage(Kind.ERROR, "ERROR: " + String.format(message, args));
- }
-
- private void log(Element elt, String message, Object... args) {
- if (verbose) {
- processingEnv.getMessager().printMessage(Kind.NOTE, String.format(message, args), elt);
- }
- }
-
- private void log(String message, Object... args) {
- if (verbose) {
- processingEnv.getMessager().printMessage(Kind.NOTE, String.format(message, args));
- }
- }
-}
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..55b23db
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RfValidator.java
@@ -0,0 +1,131 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+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({"rootOverride", "suppressErrors", "suppressWarnings", "verbose"})
+public class RfValidator extends AbstractProcessor {
+
+ private boolean clientOnly;
+ private boolean mustResolveAllMappings;
+ private List<String> rootOverride;
+ private boolean forceErrors;
+ private State state;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ String option = processingEnv.getOptions().get("rootOverride");
+ if (option != null) {
+ setRootOverride(Arrays.asList(option.split(",")));
+ }
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ // Are we finished, if so, clean up
+ if (roundEnv.processingOver()) {
+ state = null;
+ return false;
+ }
+
+ // Newly initialized or being reused?
+ if (state == null) {
+ state = forceErrors ? new State.ForTesting(processingEnv) : new State(processingEnv);
+ if (state.isPoisoned()) {
+ // Could not initialize State object, bail out
+ return false;
+ }
+ // Produce a "lite" map just for JRE-only clients
+ state.setClientOnly(clientOnly);
+ // Disallow @ProxyForName or @ServiceName that can't be resolved
+ state.setMustResolveAllMappings(mustResolveAllMappings);
+ }
+
+ try {
+ // Bootstrap the State's work queue
+ new Finder().scan(getTypesToProcess(state, roundEnv), state);
+ // Execute the work items
+ state.executeJobs();
+ } catch (HaltException ignored) {
+ // Already logged. Let any unhandled RuntimeExceptions fall out.
+ }
+ return false;
+ }
+
+ public void setClientOnly(boolean clientOnly) {
+ this.clientOnly = clientOnly;
+ }
+
+ void setForceErrors(boolean forceErrors) {
+ this.forceErrors = forceErrors;
+ }
+
+ /**
+ * Make it an error to not resolve all ProxyForName and ServiceName mappings.
+ */
+ void setMustResolveAllMappings(boolean requireAll) {
+ this.mustResolveAllMappings = requireAll;
+ }
+
+ /**
+ * Instead of scanning the round's root elements, scan these type names
+ * instead. This is used by the ValidationTool to scan pre-compiled
+ * classfiles.
+ */
+ void setRootOverride(List<String> binaryTypeNames) {
+ this.rootOverride = binaryTypeNames;
+ }
+
+ private Set<TypeElement> getTypesToProcess(State state, RoundEnvironment roundEnv) {
+ if (rootOverride == null) {
+ return ElementFilter.typesIn(roundEnv.getRootElements());
+ }
+ Set<TypeElement> toScan = new HashSet<TypeElement>();
+ for (String binaryTypeName : rootOverride) {
+ TypeElement found =
+ state.elements.getTypeElement(BinaryName.toSourceName(binaryTypeName.trim()));
+ if (found == null) {
+ state.poison(null, Messages.noSuchType(binaryTypeName));
+ } else {
+ toScan.add(found);
+ }
+ }
+ rootOverride = null;
+ return toScan;
+ }
+}
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..73efdb6
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ScannerBase.java
@@ -0,0 +1,144 @@
+/*
+ * 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.ExecutableType;
+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, Messages.redundantAnnotation(a.annotationType().getSimpleName()));
+ }
+ }
+ }
+
+ protected 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();
+ }
+ }
+
+ @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..2b2a2c4
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/State.java
@@ -0,0 +1,466 @@
+/*
+ * 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.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.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+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.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 {
+ /**
+ * Slightly tweaked implementation used when running tests.
+ */
+ static class ForTesting extends State {
+ public ForTesting(ProcessingEnvironment processingEnv) {
+ super(processingEnv);
+ }
+
+ @Override
+ boolean respectAnnotations() {
+ return false;
+ }
+ }
+
+ /**
+ * Implements comparable for priority ordering.
+ */
+ private static class Job implements Comparable<Job> {
+ private static long count;
+
+ public final TypeElement element;
+ public final ScannerBase<?> scanner;
+ private final long order = count++;
+ private final int priority;
+
+ public Job(TypeElement element, ScannerBase<?> scanner, int priority) {
+ this.element = element;
+ this.priority = priority;
+ this.scanner = scanner;
+ }
+
+ @Override
+ public int compareTo(Job o) {
+ int c = priority - o.priority;
+ if (c != 0) {
+ return c;
+ }
+ return Long.signum(order - o.order);
+ }
+
+ @Override
+ public String toString() {
+ return scanner.getClass().getSimpleName() + " " + element.getSimpleName();
+ }
+ }
+
+ /**
+ * 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 TypeMirror baseProxyType;
+ final Elements elements;
+ final DeclaredType entityProxyIdType;
+ final DeclaredType entityProxyType;
+ final DeclaredType extraTypesAnnotation;
+ final Filer filer;
+ 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 Types types;
+ final DeclaredType valueProxyType;
+ private final Map<Element, Element> clientToDomainMain;
+ private final SortedSet<Job> jobs = new TreeSet<Job>();
+ private final Messager messager;
+ private boolean poisoned;
+ private boolean requireAllMappings;
+ private final boolean suppressErrors;
+ private final boolean suppressWarnings;
+ private final boolean verbose;
+ /**
+ * Prevents duplicate messages from being emitted.
+ */
+ private final Map<Element, Set<String>> previousMessages = new HashMap<Element, Set<String>>();
+
+ private final Set<TypeElement> typesRequiringMapping = new LinkedHashSet<TypeElement>();
+ private boolean clientOnly;
+
+ public State(ProcessingEnvironment processingEnv) {
+ clientToDomainMain = new HashMap<Element, Element>();
+ elements = processingEnv.getElementUtils();
+ filer = processingEnv.getFiler();
+ messager = processingEnv.getMessager();
+ types = processingEnv.getTypeUtils();
+ suppressErrors = Boolean.parseBoolean(processingEnv.getOptions().get("suppressErrors"));
+ suppressWarnings = Boolean.parseBoolean(processingEnv.getOptions().get("suppressWarnings"));
+ verbose = Boolean.parseBoolean(processingEnv.getOptions().get("verbose"));
+
+ baseProxyType = findType("BaseProxy");
+ 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 method to a domain method.
+ */
+ public void addMapping(ExecutableElement clientMethod, ExecutableElement domainMethod) {
+ if (domainMethod == null) {
+ debug(clientMethod, "No domain mapping");
+ } else {
+ debug(clientMethod, "Found domain method %s", domainMethod.toString());
+ }
+ clientToDomainMain.put(clientMethod, domainMethod);
+ }
+
+ /**
+ * Add a mapping from a client type to a domain type.
+ */
+ public void addMapping(TypeElement clientType, TypeElement domainType) {
+ if (domainType == null) {
+ debug(clientType, "No domain mapping");
+ } else {
+ debug(clientType, "Found domain type %s", domainType.toString());
+ }
+ clientToDomainMain.put(clientType, domainType);
+ }
+
+ /**
+ * Check an element for an {@code ExtraTypes} annotation. Handles both methods
+ * and types.
+ */
+ public void checkExtraTypes(Element x) {
+ (new ExtraTypesScanner<Void>() {
+ @Override
+ public Void visitExecutable(ExecutableElement x, State state) {
+ checkForAnnotation(x, state);
+ return null;
+ }
+
+ @Override
+ public Void visitType(TypeElement x, State state) {
+ checkForAnnotation(x, state);
+ return null;
+ }
+
+ @Override
+ protected void scanExtraType(TypeElement extraType) {
+ maybeScanProxy(extraType);
+ }
+ }).scan(x, this);
+ }
+
+ /**
+ * Print a warning message if verbose mode is enabled. A warning is used to
+ * ensure that the message shows up in Eclipse's editor (a note only makes it
+ * into the error console).
+ */
+ public void debug(Element elt, String message, Object... args) {
+ if (verbose) {
+ messager.printMessage(Kind.WARNING, String.format(message, args), elt);
+ }
+ }
+
+ public void executeJobs() {
+ while (!jobs.isEmpty()) {
+ Job job = jobs.first();
+ jobs.remove(job);
+ debug(job.element, "Scanning");
+ 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());
+ }
+ }
+ if (clientOnly) {
+ // Don't want to check for mappings in client-only mode
+ return;
+ }
+ for (TypeElement element : typesRequiringMapping) {
+ if (!getClientToDomainMap().containsKey(element)) {
+ if (types.isAssignable(element.asType(), requestContextType)) {
+ poison(element, Messages.contextMustBeAnnotated(element.getSimpleName()));
+ } else {
+ poison(element, Messages.proxyMustBeAnnotated(element.getSimpleName()));
+ }
+ }
+ }
+ }
+
+ /**
+ * Utility method to look up raw types from class literals.
+ */
+ public DeclaredType findType(Class<?> clazz) {
+ return types.getDeclaredType(elements.getTypeElement(clazz.getCanonicalName()));
+ }
+
+ /**
+ * Returns a map of client elements to their domain counterparts. The keys may
+ * be RequestContext or Proxy types or methods within those types.
+ */
+ public Map<Element, Element> getClientToDomainMap() {
+ return Collections.unmodifiableMap(clientToDomainMain);
+ }
+
+ public boolean isClientOnly() {
+ return clientOnly;
+ }
+
+ public boolean isMappingRequired(TypeElement element) {
+ return typesRequiringMapping.contains(element);
+ }
+
+ 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(), 0));
+ if (!clientOnly) {
+ jobs.add(new Job(requestContext, new DomainChecker(), 1));
+ }
+ }
+
+ public void maybeScanFactory(TypeElement factoryType) {
+ if (fastFail(factoryType) || types.isSameType(requestFactoryType, factoryType.asType())) {
+ return;
+ }
+ jobs.add(new Job(factoryType, new RequestFactoryScanner(), 0));
+ jobs.add(new Job(factoryType, new DeobfuscatorBuilder(), 2));
+ }
+
+ public void maybeScanProxy(TypeElement proxyType) {
+ if (fastFail(proxyType)) {
+ return;
+ }
+ jobs.add(new Job(proxyType, new ProxyScanner(), 0));
+ if (!clientOnly) {
+ jobs.add(new Job(proxyType, new DomainChecker(), 1));
+ }
+ }
+
+ public boolean mustResolveAllAnnotations() {
+ return requireAllMappings;
+ }
+
+ /**
+ * 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) {
+ if (suppressErrors) {
+ return;
+ }
+
+ if (squelchMessage(elt, message)) {
+ return;
+ }
+
+ if (respectAnnotations()) {
+ Element check = elt;
+ while (check != null) {
+ if (check.getAnnotation(SkipInterfaceValidation.class) != null) {
+ return;
+ }
+ check = check.getEnclosingElement();
+ }
+ }
+ poisoned = true;
+ if (elt == null) {
+ messager.printMessage(Kind.ERROR, message);
+ } else {
+ messager.printMessage(Kind.ERROR, message, elt);
+ }
+ }
+
+ public void requireMapping(TypeElement interfaceElement) {
+ typesRequiringMapping.add(interfaceElement);
+ }
+
+ /**
+ * Set to {@code true} to indicate that only JVM-client support code needs to
+ * be generated.
+ */
+ public void setClientOnly(boolean clientOnly) {
+ this.clientOnly = clientOnly;
+ }
+
+ /**
+ * Set to {@code true} if it is an error for unresolved ProxyForName and
+ * ServiceName annotations to be left over.
+ */
+ public void setMustResolveAllMappings(boolean requireAllMappings) {
+ this.requireAllMappings = requireAllMappings;
+ }
+
+ /**
+ * 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) {
+ if (suppressWarnings) {
+ return;
+ }
+
+ if (squelchMessage(elt, message)) {
+ return;
+ }
+
+ if (respectAnnotations()) {
+ SuppressWarnings suppress;
+ Element check = elt;
+ while (check != null) {
+ if (check.getAnnotation(SkipInterfaceValidation.class) != null) {
+ return;
+ }
+ suppress = check.getAnnotation(SuppressWarnings.class);
+ if (suppress != null) {
+ if (Arrays.asList(suppress.value()).contains("requestfactory")) {
+ return;
+ }
+ }
+ check = check.getEnclosingElement();
+ }
+ }
+
+ messager.printMessage(Kind.WARNING, message + Messages.warnSuffix(), elt);
+ }
+
+ /**
+ * This switch allows the RfValidatorTest code to be worked on in the IDE
+ * without causing compilation failures.
+ */
+ boolean respectAnnotations() {
+ return true;
+ }
+
+ private boolean fastFail(TypeElement element) {
+ return !seen.add(element);
+ }
+
+ /**
+ * 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}.
+ * If the requested type cannot be found, the State will be poisoned.
+ */
+ private DeclaredType findType(String simpleName) {
+ TypeElement element =
+ elements.getTypeElement("com.google.web.bindery.requestfactory.shared." + simpleName);
+ if (element == null) {
+ poison(null, "Unable to find RequestFactory built-in type. "
+ + "Is requestfactory-[client|server].jar on the classpath?");
+ return null;
+ }
+ return types.getDeclaredType(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..9d47dd1
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/TransportableTypeVisitor.java
@@ -0,0 +1,116 @@
+/*
+ * 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.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;
+
+/**
+ * Scans a TypeMirror to determine if it can be transported by RequestFactory.
+ */
+class TransportableTypeVisitor extends TypeVisitorBase<Boolean> {
+
+ /**
+ * 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 (DeclaredType valueType : getValueTypes(state)) {
+ if (state.types.isAssignable(t, valueType)) {
+ 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 state.types.erasure(t).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/TypeComparator.java b/user/src/com/google/web/bindery/requestfactory/apt/TypeComparator.java
new file mode 100644
index 0000000..4de2e8f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/TypeComparator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import java.util.Comparator;
+
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+
+/**
+ * Orders TypeElements by assignability, with most-derived types ordered first,
+ * and then by name.
+ */
+class TypeComparator implements Comparator<TypeElement> {
+ private final State state;
+
+ public TypeComparator(State state) {
+ this.state = state;
+ }
+
+ @Override
+ public int compare(TypeElement a, TypeElement b) {
+ DeclaredType typeA = state.types.getDeclaredType(a);
+ DeclaredType typeB = state.types.getDeclaredType(b);
+ if (state.types.isSameType(typeA, typeB)) {
+ return 0;
+ }
+ if (state.types.isSubtype(typeA, typeB)) {
+ return -1;
+ }
+ if (state.types.isSubtype(typeB, typeA)) {
+ return 1;
+ }
+ return state.elements.getBinaryName(a).toString().compareTo(
+ state.elements.getBinaryName(b).toString());
+ }
+}
\ 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..676005f
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/TypeSimplifier.java
@@ -0,0 +1,112 @@
+/*
+ * 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) {
+ return state.types.erasure(x).accept(this, state);
+ }
+
+ @Override
+ protected TypeMirror defaultAction(TypeMirror x, State state) {
+ return state.types.getNoType(TypeKind.NONE);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/TypeVisitorBase.java b/user/src/com/google/web/bindery/requestfactory/apt/TypeVisitorBase.java
new file mode 100644
index 0000000..5468981
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/TypeVisitorBase.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.SimpleTypeVisitor6;
+
+/**
+ * Provides utility functions for type visitors.
+ *
+ * @param <T> the return type for the visitor
+ */
+class TypeVisitorBase<T> extends SimpleTypeVisitor6<T, State> {
+ /**
+ * This method should be kept in sync with
+ * {@code ValueCodex.getAllValueTypes()}. It doesn't use
+ * {@code getAllValueTypes()} because a dependency on {@code ValueCodex} would
+ * pull in a large number of dependencies into the minimal
+ * {@code requestfactory-apt.jar}.
+ */
+ protected List<DeclaredType> getValueTypes(State state) {
+ List<DeclaredType> types = new ArrayList<DeclaredType>();
+ for (TypeKind kind : TypeKind.values()) {
+ if (kind.isPrimitive()) {
+ PrimitiveType primitiveType = state.types.getPrimitiveType(kind);
+ TypeElement boxedClass = state.types.boxedClass(primitiveType);
+ types.add((DeclaredType) boxedClass.asType());
+ }
+ }
+ types.add(state.findType(BigDecimal.class));
+ types.add(state.findType(BigInteger.class));
+ types.add(state.findType(Date.class));
+ types.add(state.findType(String.class));
+ types.add(state.findType(Void.class));
+ // Avoids compile-dependency bloat
+ types.add(state.types.getDeclaredType(state.elements
+ .getTypeElement("com.google.web.bindery.autobean.shared.Splittable")));
+ return Collections.unmodifiableList(types);
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/ValidationTool.java b/user/src/com/google/web/bindery/requestfactory/apt/ValidationTool.java
new file mode 100644
index 0000000..206b9e9
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/apt/ValidationTool.java
@@ -0,0 +1,279 @@
+/*
+ * 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 java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+import javax.lang.model.SourceVersion;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+/**
+ * Provides "late" validation services when server types aren't available to the
+ * shared-interface compilation process. This tool is provided the name of an
+ * output jar and the binary names of RequestFactory interfaces that should be
+ * validated. The validation process will provide pre-computed type map builders
+ * for use by the ServiceLayer.
+ *
+ * @see http://code.google.com/p/google-web-toolkit/wiki/
+ * RequestFactoryInterfaceValidation
+ */
+public class ValidationTool {
+ /**
+ * A JavaFileManager that writes the class outputs into a jar file or a
+ * directory.
+ */
+ static class JarOrDirectoryOutputFileManager extends ForwardingJavaFileManager<JavaFileManager> {
+ private final List<MemoryJavaFileObject> toOutput = new ArrayList<MemoryJavaFileObject>();
+ private final File output;
+
+ JarOrDirectoryOutputFileManager(File output, JavaFileManager fileManager) {
+ super(fileManager);
+ this.output = output;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (output.isDirectory()) {
+ writeToDirectory();
+ } else {
+ writeToJar();
+ }
+ }
+
+ /**
+ * Not expected to be called. Overridden to prevent accidental writes to
+ * disk.
+ */
+ @Override
+ public FileObject getFileForOutput(Location location, String packageName, String relativeName,
+ FileObject sibling) throws IOException {
+ throw new UnsupportedOperationException("Not expecting to write " + packageName + "/"
+ + relativeName);
+ }
+
+ /**
+ * This method will receive generated source and class files.
+ */
+ @Override
+ public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind,
+ FileObject sibling) throws IOException {
+ String path = BinaryName.toInternalName(className);
+ String suffix;
+ switch (kind) {
+ case CLASS:
+ suffix = ".class";
+ break;
+ case SOURCE:
+ suffix = ".java";
+ break;
+ default:
+ throw new UnsupportedOperationException("Unexpected kind " + kind);
+ }
+ MemoryJavaFileObject toReturn =
+ new MemoryJavaFileObject(uri("memory:///" + path + suffix), kind);
+ if (StandardLocation.CLASS_OUTPUT.equals(location) && Kind.CLASS.equals(kind)) {
+ toOutput.add(toReturn);
+ }
+ return toReturn;
+ }
+
+ @Override
+ public boolean isSameFile(FileObject a, FileObject b) {
+ if (a instanceof MemoryJavaFileObject && b instanceof MemoryJavaFileObject) {
+ MemoryJavaFileObject memoryA = (MemoryJavaFileObject) a;
+ MemoryJavaFileObject memoryB = (MemoryJavaFileObject) b;
+ return memoryA.getKind().equals(memoryB.getKind())
+ && memoryA.toUri().equals(memoryB.toUri());
+ }
+ if (a instanceof FakeJavaFileObject && b instanceof FakeJavaFileObject) {
+ // Only one file ever created
+ return true;
+ }
+ return super.isSameFile(a, b);
+ }
+
+ private void writeToDirectory() throws IOException {
+ for (MemoryJavaFileObject file : toOutput) {
+ String path = file.toUri().getPath();
+ if (path.equals("/fake/Fake.class")) {
+ // ignore dummy class
+ continue;
+ }
+ File target = new File(output, path);
+ target.getParentFile().mkdirs();
+ FileOutputStream out = new FileOutputStream(target);
+ out.write(file.bytes.toByteArray());
+ out.close();
+ }
+ }
+
+ private void writeToJar() throws IOException, FileNotFoundException {
+ JarOutputStream jar = new JarOutputStream(new FileOutputStream(output));
+ for (MemoryJavaFileObject file : toOutput) {
+ String path = file.toUri().getPath();
+ if (path.equals("/fake/Fake.class")) {
+ // ignore dummy class
+ continue;
+ }
+ // Strip leading /
+ ZipEntry entry = new ZipEntry(path.substring(1));
+ jar.putNextEntry(entry);
+ jar.write(file.bytes.toByteArray());
+ }
+ jar.close();
+ }
+ }
+
+ /**
+ * Provides a fake type to seed the compilation process with.
+ */
+ private static class FakeJavaFileObject extends SimpleJavaFileObject {
+ public FakeJavaFileObject() {
+ super(uri("fake:///fake/Fake.java"), JavaFileObject.Kind.SOURCE);
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean arg0) throws IOException {
+ return "package fake; interface Fake {}";
+ }
+ }
+
+ /**
+ * An in-memory implementation of JavaFileObject.
+ */
+ private static class MemoryJavaFileObject extends SimpleJavaFileObject {
+ private ByteArrayOutputStream bytes;
+ private StringWriter charContents;
+
+ public MemoryJavaFileObject(URI uri, JavaFileObject.Kind kind) {
+ super(uri, kind);
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignored) throws IOException {
+ return charContents.toString();
+ }
+
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ bytes = new ByteArrayOutputStream();
+ return bytes;
+ }
+
+ @Override
+ public Writer openWriter() throws IOException {
+ charContents = new StringWriter();
+ return charContents;
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ System.exit(exec(args) ? 0 : -1);
+ }
+
+ /**
+ * A testable "main" method.
+ */
+ static boolean exec(String[] args) throws IOException {
+ if (args.length < 2) {
+ System.err.println("java -cp requestfactory-client.jar:your_server-code.jar "
+ + ValidationTool.class.getCanonicalName()
+ + " (/some/directory | output.jar) com.example.shared.MyRequestFactory");
+ System.err.println("See "
+ + "http://code.google.com/p/google-web-toolkit/wiki/RequestFactoryInterfaceValidation "
+ + "for more information.");
+ return false;
+ }
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ if (compiler == null) {
+ System.err.println("This tool must be run with a JDK, not a JRE");
+ return false;
+ }
+ if (!compiler.getSourceVersions().contains(SourceVersion.RELEASE_6)) {
+ System.err.println("This tool must be run with a Java 1.6 compiler");
+ return false;
+ }
+
+ boolean clientOnly = false;
+ List<String> argList = new ArrayList<String>(Arrays.asList(args));
+ if (argList.get(0).equals("-client")) {
+ clientOnly = true;
+ argList.remove(0);
+ }
+
+ // Control how the compile process writes data to disk
+ JavaFileManager fileManager =
+ new JarOrDirectoryOutputFileManager(new File(argList.remove(0)), compiler
+ .getStandardFileManager(null, null, null));
+
+ // Create a validator and require it to process the specified types
+ RfValidator processor = new RfValidator();
+ if (clientOnly) {
+ processor.setClientOnly(true);
+ } else {
+ processor.setMustResolveAllMappings(true);
+ }
+ processor.setRootOverride(argList);
+
+ // Create the compilation task
+ CompilationTask task =
+ compiler.getTask(null, fileManager, null, null, null, Arrays
+ .asList(new FakeJavaFileObject()));
+ task.setProcessors(Arrays.asList(processor));
+ if (!task.call()) {
+ return false;
+ }
+ // Save data only on successful compilation
+ fileManager.close();
+ return true;
+ }
+
+ /**
+ * Convenience method for discarding {@link URISyntaxException}.
+ */
+ private static URI uri(String contents) {
+ try {
+ return new URI(contents);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/Deobfuscator.java b/user/src/com/google/web/bindery/requestfactory/server/Deobfuscator.java
deleted file mode 100644
index 22abf24..0000000
--- a/user/src/com/google/web/bindery/requestfactory/server/Deobfuscator.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.web.bindery.requestfactory.server;
-
-import com.google.gwt.dev.asm.Type;
-import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedSet;
-
-/**
- * Provides access to payload deobfuscation services.
- */
-class Deobfuscator {
- public static class Builder {
- private Deobfuscator d = new Deobfuscator();
- {
- d.domainToClientType = new HashMap<String, List<String>>();
- d.operationData = new HashMap<OperationKey, OperationData>();
- d.typeTokens = new HashMap<String, String>();
- }
-
- public Builder addClientToDomainMapping(String domainBinaryName, SortedSet<Type> value) {
- List<String> clientBinaryNames;
- switch (value.size()) {
- case 0:
- clientBinaryNames = Collections.emptyList();
- break;
- case 1:
- clientBinaryNames = Collections.singletonList(value.first().getClassName());
- break;
- default:
- clientBinaryNames = new ArrayList<String>(value.size());
- for (Type t : value) {
- clientBinaryNames.add(t.getClassName());
- }
- clientBinaryNames = Collections.unmodifiableList(clientBinaryNames);
- }
- d.domainToClientType.put(domainBinaryName, clientBinaryNames);
- return this;
- }
-
- public Builder addClientToDomainMappings(Map<Type, SortedSet<Type>> data) {
- for (Map.Entry<Type, SortedSet<Type>> entry : data.entrySet()) {
- String domainBinaryName = entry.getKey().getClassName();
- SortedSet<Type> value = entry.getValue();
- addClientToDomainMapping(domainBinaryName, value);
- }
- return this;
- }
-
- public Builder addOperation(OperationKey key, OperationData data) {
- d.operationData.put(key, data);
- return this;
- }
-
- public Builder addOperationData(Map<OperationKey, OperationData> operationData) {
- d.operationData.putAll(operationData);
- return this;
- }
-
- public Builder addRawTypeToken(String token, String binaryName) {
- d.typeTokens.put(token, binaryName);
- return this;
- }
-
- public Builder addRawTypeTokens(Map<String, String> typeTokens) {
- for (Map.Entry<String, String> entry : typeTokens.entrySet()) {
- addRawTypeToken(entry.getKey(), entry.getValue());
- }
- return this;
- }
-
- public Builder addTypeTokens(Map<String, Type> typeTokens) {
- for (Map.Entry<String, Type> entry : typeTokens.entrySet()) {
- d.typeTokens.put(entry.getKey(), entry.getValue().getClassName());
- }
- return this;
- }
-
- public Deobfuscator build() {
- Deobfuscator toReturn = d;
- toReturn.domainToClientType = Collections.unmodifiableMap(toReturn.domainToClientType);
- toReturn.operationData = Collections.unmodifiableMap(toReturn.operationData);
- toReturn.typeTokens = Collections.unmodifiableMap(toReturn.typeTokens);
- d = null;
- return toReturn;
- }
-
- public Builder merge(Deobfuscator deobfuscator) {
- d.domainToClientType.putAll(deobfuscator.domainToClientType);
- d.operationData.putAll(deobfuscator.operationData);
- d.typeTokens.putAll(deobfuscator.typeTokens);
- return this;
- }
- }
-
- /**
- * Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy,
- * FooBProxy).
- */
- private Map<String, List<String>> domainToClientType;
- private Map<OperationKey, OperationData> operationData;
- /**
- * Map of obfuscated ids to binary class names.
- */
- private Map<String, String> typeTokens;
-
- Deobfuscator() {
- }
-
- /**
- * Returns the client proxy types whose {@code @ProxyFor} is exactly
- * {@code binaryTypeName}. Ordered such that the most-derived types will be
- * iterated over first.
- */
- public List<String> getClientProxies(String binaryTypeName) {
- return domainToClientType.get(binaryTypeName);
- }
-
- /**
- * Returns a method descriptor that should be invoked on the service object.
- */
- public String getDomainMethodDescriptor(String operation) {
- OperationData data = getData(operation);
- return data == null ? null : data.getDomainMethodDescriptor();
- }
-
- public String getRequestContext(String operation) {
- OperationData data = getData(operation);
- return data == null ? null : data.getRequestContext();
- }
-
- public String getRequestContextMethodDescriptor(String operation) {
- OperationData data = getData(operation);
- return data == null ? null : data.getClientMethodDescriptor();
- }
-
- public String getRequestContextMethodName(String operation) {
- OperationData data = getData(operation);
- return data == null ? null : data.getMethodName();
- }
-
- /**
- * Returns a type's binary name based on an obfuscated token.
- */
- public String getTypeFromToken(String token) {
- return typeTokens.get(token);
- }
-
- private OperationData getData(String operation) {
- OperationData data = operationData.get(new OperationKey(operation));
- return data;
- }
-}
\ No newline at end of file
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
deleted file mode 100644
index f2c8b49..0000000
--- a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ /dev/null
@@ -1,1622 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.web.bindery.requestfactory.server;
-
-import com.google.gwt.dev.asm.AnnotationVisitor;
-import com.google.gwt.dev.asm.ClassReader;
-import com.google.gwt.dev.asm.ClassVisitor;
-import com.google.gwt.dev.asm.MethodVisitor;
-import com.google.gwt.dev.asm.Opcodes;
-import com.google.gwt.dev.asm.Type;
-import com.google.gwt.dev.asm.commons.EmptyVisitor;
-import com.google.gwt.dev.asm.commons.Method;
-import com.google.gwt.dev.asm.signature.SignatureReader;
-import com.google.gwt.dev.asm.signature.SignatureVisitor;
-import com.google.gwt.dev.util.Name;
-import com.google.gwt.dev.util.Name.BinaryName;
-import com.google.gwt.dev.util.Name.SourceOrBinaryName;
-import com.google.web.bindery.autobean.shared.ValueCodex;
-import com.google.web.bindery.requestfactory.shared.BaseProxy;
-import com.google.web.bindery.requestfactory.shared.EntityProxy;
-import com.google.web.bindery.requestfactory.shared.ExtraTypes;
-import com.google.web.bindery.requestfactory.shared.InstanceRequest;
-import com.google.web.bindery.requestfactory.shared.ProxyFor;
-import com.google.web.bindery.requestfactory.shared.ProxyForName;
-import com.google.web.bindery.requestfactory.shared.Request;
-import com.google.web.bindery.requestfactory.shared.RequestContext;
-import com.google.web.bindery.requestfactory.shared.RequestFactory;
-import com.google.web.bindery.requestfactory.shared.Service;
-import com.google.web.bindery.requestfactory.shared.ServiceName;
-import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
-import com.google.web.bindery.requestfactory.shared.ValueProxy;
-import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Encapsulates validation logic to determine if a {@link RequestFactory}
- * interface, its {@link RequestContext}, and associated {@link EntityProxy}
- * interfaces match their domain counterparts. This implementation examines the
- * classfiles directly in order to avoid the need to load the types into the
- * JVM.
- * <p>
- * This class is amenable to being used as a unit test:
- *
- * <pre>
- * public void testRequestFactory() {
- * Logger logger = Logger.getLogger("");
- * RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(
- * logger, new ClassLoaderLoader(MyRequestContext.class.getClassLoader()));
- * v.validateRequestContext(MyRequestContext.class.getName());
- * assertFalse(v.isPoisoned());
- * }
- * </pre>
- * This class also has a {@code main} method and can be used as a build-time
- * tool:
- *
- * <pre>
- * java -cp gwt-servlet.jar:your-code.jar \
- * com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator \
- * com.example.MyRequestFactory
- * </pre>
- */
-public class RequestFactoryInterfaceValidator {
- /**
- * An implementation of {@link Loader} that uses a {@link ClassLoader} to
- * retrieve the class files.
- */
- public static class ClassLoaderLoader implements Loader {
- private final ClassLoader loader;
-
- public ClassLoaderLoader(ClassLoader loader) {
- this.loader = loader;
- }
-
- public boolean exists(String resource) {
- return loader.getResource(resource) != null;
- }
-
- public InputStream getResourceAsStream(String resource) {
- return loader.getResourceAsStream(resource);
- }
- }
-
- /**
- * Abstracts the mechanism by which class files are loaded.
- *
- * @see ClassLoaderLoader
- */
- public interface Loader {
- /**
- * Returns true if the specified resource can be loaded.
- *
- * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
- */
- boolean exists(String resource);
-
- /**
- * Returns an InputStream to access the specified resource, or
- * <code>null</code> if no such resource exists.
- *
- * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
- */
- InputStream getResourceAsStream(String resource);
- }
-
- /**
- * Improves error messages by providing context for the user.
- * <p>
- * Visible for testing.
- */
- static class ErrorContext {
- private final Logger logger;
- private final ErrorContext parent;
- private Type currentType;
- private Method currentMethod;
- private RequestFactoryInterfaceValidator validator;
-
- public ErrorContext(Logger logger) {
- this.logger = logger;
- this.parent = null;
- }
-
- protected ErrorContext(ErrorContext parent) {
- this.logger = parent.logger;
- this.parent = parent;
- this.validator = parent.validator;
- }
-
- public void poison(String msg, Object... args) {
- poison();
- logger.logp(Level.SEVERE, currentType(), currentMethod(), String.format(msg, args));
- validator.poisoned = true;
- }
-
- public void poison(String msg, Throwable t) {
- poison();
- logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
- validator.poisoned = true;
- }
-
- public ErrorContext setMethod(Method method) {
- ErrorContext toReturn = fork();
- toReturn.currentMethod = method;
- return toReturn;
- }
-
- public ErrorContext setType(Type type) {
- ErrorContext toReturn = fork();
- toReturn.currentType = type;
- return toReturn;
- }
-
- public void spam(String msg, Object... args) {
- logger.logp(Level.FINEST, currentType(), currentMethod(), String.format(msg, args));
- }
-
- protected ErrorContext fork() {
- return new ErrorContext(this);
- }
-
- void setValidator(RequestFactoryInterfaceValidator validator) {
- assert this.validator == null : "Cannot set validator twice";
- this.validator = validator;
- }
-
- private String currentMethod() {
- if (currentMethod != null) {
- return print(currentMethod);
- }
- if (parent != null) {
- return parent.currentMethod();
- }
- return null;
- }
-
- private String currentType() {
- if (currentType != null) {
- return print(currentType);
- }
- if (parent != null) {
- return parent.currentType();
- }
- return null;
- }
-
- /**
- * Populate {@link RequestFactoryInterfaceValidator#badTypes} with the
- * current context.
- */
- private void poison() {
- if (currentType != null) {
- validator.badTypes.add(currentType.getClassName());
- }
- if (parent != null) {
- parent.poison();
- }
- }
- }
-
- /**
- * Used internally as a placeholder for types that cannot be mapped to a
- * domain object.
- */
- interface MissingDomainType {
- }
-
- /**
- * Collects the ProxyFor or Service annotation from an EntityProxy or
- * RequestContext type.
- */
- private class DomainMapper extends EmptyVisitor {
- private final ErrorContext logger;
- private String domainInternalName;
- private List<Class<? extends Annotation>> found = new ArrayList<Class<? extends Annotation>>();
- private String locatorInternalName;
-
- public DomainMapper(ErrorContext logger) {
- this.logger = logger;
- logger.spam("Finding domain mapping annotation");
- }
-
- public String getDomainInternalName() {
- return domainInternalName;
- }
-
- public String getLocatorInternalName() {
- return locatorInternalName;
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName,
- String[] interfaces) {
- if ((access & Opcodes.ACC_INTERFACE) == 0) {
- logger.poison("Type must be an interface");
- }
- }
-
- /**
- * This method examines one annotation at a time.
- */
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- // Set to true if the annotation should have class literal values
- boolean expectClasses = false;
- // Set to true if the annonation has string values
- boolean expectNames = false;
-
- if (desc.equals(Type.getDescriptor(ProxyFor.class))) {
- expectClasses = true;
- found.add(ProxyFor.class);
- } else if (desc.equals(Type.getDescriptor(ProxyForName.class))) {
- expectNames = true;
- found.add(ProxyForName.class);
- } else if (desc.equals(Type.getDescriptor(Service.class))) {
- expectClasses = true;
- found.add(Service.class);
- } else if (desc.equals(Type.getDescriptor(ServiceName.class))) {
- expectNames = true;
- found.add(ServiceName.class);
- }
-
- if (expectClasses) {
- return new EmptyVisitor() {
- @Override
- public void visit(String name, Object value) {
- if ("value".equals(name)) {
- domainInternalName = ((Type) value).getInternalName();
- } else if ("locator".equals(name)) {
- locatorInternalName = ((Type) value).getInternalName();
- }
- }
- };
- }
-
- if (expectNames) {
- return new EmptyVisitor() {
- @Override
- public void visit(String name, Object value) {
- String sourceName;
- boolean locatorRequired = "locator".equals(name);
- boolean valueRequired = "value".equals(name);
- if (valueRequired || locatorRequired) {
- sourceName = (String) value;
- } else {
- return;
- }
-
- /*
- * The input is a source name, so we need to convert it to an
- * internal name. We'll do this by substituting dollar signs for the
- * last slash in the name until there are no more slashes.
- */
- StringBuffer desc = new StringBuffer(sourceName.replace('.', '/'));
- while (!loader.exists(desc.toString() + ".class")) {
- logger.spam("Did not find " + desc.toString());
- int idx = desc.lastIndexOf("/");
- if (idx == -1) {
- if (locatorRequired) {
- logger.poison("Cannot find locator named %s", value);
- } else if (valueRequired) {
- logger.poison("Cannot find domain type named %s", value);
- }
- return;
- }
- desc.setCharAt(idx, '$');
- }
-
- if (locatorRequired) {
- locatorInternalName = desc.toString();
- logger.spam(locatorInternalName);
- } else if (valueRequired) {
- domainInternalName = desc.toString();
- logger.spam(domainInternalName);
- } else {
- throw new RuntimeException("Should not reach here");
- }
- }
- };
- }
- return null;
- }
-
- @Override
- public void visitEnd() {
- // Only allow one annotation
- if (found.size() > 1) {
- StringBuilder sb = new StringBuilder();
- for (Class<?> clazz : found) {
- sb.append(" @").append(clazz.getSimpleName());
- }
- logger.poison("Redundant domain mapping annotations present:%s", sb.toString());
- }
- }
- }
-
- private class ExtraTypesCollector extends EmptyVisitor {
- private final ErrorContext logger;
- private final Set<Type> collected = new HashSet<Type>();
-
- public ExtraTypesCollector(ErrorContext logger) {
- this.logger = logger;
- logger.spam("Collecting extra types");
- }
-
- public Type[] exec(Type type) {
- for (Type toExamine : getSupertypes(logger, type)) {
- RequestFactoryInterfaceValidator.this.visit(logger, toExamine.getInternalName(), this);
- }
- return collected.toArray(new Type[collected.size()]);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (!desc.equals(Type.getDescriptor(ExtraTypes.class))) {
- return null;
- }
-
- return new EmptyVisitor() {
-
- @Override
- public AnnotationVisitor visitArray(String name) {
- if (!"value".equals(name)) {
- return null;
- }
- return new EmptyVisitor() {
- @Override
- public void visit(String name, Object value) {
- collected.add((Type) value);
- }
- };
- }
-
- };
- }
- }
-
- /**
- * Collects information about domain objects. This visitor is intended to be
- * iteratively applied to collect all methods in a type hierarchy.
- */
- private class MethodsInHierarchyCollector extends EmptyVisitor {
- private final ErrorContext logger;
- private Set<RFMethod> methods = new LinkedHashSet<RFMethod>();
- private Set<String> seen = new HashSet<String>();
-
- private MethodsInHierarchyCollector(ErrorContext logger) {
- this.logger = logger;
- }
-
- public Set<RFMethod> exec(String internalName) {
- RequestFactoryInterfaceValidator.this.visit(logger, internalName, this);
-
- Map<RFMethod, RFMethod> toReturn = new HashMap<RFMethod, RFMethod>();
- // Return most-derived methods
- for (RFMethod method : methods) {
- RFMethod key =
- new RFMethod(method.getName(), Type.getMethodDescriptor(Type.VOID_TYPE, method
- .getArgumentTypes()));
-
- RFMethod compareTo = toReturn.get(key);
- if (compareTo == null) {
- toReturn.put(key, method);
- } else if (isAssignable(logger, compareTo.getReturnType(), method.getReturnType())) {
- toReturn.put(key, method);
- }
- }
-
- return new HashSet<RFMethod>(toReturn.values());
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName,
- String[] interfaces) {
- if (!seen.add(name)) {
- return;
- }
- if (!objectType.getInternalName().equals(superName)) {
- RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
- }
- if (interfaces != null) {
- for (String intf : interfaces) {
- RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
- }
- }
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc, String signature,
- String[] exceptions) {
- // Ignore initializers
- if ("<clinit>".equals(name) || "<init>".equals(name)) {
- return null;
- }
- final RFMethod method = new RFMethod(name, desc);
- method.setDeclaredStatic((access & Opcodes.ACC_STATIC) != 0);
- method.setDeclaredSignature(signature);
- methods.add(method);
-
- return new EmptyVisitor() {
- @Override
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- if (desc.equals(Type.getDescriptor(SkipInterfaceValidation.class))) {
- method.setValidationSkipped(true);
- }
- return null;
- }
- };
- }
- }
-
- private static class RFMethod extends Method {
- private boolean isDeclaredStatic;
- private String signature;
- private boolean isValidationSkipped;
-
- public RFMethod(String name, String desc) {
- super(name, desc);
- }
-
- public String getSignature() {
- return signature;
- }
-
- public boolean isDeclaredStatic() {
- return isDeclaredStatic;
- }
-
- public boolean isValidationSkipped() {
- return isValidationSkipped;
- }
-
- public void setDeclaredSignature(String signature) {
- this.signature = signature;
- }
-
- public void setDeclaredStatic(boolean value) {
- isDeclaredStatic = value;
- }
-
- public void setValidationSkipped(boolean isValidationSkipped) {
- this.isValidationSkipped = isValidationSkipped;
- }
-
- @Override
- public String toString() {
- return (isDeclaredStatic ? "static " : "") + super.toString();
- }
- }
-
- private class SupertypeCollector extends EmptyVisitor {
- private final ErrorContext logger;
- private final Set<String> seen = new HashSet<String>();
- private final List<Type> supers = new ArrayList<Type>();
-
- public SupertypeCollector(ErrorContext logger) {
- this.logger = logger;
- }
-
- public List<Type> exec(Type type) {
- RequestFactoryInterfaceValidator.this.visit(logger, type.getInternalName(), this);
- return supers;
- }
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName,
- String[] interfaces) {
- if (!seen.add(name)) {
- return;
- }
- supers.add(Type.getObjectType(name));
- if (!objectType.getInternalName().equals(name)) {
- RequestFactoryInterfaceValidator.this.visit(logger, superName, this);
- }
- if (interfaces != null) {
- for (String intf : interfaces) {
- RequestFactoryInterfaceValidator.this.visit(logger, intf, this);
- }
- }
- }
- }
-
- /**
- * Return all types referenced by a method signature.
- */
- private static class TypesInSignatureCollector extends SignatureAdapter {
- private final Set<Type> found = new HashSet<Type>();
-
- public Type[] getFound() {
- return found.toArray(new Type[found.size()]);
- }
-
- @Override
- public SignatureVisitor visitArrayType() {
- return this;
- }
-
- @Override
- public SignatureVisitor visitClassBound() {
- return this;
- }
-
- @Override
- public void visitClassType(String name) {
- found.add(Type.getObjectType(name));
- }
-
- @Override
- public SignatureVisitor visitExceptionType() {
- return this;
- }
-
- @Override
- public SignatureVisitor visitInterface() {
- return this;
- }
-
- @Override
- public SignatureVisitor visitInterfaceBound() {
- return this;
- }
-
- @Override
- public SignatureVisitor visitParameterType() {
- return this;
- }
-
- @Override
- public SignatureVisitor visitReturnType() {
- return this;
- }
-
- @Override
- public SignatureVisitor visitSuperclass() {
- return this;
- }
-
- @Override
- public SignatureVisitor visitTypeArgument(char wildcard) {
- return this;
- }
- }
-
- static final Set<Class<?>> VALUE_TYPES = ValueCodex.getAllValueTypes();
-
- public static void main(String[] args) {
- if (args.length == 0) {
- System.err.println("Usage: java -cp gwt-servlet.jar:your-code.jar "
- + RequestFactoryInterfaceValidator.class.getCanonicalName()
- + " com.example.MyRequestFactory");
- System.exit(1);
- }
- RequestFactoryInterfaceValidator validator =
- new RequestFactoryInterfaceValidator(Logger
- .getLogger(RequestFactoryInterfaceValidator.class.getName()), new ClassLoaderLoader(
- Thread.currentThread().getContextClassLoader()));
- validator.validateRequestFactory(args[0]);
- System.exit(validator.isPoisoned() ? 1 : 0);
- }
-
- static String messageCouldNotFindMethod(Type domainType, List<? extends Method> methods) {
- StringBuilder sb = new StringBuilder();
- sb.append(String.format("Could not find matching method in %s.\nPossible matches:\n",
- print(domainType)));
- for (Method domainMethod : methods) {
- sb.append(" ").append(print(domainMethod)).append("\n");
- }
- return sb.toString();
- }
-
- private static String print(Method method) {
- StringBuilder sb = new StringBuilder();
- sb.append(print(method.getReturnType())).append(" ").append(method.getName()).append("(");
- for (Type t : method.getArgumentTypes()) {
- sb.append(print(t)).append(" ");
- }
- sb.append(")");
- return sb.toString();
- }
-
- private static String print(Type type) {
- return SourceOrBinaryName.toSourceName(type.getClassName());
- }
-
- /**
- * A set of binary type names that are known to be bad.
- */
- private final Set<String> badTypes = new HashSet<String>();
- /**
- * The type {@link BaseProxy}.
- */
- private final Type baseProxyIntf = Type.getType(BaseProxy.class);
- /**
- * Maps client types (e.g. FooProxy) to server domain types (e.g. Foo).
- */
- private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
- /**
- * Maps client types (e.g. FooProxy or FooContext) to their locator types
- * (e.g. FooLocator or FooServiceLocator).
- */
- private final Map<Type, Type> clientToLocatorMap = new HashMap<Type, Type>();
- /**
- * Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy,
- * FooBProxy).
- */
- private final Map<Type, SortedSet<Type>> domainToClientType =
- new HashMap<Type, SortedSet<Type>>();
- /**
- * The type {@link EntityProxy}.
- */
- private final Type entityProxyIntf = Type.getType(EntityProxy.class);
- /**
- * The type {@link Enum}.
- */
- private final Type enumType = Type.getType(Enum.class);
- /**
- * A placeholder type for client types that could not be resolved to a domain
- * type.
- */
- private final Type errorType = Type.getType(MissingDomainType.class);
- /**
- * The type {@link InstanceRequest}.
- */
- private final Type instanceRequestIntf = Type.getType(InstanceRequest.class);
- private final Loader loader;
- /**
- * A cache of all methods defined in a type hierarchy.
- */
- private final Map<Type, Set<RFMethod>> methodsInHierarchy = new HashMap<Type, Set<RFMethod>>();
- /**
- * Not static because it depends on {@link #parentLogger}.
- */
- private final Comparator<Type> typeNameComparator = new Comparator<Type>() {
- @Override
- public int compare(Type a, Type b) {
- if (isAssignable(parentLogger, a, b)) {
- return 1;
- } else if (isAssignable(parentLogger, b, a)) {
- return -1;
- }
- return a.getInternalName().compareTo(b.getInternalName());
- }
- };
- /**
- * Used to resolve obfuscated type tokens.
- */
- private final Map<String, Type> typeTokens = new HashMap<String, Type>();
- /**
- * The type {@link Object}.
- */
- private final Type objectType = Type.getObjectType("java/lang/Object");
- /**
- * Maps obfuscated operation names to dispatch information.
- */
- private final Map<OperationKey, OperationData> operationData =
- new HashMap<OperationKey, OperationData>();
- private final ErrorContext parentLogger;
- private boolean poisoned;
- /**
- * The type {@link Request}.
- */
- private final Type requestIntf = Type.getType(Request.class);
-
- /**
- * The type {@link RequestContext}.
- */
- private final Type requestContextIntf = Type.getType(RequestContext.class);
-
- /**
- * A map of a type to all types that it could be assigned to.
- */
- private final Map<Type, List<Type>> supertypes = new HashMap<Type, List<Type>>();
-
- /**
- * The type {@link ValueProxy}.
- */
- private final Type valueProxyIntf = Type.getType(ValueProxy.class);
-
- /**
- * A set to prevent re-validation of a type.
- */
- private final Set<String> validatedTypes = new HashSet<String>();
-
- /**
- * Contains vaue types (e.g. Integer).
- */
- private final Set<Type> valueTypes = new HashSet<Type>();
-
- /**
- * Maps a domain object to the type returned from its getId method.
- */
- private final Map<Type, Type> unresolvedKeyTypes = new HashMap<Type, Type>();
-
- {
- for (Class<?> clazz : VALUE_TYPES) {
- valueTypes.add(Type.getType(clazz));
- }
- }
-
- public RequestFactoryInterfaceValidator(Logger logger, Loader loader) {
- this.parentLogger = new ErrorContext(logger);
- parentLogger.setValidator(this);
- this.loader = loader;
- }
-
- /**
- * Visible for testing.
- */
- RequestFactoryInterfaceValidator(ErrorContext errorContext, Loader loader) {
- this.parentLogger = errorContext;
- this.loader = loader;
- errorContext.setValidator(this);
- }
-
- /**
- * Reset the poisoned status of the validator so that it may be reused without
- * destroying cached state.
- */
- public void antidote() {
- poisoned = false;
- }
-
- public Deobfuscator getDeobfuscator() {
- return new Deobfuscator.Builder().addClientToDomainMappings(domainToClientType)
- .addOperationData(operationData).addTypeTokens(typeTokens).build();
- }
-
- /**
- * Returns true if validation failed.
- */
- public boolean isPoisoned() {
- return poisoned;
- }
-
- /**
- * This method checks an EntityProxy interface against its peer domain object
- * to determine if the server code would be able to process a request using
- * the methods defined in the EntityProxy interface. It does not perform any
- * checks as to whether or not the EntityProxy could actually be generated by
- * the Generator.
- * <p>
- * This method may be called repeatedly on a single instance of the validator.
- * Doing so will amortize type calculation costs.
- * <p>
- * Checks implemented:
- * <ul>
- * <li> <code>binaryName</code> implements EntityProxy</li>
- * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
- * annotation</li>
- * <li>The domain object has getId() and getVersion() methods</li>
- * <li>All property methods in the EntityProxy can be mapped onto an
- * equivalent domain method (unless validation is skipped for the method)</li>
- * <li>All referenced proxy types are valid</li>
- * </ul>
- *
- * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
- * EntityProxy subtype
- */
- public void validateEntityProxy(String binaryName) {
- validateProxy(binaryName, entityProxyIntf, true);
- }
-
- /**
- * Determine if the specified type implements a proxy interface and apply the
- * appropriate validations. This can be used as a general-purpose entry method
- * when writing unit tests.
- *
- * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
- * EntityProxy or ValueProxy subtype
- */
- public void validateProxy(String binaryName) {
- /*
- * Don't call fastFail() here or the proxy may not be validated, since
- * validateXProxy delegates to validateProxy() which would re-check.
- */
- Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
- if (isAssignable(parentLogger, entityProxyIntf, proxyType)) {
- validateEntityProxy(binaryName);
- } else if (isAssignable(parentLogger, valueProxyIntf, proxyType)) {
- validateValueProxy(binaryName);
- } else {
- parentLogger.poison("%s is neither an %s nor a %s", print(proxyType), print(entityProxyIntf),
- print(valueProxyIntf));
- }
- }
-
- /**
- * This method checks a RequestContext interface against its peer domain
- * domain object to determine if the server code would be able to process a
- * request using the the methods defined in the RequestContext interface. It
- * does not perform any checks as to whether or not the RequestContext could
- * actually be generated by the Generator.
- * <p>
- * This method may be called repeatedly on a single instance of the validator.
- * Doing so will amortize type calculation costs.
- * <p>
- * Checks implemented:
- * <ul>
- * <li> <code>binaryName</code> implements RequestContext</li>
- * <li><code>binaryName</code> has a {@link Service} or {@link ServiceName}
- * annotation</li>
- * <li>All service methods in the RequestContext can be mapped onto an
- * equivalent domain method (unless validation is skipped for the method)</li>
- * <li>All referenced EntityProxy types are valid</li>
- * </ul>
- *
- * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
- * RequestContext subtype
- * @see #validateEntityProxy(String)
- */
- public void validateRequestContext(String binaryName) {
- if (fastFail(binaryName)) {
- return;
- }
-
- Type requestContextType = Type.getObjectType(BinaryName.toInternalName(binaryName));
- final ErrorContext logger = parentLogger.setType(requestContextType);
-
- // Quick sanity check for calling code
- if (!isAssignable(logger, requestContextIntf, requestContextType)) {
- logger.poison("%s is not a %s", print(requestContextType), RequestContext.class
- .getSimpleName());
- return;
- }
-
- Type domainServiceType = getDomainType(logger, requestContextType, false);
- if (domainServiceType == errorType) {
- logger.poison("The type %s must be annotated with a @%s or @%s annotation", BinaryName
- .toSourceName(binaryName), Service.class.getSimpleName(), ServiceName.class
- .getSimpleName());
- return;
- }
-
- for (RFMethod method : getMethodsInHierarchy(logger, requestContextType)) {
- // Ignore methods in RequestContext itself
- if (findCompatibleMethod(logger, requestContextIntf, method, false, true, true) != null) {
- continue;
- }
-
- // Check the client method against the domain
- Method found =
- checkClientMethodInDomain(logger, method, domainServiceType, !clientToLocatorMap
- .containsKey(requestContextType));
- if (found != null) {
- OperationKey key = new OperationKey(binaryName, method.getName(), method.getDescriptor());
- OperationData data =
- new OperationData.Builder().setClientMethodDescriptor(method.getDescriptor())
- .setDomainMethodDescriptor(found.getDescriptor()).setMethodName(method.getName())
- .setRequestContext(requestContextType.getClassName()).build();
- operationData.put(key, data);
- }
- maybeCheckReferredProxies(logger, method);
- }
-
- maybeCheckExtraTypes(logger, requestContextType);
- checkUnresolvedKeyTypes(logger);
- }
-
- /**
- * This method checks a RequestFactory interface.
- * <p>
- * This method may be called repeatedly on a single instance of the validator.
- * Doing so will amortize type calculation costs. It does not perform any
- * checks as to whether or not the RequestContext could actually be generated
- * by the Generator.
- * <p>
- * Checks implemented:
- * <ul>
- * <li> <code>binaryName</code> implements RequestFactory</li>
- * <li>All referenced RequestContext types are valid</li>
- * </ul>
- *
- * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
- * RequestContext subtype
- * @see #validateRequestContext(String)
- */
- public void validateRequestFactory(String binaryName) {
- if (fastFail(binaryName)) {
- return;
- }
-
- Type requestFactoryType = Type.getObjectType(BinaryName.toInternalName(binaryName));
- ErrorContext logger = parentLogger.setType(requestFactoryType);
-
- // Quick sanity check for calling code
- if (!isAssignable(logger, Type.getType(RequestFactory.class), requestFactoryType)) {
- logger.poison("%s is not a %s", print(requestFactoryType), RequestFactory.class
- .getSimpleName());
- return;
- }
-
- // Validate each RequestContext method in the RF
- for (Method contextMethod : getMethodsInHierarchy(logger, requestFactoryType)) {
- Type returnType = contextMethod.getReturnType();
- if (isAssignable(logger, requestContextIntf, returnType)) {
- validateRequestContext(returnType.getClassName());
- }
- }
-
- maybeCheckExtraTypes(logger, requestFactoryType);
- }
-
- /**
- * This method checks a ValueProxy interface against its peer domain object to
- * determine if the server code would be able to process a request using the
- * methods defined in the ValueProxy interface. It does not perform any checks
- * as to whether or not the ValueProxy could actually be generated by the
- * Generator.
- * <p>
- * This method may be called repeatedly on a single instance of the validator.
- * Doing so will amortize type calculation costs.
- * <p>
- * Checks implemented:
- * <ul>
- * <li> <code>binaryName</code> implements ValueProxy</li>
- * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
- * annotation</li>
- * <li>All property methods in the EntityProxy can be mapped onto an
- * equivalent domain method (unless validation is skipped for the method)</li>
- * <li>All referenced proxy types are valid</li>
- * </ul>
- *
- * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
- * EntityProxy subtype
- */
- public void validateValueProxy(String binaryName) {
- validateProxy(binaryName, valueProxyIntf, false);
- }
-
- /**
- * Record the mapping of a domain type to a client type. Proxy types will be
- * added to {@link #domainToClientType}.
- */
- private void addToDomainMap(ErrorContext logger, Type domainType, Type clientType) {
- clientToDomainType.put(clientType, domainType);
-
- if (isAssignable(logger, baseProxyIntf, clientType)) {
- SortedSet<Type> list = domainToClientType.get(domainType);
- if (list == null) {
- list = new TreeSet<Type>(typeNameComparator);
- domainToClientType.put(domainType, list);
- }
- list.add(clientType);
- }
- }
-
- /**
- * Check that a given method RequestContext method declaration can be mapped
- * to the server's domain type.
- */
- private RFMethod checkClientMethodInDomain(ErrorContext logger, RFMethod method,
- Type domainServiceType, boolean requireStaticMethodsForRequestType) {
- logger = logger.setMethod(method);
-
- // Create a "translated" method declaration to search for
- // Request<BlahProxy> foo(int a, BarProxy bar) -> Blah foo(int a, Bar bar);
- Type returnType = getReturnType(logger, method);
- Method searchFor =
- createDomainMethod(logger, new Method(method.getName(), returnType, method
- .getArgumentTypes()));
-
- RFMethod found =
- findCompatibleServiceMethod(logger, domainServiceType, searchFor, !method
- .isValidationSkipped());
-
- if (found != null) {
- boolean isInstance = isAssignable(logger, instanceRequestIntf, method.getReturnType());
- if (isInstance && found.isDeclaredStatic()) {
- logger.poison("The method %s is declared to return %s, but the"
- + " service method is static", method.getName(), InstanceRequest.class
- .getCanonicalName());
- } else if (requireStaticMethodsForRequestType && !isInstance && !found.isDeclaredStatic()) {
- logger.poison("The method %s is declared to return %s, but the"
- + " service method is not static", method.getName(), Request.class.getCanonicalName());
- }
- }
- return found;
- }
-
- /**
- * Check that the domain object has <code>getId()</code> and
- * <code>getVersion</code> methods.
- */
- private void checkIdAndVersion(ErrorContext logger, Type domainType) {
- if (objectType.equals(domainType)) {
- return;
- }
- logger = logger.setType(domainType);
- String findMethodName = "find" + BinaryName.getShortClassName(domainType.getClassName());
- Type keyType = null;
- RFMethod findMethod = null;
-
- boolean foundFind = false;
- boolean foundId = false;
- boolean foundVersion = false;
- for (RFMethod method : getMethodsInHierarchy(logger, domainType)) {
- if ("getId".equals(method.getName()) && method.getArgumentTypes().length == 0) {
- foundId = true;
- keyType = method.getReturnType();
- if (!isResolvedKeyType(logger, keyType)) {
- unresolvedKeyTypes.put(domainType, keyType);
- }
- } else if ("getVersion".equals(method.getName()) && method.getArgumentTypes().length == 0) {
- foundVersion = true;
- if (!isResolvedKeyType(logger, method.getReturnType())) {
- unresolvedKeyTypes.put(domainType, method.getReturnType());
- }
- } else if (findMethodName.equals(method.getName()) && method.getArgumentTypes().length == 1) {
- foundFind = true;
- findMethod = method;
- }
- if (foundFind && foundId && foundVersion) {
- break;
- }
- }
- if (!foundId) {
- logger.poison("There is no getId() method in type %s", print(domainType));
- }
- if (!foundVersion) {
- logger.poison("There is no getVersion() method in type %s", print(domainType));
- }
-
- if (foundFind) {
- if (keyType != null && !isAssignable(logger, findMethod.getArgumentTypes()[0], keyType)) {
- logger.poison("The key type returned by %s getId()"
- + " cannot be used as the argument to %s(%s)", print(keyType), findMethod.getName(),
- print(findMethod.getArgumentTypes()[0]));
- }
- if (!findMethod.isDeclaredStatic()) {
- logger.poison("The %s method must be static", findMethodName);
- }
- } else {
- logger.poison("There is no %s method in type %s that returns %2$s", findMethodName,
- print(domainType));
- }
- }
-
- /**
- * Ensure that the given property method on an EntityProxy exists on the
- * domain object.
- */
- private void checkPropertyMethod(ErrorContext logger, RFMethod clientPropertyMethod,
- Type domainType) {
- logger = logger.setMethod(clientPropertyMethod);
-
- findCompatiblePropertyMethod(logger, domainType, createDomainMethod(logger,
- clientPropertyMethod), !clientPropertyMethod.isValidationSkipped());
- }
-
- private void checkUnresolvedKeyTypes(ErrorContext logger) {
- unresolvedKeyTypes.values().removeAll(domainToClientType.keySet());
- if (unresolvedKeyTypes.isEmpty()) {
- return;
- }
-
- for (Map.Entry<Type, Type> type : unresolvedKeyTypes.entrySet()) {
- logger.setType(type.getKey()).poison(
- "The domain type %s uses a non-simple key type (%s)"
- + " in its getId() or getVersion() method that" + " does not have a proxy mapping.",
- print(type.getKey()), print(type.getValue()));
- }
- }
-
- /**
- * Convert a method declaration using client types (e.g. FooProxy) to domain
- * types (e.g. Foo).
- */
- private Method createDomainMethod(ErrorContext logger, Method clientMethod) {
- Type[] args = clientMethod.getArgumentTypes();
- for (int i = 0, j = args.length; i < j; i++) {
- args[i] = getDomainType(logger, args[i], true);
- }
- Type returnType = getDomainType(logger, clientMethod.getReturnType(), true);
- return new Method(clientMethod.getName(), returnType, args);
- }
-
- /**
- * Common checks to quickly determine if a type needs to be checked.
- */
- private boolean fastFail(String binaryName) {
- if (!Name.isBinaryName(binaryName)) {
- parentLogger.poison("%s is not a binary name", binaryName);
- return true;
- }
-
- // Allow the poisoned flag to be reset without losing data
- if (badTypes.contains(binaryName)) {
- parentLogger.poison("Type type %s was previously marked as bad", binaryName);
- return true;
- }
-
- // Don't revalidate the same type
- if (!validatedTypes.add(binaryName)) {
- return true;
- }
- return false;
- }
-
- /**
- * Finds a compatible method declaration in <code>domainType</code>'s
- * hierarchy that is assignment-compatible with the given Method.
- */
- private RFMethod findCompatibleMethod(final ErrorContext logger, Type domainType,
- Method searchFor, boolean mustFind, boolean allowOverloads, boolean boxReturnTypes) {
- String methodName = searchFor.getName();
- Type[] clientArgs = searchFor.getArgumentTypes();
- Type clientReturnType = searchFor.getReturnType();
- if (boxReturnTypes) {
- clientReturnType = maybeBoxType(clientReturnType);
- }
- // Pull all methods out of the domain type
- Map<String, List<RFMethod>> domainLookup = new LinkedHashMap<String, List<RFMethod>>();
- for (RFMethod method : getMethodsInHierarchy(logger, domainType)) {
- List<RFMethod> list = domainLookup.get(method.getName());
- if (list == null) {
- list = new ArrayList<RFMethod>();
- domainLookup.put(method.getName(), list);
- }
- list.add(method);
- }
-
- // Find the matching method in the domain object
- List<RFMethod> methods = domainLookup.get(methodName);
- if (methods == null) {
- if (mustFind) {
- logger.poison("Could not find any methods named %s in %s", methodName, print(domainType));
- }
- return null;
- }
- if (methods.size() > 1 && !allowOverloads) {
- StringBuilder sb = new StringBuilder();
- sb.append(String.format("Method overloads found in type %s named %s:\n", print(domainType),
- methodName));
- for (RFMethod method : methods) {
- sb.append(" ").append(print(method)).append("\n");
- }
- logger.poison(sb.toString());
- return null;
- }
-
- // Check each overloaded name
- for (RFMethod domainMethod : methods) {
- Type[] domainArgs = domainMethod.getArgumentTypes();
- Type domainReturnType = domainMethod.getReturnType();
- if (boxReturnTypes) {
- /*
- * When looking for the implementation of a Request<Integer>, we want to
- * match either int or Integer, so we'll box the domain method's return
- * type.
- */
- domainReturnType = maybeBoxType(domainReturnType);
- }
-
- /*
- * Make sure the client args can be passed into the domain args and the
- * domain return type into the client return type.
- */
- if (isAssignable(logger, domainArgs, clientArgs)
- && isAssignable(logger, clientReturnType, domainReturnType)) {
-
- logger.spam("Mapped client method " + print(searchFor) + " to " + print(domainMethod));
- return domainMethod;
- }
- }
- if (mustFind) {
- logger.poison(messageCouldNotFindMethod(domainType, methods));
- }
- return null;
- }
-
- /**
- * Finds a compatible method declaration in <code>domainType</code>'s
- * hierarchy that is assignment-compatible with the given Method.
- */
- private RFMethod findCompatiblePropertyMethod(final ErrorContext logger, Type domainType,
- Method searchFor, boolean mustFind) {
- return findCompatibleMethod(logger, domainType, searchFor, mustFind, false, false);
- }
-
- /**
- * Finds a compatible method declaration in <code>domainType</code>'s
- * hierarchy that is assignment-compatible with the given Method.
- */
- private RFMethod findCompatibleServiceMethod(final ErrorContext logger, Type domainType,
- Method searchFor, boolean mustFind) {
- return findCompatibleMethod(logger, domainType, searchFor, mustFind, true, true);
- }
-
- /**
- * This looks like it should be a utility method somewhere else, but I can't
- * find it.
- */
- private Type getBoxedType(Type primitive) {
- switch (primitive.getSort()) {
- case Type.BOOLEAN:
- return Type.getType(Boolean.class);
- case Type.BYTE:
- return Type.getType(Byte.class);
- case Type.CHAR:
- return Type.getType(Character.class);
- case Type.DOUBLE:
- return Type.getType(Double.class);
- case Type.FLOAT:
- return Type.getType(Float.class);
- case Type.INT:
- return Type.getType(Integer.class);
- case Type.LONG:
- return Type.getType(Long.class);
- case Type.SHORT:
- return Type.getType(Short.class);
- case Type.VOID:
- return Type.getType(Void.class);
- }
- throw new RuntimeException(primitive.getDescriptor() + " is not a primitive type");
- }
-
- /**
- * Convert the type used in a client-side EntityProxy or RequestContext
- * declaration to the equivalent domain type. Value types and supported
- * collections are a pass-through. EntityProxy types will be resolved to their
- * domain object type. RequestContext types will be resolved to their service
- * object.
- */
- private Type getDomainType(ErrorContext logger, Type clientType, boolean requireMapping) {
- Type domainType = clientToDomainType.get(clientType);
- if (domainType != null) {
- return domainType;
- }
- if (isValueType(logger, clientType) || isCollectionType(logger, clientType)) {
- domainType = clientType;
- } else if (entityProxyIntf.equals(clientType) || valueProxyIntf.equals(clientType)) {
- domainType = objectType;
- } else {
- logger = logger.setType(clientType);
- DomainMapper pv = new DomainMapper(logger);
- visit(logger, clientType.getInternalName(), pv);
- if (pv.getDomainInternalName() == null) {
- if (requireMapping) {
- logger.poison("%s has no mapping to a domain type (e.g. @%s or @%s)", print(clientType),
- ProxyFor.class.getSimpleName(), Service.class.getSimpleName());
- }
- domainType = errorType;
- } else {
- domainType = Type.getObjectType(pv.getDomainInternalName());
- }
- if (pv.getLocatorInternalName() != null) {
- Type locatorType = Type.getObjectType(pv.getLocatorInternalName());
- clientToLocatorMap.put(clientType, locatorType);
- }
- }
- addToDomainMap(logger, domainType, clientType);
- if (domainType != errorType) {
- maybeCheckProxyType(logger, clientType);
- }
- return domainType;
- }
-
- /**
- * Collect all of the methods defined within a type hierarchy.
- */
- private Set<RFMethod> getMethodsInHierarchy(ErrorContext logger, Type domainType) {
- Set<RFMethod> toReturn = methodsInHierarchy.get(domainType);
- if (toReturn == null) {
- logger = logger.setType(domainType);
- toReturn = new MethodsInHierarchyCollector(logger).exec(domainType.getInternalName());
- methodsInHierarchy.put(domainType, Collections.unmodifiableSet(toReturn));
- }
- return toReturn;
- }
-
- /**
- * Examines a generic RequestContext method declaration and determines the
- * expected domain return type. This implementation is limited in that it will
- * not attempt to resolve type bounds since that would essentially require
- * implementing TypeOracle. In the case where the type bound cannot be
- * resolved, this method will return Object's type.
- */
- private Type getReturnType(ErrorContext logger, RFMethod method) {
- logger = logger.setMethod(method);
- final String[] returnType = {objectType.getInternalName()};
- String signature = method.getSignature();
-
- final int expectedCount;
- if (method.getReturnType().equals(instanceRequestIntf)) {
- expectedCount = 2;
- } else if (method.getReturnType().equals(requestIntf)) {
- expectedCount = 1;
- } else {
- logger.spam("Punting on " + signature);
- return Type.getObjectType(returnType[0]);
- }
-
- // TODO(bobv): If a class-based TypeOracle is built, use that instead
- new SignatureReader(signature).accept(new SignatureAdapter() {
- @Override
- public SignatureVisitor visitReturnType() {
- return new SignatureAdapter() {
- int count;
-
- @Override
- public SignatureVisitor visitTypeArgument(char wildcard) {
- if (++count == expectedCount) {
- return new SignatureAdapter() {
- @Override
- public void visitClassType(String name) {
- returnType[0] = name;
- }
- };
- }
- return super.visitTypeArgument(wildcard);
- }
- };
- }
- });
-
- logger.spam("Extracted " + returnType[0]);
- return Type.getObjectType(returnType[0]);
- }
-
- private List<Type> getSupertypes(ErrorContext logger, Type type) {
- if (type.getSort() != Type.OBJECT) {
- return Collections.emptyList();
- }
- List<Type> toReturn = supertypes.get(type);
- if (toReturn != null) {
- return toReturn;
- }
-
- logger = logger.setType(type);
-
- toReturn = new SupertypeCollector(logger).exec(type);
- supertypes.put(type, Collections.unmodifiableList(toReturn));
- return toReturn;
- }
-
- private boolean isAssignable(ErrorContext logger, Type possibleSupertype, Type possibleSubtype) {
- // Fast-path for same type
- if (possibleSupertype.equals(possibleSubtype)) {
- return true;
- }
-
- // Supertype calculation is cached
- List<Type> allSupertypes = getSupertypes(logger, possibleSubtype);
- return allSupertypes.contains(possibleSupertype);
- }
-
- private boolean isAssignable(ErrorContext logger, Type[] possibleSupertypes,
- Type[] possibleSubtypes) {
- // Check the same number of types
- if (possibleSupertypes.length != possibleSubtypes.length) {
- return false;
- }
- for (int i = 0, j = possibleSupertypes.length; i < j; i++) {
- if (!isAssignable(logger, possibleSupertypes[i], possibleSubtypes[i])) {
- return false;
- }
- }
- return true;
- }
-
- private boolean isCollectionType(@SuppressWarnings("unused") ErrorContext logger, Type type) {
- // keeping the logger arg just for internal consistency for our small minds
- return "java/util/List".equals(type.getInternalName())
- || "java/util/Set".equals(type.getInternalName());
- }
-
- /**
- * Keep in sync with {@code ReflectiveServiceLayer.isKeyType()}.
- */
- private boolean isResolvedKeyType(ErrorContext logger, Type type) {
- if (isValueType(logger, type)) {
- return true;
- }
-
- // We have already seen a mapping for the key type
- if (domainToClientType.containsKey(type)) {
- return true;
- }
-
- return false;
- }
-
- private boolean isValueType(ErrorContext logger, Type type) {
- if (type.getSort() != Type.OBJECT) {
- return true;
- }
- if (valueTypes.contains(type)) {
- return true;
- }
- logger = logger.setType(type);
- if (isAssignable(logger, enumType, type)) {
- return true;
- }
- return false;
- }
-
- private Type maybeBoxType(Type maybePrimitive) {
- if (maybePrimitive.getSort() == Type.OBJECT) {
- return maybePrimitive;
- }
- return getBoxedType(maybePrimitive);
- }
-
- /**
- * Examines a type for an {@link ExtraTypes} annotation and processes the
- * referred types.
- */
- private void maybeCheckExtraTypes(ErrorContext logger, Type type) {
- maybeCheckProxyType(logger, new ExtraTypesCollector(logger.setType(type)).exec(type));
- }
-
- /**
- * Examine an array of Types and call {@link #validateEntityProxy(String)} or
- * {@link #validateValueProxy(String)} if the type is a proxy.
- */
- private void maybeCheckProxyType(ErrorContext logger, Type... types) {
- for (Type type : types) {
- if (isAssignable(logger, entityProxyIntf, type)) {
- validateEntityProxy(type.getClassName());
- } else if (isAssignable(logger, valueProxyIntf, type)) {
- validateValueProxy(type.getClassName());
- } else if (isAssignable(logger, baseProxyIntf, type)) {
- logger.poison(
- "Invalid type hierarchy for %s. Only types derived from %s or %s may be used.",
- print(type), print(entityProxyIntf), print(valueProxyIntf));
- }
- }
- }
-
- /**
- * Examine the arguments and return value of a method and check any
- * EntityProxies referred.
- */
- private void maybeCheckReferredProxies(ErrorContext logger, RFMethod method) {
- if (method.getSignature() != null) {
- TypesInSignatureCollector collector = new TypesInSignatureCollector();
- SignatureReader reader = new SignatureReader(method.getSignature());
- reader.accept(collector);
- maybeCheckProxyType(logger, collector.getFound());
- } else {
- Type[] argTypes = method.getArgumentTypes();
- Type returnType = getReturnType(logger, method);
-
- // Check EntityProxy args ond return types against the domain
- maybeCheckProxyType(logger, argTypes);
- maybeCheckProxyType(logger, returnType);
- }
- }
-
- /**
- * Returns {@code true} if the type is assignable to EntityProxy or ValueProxy
- * and has a mapping to a domain type.
- *
- * @see com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModel#shouldAttemptProxyValidation()
- */
- private boolean shouldAttemptProxyValidation(ErrorContext logger, Type type) {
- logger = logger.setType(type);
- if (!isAssignable(logger, entityProxyIntf, type) && !isAssignable(logger, valueProxyIntf, type)) {
- return false;
- }
- if (getDomainType(logger, type, false) == errorType) {
- return false;
- }
- return true;
- }
-
- private void validateProxy(String binaryName, Type expectedType, boolean requireId) {
- if (fastFail(binaryName)) {
- return;
- }
-
- Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
- typeTokens.put(OperationKey.hash(binaryName), proxyType);
- ErrorContext logger = parentLogger.setType(proxyType);
-
- // Quick sanity check for calling code
- if (!isAssignable(logger, expectedType, proxyType)) {
- parentLogger.poison("%s is not a %s", print(proxyType), print(expectedType));
- return;
- }
-
- // Check supertypes first
- for (Type supertype : getSupertypes(logger, proxyType)) {
- if (shouldAttemptProxyValidation(logger, supertype)) {
- maybeCheckProxyType(logger, supertype);
- }
- }
-
- // Find the domain type
- Type domainType = getDomainType(logger, proxyType, false);
- if (domainType == errorType) {
- logger.poison("The type %s must be annotated with a @%s or @%s annotation", BinaryName
- .toSourceName(binaryName), ProxyFor.class.getSimpleName(), ProxyForName.class
- .getSimpleName());
- return;
- }
-
- // Check for getId() and getVersion() in domain if no locator is specified
- if (requireId) {
- Type locatorType = clientToLocatorMap.get(proxyType);
- if (locatorType == null) {
- checkIdAndVersion(logger, domainType);
- }
- }
-
- // Collect all methods in the client proxy type
- Set<RFMethod> clientPropertyMethods = getMethodsInHierarchy(logger, proxyType);
-
- // Find the equivalent domain getter/setter method
- for (RFMethod clientPropertyMethod : clientPropertyMethods) {
- // Ignore stableId(). Can't use descriptor due to overrides
- if ("stableId".equals(clientPropertyMethod.getName())
- && clientPropertyMethod.getArgumentTypes().length == 0) {
- continue;
- }
- checkPropertyMethod(logger, clientPropertyMethod, domainType);
- maybeCheckReferredProxies(logger, clientPropertyMethod);
- }
- maybeCheckExtraTypes(logger, proxyType);
- }
-
- /**
- * Load the classfile for the given binary name and apply the provided
- * visitor.
- *
- * @return <code>true</code> if the visitor was successfully visited
- */
- private boolean visit(ErrorContext logger, String internalName, ClassVisitor visitor) {
- assert Name.isInternalName(internalName) : "internalName";
- logger.spam("Visiting " + internalName);
- InputStream inputStream = null;
- try {
- inputStream = loader.getResourceAsStream(internalName + ".class");
- if (inputStream == null) {
- logger.poison("Could not find class file for " + internalName);
- return false;
- }
- ClassReader reader = new ClassReader(inputStream);
- reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG
- | ClassReader.SKIP_FRAMES);
- return true;
- } catch (IOException e) {
- logger.poison("Unable to open " + internalName, e);
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException ignored) {
- }
- }
- }
- return false;
- }
-}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
index c4c82c2..4289ce5 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryJarExtractor.java
@@ -29,17 +29,18 @@
import com.google.gwt.dev.asm.Type;
import com.google.gwt.dev.asm.commons.Method;
import com.google.gwt.dev.util.Name;
+import com.google.gwt.dev.util.Name.SourceOrBinaryName;
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.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
-import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ErrorContext;
-import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.Loader;
+import com.google.web.bindery.requestfactory.apt.RfValidator;
+import com.google.web.bindery.requestfactory.apt.ValidationTool;
+import com.google.web.bindery.requestfactory.gwt.client.RequestBatcher;
import com.google.web.bindery.requestfactory.shared.BaseProxy;
import com.google.web.bindery.requestfactory.shared.DefaultProxyStore;
import com.google.web.bindery.requestfactory.shared.EntityProxy;
import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
import com.google.web.bindery.requestfactory.shared.EntityProxyId;
+import com.google.web.bindery.requestfactory.shared.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;
@@ -63,11 +64,9 @@
import com.google.web.bindery.requestfactory.shared.ValueProxy;
import com.google.web.bindery.requestfactory.shared.WriteOperation;
import com.google.web.bindery.requestfactory.vm.RequestFactorySource;
-import com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver;
import com.google.web.bindery.requestfactory.vm.testing.UrlRequestTransport;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -92,6 +91,7 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
+import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
@@ -109,6 +109,26 @@
*/
/**
+ * An implementation of {@link Loader} that uses a {@link ClassLoader} to
+ * retrieve the class files.
+ */
+ public static class ClassLoaderLoader implements Loader {
+ private final ClassLoader loader;
+
+ public ClassLoaderLoader(ClassLoader loader) {
+ this.loader = loader;
+ }
+
+ public boolean exists(String resource) {
+ return loader.getResource(resource) != null;
+ }
+
+ public InputStream getResourceAsStream(String resource) {
+ return loader.getResourceAsStream(resource);
+ }
+ }
+
+ /**
* Describes a way to emit the contents of a classpath, typically into a JAR
* or filesystem directory.
*/
@@ -155,6 +175,28 @@
}
/**
+ * Abstracts the mechanism by which class files are loaded.
+ *
+ * @see ClassLoaderLoader
+ */
+ public interface Loader {
+ /**
+ * Returns true if the specified resource can be loaded.
+ *
+ * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
+ */
+ boolean exists(String resource);
+
+ /**
+ * Returns an InputStream to access the specified resource, or
+ * <code>null</code> if no such resource exists.
+ *
+ * @param resource a resource name (e.g. <code>com/example/Foo.class</code>)
+ */
+ InputStream getResourceAsStream(String resource);
+ }
+
+ /**
* Controls what is emitted by the tool.
*/
public enum Mode {
@@ -206,6 +248,104 @@
protected abstract boolean matches(String target);
}
+ /**
+ * Improves error messages by providing context for the user.
+ * <p>
+ * Visible for testing.
+ */
+ static class ErrorContext {
+ private static String print(Method method) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(print(method.getReturnType())).append(" ").append(method.getName()).append("(");
+ for (Type t : method.getArgumentTypes()) {
+ sb.append(print(t)).append(" ");
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ private static String print(Type type) {
+ return SourceOrBinaryName.toSourceName(type.getClassName());
+ }
+
+ private final Logger logger;
+ private final ErrorContext parent;
+
+ private Type currentType;
+
+ private Method currentMethod;
+
+ public ErrorContext(Logger logger) {
+ this.logger = logger;
+ this.parent = null;
+ }
+
+ protected ErrorContext(ErrorContext parent) {
+ this.logger = parent.logger;
+ this.parent = parent;
+ }
+
+ public void poison(String msg, Object... args) {
+ poison();
+ logger.logp(Level.SEVERE, currentType(), currentMethod(), String.format(msg, args));
+ }
+
+ public void poison(String msg, Throwable t) {
+ poison();
+ logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t);
+ }
+
+ public ErrorContext setMethod(Method method) {
+ ErrorContext toReturn = fork();
+ toReturn.currentMethod = method;
+ return toReturn;
+ }
+
+ public ErrorContext setType(Type type) {
+ ErrorContext toReturn = fork();
+ toReturn.currentType = type;
+ return toReturn;
+ }
+
+ public void spam(String msg, Object... args) {
+ logger.logp(Level.FINEST, currentType(), currentMethod(), String.format(msg, args));
+ }
+
+ protected ErrorContext fork() {
+ return new ErrorContext(this);
+ }
+
+ private String currentMethod() {
+ if (currentMethod != null) {
+ return print(currentMethod);
+ }
+ if (parent != null) {
+ return parent.currentMethod();
+ }
+ return null;
+ }
+
+ private String currentType() {
+ if (currentType != null) {
+ return print(currentType);
+ }
+ if (parent != null) {
+ return parent.currentType();
+ }
+ return null;
+ }
+
+ /**
+ * Populate {@link RequestFactoryInterfaceValidator#badTypes} with the
+ * current context.
+ */
+ private void poison() {
+ if (parent != null) {
+ parent.poison();
+ }
+ }
+ }
+
private class AnnotationProcessor implements AnnotationVisitor {
private final String sourceType;
private final AnnotationVisitor av;
@@ -631,11 +771,12 @@
@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,
+ 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, RequestBatcher.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};
@@ -645,18 +786,20 @@
private static final int MAX_THREADS = 4;
static {
+ List<Class<?>> aptClasses =
+ Collections.unmodifiableList(Arrays.<Class<?>> asList(RfValidator.class,
+ ValidationTool.class));
List<Class<?>> sharedClasses = Arrays.<Class<?>> asList(SHARED_CLASSES);
List<Class<?>> clientClasses = new ArrayList<Class<?>>();
clientClasses.addAll(sharedClasses);
- clientClasses.add(RfApt.class);
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));
@@ -676,9 +819,12 @@
* lookup, since the gwt-user code is compiled separately from its tests.
*/
try {
- SEEDS.put("test" + CODE_AND_SOURCE, Collections.unmodifiableList(Arrays.<Class<?>> asList(
- Class.forName("com.google.web.bindery.requestfactory.vm.RequestFactoryJreSuite"), Class
- .forName("com.google.web.bindery.requestfactory.server.SimpleBar"))));
+ List<Class<?>> testClasses =
+ Collections.unmodifiableList(Arrays.<Class<?>> asList(Class
+ .forName("com.google.web.bindery.requestfactory.vm.RequestFactoryJreSuite"), Class
+ .forName("com.google.web.bindery.requestfactory.server.SimpleBar")));
+ SEEDS.put("test", testClasses);
+ SEEDS.put("test" + SOURCE_ONLY, testClasses);
} catch (ClassNotFoundException ignored) {
}
}
@@ -699,11 +845,12 @@
System.err.println("Unknown target: " + target);
System.exit(1);
}
- Map<String, byte[]> resources = createResources(target, seeds);
+ Map<String, byte[]> resources = createResources(seeds);
Mode mode = Mode.match(target);
Logger errorContext = Logger.getLogger(RequestFactoryJarExtractor.class.getName());
- ClassLoaderLoader classLoader =
- new ClassLoaderLoader(Thread.currentThread().getContextClassLoader());
+ RequestFactoryJarExtractor.ClassLoaderLoader classLoader =
+ new RequestFactoryJarExtractor.ClassLoaderLoader(Thread.currentThread()
+ .getContextClassLoader());
JarEmitter jarEmitter = new JarEmitter(new File(args[1]));
RequestFactoryJarExtractor extractor =
new RequestFactoryJarExtractor(errorContext, classLoader, jarEmitter, seeds, resources,
@@ -712,20 +859,14 @@
System.exit(extractor.isExecutionFailed() ? 1 : 0);
}
- private static Map<String, byte[]> createResources(String target, List<Class<?>> seeds)
+ private static Map<String, byte[]> createResources(List<Class<?>> seeds)
throws UnsupportedEncodingException, IOException {
Map<String, byte[]> resources;
- if (seeds.contains(RfApt.class)) {
+ if (seeds.contains(RfValidator.class)) {
// Add the annotation processor manifest
resources =
Collections.singletonMap("META-INF/services/" + Processor.class.getCanonicalName(),
- RfApt.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();
- TypeTokenResolver resolver = TypeTokenResolver.loadFromClasspath();
- resolver.store(out);
- resources = Collections.singletonMap(TypeTokenResolver.TOKEN_MANIFEST, out.toByteArray());
+ RfValidator.class.getCanonicalName().getBytes("UTF-8"));
} else {
resources = Collections.emptyMap();
}
@@ -746,8 +887,8 @@
*
* @return <code>true</code> if the visitor was successfully visited
*/
- private static boolean visit(ErrorContext logger, Loader loader, String internalName,
- ClassVisitor visitor) {
+ private static boolean visit(RequestFactoryJarExtractor.ErrorContext logger,
+ RequestFactoryJarExtractor.Loader loader, String internalName, ClassVisitor visitor) {
assert Name.isInternalName(internalName) : "internalName";
logger.spam("Visiting " + internalName);
InputStream inputStream = null;
@@ -778,8 +919,8 @@
private final Emitter emitter;
private final ExecutorService ex;
private final BlockingQueue<Future<?>> inProcess = new LinkedBlockingQueue<Future<?>>();
- private final ErrorContext logger;
- private final Loader loader;
+ private final RequestFactoryJarExtractor.ErrorContext logger;
+ private final RequestFactoryJarExtractor.Loader loader;
private final Mode mode;
private final Map<String, byte[]> resources;
private final List<Class<?>> seeds;
@@ -787,9 +928,9 @@
private final Set<String> sources = new ConcurrentSkipListSet<String>();
private final ExecutorService writerService;
- public RequestFactoryJarExtractor(Logger logger, Loader loader, Emitter emitter,
- List<Class<?>> seeds, Map<String, byte[]> resources, Mode mode) {
- this.logger = new ErrorContext(logger);
+ public RequestFactoryJarExtractor(Logger logger, RequestFactoryJarExtractor.Loader loader,
+ Emitter emitter, List<Class<?>> seeds, Map<String, byte[]> resources, Mode mode) {
+ this.logger = new RequestFactoryJarExtractor.ErrorContext(logger);
this.loader = loader;
this.emitter = emitter;
this.resources = resources;
diff --git a/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
index e62d8b9..1c602bd 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
+++ b/user/src/com/google/web/bindery/requestfactory/server/ResolverServiceLayer.java
@@ -24,12 +24,12 @@
import com.google.web.bindery.requestfactory.shared.RequestFactory;
import com.google.web.bindery.requestfactory.shared.Service;
import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.vm.impl.Deobfuscator;
import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
-import java.util.logging.Logger;
/**
* Implements all of the resolution methods in ServiceLayer.
@@ -37,24 +37,14 @@
final class ResolverServiceLayer extends ServiceLayerDecorator {
private static Deobfuscator deobfuscator;
- private static final Logger log = Logger.getLogger(ServiceLayer.class.getName());
- private static synchronized boolean updateDeobfuscator(ClassLoader classLoader, String binaryName) {
- RequestFactoryInterfaceValidator validator =
- new RequestFactoryInterfaceValidator(log,
- new RequestFactoryInterfaceValidator.ClassLoaderLoader(classLoader));
- validator.antidote();
- validator.validateRequestFactory(binaryName);
- if (validator.isPoisoned()) {
- return false;
+ private static synchronized void updateDeobfuscator(Class<? extends RequestFactory> clazz,
+ ClassLoader resolveClassesWith) {
+ Deobfuscator.Builder builder = Deobfuscator.Builder.load(clazz, resolveClassesWith);
+ if (deobfuscator != null) {
+ builder.merge(deobfuscator);
}
- if (deobfuscator == null) {
- deobfuscator = validator.getDeobfuscator();
- } else {
- deobfuscator =
- new Deobfuscator.Builder().merge(deobfuscator).merge(validator.getDeobfuscator()).build();
- }
- return true;
+ deobfuscator = builder.build();
}
@Override
@@ -182,10 +172,9 @@
@Override
public Class<? extends RequestFactory> resolveRequestFactory(String binaryName) {
- if (!updateDeobfuscator(getTop().getDomainClassLoader(), binaryName)) {
- die(null, "The RequestFactory %s did not pass validation", binaryName);
- }
- return forName(binaryName).asSubclass(RequestFactory.class);
+ Class<? extends RequestFactory> toReturn = forName(binaryName).asSubclass(RequestFactory.class);
+ updateDeobfuscator(toReturn, getTop().getDomainClassLoader());
+ return toReturn;
}
@Override
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
index c2b69c1..3bc051d 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/SkipInterfaceValidation.java
@@ -30,6 +30,6 @@
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.METHOD, ElementType.TYPE})
+@Target({ElementType.METHOD, ElementType.PACKAGE, ElementType.TYPE})
public @interface SkipInterfaceValidation {
}
diff --git a/user/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/InProcessRequestContext.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
index 6dcedca..86390c8 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
@@ -30,8 +30,8 @@
import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
import com.google.web.bindery.requestfactory.shared.impl.RequestData;
import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId;
+import com.google.web.bindery.requestfactory.vm.impl.Deobfuscator;
import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
-import com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
@@ -184,14 +184,14 @@
static final Object[] NO_ARGS = new Object[0];
private final Class<? extends RequestContext> context;
+ private final Deobfuscator deobfuscator;
private final Dialect dialect;
- private final TypeTokenResolver tokenResolver;
protected InProcessRequestContext(InProcessRequestFactory factory, Dialect dialect,
Class<? extends RequestContext> context) {
super(factory, dialect);
this.context = context;
- this.tokenResolver = factory.getTypeTokenResolver();
+ this.deobfuscator = factory.getDeobfuscator();
this.dialect = dialect;
}
@@ -205,7 +205,7 @@
@Override
protected <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz, SimpleProxyId<T> id,
boolean useAppendedContexts) {
- if (tokenResolver.isReferencedType(clazz.getName())) {
+ if (deobfuscator.isReferencedType(clazz.getName())) {
return super.createProxy(clazz, id, useAppendedContexts);
}
throw new IllegalArgumentException("Unknown proxy type " + clazz.getName());
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
index fc3802b..05b3f03 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestFactory.java
@@ -31,10 +31,9 @@
import com.google.web.bindery.requestfactory.shared.impl.EntityProxyCategory;
import com.google.web.bindery.requestfactory.shared.impl.ValueProxyCategory;
import com.google.web.bindery.requestfactory.vm.InProcessRequestContext.RequestContextHandler;
+import com.google.web.bindery.requestfactory.vm.impl.Deobfuscator;
import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
-import com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver;
-import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -74,15 +73,17 @@
}
private final Class<? extends RequestFactory> requestFactoryInterface;
- private final TypeTokenResolver deobfuscator;
+ private final Deobfuscator deobfuscator;
public InProcessRequestFactory(Class<? extends RequestFactory> requestFactoryInterface) {
this.requestFactoryInterface = requestFactoryInterface;
- try {
- deobfuscator = TypeTokenResolver.loadFromClasspath();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ deobfuscator =
+ Deobfuscator.Builder.load(requestFactoryInterface,
+ Thread.currentThread().getContextClassLoader()).build();
+ }
+
+ public Deobfuscator getDeobfuscator() {
+ return deobfuscator;
}
@Override
@@ -131,8 +132,4 @@
protected String getTypeToken(Class<? extends BaseProxy> clazz) {
return isEntityType(clazz) || isValueType(clazz) ? OperationKey.hash(clazz.getName()) : null;
}
-
- TypeTokenResolver getTypeTokenResolver() {
- return deobfuscator;
- }
}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/impl/Deobfuscator.java b/user/src/com/google/web/bindery/requestfactory/vm/impl/Deobfuscator.java
new file mode 100644
index 0000000..5c16de1
--- /dev/null
+++ b/user/src/com/google/web/bindery/requestfactory/vm/impl/Deobfuscator.java
@@ -0,0 +1,188 @@
+/*
+ * 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.vm.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides access to payload deobfuscation services for server and JVM-based
+ * clients. The deobfuscation data is baked into GWT-based clients by the
+ * generator.
+ */
+public class Deobfuscator {
+ /**
+ * Creates Deobfuscators.
+ */
+ public static class Builder {
+ /**
+ * Load a pre-computed Builder from the classpath. The builder
+ * implementation is expected to have been generated by the annotation
+ * processor as part of the build process.
+ *
+ * @see com.google.web.bindery.requestfactory.apt.DeobfuscatorBuilder
+ * @see com.google.web.bindery.requestfactory.server.ResolverServiceLayer
+ */
+ public static Builder load(Class<?> clazz, ClassLoader resolveClassesWith) {
+ Throwable ex;
+ try {
+ Class<?> found;
+ try {
+ // Used by the server
+ found = Class.forName(clazz.getName() + GENERATED_SUFFIX, false, resolveClassesWith);
+ } catch (ClassNotFoundException ignored) {
+ // Used by JRE-only clients
+ found = Class.forName(clazz.getName() + GENERATED_SUFFIX_LITE, false, resolveClassesWith);
+ }
+ Class<? extends Builder> builderClass = found.asSubclass(Builder.class);
+ Builder builder = builderClass.newInstance();
+ return builder;
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("The RequestFactory ValidationTool must be run for the "
+ + clazz.getCanonicalName() + " RequestFactory type");
+ } catch (InstantiationException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ }
+ throw new RuntimeException(ex);
+ }
+
+ private Deobfuscator d = new Deobfuscator();
+
+ {
+ d.domainToClientType = new HashMap<String, List<String>>();
+ d.operationData = new HashMap<OperationKey, OperationData>();
+ d.typeTokens = new HashMap<String, String>();
+ }
+
+ public Deobfuscator build() {
+ Deobfuscator toReturn = d;
+ toReturn.domainToClientType = Collections.unmodifiableMap(toReturn.domainToClientType);
+ toReturn.operationData = Collections.unmodifiableMap(toReturn.operationData);
+ toReturn.referencedTypes =
+ Collections.unmodifiableSet(new HashSet<String>(toReturn.typeTokens.values()));
+ toReturn.typeTokens = Collections.unmodifiableMap(toReturn.typeTokens);
+ d = null;
+ return toReturn;
+ }
+
+ public Builder merge(Deobfuscator existing) {
+ d.domainToClientType.putAll(existing.domainToClientType);
+ d.operationData.putAll(existing.operationData);
+ // referencedTypes recomputed in build()
+ d.typeTokens.putAll(existing.typeTokens);
+ return this;
+ }
+
+ public Builder withClientToDomainMappings(String domainBinaryName, List<String> value) {
+ List<String> clientBinaryNames;
+ switch (value.size()) {
+ case 0:
+ clientBinaryNames = Collections.emptyList();
+ break;
+ case 1:
+ clientBinaryNames = Collections.singletonList(value.get(0));
+ break;
+ default:
+ clientBinaryNames = Collections.unmodifiableList(new ArrayList<String>(value));
+ }
+ d.domainToClientType.put(domainBinaryName, clientBinaryNames);
+ return this;
+ }
+
+ public Builder withOperation(OperationKey key, OperationData data) {
+ d.operationData.put(key, data);
+ return this;
+ }
+
+ public Builder withRawTypeToken(String token, String binaryName) {
+ d.typeTokens.put(token, binaryName);
+ return this;
+ }
+ }
+
+ private static final String GENERATED_SUFFIX = "DeobfuscatorBuilder";
+ private static final String GENERATED_SUFFIX_LITE = GENERATED_SUFFIX + "Lite";
+
+ /**
+ * Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy,
+ * FooBProxy).
+ */
+ private Map<String, List<String>> domainToClientType;
+ private Map<OperationKey, OperationData> operationData;
+ private Set<String> referencedTypes;
+ /**
+ * Map of obfuscated ids to binary class names.
+ */
+ private Map<String, String> typeTokens;
+
+ Deobfuscator() {
+ }
+
+ /**
+ * Returns the client proxy types whose {@code @ProxyFor} is exactly
+ * {@code binaryTypeName}. Ordered such that the most-derived types will be
+ * iterated over first.
+ */
+ public List<String> getClientProxies(String binaryTypeName) {
+ return domainToClientType.get(binaryTypeName);
+ }
+
+ /**
+ * Returns a method descriptor that should be invoked on the service object.
+ */
+ public String getDomainMethodDescriptor(String operation) {
+ OperationData data = getData(operation);
+ return data == null ? null : data.getDomainMethodDescriptor();
+ }
+
+ public String getRequestContext(String operation) {
+ OperationData data = getData(operation);
+ return data == null ? null : data.getRequestContext();
+ }
+
+ public String getRequestContextMethodDescriptor(String operation) {
+ OperationData data = getData(operation);
+ return data == null ? null : data.getClientMethodDescriptor();
+ }
+
+ public String getRequestContextMethodName(String operation) {
+ OperationData data = getData(operation);
+ return data == null ? null : data.getMethodName();
+ }
+
+ /**
+ * Returns a type's binary name based on an obfuscated token.
+ */
+ public String getTypeFromToken(String token) {
+ return typeTokens.get(token);
+ }
+
+ public boolean isReferencedType(String name) {
+ return referencedTypes.contains(name);
+ }
+
+ private OperationData getData(String operation) {
+ OperationData data = operationData.get(new OperationKey(operation));
+ return data;
+ }
+}
diff --git a/user/src/com/google/web/bindery/requestfactory/server/OperationData.java b/user/src/com/google/web/bindery/requestfactory/vm/impl/OperationData.java
similarity index 87%
rename from user/src/com/google/web/bindery/requestfactory/server/OperationData.java
rename to user/src/com/google/web/bindery/requestfactory/vm/impl/OperationData.java
index 09a2aa6..04225bf 100644
--- a/user/src/com/google/web/bindery/requestfactory/server/OperationData.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/impl/OperationData.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.web.bindery.requestfactory.server;
+package com.google.web.bindery.requestfactory.vm.impl;
import com.google.gwt.dev.asm.Type;
import com.google.gwt.dev.asm.commons.Method;
@@ -21,7 +21,7 @@
/**
* Describes operations that the client may ask the server to perform.
*/
-class OperationData {
+public class OperationData {
/**
* Creates {@link OperationData} instances.
*/
@@ -49,22 +49,22 @@
return toReturn;
}
- public Builder setClientMethodDescriptor(String clientMethodDescriptor) {
+ public Builder withClientMethodDescriptor(String clientMethodDescriptor) {
d.clientMethodDescriptor = clientMethodDescriptor;
return this;
}
- public Builder setDomainMethodDescriptor(String domainMethodDescriptor) {
+ public Builder withDomainMethodDescriptor(String domainMethodDescriptor) {
d.domainMethodDescriptor = domainMethodDescriptor;
return this;
}
- public Builder setMethodName(String methodName) {
+ public Builder withMethodName(String methodName) {
d.methodName = methodName;
return this;
}
- public Builder setRequestContext(String requestContext) {
+ public Builder withRequestContext(String requestContext) {
d.requestContextBinaryName = requestContext;
return this;
}
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
deleted file mode 100644
index 5699c9a..0000000
--- a/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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.vm.impl;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URL;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-/**
- * Resolves payload type tokens to binary class names.
- */
-public class TypeTokenResolver {
- /**
- * Constructs {@link TypeTokenResolver} instances.
- */
- public static class Builder {
- private TypeTokenResolver d = new TypeTokenResolver();
-
- /**
- * To be removed as well.
- */
- public Builder addTypeToken(String token, String binaryName) {
- d.typeTokens.put(token, binaryName);
- return this;
- }
-
- public TypeTokenResolver build() {
- TypeTokenResolver toReturn = d;
- toReturn.typeTokens = Collections.unmodifiableMap(toReturn.typeTokens);
- toReturn.referencedTypes =
- Collections.unmodifiableSet(new HashSet<String>(toReturn.typeTokens.values()));
- d = null;
- return toReturn;
- }
-
- /**
- * Adds data from a single InputStream to the state accumulated by the
- * Builder.
- */
- public void load(InputStream in) throws IOException {
- Properties props = new Properties();
- props.load(in);
- for (Map.Entry<Object, Object> entry : props.entrySet()) {
- addTypeToken(entry.getKey().toString(), entry.getValue().toString());
- }
- in.close();
- }
- }
-
- public static final String TOKEN_MANIFEST = "META-INF/requestFactory/typeTokens";
-
- /**
- * Loads all resources on path {@value #TOKEN_MANIFEST} and creates a
- * TypeTokenResolver.
- */
- public static TypeTokenResolver loadFromClasspath() throws IOException {
- Builder builder;
- boolean mustLoad = true;
- try {
- // Look for a pre-cooked Builder type
- Class<?> maybeBuilderImpl =
- Class.forName(TypeTokenResolver.class.getName() + "BuilderImpl", false, Thread
- .currentThread().getContextClassLoader());
- builder = maybeBuilderImpl.asSubclass(Builder.class).newInstance();
- mustLoad = false;
- } catch (ClassNotFoundException ignored) {
- // Try manifest-based approach
- builder = new Builder();
- } catch (InstantiationException e) {
- throw new RuntimeException("Could not instantiate TypeTokenResolverImpl", e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException("Could not instantiate TypeTokenResolverImpl", e);
- }
- Enumeration<URL> locations =
- Thread.currentThread().getContextClassLoader().getResources(TOKEN_MANIFEST);
- if (mustLoad && !locations.hasMoreElements()) {
- throw new RuntimeException("No token manifest found. Did the RequestFactory annotation"
- + " processor run? Check classpath for " + TOKEN_MANIFEST + " file and ensure that"
- + " your proxy types are compiled with the requestfactory-apt.jar on javac's classpath.");
- }
- while (locations.hasMoreElements()) {
- builder.load(locations.nextElement().openStream());
- }
- return builder.build();
- }
-
- /**
- * The values of {@link #typeTokens}.
- */
- private Set<String> referencedTypes;
-
- /**
- * Map of obfuscated ids to binary class names.
- */
- private Map<String, String> typeTokens = new HashMap<String, String>();
-
- public SortedMap<String, String> getAllTypeTokens() {
- return new TreeMap<String, String>(typeTokens);
- }
-
- public String getTypeFromToken(String typeToken) {
- return typeTokens.get(typeToken);
- }
-
- public boolean isReferencedType(String binaryName) {
- return referencedTypes.contains(binaryName);
- }
-
- /**
- * Closes the OutputStream.
- */
- public void store(OutputStream out) throws IOException {
- Properties props = new Properties();
- props.putAll(typeTokens);
- props.store(out, "GENERATED FILE -- DO NOT EDIT");
- out.close();
- }
-}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/DiagnosticComparator.java b/user/test/com/google/web/bindery/requestfactory/apt/DiagnosticComparator.java
new file mode 100644
index 0000000..80b1938
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/DiagnosticComparator.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import java.util.Comparator;
+
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+/**
+ * Orders Diagnostic objects by filename, position, and message.
+ */
+class DiagnosticComparator implements Comparator<Diagnostic<? extends JavaFileObject>> {
+ @Override
+ public int compare(Diagnostic<? extends JavaFileObject> o1,
+ Diagnostic<? extends JavaFileObject> o2) {
+ int c;
+ if (o1.getSource() != null && o2.getSource() != null) {
+ c = o1.getSource().toUri().toString().compareTo(o2.getSource().toUri().toString());
+ if (c != 0) {
+ return c;
+ }
+ }
+ long p = o1.getPosition() - o2.getPosition();
+ if (p != 0) {
+ return Long.signum(p);
+ }
+ return o1.getMessage(null).compareTo(o2.getMessage(null));
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyCheckDomainMapping.java b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyCheckDomainMapping.java
new file mode 100644
index 0000000..6a1ee98
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyCheckDomainMapping.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+
+@Expected({
+ @Expect(method = "domainGetIdStatic"),
+ @Expect(method = "domainGetVersionStatic"),
+ @Expect(method = "domainFindNotStatic", args = "Domain"),
+ @Expect(method = "domainMethodWrongModifier", args = {"false", "getFoo"}),
+ @Expect(method = "domainNoDefaultConstructor", args = {
+ "Domain", "EntityProxyCheckDomainMapping", "RequestContext"}, warning = true)})
+@ProxyFor(EntityProxyCheckDomainMapping.Domain.class)
+@SuppressWarnings("requestfactory")
+interface EntityProxyCheckDomainMapping extends EntityProxy {
+ public static class Domain {
+ public static String getFoo() {
+ return null;
+ }
+
+ public static String getId() {
+ return null;
+ }
+
+ public static String getVersion() {
+ return null;
+ }
+
+ public Domain(@SuppressWarnings("unused") boolean ignored) {
+ }
+
+ public Domain findDomain(@SuppressWarnings("unused") String id) {
+ return null;
+ }
+ }
+
+ String getFoo();
+
+ @Expect(method = "domainMissingMethod", args = "java.lang.String getMissingProperty()")
+ String getMissingProperty();
+
+ @Expected({
+ @Expect(method = "methodNoDomainPeer", args = {"java.lang.Object", "false"}, warning = true),
+ @Expect(method = "untransportableType", args = "java.lang.Object")})
+ Object getUntransportable();
+
+ @Expect(method = "methodNoDomainPeer", args = {"java.lang.Object", "true"}, warning = true)
+ void setUntransportable(
+ @Expect(method = "untransportableType", args = "java.lang.Object") Object obj);
+
+ @Expected({
+ @Expect(method = "proxyOnlyGettersSetters"),
+ @Expect(method = "domainMissingMethod", args = "java.lang.String notAProperty()")})
+ String notAProperty();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMismatchedFind.java b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMismatchedFind.java
new file mode 100644
index 0000000..ad943b8
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMismatchedFind.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+
+@Expect(method = "domainMissingFind", args = {
+ "com.google.web.bindery.requestfactory.apt.EntityProxyMismatchedFind.Domain", "Domain",
+ "java.lang.String", "EntityProxyMismatchedFind"}, warning = true)
+@ProxyFor(EntityProxyMismatchedFind.Domain.class)
+@SuppressWarnings("requestfactory")
+interface EntityProxyMismatchedFind extends EntityProxy {
+ public static class Domain {
+ public static Domain findDomain(@SuppressWarnings("unused") Integer id) {
+ return null;
+ }
+
+ public String getId() {
+ return null;
+ }
+
+ public String getVersion() {
+ return null;
+ }
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainLocatorMethods.java b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainLocatorMethods.java
new file mode 100644
index 0000000..a222e01
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainLocatorMethods.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+
+@Expected({
+ @Expect(method = "domainNoGetId", args = "com.google.web.bindery.requestfactory.apt.EntityProxyMissingDomainLocatorMethods.Domain"),
+ @Expect(method = "domainNoGetVersion", args = "com.google.web.bindery.requestfactory.apt.EntityProxyMissingDomainLocatorMethods.Domain")})
+@ProxyFor(EntityProxyMissingDomainLocatorMethods.Domain.class)
+interface EntityProxyMissingDomainLocatorMethods extends EntityProxy {
+ public static class Domain {
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainType.java b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainType.java
new file mode 100644
index 0000000..2282bc8
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/EntityProxyMissingDomainType.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+
+@Expect(method = "proxyMissingDomainType", args = "does.not.exist", warning = true)
+@ProxyForName("does.not.exist")
+interface EntityProxyMissingDomainType extends EntityProxy {
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/Expect.java b/user/test/com/google/web/bindery/requestfactory/apt/Expect.java
new file mode 100644
index 0000000..953b2b5
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/Expect.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This annotation is applied to any element that is expected to be the target
+ * of an error or a warning from the validator.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@interface Expect {
+ /**
+ * The arguments to be passed to {@code method}.
+ */
+ String[] args() default {};
+
+ /**
+ * The name of a method defined in {@link Messages}.
+ */
+ String method();
+
+ /**
+ * Specifies whether the diagnostic will be a warning or an error.
+ */
+ boolean warning() default false;
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/ExpectCollector.java b/user/test/com/google/web/bindery/requestfactory/apt/ExpectCollector.java
new file mode 100644
index 0000000..31f7cde
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/ExpectCollector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.ElementScanner6;
+import javax.tools.Diagnostic.Kind;
+
+/**
+ * This is a trivial scanner that collects {@code @Expect} declarations on a
+ * type. It does not perform any type-chasing or supertype traversal. The named
+ * method on the {@link Messages} class is invoked with the provided arguments
+ * and the resulting message is emitted to the {@link Messager}.
+ */
+@SupportedAnnotationTypes({
+ "com.google.web.bindery.requestfactory.apt.Expect",
+ "com.google.web.bindery.requestfactory.apt.Expected"})
+@SupportedSourceVersion(SourceVersion.RELEASE_6)
+class ExpectCollector extends AbstractProcessor {
+ class Scanner extends ElementScanner6<Void, Void> {
+
+ private final Messager messager;
+
+ public Scanner(Messager messager) {
+ this.messager = messager;
+ }
+
+ @Override
+ public Void scan(Element e, Void p) {
+ Expect expect = e.getAnnotation(Expect.class);
+ if (expect != null) {
+ addExpect(expect, e);
+ }
+ Expected expected = e.getAnnotation(Expected.class);
+ if (expected != null) {
+ for (Expect v : expected.value()) {
+ addExpect(v, e);
+ }
+ }
+ return super.scan(e, p);
+ }
+
+ private void addExpect(Expect expect, Element accumulator) {
+ Method toInvoke = null;
+ for (Method m : Messages.class.getDeclaredMethods()) {
+ if (m.getName().equals(expect.method())) {
+ toInvoke = m;
+ break;
+ }
+ }
+ if (toInvoke == null) {
+ throw new RuntimeException("No method named " + expect.method());
+ }
+ String[] originalArgs = expect.args();
+ Object[] args = new Object[originalArgs.length];
+ for (int i = 0, j = args.length; i < j; i++) {
+ // Special case for domainMethodWrongModifier
+ if (boolean.class.equals(toInvoke.getParameterTypes()[i])) {
+ args[i] = Boolean.valueOf(originalArgs[i]);
+ } else {
+ args[i] = originalArgs[i];
+ }
+ }
+ String message;
+ Throwable ex;
+ try {
+ message = (String) toInvoke.invoke(null, args);
+ if (expect.warning()) {
+ message += Messages.warnSuffix();
+ }
+ messager.printMessage(expect.warning() ? Kind.WARNING : Kind.ERROR, message, accumulator);
+ return;
+ } catch (IllegalArgumentException e) {
+ ex = e;
+ } catch (IllegalAccessException e) {
+ ex = e;
+ } catch (InvocationTargetException e) {
+ ex = e.getCause();
+ }
+ throw new RuntimeException("Could not get test message", ex);
+ }
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ Scanner scanner = new Scanner(processingEnv.getMessager());
+ scanner.scan(roundEnv.getElementsAnnotatedWith(Expect.class), null);
+ scanner.scan(roundEnv.getElementsAnnotatedWith(Expected.class), null);
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/Expected.java b/user/test/com/google/web/bindery/requestfactory/apt/Expected.java
new file mode 100644
index 0000000..5e73483
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/Expected.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Allows several {@link Expect} annotations to be applied to an element.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@interface Expected {
+ Expect[] value();
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/MyRequestContext.java b/user/test/com/google/web/bindery/requestfactory/apt/MyRequestContext.java
new file mode 100644
index 0000000..7f6b579
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/MyRequestContext.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.JsonRpcProxy;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.ProxyForName;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+/*
+ * No error about a missing mapping expected because this type isn't referenced
+ * from a RequestFactory.
+ */
+interface MyRequestContext extends RequestContext {
+ @Expect(method = "proxyMustBeAnnotated", args = "ProxyMissingAnnotation")
+ interface ProxyMissingAnnotation extends EntityProxy {
+ }
+
+ @Expected({
+ @Expect(method = "proxyMissingDomainType", args = "bad", warning = true),
+ @Expect(method = "redundantAnnotation", args = "ProxyForName"),
+ @Expect(method = "redundantAnnotation", args = "JsonRpcProxy")})
+ @ProxyFor(ProxyWithRedundantAnnotations.Domain.class)
+ @ProxyForName("bad")
+ @JsonRpcProxy
+ interface ProxyWithRedundantAnnotations extends ValueProxy {
+ static class Domain {
+ }
+ }
+
+ /**
+ * Because this is not referenced from the context, it shouldn't generate an
+ * error.
+ */
+ interface UnusedProxyBase extends EntityProxy {
+ }
+
+ @Expect(method = "untransportableType", args = "java.lang.Object")
+ InstanceRequest<ProxyWithRedundantAnnotations, Object> badInstanceReturn();
+
+ @Expect(method = "contextRequiredReturnTypes", args = {"Request", "InstanceRequest"})
+ String badMethod();
+
+ Request<Void> badParam(@Expect(method = "untransportableType", args = "java.lang.Object") Object o);
+
+ @Expect(method = "untransportableType", args = "java.lang.Object")
+ Request<Object> badReturn();
+
+ Request<ProxyMissingAnnotation> forceAnnotation();
+
+ @Expect(method = "rawType")
+ @SuppressWarnings("rawtypes")
+ InstanceRequest rawInstance();
+
+ @Expect(method = "rawType")
+ @SuppressWarnings("rawtypes")
+ Request rawRequest();
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/MyRequestFactory.java b/user/test/com/google/web/bindery/requestfactory/apt/MyRequestFactory.java
new file mode 100644
index 0000000..e5444c3
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/MyRequestFactory.java
@@ -0,0 +1,53 @@
+/*
+ * 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.JsonRpcService;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.RequestFactory;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext;
+
+interface MyRequestFactory extends RequestFactory {
+
+ @Expect(method = "contextMustBeAnnotated", args = "ReferencedContextWithoutMapping")
+ interface ReferencedContextWithoutMapping extends RequestContext {
+ }
+
+ // No error expected
+ interface UnreferencedContextWithoutMapping extends RequestContext {
+ }
+
+ @Expected({
+ @Expect(method = "redundantAnnotation", args = "ServiceName"),
+ @Expect(method = "redundantAnnotation", args = "JsonRpcService")})
+ @Service(Object.class)
+ @ServiceName("java.lang.Object")
+ @JsonRpcService
+ interface UnreferencedContextWithRedundantMapping extends RequestContext {
+ }
+
+ @Expect(method = "factoryMustReturnInterface", args = "AbstractRequestContext")
+ AbstractRequestContext concreteContext();
+
+ ReferencedContextWithoutMapping shouldError();
+
+ @Expected({
+ @Expect(method = "factoryNoMethodParameters"),
+ @Expect(method = "factoryMustBeAssignable", args = "RequestContext")})
+ String stringMethod(String bad);
+}
\ No newline at end of file
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RequestContextMissingDomainType.java b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextMissingDomainType.java
new file mode 100644
index 0000000..57de96a
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextMissingDomainType.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.ServiceName;
+
+@Expect(method = "contextMissingDomainType", args = "does.not.exist", warning = true)
+@ServiceName("does.not.exist")
+interface RequestContextMissingDomainType extends RequestContext {
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RequestContextUsingUnmappedProxy.java b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextUsingUnmappedProxy.java
new file mode 100644
index 0000000..ddb0efd
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextUsingUnmappedProxy.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+
+@Service(RequestContextUsingUnmappedProxy.Domain.class)
+interface RequestContextUsingUnmappedProxy extends RequestContext {
+ public static class Domain {
+ }
+
+ @Expected({
+ @Expect(method = "methodNoDomainPeer", args = {
+ "com.google.web.bindery.requestfactory.apt.EntityProxyMissingDomainType", "false"}, warning = true),
+ @Expect(method = "methodNoDomainPeer", args = {
+ "com.google.web.bindery.requestfactory.apt.EntityProxyMissingDomainType", "true"}, warning = true)})
+ Request<EntityProxyMissingDomainType> cannotVerify(EntityProxyMissingDomainType proxy);
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedBoxes.java b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedBoxes.java
new file mode 100644
index 0000000..ee854aa
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedBoxes.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+import com.google.web.bindery.requestfactory.shared.ValueProxy;
+
+/**
+ * Checks that proxy property accessors match exactly and that context method
+ * arguments must match exactly.
+ */
+@Service(RequestContextWithMismatchedBoxes.Domain.class)
+interface RequestContextWithMismatchedBoxes extends RequestContext {
+ @Expect(method = "domainMissingMethod", args = "java.lang.Void checkBoxed(int)")
+ Request<Void> checkBoxed(int value);
+
+ @Expect(method = "domainMissingMethod", args = "java.lang.Void checkPrimitive(java.lang.Integer)")
+ Request<Void> checkPrimitive(Integer value);
+
+ @ProxyFor(Domain.class)
+ interface ProxyMismatchedGetterA extends ValueProxy {
+ @Expect(method = "domainMissingMethod", args = "int getBoxed()")
+ int getBoxed();
+
+ @Expect(method = "domainMissingMethod", args = "java.lang.Integer getPrimitive()")
+ Integer getPrimitive();
+ }
+
+ static class Domain {
+ static void checkBoxed(@SuppressWarnings("unused") Integer value) {
+ }
+
+ static void checkPrimitive(@SuppressWarnings("unused") int value) {
+ }
+
+ Integer getBoxed() {
+ return null;
+ }
+
+ int getPrimitive() {
+ return 0;
+ }
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedInstance.java b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedInstance.java
new file mode 100644
index 0000000..26c428d
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RequestContextWithMismatchedInstance.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.EntityProxy;
+import com.google.web.bindery.requestfactory.shared.InstanceRequest;
+import com.google.web.bindery.requestfactory.shared.ProxyFor;
+import com.google.web.bindery.requestfactory.shared.Request;
+import com.google.web.bindery.requestfactory.shared.RequestContext;
+import com.google.web.bindery.requestfactory.shared.Service;
+
+/**
+ * Tests Request and InstanceRequest methods bound to methods with the wrong
+ * static modifier.
+ */
+@Expected({
+ @Expect(method = "domainMethodWrongModifier", args = {"true", "instanceMethod"}),
+ @Expect(method = "domainMethodWrongModifier", args = {"false", "staticMethod"})})
+@Service(RequestContextWithMismatchedInstance.Domain.class)
+interface RequestContextWithMismatchedInstance extends RequestContext {
+ static class Domain {
+ public static Domain findDomain(@SuppressWarnings("unused") String id) {
+ return null;
+ }
+
+ public static void staticMethod() {
+ }
+
+ public String getId() {
+ return null;
+ }
+
+ public String getVersion() {
+ return null;
+ }
+
+ public void instanceMethod() {
+ }
+ }
+
+ @ProxyFor(Domain.class)
+ interface Proxy extends EntityProxy {
+ }
+
+ Request<Void> instanceMethod();
+
+ InstanceRequest<Proxy, Void> staticMethod();
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/RfValidatorTest.java b/user/test/com/google/web/bindery/requestfactory/apt/RfValidatorTest.java
new file mode 100644
index 0000000..a6c3ec7
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/RfValidatorTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.gwt.dev.util.Util;
+import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryPolymorphicTest;
+import com.google.web.bindery.requestfactory.shared.LoggingRequest;
+import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
+import com.google.web.bindery.requestfactory.shared.TestRequestFactory;
+import com.google.web.bindery.requestfactory.shared.impl.FindRequest;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.TreeSet;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+/**
+ * Integration test of {@link RfValidator} using the Java6 tools API to invoke
+ * the Java compiler. This test requires that the gwt-user source is available
+ * on the classpath.
+ */
+public class RfValidatorTest extends TestCase {
+ /**
+ * Provides access to a source file from the classpath.
+ */
+ private static class UriJavaFileObject extends SimpleJavaFileObject {
+ public static UriJavaFileObject create(Class<?> toLoad) {
+ try {
+ String path = toLoad.getName().replace('.', '/') + ".java";
+ /*
+ * SimpleJavaFileObject does not like the URI's created from jar URLs
+ * since their path does not start with a leading forward-slash. The
+ * choice of "classpath" as the scheme is arbitrary and meaningless.
+ */
+ URI fakeLocation = new URI("classpath:/" + path);
+ return new UriJavaFileObject(fakeLocation, Thread.currentThread().getContextClassLoader()
+ .getResource(path));
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final URL contents;
+
+ public UriJavaFileObject(URI fakeLocation, URL contents) throws URISyntaxException {
+ super(fakeLocation, JavaFileObject.Kind.SOURCE);
+ this.contents = contents;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignored) throws IOException {
+ return Util.readStreamAsString(contents.openStream());
+ }
+ }
+
+ /**
+ * Smoke test to ensure that appropriate errors and warnings are emitted.
+ */
+ public void testErrorsAndWarnings() throws IOException {
+ testGeneratedMessages(EntityProxyCheckDomainMapping.class);
+ testGeneratedMessages(EntityProxyMismatchedFind.class);
+ testGeneratedMessages(EntityProxyMissingDomainLocatorMethods.class);
+ testGeneratedMessages(EntityProxyMissingDomainType.class);
+ testGeneratedMessages(MyRequestContext.class);
+ testGeneratedMessages(MyRequestFactory.class);
+ testGeneratedMessages(RequestContextMissingDomainType.class);
+ testGeneratedMessages(RequestContextUsingUnmappedProxy.class,
+ EntityProxyMissingDomainType.class);
+ testGeneratedMessages(RequestContextWithMismatchedBoxes.class);
+ testGeneratedMessages(RequestContextWithMismatchedInstance.class);
+ }
+
+ /**
+ * The target classes for this method don't contain any {@code @Expect}
+ * annotations, so this test will verify that they are error- and
+ * warning-free.
+ */
+ public void testTestClasses() throws IOException {
+ testGeneratedMessages(RequestFactoryPolymorphicTest.class);
+ testGeneratedMessages(FindRequest.class);
+ testGeneratedMessages(LoggingRequest.class);
+ testGeneratedMessages(SimpleRequestFactory.class);
+ testGeneratedMessages(TestRequestFactory.class);
+ }
+
+ /**
+ * The target classes for this method don't contain any {@code @Expect}
+ * annotations, so this test will verify that they are error- and
+ * warning-free.
+ *
+ * @throws IOException
+ */
+ public void testTestClassesClientOnly() throws IOException {
+ testGeneratedMessages(true, RequestFactoryPolymorphicTest.class);
+ testGeneratedMessages(true, FindRequest.class);
+ testGeneratedMessages(true, LoggingRequest.class);
+ testGeneratedMessages(true, SimpleRequestFactory.class);
+ testGeneratedMessages(true, TestRequestFactory.class);
+ }
+
+ private void assertEquals(TreeSet<Diagnostic<? extends JavaFileObject>> expected,
+ TreeSet<Diagnostic<? extends JavaFileObject>> actual) {
+ List<Diagnostic<?>> unmatched = new ArrayList<Diagnostic<?>>();
+
+ // Remove actual elements from expect, saving any unexpected elements
+ for (Diagnostic<? extends JavaFileObject> d : actual) {
+ if (!expected.remove(d)) {
+ unmatched.add(d);
+ }
+ }
+
+ assertTrue("Did not see expected errors: " + expected + "\n\nLeftovers :" + unmatched, expected
+ .isEmpty());
+ assertTrue("Unexpected errors: " + unmatched, unmatched.isEmpty());
+ }
+
+ private void testGeneratedMessages(Class<?>... classes) throws IOException {
+ testGeneratedMessages(false, classes);
+ }
+
+ /**
+ * Run the annotation processor over one or more classes and verify that the
+ * appropriate messages are generated.
+ */
+ private void testGeneratedMessages(boolean clientOnly, Class<?>... classes) throws IOException {
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ if (compiler == null) {
+ // This test is being run without a full JDK
+ return;
+ }
+
+ // Don't spray files in random locations
+ File tempFile = File.createTempFile(RfValidatorTest.class.getSimpleName(), ".jar");
+ tempFile.deleteOnExit();
+ JavaFileManager fileManager =
+ new ValidationTool.JarOrDirectoryOutputFileManager(tempFile, compiler
+ .getStandardFileManager(null, null, null));
+
+ List<JavaFileObject> files = new ArrayList<JavaFileObject>(classes.length);
+ for (Class<?> clazz : classes) {
+ JavaFileObject obj = UriJavaFileObject.create(clazz);
+ files.add(obj);
+ }
+ StringWriter errorWriter = new StringWriter();
+ RfValidator rfValidator = new RfValidator();
+ rfValidator.setForceErrors(true);
+ rfValidator.setClientOnly(clientOnly);
+
+ DiagnosticCollector<JavaFileObject> expectedCollector =
+ new DiagnosticCollector<JavaFileObject>();
+ CompilationTask expectedTask =
+ compiler.getTask(errorWriter, fileManager, expectedCollector, Arrays.asList("-proc:only"),
+ null, files);
+ expectedTask.setProcessors(Arrays.asList(new ExpectCollector()));
+ expectedTask.call();
+
+ DiagnosticCollector<JavaFileObject> actualCollector = new DiagnosticCollector<JavaFileObject>();
+ CompilationTask actualTask =
+ compiler.getTask(errorWriter, fileManager, actualCollector, Arrays.asList("-proc:only"),
+ null, files);
+ actualTask.setProcessors(Arrays.asList(rfValidator));
+ actualTask.call();
+
+ TreeSet<Diagnostic<? extends JavaFileObject>> expected =
+ new TreeSet<Diagnostic<? extends JavaFileObject>>(new DiagnosticComparator());
+ expected.addAll(expectedCollector.getDiagnostics());
+ TreeSet<Diagnostic<? extends JavaFileObject>> actual =
+ new TreeSet<Diagnostic<? extends JavaFileObject>>(new DiagnosticComparator());
+ actual.addAll(actualCollector.getDiagnostics());
+ assertEquals(expected, actual);
+ }
+}
diff --git a/user/test/com/google/web/bindery/requestfactory/apt/package-info.java b/user/test/com/google/web/bindery/requestfactory/apt/package-info.java
new file mode 100644
index 0000000..d2eab38
--- /dev/null
+++ b/user/test/com/google/web/bindery/requestfactory/apt/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ * By disabling the following annotation, a manual check can be made against the
+ * various {@link Expect} annotations and the IDE's behavior.
+ */
+@SkipInterfaceValidation
+package com.google.web.bindery.requestfactory.apt;
+
+import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
+
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
index 5a2f008..2505961 100644
--- a/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/RequestFactoryGwtJreSuite.java
@@ -15,6 +15,7 @@
*/
package com.google.web.bindery.requestfactory.gwt;
+import com.google.web.bindery.requestfactory.apt.RfValidatorTest;
import com.google.web.bindery.requestfactory.gwt.rebind.model.RequestFactoryModelTest;
import junit.framework.Test;
@@ -33,6 +34,7 @@
TestSuite suite = new TestSuite(
"requestfactory package tests that require the JRE and gwt-user");
suite.addTestSuite(RequestFactoryModelTest.class);
+ suite.addTestSuite(RfValidatorTest.class);
return suite;
}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
index 7ba01ad..fb15660 100644
--- a/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
+++ b/user/test/com/google/web/bindery/requestfactory/server/BoxesAndPrimitivesJreTest.java
@@ -15,111 +15,20 @@
*/
package com.google.web.bindery.requestfactory.server;
-import com.google.gwt.dev.asm.Type;
-import com.google.gwt.dev.asm.commons.Method;
-import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.VisibleErrorContext;
import com.google.web.bindery.requestfactory.shared.BoxesAndPrimitivesTest;
-import com.google.web.bindery.requestfactory.shared.EntityProxy;
-import com.google.web.bindery.requestfactory.shared.ProxyFor;
-import com.google.web.bindery.requestfactory.shared.Request;
-import com.google.web.bindery.requestfactory.shared.RequestContext;
-import com.google.web.bindery.requestfactory.shared.Service;
-import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
-
-import java.util.Arrays;
-import java.util.logging.Logger;
/**
- * A JRE version of {@link BoxesAndPrimitivesTest} with additional validation
- * tests.
+ * A JRE version of {@link BoxesAndPrimitivesTest}.
*/
-@SkipInterfaceValidation
public class BoxesAndPrimitivesJreTest extends BoxesAndPrimitivesTest {
- @Service(ServiceImpl.class)
- interface ContextMismatchedParameterA extends RequestContext {
- Request<Void> checkBoxed(int value);
- }
-
- @Service(ServiceImpl.class)
- interface ContextMismatchedParameterB extends RequestContext {
- Request<Void> checkPrimitive(Integer value);
- }
-
- @ProxyFor(Entity.class)
- interface ProxyMismatchedGetterA extends EntityProxy {
- int getBoxed();
- }
-
- @ProxyFor(Entity.class)
- interface ProxyMismatchedGetterB extends EntityProxy {
- Integer getPrimitive();
- }
-
- private VisibleErrorContext errors;
- private RequestFactoryInterfaceValidator v;
-
@Override
public String getModuleName() {
return null;
}
- public void test() {
- RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(
- Logger.getAnonymousLogger(),
- new RequestFactoryInterfaceValidator.ClassLoaderLoader(
- getClass().getClassLoader()));
- v.validateRequestFactory(Factory.class.getName());
- assertFalse(v.isPoisoned());
- }
-
- /**
- * Tests that mismatched primitive verses boxed getters are correctly
- * reported.
- */
- public void testMismatchedGetters() {
- v.validateEntityProxy(ProxyMismatchedGetterA.class.getName());
- v.validateEntityProxy(ProxyMismatchedGetterB.class.getName());
- assertTrue(v.isPoisoned());
-
- String getBoxedMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
- Type.getType(Entity.class),
- Arrays.asList(new Method("getBoxed", "()Ljava/lang/Integer;")));
- String getPrimitiveMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
- Type.getType(Entity.class),
- Arrays.asList(new Method("getPrimitive", "()I")));
- assertEquals(Arrays.asList(getBoxedMessage, getPrimitiveMessage),
- errors.logs);
- }
-
- /**
- * Tests that mismatched parameter types are correctly reported.
- */
- public void testMismatchedParameters() {
- v.validateRequestContext(ContextMismatchedParameterA.class.getName());
- v.validateRequestContext(ContextMismatchedParameterB.class.getName());
-
- String checkBoxedMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
- Type.getType(ServiceImpl.class),
- Arrays.asList(new Method("checkBoxed", "(Ljava/lang/Integer;)V")));
- String checkPrimitiveMessage = RequestFactoryInterfaceValidator.messageCouldNotFindMethod(
- Type.getType(ServiceImpl.class),
- Arrays.asList(new Method("checkPrimitive", "(I)V")));
- assertEquals(Arrays.asList(checkBoxedMessage, checkPrimitiveMessage),
- errors.logs);
- }
-
@Override
protected Factory createFactory() {
return RequestFactoryJreTest.createInProcess(Factory.class);
}
-
- @Override
- protected void gwtSetUp() {
- super.gwtSetUp();
- errors = new VisibleErrorContext(Logger.getAnonymousLogger());
- v = new RequestFactoryInterfaceValidator(errors,
- new RequestFactoryInterfaceValidator.ClassLoaderLoader(
- getClass().getClassLoader()));
- }
}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
deleted file mode 100644
index 934ee07..0000000
--- a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ /dev/null
@@ -1,522 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.web.bindery.requestfactory.server;
-
-import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
-import com.google.web.bindery.requestfactory.shared.EntityProxy;
-import com.google.web.bindery.requestfactory.shared.InstanceRequest;
-import com.google.web.bindery.requestfactory.shared.Locator;
-import com.google.web.bindery.requestfactory.shared.ProxyFor;
-import com.google.web.bindery.requestfactory.shared.ProxyForName;
-import com.google.web.bindery.requestfactory.shared.Request;
-import com.google.web.bindery.requestfactory.shared.RequestContext;
-import com.google.web.bindery.requestfactory.shared.RequestFactory;
-import com.google.web.bindery.requestfactory.shared.Service;
-import com.google.web.bindery.requestfactory.shared.ServiceName;
-import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
-import com.google.web.bindery.requestfactory.shared.SkipInterfaceValidation;
-import com.google.web.bindery.requestfactory.shared.ValueProxy;
-import com.google.web.bindery.requestfactory.shared.impl.FindRequest;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * JRE tests for {@link RequestFactoryInterfaceValidator}.
- */
-@SkipInterfaceValidation
-public class RequestFactoryInterfaceValidatorTest extends TestCase {
- static class ClinitEntity {
- static ClinitEntity findClinitEntity(@SuppressWarnings("unused") String key) {
- return null;
- }
-
- static ClinitEntity request() {
- return null;
- }
-
- Object OBJECT = new Object();
-
- String getId() {
- return null;
- }
-
- int getVersion() {
- return 0;
- }
- }
- @ProxyFor(ClinitEntity.class)
- interface ClinitEntityProxy extends EntityProxy {
- Object OBJECT = new Object();
- }
- @Service(ClinitEntity.class)
- interface ClinitRequestContext extends RequestContext {
- Object OBJECT = new Object();
-
- Request<ClinitEntityProxy> request();
- }
-
- interface ClinitRequestFactory extends RequestFactory {
- Object OBJECT = new Object();
-
- ClinitRequestContext context();
- }
-
- static class Domain {
- static int fooStatic(@SuppressWarnings("unused") int a) {
- return 0;
- }
-
- int foo(@SuppressWarnings("unused") int a) {
- return 0;
- }
-
- java.sql.Date getSqlDate() {
- return null;
- }
- }
-
- @ProxyFor(Domain.class)
- interface DomainProxy extends EntityProxy {
- }
-
- interface DomainProxyMissingAnnotation extends EntityProxy {
- }
-
- static class DomainWithOverloads {
- void foo() {
- }
-
- void foo(@SuppressWarnings("unused") int a) {
- }
-
- String getId() {
- return null;
- }
-
- int getVersion() {
- return 0;
- }
- }
-
- @ProxyFor(DomainWithOverloads.class)
- interface DomainWithOverloadsProxy extends EntityProxy {
- void foo();
- }
-
- @ProxyFor(Domain.class)
- interface DomainWithSqlDateProxy extends EntityProxy {
- java.sql.Date getSqlDate();
- }
-
- /**
- * Tests that the validator reports non-static finder methods.
- */
- static class EntityWithInstanceFind {
- public String getId() {
- return null;
- }
-
- public int getVersion() {
- return 0;
- }
-
- /**
- * This method should be static.
- */
- EntityWithInstanceFind findEntityWithInstanceFind(@SuppressWarnings("unused") String key) {
- return null;
- }
- }
-
- @ProxyFor(EntityWithInstanceFind.class)
- interface EntityWithInstanceFindProxy extends EntityProxy {
- }
-
- class Foo {
- }
-
- @ProxyFor(HasListDomain.class)
- interface HasList extends EntityProxy {
- List<ReachableOnlyThroughReturnedList> getList();
-
- void setList(List<ReachableOnlyThroughParamaterList> list);
- }
-
- static class HasListDomain extends Domain {
- public String getId() {
- return null;
- }
-
- public int getVersion() {
- return 0;
- }
-
- List<Domain> getList() {
- return null;
- }
-
- void setList(@SuppressWarnings("unused") List<Domain> value) {
- }
- }
-
- /**
- * An entity type without the usual boilerplate.
- */
- class LocatorEntity {
- }
-
- class LocatorEntityLocator extends Locator<LocatorEntity, String> {
- @Override
- public LocatorEntity create(Class<? extends LocatorEntity> clazz) {
- return null;
- }
-
- @Override
- public LocatorEntity find(Class<? extends LocatorEntity> clazz, String id) {
- return null;
- }
-
- @Override
- public Class<LocatorEntity> getDomainType() {
- return null;
- }
-
- @Override
- public String getId(LocatorEntity domainObject) {
- return null;
- }
-
- @Override
- public Class<String> getIdType() {
- return null;
- }
-
- @Override
- public Object getVersion(LocatorEntity domainObject) {
- return null;
- }
- }
-
- @ProxyFor(value = LocatorEntity.class, locator = LocatorEntityLocator.class)
- interface LocatorEntityProxy extends EntityProxy {
- }
-
- @ProxyForName(value = "com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.LocatorEntity", locator = "badLocator")
- interface LocatorEntityProxyWithBadLocator extends EntityProxy {
- }
-
- @ProxyForName(value = "badDomainType", locator = "com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest.LocatorEntityProxyWithBadServiceName")
- interface LocatorEntityProxyWithBadServiceName extends EntityProxy {
- }
-
- @ProxyFor(value = Value.class)
- interface MyValueProxy extends ValueProxy {
- }
-
- @ProxyFor(Domain.class)
- interface ReachableOnlyThroughParamaterList extends EntityProxy {
- }
-
- @ProxyFor(Domain.class)
- interface ReachableOnlyThroughReturnedList extends EntityProxy {
- }
-
- interface RequestContextMissingAnnotation extends RequestContext {
- }
-
- @Service(Domain.class)
- interface ServiceRequestMismatchedArity extends RequestContext {
- InstanceRequest<DomainProxy, Integer> foo(int a, int b);
-
- Request<Integer> fooStatic(int a, int b);
- }
-
- @Service(Domain.class)
- interface ServiceRequestMismatchedParam extends RequestContext {
- Request<Integer> foo(long a);
- }
- @Service(Domain.class)
- interface ServiceRequestMismatchedReturn extends RequestContext {
- Request<Long> foo(int a);
- }
-
- @Service(Domain.class)
- interface ServiceRequestMismatchedStatic extends RequestContext {
- Request<Integer> foo(int a);
-
- InstanceRequest<DomainProxy, Integer> fooStatic(int a);
- }
-
- @Service(Domain.class)
- interface ServiceRequestMissingMethod extends RequestContext {
- Request<Integer> doesNotExist(int a);
- }
-
- @Service(Domain.class)
- interface SkipValidationChecksReferredProxies extends ValueProxy {
- @SkipInterfaceValidation
- // still validates other proxies
- DomainProxyMissingAnnotation getDomainProxyMissingAnnotation();
- }
-
- @Service(Domain.class)
- interface SkipValidationContext extends RequestContext {
- @SkipInterfaceValidation
- Request<Integer> doesNotExist(int a);
-
- @SkipInterfaceValidation
- Request<Long> foo(int a);
- }
-
- @Service(Domain.class)
- interface SkipValidationProxy extends ValueProxy {
- @SkipInterfaceValidation
- boolean doesNotExist();
- }
-
- @ProxyFor(Domain.class)
- @ProxyForName("Domain")
- @Service(Domain.class)
- @ServiceName("Domain")
- interface TooManyAnnotations extends RequestContext {
- }
-
- static class UnexpectedIdAndVersionDomain {
- Random getId() {
- return null;
- }
-
- Random getVersion() {
- return null;
- }
- }
-
- @ProxyFor(UnexpectedIdAndVersionDomain.class)
- interface UnexpectedIdAndVersionProxy extends EntityProxy {
- }
-
- static class Value {
- }
-
- static class VisibleErrorContext extends RequestFactoryInterfaceValidator.ErrorContext {
- final List<String> logs;
-
- public VisibleErrorContext(Logger logger) {
- super(logger);
- logs = new ArrayList<String>();
- }
-
- public VisibleErrorContext(VisibleErrorContext that) {
- super(that);
- this.logs = that.logs;
- }
-
- @Override
- public void poison(String msg, Object... args) {
- logs.add(String.format(msg, args));
- super.poison(msg, args);
- }
-
- @Override
- public void poison(String msg, Throwable t) {
- logs.add(msg);
- super.poison(msg, t);
- }
-
- @Override
- protected VisibleErrorContext fork() {
- return new VisibleErrorContext(this);
- }
- }
-
- RequestFactoryInterfaceValidator v;;
-
- private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");;
-
- private VisibleErrorContext errors;
-
- /**
- * Ensure that calling {@link RequestFactoryInterfaceValidator#antidote()}
- * doesn't cause information to be lost.
- */
- public void testAntidote() {
- v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
- assertTrue(v.isPoisoned());
- v.antidote();
- assertFalse(v.isPoisoned());
- v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testBadLocatorName() {
- v.validateEntityProxy(LocatorEntityProxyWithBadLocator.class.getName());
- assertTrue(v.isPoisoned());
- assertTrue(errors.logs.contains("Cannot find locator named badLocator"));
- }
-
- public void testBadServiceName() {
- v.validateEntityProxy(LocatorEntityProxyWithBadServiceName.class.getName());
- assertTrue(v.isPoisoned());
- assertTrue(errors.logs.contains("Cannot find domain type named badDomainType"));
- }
-
- /**
- * Test that subclasses of {@code java.util.Date} are not transportable.
- */
- public void testDateSubclass() {
- v.validateEntityProxy(DomainWithSqlDateProxy.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testFindMustBeStatic() {
- v.validateEntityProxy(EntityWithInstanceFindProxy.class.getName());
- assertTrue(v.isPoisoned());
- assertTrue(errors.logs.contains("The findEntityWithInstanceFind method must be static"));
- }
-
- /**
- * Test the {@link FindRequest} context used to implement find().
- */
- public void testFindRequestContext() {
- v.validateRequestContext(FindRequest.class.getName());
- }
-
- /**
- * Make sure that proxy types referenced through type parameters of method
- * return types and paramaters types are examined.
- */
- public void testFollowingTypeParameters() {
- v.validateEntityProxy(HasList.class.getName());
- Deobfuscator d = v.getDeobfuscator();
- assertNotNull(d.getClientProxies(HasListDomain.class.getName()));
- assertTrue(d.getClientProxies(Domain.class.getName()).contains(
- ReachableOnlyThroughParamaterList.class.getName()));
- assertTrue(d.getClientProxies(Domain.class.getName()).contains(
- ReachableOnlyThroughReturnedList.class.getName()));
- }
-
- /**
- * Ensure that the <clinit> methods don't interfere with validation.
- */
- public void testIntecfacesWithClinits() {
- v.validateRequestFactory(ClinitRequestFactory.class.getName());
- assertFalse(v.isPoisoned());
- }
-
- public void testLocatorProxy() {
- v.validateEntityProxy(LocatorEntityProxy.class.getName());
- assertFalse(v.isPoisoned());
- }
-
- public void testMismatchedArity() {
- v.validateRequestContext(ServiceRequestMismatchedArity.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testMismatchedParamType() {
- v.validateRequestContext(ServiceRequestMismatchedParam.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testMismatchedReturnType() {
- v.validateRequestContext(ServiceRequestMismatchedReturn.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testMismatchedStatic() {
- v.validateRequestContext(ServiceRequestMismatchedStatic.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testMissingDomainAnnotations() {
- v.validateEntityProxy(DomainProxyMissingAnnotation.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testMissingIdAndVersion() {
- v.validateEntityProxy(DomainProxy.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testMissingMethod() {
- v.validateRequestContext(ServiceRequestMissingMethod.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testMissingServiceAnnotations() {
- v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testOverloadedMethod() {
- v.validateEntityProxy(DomainWithOverloadsProxy.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testSkipValidationChecksReferredProxies() {
- v.validateValueProxy(SkipValidationChecksReferredProxies.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testSkipValidationContext() {
- v.validateRequestContext(SkipValidationContext.class.getName());
- assertFalse(v.isPoisoned());
- }
-
- public void testSkipValidationProxy() {
- v.validateValueProxy(SkipValidationProxy.class.getName());
- assertFalse(v.isPoisoned());
- }
-
- /**
- * Perform a full test of the RequestFactory used for most tests.
- */
- public void testTestCodeFactories() {
- v.validateRequestFactory(SimpleRequestFactory.class.getName());
- assertFalse(v.isPoisoned());
- }
-
- public void testTooManyAnnotations() {
- v.validateRequestContext(TooManyAnnotations.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testUnexpectedIdAndVersion() {
- v.validateEntityProxy(UnexpectedIdAndVersionProxy.class.getName());
- assertTrue(v.isPoisoned());
- }
-
- public void testValueType() {
- v.validateValueProxy(MyValueProxy.class.getName());
- assertFalse(v.isPoisoned());
- }
-
- @Override
- protected void setUp() throws Exception {
- Logger logger = Logger.getLogger("");
- logger.setLevel(DUMP_PAYLOAD ? Level.ALL : Level.OFF);
- errors = new VisibleErrorContext(logger);
- v =
- new RequestFactoryInterfaceValidator(errors, new ClassLoaderLoader(Thread.currentThread()
- .getContextClassLoader()));
- }
-}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java
index ad762f4..04a3685 100644
--- a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryJreTest.java
@@ -19,17 +19,11 @@
import com.google.web.bindery.event.shared.SimpleEventBus;
import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryTest;
import com.google.web.bindery.requestfactory.server.testing.InProcessRequestTransport;
-import com.google.web.bindery.requestfactory.shared.BaseProxy;
import com.google.web.bindery.requestfactory.shared.RequestFactory;
-import com.google.web.bindery.requestfactory.shared.SimpleBarProxy;
-import com.google.web.bindery.requestfactory.shared.SimpleFooProxy;
import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
import com.google.web.bindery.requestfactory.vm.RequestFactorySource;
-import com.google.web.bindery.requestfactory.vm.impl.OperationKey;
-import com.google.web.bindery.requestfactory.vm.impl.TypeTokenResolver;
import com.google.web.bindery.requestfactory.vm.testing.UrlRequestTransport;
-import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -37,8 +31,7 @@
* Runs the RequestFactory tests in-process.
*/
public class RequestFactoryJreTest extends RequestFactoryTest {
- private static final String TEST_SERVER_ADDRESS = System
- .getProperty("RequestFactory.testUrl");
+ private static final String TEST_SERVER_ADDRESS = System.getProperty("RequestFactory.testUrl");
public static <T extends RequestFactory> T createInProcess(Class<T> clazz) {
EventBus eventBus = new SimpleEventBus();
@@ -63,20 +56,8 @@
return null;
}
- public void testTypeTokenResolver() throws IOException {
- TypeTokenResolver resolver = TypeTokenResolver.loadFromClasspath();
- testResolver(resolver, SimpleBarProxy.class);
- testResolver(resolver, SimpleFooProxy.class);
- }
-
@Override
protected SimpleRequestFactory createFactory() {
return createInProcess(SimpleRequestFactory.class);
}
-
- private void testResolver(TypeTokenResolver resolver, Class<? extends BaseProxy> type) {
- String token = OperationKey.hash(type.getName());
- String typeName = resolver.getTypeFromToken(token);
- assertEquals(type.getName(), typeName);
- }
}
diff --git a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryPolymorphicJreTest.java b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryPolymorphicJreTest.java
index bcf5a23..e10c68a 100644
--- a/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryPolymorphicJreTest.java
+++ b/user/test/com/google/web/bindery/requestfactory/server/RequestFactoryPolymorphicJreTest.java
@@ -16,11 +16,10 @@
package com.google.web.bindery.requestfactory.server;
import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryPolymorphicTest;
-import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidator.ClassLoaderLoader;
import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.vm.impl.Deobfuscator;
import java.util.List;
-import java.util.logging.Logger;
/**
* A JRE version of {@link RequestFactoryPolymorphicTest} that includes
@@ -120,11 +119,7 @@
@Override
protected void gwtSetUp() throws Exception {
super.gwtSetUp();
- Logger logger = Logger.getLogger(getClass().getName());
- ClassLoaderLoader loader = new ClassLoaderLoader(getClass().getClassLoader());
- RequestFactoryInterfaceValidator v = new RequestFactoryInterfaceValidator(logger, loader);
- v.validateRequestFactory(Factory.class.getName());
- deobfuscator = v.getDeobfuscator();
+ deobfuscator = Deobfuscator.Builder.load(Factory.class, getClass().getClassLoader()).build();
}
private void check(Class<?> domainType, Class<? extends BaseProxy> declaredReturnType,
diff --git a/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java b/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java
index 5b8e40c..e313263 100644
--- a/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java
+++ b/user/test/com/google/web/bindery/requestfactory/server/SimpleBar.java
@@ -15,10 +15,9 @@
*/
package com.google.web.bindery.requestfactory.server;
-import com.google.gwt.dev.util.collect.HashSet;
-
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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..350b8c9 100644
--- a/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
+++ b/user/test/com/google/web/bindery/requestfactory/shared/TestFooPolymorphicRequest.java
@@ -17,7 +17,8 @@
/**
* 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 {
diff --git a/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java b/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
index ba26e92..f0cb5a2 100644
--- a/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
+++ b/user/test/com/google/web/bindery/requestfactory/vm/RequestFactoryJreSuite.java
@@ -22,7 +22,6 @@
import com.google.web.bindery.requestfactory.server.LocatorJreTest;
import com.google.web.bindery.requestfactory.server.RequestFactoryChainedContextJreTest;
import com.google.web.bindery.requestfactory.server.RequestFactoryExceptionPropagationJreTest;
-import com.google.web.bindery.requestfactory.server.RequestFactoryInterfaceValidatorTest;
import com.google.web.bindery.requestfactory.server.RequestFactoryJreTest;
import com.google.web.bindery.requestfactory.server.RequestFactoryPolymorphicJreTest;
import com.google.web.bindery.requestfactory.server.RequestFactoryUnicodeEscapingJreTest;
@@ -49,7 +48,6 @@
suite.addTestSuite(LocatorJreTest.class);
suite.addTestSuite(RequestFactoryChainedContextJreTest.class);
suite.addTestSuite(RequestFactoryExceptionPropagationJreTest.class);
- suite.addTestSuite(RequestFactoryInterfaceValidatorTest.class);
suite.addTestSuite(RequestFactoryJreTest.class);
suite.addTestSuite(RequestFactoryPolymorphicJreTest.class);
suite.addTestSuite(RequestFactoryUnicodeEscapingJreTest.class);