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