Make FieldSerializers use reflection directly instead of round-tripping through JSNI. Speeds up DevMode.

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

Review by: jbrosenberg@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9690 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/rpc/impl/ReflectionHelper.java b/user/src/com/google/gwt/user/client/rpc/impl/ReflectionHelper.java
index 924abf6..4fce9fb 100644
--- a/user/src/com/google/gwt/user/client/rpc/impl/ReflectionHelper.java
+++ b/user/src/com/google/gwt/user/client/rpc/impl/ReflectionHelper.java
@@ -15,19 +15,83 @@
  */
 package com.google.gwt.user.client.rpc.impl;
 
+import com.google.gwt.dev.util.Pair;
+
+import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.util.LinkedHashMap;
 
 /**
  * Provides access to reflection capability, but only when running from 
  * bytecode.
  */
 public class ReflectionHelper {
+  
+  // Only used from single-threaded JS. Doesn't need to be thread-safe.
+  private static class Cache<K, V extends AccessibleObject>    
+      extends LinkedHashMap<K, V> {
+    
+    private static final int MAX_SIZE = 1024; 
+    
+    // These values lifted from defaults. 
+    private static final int INITIAL_CAPACITY = 16;
+    private static final float LOAD_FACTOR = 0.75f;
+        
+    public Cache() {
+      super(INITIAL_CAPACITY, LOAD_FACTOR, true);
+    }
+
+    @Override
+    protected boolean removeEldestEntry(
+        java.util.Map.Entry<K, V> eldest) {
+      return size() > MAX_SIZE;
+    }
+  }
+
+  private static final Cache<Class<?>, Constructor> constructorCache
+      = new Cache<Class<?>, Constructor>();
+
+  private static final Cache<Pair<Class<?>,String>, Field> fieldCache
+      = new Cache<Pair<Class<?>,String>, Field>();
+
+  private static Field findField(Class<?> klass, String name) {
+    Pair<Class<?>, String> key = Pair.<Class<?>,String>create(klass, name); 
+    Field f = fieldCache.get(key);
+    if (f == null) {
+      try {
+        f = klass.getDeclaredField(name);
+      } catch (NoSuchFieldException ex) {
+        throw new RuntimeException(
+            "Unable to find field " + klass.getName() + "." + name, ex);
+      }
+      f.setAccessible(true);
+      fieldCache.put(key, f);
+    }
+    return f;
+  }
+  
+  /**
+   * Gets the value of a field.
+   */
+  public static Object getField(Class<?> klass, Object obj, String name) {
+    Field f = findField(klass, name);
+    try {
+      return f.get(obj);
+    } catch (IllegalAccessException ex) {
+      throw new RuntimeException("Unexpected failure", ex);
+    }
+  }
 
   /**
    * Loads {@code klass} using Class.forName.
    */
-  public static Class<?> loadClass(String klass) throws Exception {
-    return Class.forName(klass);
+  public static Class<?> loadClass(String klass) {
+    try {
+      return Class.forName(klass);
+    } catch (ClassNotFoundException ex) {
+      throw new RuntimeException("Unable to find class " + klass, ex);
+    }
   }
 
   /**
@@ -35,10 +99,31 @@
    * constructor. The constructor may have any access modifier (for example,
    * private).
    */
-  public static <T> T newInstance(Class<T> klass)
-      throws Exception {
-    Constructor<T> c = klass.getDeclaredConstructor();
-    c.setAccessible(true);
-    return c.newInstance();
+  @SuppressWarnings("unchecked")
+  public static <T> T newInstance(Class<T> klass) {
+    Constructor<T> c = (Constructor<T>) constructorCache.get(klass);
+    try {
+      if (c == null) {
+        c = klass.getDeclaredConstructor();
+        c.setAccessible(true);
+        constructorCache.put(klass, c);
+      }
+      return c.newInstance();
+    } catch (Exception ex) {
+      throw new RuntimeException("Unexpected failure", ex);
+    }
+  }
+
+  /**
+   * Sets the value of a field.
+   */
+  public static void setField(Class<?> klass, Object obj, String name,
+      Object value) throws Exception {
+    Field f = findField(klass, name);
+    try {
+      f.set(obj, value);
+    } catch (IllegalAccessException ex) {
+      throw new RuntimeException("Unexpected failure", ex);
+    }
   }  
 }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java b/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
index a8102ba..8396cf4 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/FieldSerializerCreator.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.client.UnsafeNativeLong;
 import com.google.gwt.core.client.impl.WeakMapping;
 import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.GeneratorContextExt;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
@@ -33,6 +34,7 @@
 import com.google.gwt.user.client.rpc.SerializationStreamReader;
 import com.google.gwt.user.client.rpc.SerializationStreamWriter;
 import com.google.gwt.user.client.rpc.core.java.lang.Object_Array_CustomFieldSerializer;
+import com.google.gwt.user.client.rpc.impl.ReflectionHelper;
 import com.google.gwt.user.client.rpc.impl.TypeHandler;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
@@ -51,8 +53,17 @@
  */
 public class FieldSerializerCreator {
 
+  /* NB: FieldSerializerCreator generates two different sets of code for 
+   * DevMode and ProdMode. In ProdMode, the generated code uses the JSNI    
+   * violator pattern to access private class members. In DevMode, the generated
+   * code uses ReflectionHelper instead of JSNI to avoid the many JSNI 
+   * boundary-crossings which are slow in DevMode. 
+   */
+  
   private static final String WEAK_MAPPING_CLASS_NAME = WeakMapping.class.getName();
 
+  private final GeneratorContextExt context;
+  
   private final JClassType customFieldSerializer;
 
   private final boolean customFieldSerializerHasInstantiate;
@@ -61,6 +72,12 @@
 
   private final boolean isJRE;
 
+  private final boolean isProd;
+
+  private final String methodEnd;
+  
+  private final String methodStart;
+  
   private final JClassType serializableClass;
 
   private final JField[] serializableFields;
@@ -76,15 +93,19 @@
   /**
    * Constructs a field serializer for the class.
    */
-  public FieldSerializerCreator(TypeOracle typeOracle,
+  public FieldSerializerCreator(GeneratorContextExt context,
       SerializableTypeOracle typesSentFromBrowser,
       SerializableTypeOracle typesSentToBrowser, JClassType requestedClass,
       JClassType customFieldSerializer) {
+    this.context = context;
+    this.isProd = context.isProdMode();
+    methodStart = isProd ? "/*-{" : "{";  
+    methodEnd = isProd ? "}-*/;" : "}";
     this.customFieldSerializer = customFieldSerializer;
     assert (requestedClass != null);
     assert (requestedClass.isClass() != null || requestedClass.isArray() != null);
 
-    this.typeOracle = typeOracle;
+    this.typeOracle = context.getTypeOracle();    
     this.typesSentFromBrowser = typesSentFromBrowser;
     this.typesSentToBrowser = typesSentToBrowser;
     serializableClass = requestedClass;
@@ -191,6 +212,7 @@
     composerFactory.addImport(SerializationException.class.getCanonicalName());
     composerFactory.addImport(SerializationStreamReader.class.getCanonicalName());
     composerFactory.addImport(SerializationStreamWriter.class.getCanonicalName());
+    composerFactory.addImport(ReflectionHelper.class.getCanonicalName());
     composerFactory.addAnnotationDeclaration("@SuppressWarnings(\"deprecation\")");
     if (needsTypeHandler()) {
       composerFactory.addImplementedInterface(TypeHandler.class.getCanonicalName());
@@ -298,8 +320,10 @@
     JClassType isClass = serializableClass.isClass();
 
     boolean useViolator = false;
+    boolean isAccessible = true;
     if (isEnum == null && isClass != null) {
-      useViolator = !classIsAccessible() || !ctorIsAccessible();
+      isAccessible = classIsAccessible() && ctorIsAccessible(); 
+      useViolator = !isAccessible && isProd;
     }
 
     sourceWriter.print("public static" + (useViolator ? " native " : " "));
@@ -319,8 +343,14 @@
           + qualifiedSourceName + ".values();");
       sourceWriter.println("assert (ordinal >= 0 && ordinal < values.length);");
       sourceWriter.println("return values[ordinal];");
-    } else if (useViolator) {
-      sourceWriter.println("return @" + qualifiedSourceName + "::new()();");
+    } else if (!isAccessible) {
+      if (isProd) {
+        sourceWriter.println("return @" + qualifiedSourceName + "::new()();");
+      } else {
+        sourceWriter.println(
+            "return ReflectionHelper.newInstance(" + qualifiedSourceName
+                + ".class);");
+      }
     } else {
       sourceWriter.println("return new " + qualifiedSourceName + "();");
     }
@@ -634,26 +664,42 @@
   private void writeFieldGet(JField serializableField) {
     JType fieldType = serializableField.getType();
     String fieldTypeQualifiedSourceName = fieldType.getQualifiedSourceName();
+    String serializableClassQualifedName = serializableClass.getQualifiedSourceName();
     String fieldName = serializableField.getName();
 
     maybeSuppressLongWarnings(fieldType);
-    sourceWriter.print("private static native ");
+    sourceWriter.print("private static " + (isProd ? "native " : ""));    
     sourceWriter.print(fieldTypeQualifiedSourceName);
     sourceWriter.print(" get");
     sourceWriter.print(Shared.capitalize(fieldName));
     sourceWriter.print("(");
-    sourceWriter.print(serializableClass.getQualifiedSourceName());
-    sourceWriter.println(" instance) /*-{");
+    sourceWriter.print(serializableClassQualifedName);
+    sourceWriter.print(" instance) ");    
+    sourceWriter.println(methodStart);
+    
     sourceWriter.indent();
 
-    sourceWriter.print("return instance.@");
-    sourceWriter.print(SerializationUtils.getRpcTypeName(serializableClass));
-    sourceWriter.print("::");
-    sourceWriter.print(fieldName);
-    sourceWriter.println(";");
+    if (context.isProdMode()) {
+      sourceWriter.print("return instance.@");
+      sourceWriter.print(SerializationUtils.getRpcTypeName(serializableClass));
+      sourceWriter.print("::");
+      sourceWriter.print(fieldName);
+      sourceWriter.println(";");
+    } else {
+      sourceWriter.print("return ");
+      JPrimitiveType primType = fieldType.isPrimitive();
+      if (primType != null) {
+        sourceWriter.print("(" + primType.getQualifiedBoxedSourceName() + ") ");
+      } else {
+        sourceWriter.print("(" + fieldTypeQualifiedSourceName + ") "); 
+      }
+      sourceWriter.println(
+          "ReflectionHelper.getField(" + serializableClassQualifedName
+              + ".class, instance, \"" + fieldName + "\");");
+    }
 
     sourceWriter.outdent();
-    sourceWriter.println("}-*/;");
+    sourceWriter.println(methodEnd);
     sourceWriter.println();
   }
 
@@ -667,24 +713,32 @@
     String fieldName = serializableField.getName();
 
     maybeSuppressLongWarnings(fieldType);
-    sourceWriter.print("private static native void ");
+    sourceWriter.print("private static " + (isProd ? "native " : "") + "void");
     sourceWriter.print(" set");
     sourceWriter.print(Shared.capitalize(fieldName));
     sourceWriter.print("(");
     sourceWriter.print(serializableClassQualifedName);
     sourceWriter.print(" instance, ");
     sourceWriter.print(fieldTypeQualifiedSourceName);
-    sourceWriter.println(" value) /*-{");
+    sourceWriter.println(" value) ");
+    sourceWriter.println(methodStart);
+    
     sourceWriter.indent();
 
-    sourceWriter.print("instance.@");
-    sourceWriter.print(SerializationUtils.getRpcTypeName(serializableClass));
-    sourceWriter.print("::");
-    sourceWriter.print(fieldName);
-    sourceWriter.println(" = value;");
+    if (context.isProdMode()) {
+      sourceWriter.print("instance.@");
+      sourceWriter.print(SerializationUtils.getRpcTypeName(serializableClass));
+      sourceWriter.print("::");
+      sourceWriter.print(fieldName);
+      sourceWriter.println(" = value;");
+    } else {
+      sourceWriter.println(
+          "ReflectionHelper.setField(" + serializableClassQualifedName
+              + ".class, instance, \"" + fieldName + "\", value);");
+    }
 
     sourceWriter.outdent();
-    sourceWriter.println("}-*/;");
+    sourceWriter.println(methodEnd);
     sourceWriter.println();
   }
 
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
index 99e2ad8..89c67c6 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
@@ -234,7 +234,7 @@
   
       JClassType customFieldSerializer = SerializableTypeOracleBuilder.findCustomFieldSerializer(
           typeOracle, type);
-      FieldSerializerCreator creator = new FieldSerializerCreator(typeOracle,
+      FieldSerializerCreator creator = new FieldSerializerCreator(context,
           serializationOracle, deserializationOracle, (JClassType) type,
           customFieldSerializer);
       creator.realize(logger, ctx);
diff --git a/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/impl/ReflectionHelper.java b/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/impl/ReflectionHelper.java
index e77b80c..500a3e4 100644
--- a/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/impl/ReflectionHelper.java
+++ b/user/super/com/google/gwt/user/translatable/com/google/gwt/user/client/rpc/impl/ReflectionHelper.java
@@ -23,12 +23,33 @@
  */
 @GwtScriptOnly
 public class ReflectionHelper {
-  public static Class<?> loadClass(String name) throws Exception {
+  
+  public static Object getField(Class<?> klass, Object obj, String name) {
     throw new RuntimeException("ReflectionHelper can't be used from web mode.");
   }
 
-  public static <T> T newInstance(Class<T> klass)
-      throws Exception {
+  /**
+   * Loads {@code klass} using Class.forName.
+   */
+  public static Class<?> loadClass(String klass) {
+    throw new RuntimeException("ReflectionHelper can't be used from web mode.");
+  }
+
+  /**
+   * Creates a new instance of {@code klass}. The class must have a no-arg
+   * constructor. The constructor may have any access modifier (for example,
+   * private).
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> T newInstance(Class<T> klass) {
+    throw new RuntimeException("ReflectionHelper can't be used from web mode.");
+  }
+
+  /**
+   * Sets the value of a field.
+   */
+  public static void setField(Class<?> klass, Object obj, String name,
+      Object value) {
     throw new RuntimeException("ReflectionHelper can't be used from web mode.");
   }
 }