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