Eliminate hotspots in ProxyAutoBean code by memoizing data.
Patch by: bobv
Review by: rchandia

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9312 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java b/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java
index ac2653b..315e185 100644
--- a/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java
+++ b/user/src/com/google/gwt/autobean/server/MethodPropertyContext.java
@@ -23,6 +23,7 @@
 import java.lang.reflect.Type;
 import java.util.Collection;
 import java.util.Map;
+import java.util.WeakHashMap;
 
 /**
  * A base type to handle analyzing the return value of a getter method. The
@@ -30,47 +31,61 @@
  */
 abstract class MethodPropertyContext implements CollectionPropertyContext,
     MapPropertyContext {
-  private final Class<?> keyType;
-  private final Class<?> valueType;
-  private final Class<?> elementType;
-  private final Class<?> type;
+  private static class Data {
+    Class<?> keyType;
+    Class<?> valueType;
+    Class<?> elementType;
+    Class<?> type;
+  }
+
+  /**
+   * Save prior instances in order to decrease the amount of data computed.
+   */
+  private static final Map<Method, Data> cache = new WeakHashMap<Method, Data>();
+
+  private final Data data;
 
   public MethodPropertyContext(Method getter) {
-    this.type = getter.getReturnType();
+    synchronized (cache) {
+      Data previous = cache.get(getter);
+      if (previous != null) {
+        this.data = previous;
+        return;
+      }
 
-    // Compute collection element type
-    if (Collection.class.isAssignableFrom(getType())) {
-      elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
-          Collection.class, getter.getGenericReturnType(),
-          getter.getReturnType()));
-      keyType = valueType = null;
-    } else if (Map.class.isAssignableFrom(getType())) {
-      Type[] types = TypeUtils.getParameterization(Map.class,
-          getter.getGenericReturnType());
-      keyType = TypeUtils.ensureBaseType(types[0]);
-      valueType = TypeUtils.ensureBaseType(types[1]);
-      elementType = null;
-    } else {
-      elementType = keyType = valueType = null;
+      this.data = new Data();
+      data.type = getter.getReturnType();
+      // Compute collection element type
+      if (Collection.class.isAssignableFrom(getType())) {
+        data.elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+            Collection.class, getter.getGenericReturnType(),
+            getter.getReturnType()));
+      } else if (Map.class.isAssignableFrom(getType())) {
+        Type[] types = TypeUtils.getParameterization(Map.class,
+            getter.getGenericReturnType());
+        data.keyType = TypeUtils.ensureBaseType(types[0]);
+        data.valueType = TypeUtils.ensureBaseType(types[1]);
+      }
+      cache.put(getter, data);
     }
   }
 
   public abstract boolean canSet();
 
   public Class<?> getElementType() {
-    return elementType;
+    return data.elementType;
   }
 
   public Class<?> getKeyType() {
-    return keyType;
+    return data.keyType;
   }
 
   public Class<?> getType() {
-    return type;
+    return data.type;
   }
 
   public Class<?> getValueType() {
-    return valueType;
+    return data.valueType;
   }
 
   public abstract void set(Object value);
diff --git a/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java b/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java
index 7c4772d..0992adc 100644
--- a/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java
+++ b/user/src/com/google/gwt/autobean/server/ProxyAutoBean.java
@@ -28,9 +28,10 @@
 import java.lang.reflect.Proxy;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.WeakHashMap;
 
 /**
  * An implementation of an AutoBean that uses reflection.
@@ -38,9 +39,60 @@
  * @param <T> the type of interface being wrapped
  */
 class ProxyAutoBean<T> extends AbstractAutoBean<T> {
+  private static class Data {
+    final List<Method> getters = new ArrayList<Method>();
+    final List<String> getterNames = new ArrayList<String>();
+    final List<PropertyType> propertyType = new ArrayList<PropertyType>();
+  }
+
+  private enum PropertyType {
+    VALUE, REFERENCE, COLLECTION, MAP;
+  }
+
+  private static final Map<Class<?>, Data> cache = new WeakHashMap<Class<?>, Data>();
+
+  private static Data calculateData(Class<?> beanType) {
+    Data toReturn;
+    synchronized (cache) {
+      toReturn = cache.get(beanType);
+      if (toReturn == null) {
+        toReturn = new Data();
+        for (Method method : beanType.getMethods()) {
+          if (BeanMethod.GET.matches(method)) {
+            toReturn.getters.add(method);
+
+            String name;
+            PropertyName annotation = method.getAnnotation(PropertyName.class);
+            if (annotation != null) {
+              name = annotation.value();
+            } else {
+              name = method.getName();
+              name = Character.toLowerCase(name.charAt(3))
+                  + (name.length() >= 5 ? name.substring(4) : "");
+            }
+            toReturn.getterNames.add(name);
+
+            Class<?> returnType = method.getReturnType();
+            if (TypeUtils.isValueType(returnType)) {
+              toReturn.propertyType.add(PropertyType.VALUE);
+            } else if (Collection.class.isAssignableFrom(returnType)) {
+              toReturn.propertyType.add(PropertyType.COLLECTION);
+            } else if (Map.class.isAssignableFrom(returnType)) {
+              toReturn.propertyType.add(PropertyType.MAP);
+            } else {
+              toReturn.propertyType.add(PropertyType.REFERENCE);
+            }
+          }
+        }
+        cache.put(beanType, toReturn);
+      }
+    }
+    return toReturn;
+  }
+
   private final Class<T> beanType;
   private final Configuration configuration;
-  private final List<Method> getters;
+  private final Data data;
   private final T shim;
 
   // These constructors mirror the generated constructors.
@@ -50,7 +102,7 @@
     super(factory);
     this.beanType = (Class<T>) beanType;
     this.configuration = configuration;
-    this.getters = calculateGetters();
+    this.data = calculateData(beanType);
     this.shim = createShim();
   }
 
@@ -63,7 +115,7 @@
     }
     this.beanType = (Class<T>) beanType;
     this.configuration = configuration;
-    this.getters = calculateGetters();
+    this.data = calculateData(beanType);
     this.shim = createShim();
   }
 
@@ -71,7 +123,7 @@
     super(toClone, deep);
     this.beanType = toClone.beanType;
     this.configuration = toClone.configuration;
-    this.getters = toClone.getters;
+    this.data = toClone.data;
     this.shim = createShim();
   }
 
@@ -156,16 +208,15 @@
   // TODO: Port to model-based when class-based TypeOracle is available.
   @Override
   protected void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx) {
-    for (final Method getter : getters) {
-      String name;
-      PropertyName annotation = getter.getAnnotation(PropertyName.class);
-      if (annotation != null) {
-        name = annotation.value();
-      } else {
-        name = getter.getName();
-        name = Character.toLowerCase(name.charAt(3))
-            + (name.length() >= 5 ? name.substring(4) : "");
-      }
+    assert data.getters.size() == data.getterNames.size()
+        && data.getters.size() == data.propertyType.size();
+    Iterator<Method> getterIt = data.getters.iterator();
+    Iterator<String> nameIt = data.getterNames.iterator();
+    Iterator<PropertyType> typeIt = data.propertyType.iterator();
+    while (getterIt.hasNext()) {
+      Method getter = getterIt.next();
+      String name = nameIt.next();
+      PropertyType propertyType = typeIt.next();
 
       // Use the shim to handle automatic wrapping
       Object value;
@@ -184,42 +235,51 @@
       MethodPropertyContext x = isUsingSimplePeer() ? new BeanPropertyContext(
           this, getter) : new GetterPropertyContext(this, getter);
 
-      if (TypeUtils.isValueType(x.getType())) {
-        if (visitor.visitValueProperty(name, value, x)) {
-        }
-        visitor.endVisitValueProperty(name, value, x);
-      } else if (Collection.class.isAssignableFrom(x.getType())) {
-        // Workaround for generics bug in mac javac 1.6.0_22
-        @SuppressWarnings("rawtypes")
-        AutoBean temp = AutoBeanUtils.getAutoBean((Collection) value);
-        @SuppressWarnings("unchecked")
-        AutoBean<Collection<?>> bean = (AutoBean<Collection<?>>) temp;
-        if (visitor.visitCollectionProperty(name, bean, x)) {
-          if (value != null) {
-            ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+      switch (propertyType) {
+        case VALUE: {
+          if (visitor.visitValueProperty(name, value, x)) {
           }
+          visitor.endVisitValueProperty(name, value, x);
+          break;
         }
-        visitor.endVisitCollectionProperty(name, bean, x);
-      } else if (Map.class.isAssignableFrom(x.getType())) {
-        // Workaround for generics bug in mac javac 1.6.0_22
-        @SuppressWarnings("rawtypes")
-        AutoBean temp = AutoBeanUtils.getAutoBean((Map) value);
-        @SuppressWarnings("unchecked")
-        AutoBean<Map<?, ?>> bean = (AutoBean<Map<?, ?>>) temp;
-        if (visitor.visitMapProperty(name, bean, x)) {
-          if (value != null) {
-            ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+        case COLLECTION: {
+          // Workaround for generics bug in mac javac 1.6.0_22
+          @SuppressWarnings("rawtypes")
+          AutoBean temp = AutoBeanUtils.getAutoBean((Collection) value);
+          @SuppressWarnings("unchecked")
+          AutoBean<Collection<?>> bean = (AutoBean<Collection<?>>) temp;
+          if (visitor.visitCollectionProperty(name, bean, x)) {
+            if (value != null) {
+              ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+            }
           }
+          visitor.endVisitCollectionProperty(name, bean, x);
+          break;
         }
-        visitor.endVisitMapProperty(name, bean, x);
-      } else {
-        ProxyAutoBean<?> bean = (ProxyAutoBean<?>) AutoBeanUtils.getAutoBean(value);
-        if (visitor.visitReferenceProperty(name, bean, x)) {
-          if (value != null) {
-            bean.traverse(visitor, ctx);
+        case MAP: {
+          // Workaround for generics bug in mac javac 1.6.0_22
+          @SuppressWarnings("rawtypes")
+          AutoBean temp = AutoBeanUtils.getAutoBean((Map) value);
+          @SuppressWarnings("unchecked")
+          AutoBean<Map<?, ?>> bean = (AutoBean<Map<?, ?>>) temp;
+          if (visitor.visitMapProperty(name, bean, x)) {
+            if (value != null) {
+              ((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
+            }
           }
+          visitor.endVisitMapProperty(name, bean, x);
+          break;
         }
-        visitor.endVisitReferenceProperty(name, bean, x);
+        case REFERENCE: {
+          ProxyAutoBean<?> bean = (ProxyAutoBean<?>) AutoBeanUtils.getAutoBean(value);
+          if (visitor.visitReferenceProperty(name, bean, x)) {
+            if (value != null) {
+              bean.traverse(visitor, ctx);
+            }
+          }
+          visitor.endVisitReferenceProperty(name, bean, x);
+          break;
+        }
       }
     }
   }
@@ -232,16 +292,6 @@
     return values;
   }
 
-  private List<Method> calculateGetters() {
-    List<Method> toReturn = new ArrayList<Method>();
-    for (Method method : beanType.getMethods()) {
-      if (BeanMethod.GET.matches(method)) {
-        toReturn.add(method);
-      }
-    }
-    return Collections.unmodifiableList(toReturn);
-  }
-
   private T createShim() {
     T toReturn = AutoBeanFactoryMagic.makeProxy(beanType, new ShimHandler<T>(
         this, getWrapped()));
diff --git a/user/src/com/google/gwt/autobean/shared/ValueCodex.java b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
index 19ef456..d4a452f 100644
--- a/user/src/com/google/gwt/autobean/shared/ValueCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/ValueCodex.java
@@ -237,8 +237,7 @@
   private static <T> Type findType(Class<T> clazz) {
     Type type = typesByClass.get(clazz);
     if (type == null) {
-      // Necessary due to lack of Class.isAssignable() in client-side
-      if (clazz.getEnumConstants() != null) {
+      if (clazz.isEnum()) {
         return Type.ENUM;
       }
     }