Make TypeTokenResolver and RequestFactory's annotation processor easier to integrate with Adroid ADT build process by generating a pre-populated TypeTokenBuilder class.
This avoids the need to manipulate resource inclusion in the usual project setup.
Patch by: bobv
Review by: rjrjr, rice

Review at http://gwt-code-reviews.appspot.com/1462801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10348 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java b/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java
index a4703a2..2afd346 100644
--- a/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java
+++ b/user/src/com/google/web/bindery/requestfactory/apt/RfApt.java
@@ -22,10 +22,13 @@
 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;
@@ -41,6 +44,7 @@
 import javax.lang.model.util.Types;
 import javax.tools.Diagnostic.Kind;
 import javax.tools.FileObject;
+import javax.tools.JavaFileObject;
 import javax.tools.StandardLocation;
 
 /**
@@ -108,7 +112,7 @@
 
     // 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();
@@ -119,6 +123,39 @@
       } 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;
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java b/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java
index 4ed5112..5699c9a 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/impl/TypeTokenResolver.java
@@ -26,6 +26,8 @@
 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.
@@ -75,10 +77,26 @@
    * TypeTokenResolver.
    */
   public static TypeTokenResolver loadFromClasspath() throws IOException {
-    Builder builder = new Builder();
+    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 (!locations.hasMoreElements()) {
+    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.");
@@ -99,6 +117,10 @@
    */
   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);
   }