7187618: PropertyDescriptor Performance Slow
authormalenkov
Mon, 30 Jul 2012 13:35:10 +0400
changeset 13356 89a34c00fd8c
parent 13355 30235270222c
child 13357 6a75209a8aeb
7187618: PropertyDescriptor Performance Slow Reviewed-by: rupashka
jdk/src/share/classes/com/sun/beans/TypeResolver.java
jdk/src/share/classes/com/sun/beans/finder/MethodFinder.java
jdk/src/share/classes/java/beans/IndexedPropertyDescriptor.java
jdk/src/share/classes/java/beans/Introspector.java
jdk/src/share/classes/java/beans/PropertyDescriptor.java
jdk/test/java/beans/Performance/Test7122740.java
jdk/test/java/beans/Performance/Test7184799.java
--- a/jdk/src/share/classes/com/sun/beans/TypeResolver.java	Fri Jul 27 22:39:44 2012 -0700
+++ b/jdk/src/share/classes/com/sun/beans/TypeResolver.java	Mon Jul 30 13:35:10 2012 +0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -45,6 +45,9 @@
  * @author Sergey Malenkov
  */
 public final class TypeResolver {
+
+    private static final WeakCache<Type, Map<Type, Type>> CACHE = new WeakCache<>();
+
     /**
      * Replaces the given {@code type} in an inherited method
      * with the actual type it has in the given {@code inClass}.
@@ -149,12 +152,55 @@
      * @param formal  the type where occurrences of the variables
      *                in {@code actual} will be replaced by the corresponding bound values
      * @return a resolved type
-     *
-     * @see #TypeResolver(Type)
-     * @see #resolve(Type)
      */
     public static Type resolve(Type actual, Type formal) {
-        return getTypeResolver(actual).resolve(formal);
+        if (formal instanceof Class) {
+            return formal;
+        }
+        if (formal instanceof GenericArrayType) {
+            Type comp = ((GenericArrayType) formal).getGenericComponentType();
+            comp = resolve(actual, comp);
+            return (comp instanceof Class)
+                    ? Array.newInstance((Class<?>) comp, 0).getClass()
+                    : GenericArrayTypeImpl.make(comp);
+        }
+        if (formal instanceof ParameterizedType) {
+            ParameterizedType fpt = (ParameterizedType) formal;
+            Type[] actuals = resolve(actual, fpt.getActualTypeArguments());
+            return ParameterizedTypeImpl.make(
+                    (Class<?>) fpt.getRawType(), actuals, fpt.getOwnerType());
+        }
+        if (formal instanceof WildcardType) {
+            WildcardType fwt = (WildcardType) formal;
+            Type[] upper = resolve(actual, fwt.getUpperBounds());
+            Type[] lower = resolve(actual, fwt.getLowerBounds());
+            return new WildcardTypeImpl(upper, lower);
+        }
+        if (formal instanceof TypeVariable) {
+            Map<Type, Type> map;
+            synchronized (CACHE) {
+                map = CACHE.get(actual);
+                if (map == null) {
+                    map = new HashMap<>();
+                    prepare(map, actual);
+                    CACHE.put(actual, map);
+                }
+            }
+            Type result = map.get(formal);
+            if (result == null || result.equals(formal)) {
+                return formal;
+            }
+            result = fixGenericArray(result);
+            // A variable can be bound to another variable that is itself bound
+            // to something.  For example, given:
+            // class Super<T> {...}
+            // class Mid<X> extends Super<T> {...}
+            // class Sub extends Mid<String>
+            // the variable T is bound to X, which is in turn bound to String.
+            // So if we have to resolve T, we need the tail recursion here.
+            return resolve(actual, result);
+        }
+        throw new IllegalArgumentException("Bad Type kind: " + formal.getClass());
     }
 
     /**
@@ -164,12 +210,14 @@
      * @param actual   the type that supplies bindings for type variables
      * @param formals  the array of types to resolve
      * @return an array of resolved types
-     *
-     * @see #TypeResolver(Type)
-     * @see #resolve(Type[])
      */
     public static Type[] resolve(Type actual, Type[] formals) {
-        return getTypeResolver(actual).resolve(formals);
+        int length = formals.length;
+        Type[] actuals = new Type[length];
+        for (int i = 0; i < length; i++) {
+            actuals[i] = resolve(actual, formals[i]);
+        }
+        return actuals;
     }
 
     /**
@@ -228,32 +276,6 @@
         return classes;
     }
 
-    public static TypeResolver getTypeResolver(Type type) {
-        synchronized (CACHE) {
-            TypeResolver resolver = CACHE.get(type);
-            if (resolver == null) {
-                resolver = new TypeResolver(type);
-                CACHE.put(type, resolver);
-            }
-            return resolver;
-        }
-    }
-
-    private static final WeakCache<Type, TypeResolver> CACHE = new WeakCache<>();
-
-    private final Map<TypeVariable<?>, Type> map = new HashMap<>();
-
-    /**
-     * Constructs the type resolver for the given actual type.
-     *
-     * @param actual  the type that supplies bindings for type variables
-     *
-     * @see #prepare(Type)
-     */
-    private TypeResolver(Type actual) {
-        prepare(actual);
-    }
-
     /**
      * Fills the map from type parameters
      * to types as seen by the given {@code type}.
@@ -265,9 +287,10 @@
      * to a {@link ParameterizedType ParameterizedType} with no parameters,
      * or it represents the erasure of a {@link ParameterizedType ParameterizedType}.
      *
+     * @param map   the mappings of all type variables
      * @param type  the next type in the hierarchy
      */
-    private void prepare(Type type) {
+    private static void prepare(Map<Type, Type> map, Type type) {
         Class<?> raw = (Class<?>)((type instanceof Class<?>)
                 ? type
                 : ((ParameterizedType)type).getRawType());
@@ -280,91 +303,25 @@
 
         assert formals.length == actuals.length;
         for (int i = 0; i < formals.length; i++) {
-            this.map.put(formals[i], actuals[i]);
+            map.put(formals[i], actuals[i]);
         }
         Type gSuperclass = raw.getGenericSuperclass();
         if (gSuperclass != null) {
-            prepare(gSuperclass);
+            prepare(map, gSuperclass);
         }
         for (Type gInterface : raw.getGenericInterfaces()) {
-            prepare(gInterface);
+            prepare(map, gInterface);
         }
         // If type is the raw version of a parameterized class, we type-erase
         // all of its type variables, including inherited ones.
         if (type instanceof Class<?> && formals.length > 0) {
-            for (Map.Entry<TypeVariable<?>, Type> entry : this.map.entrySet()) {
+            for (Map.Entry<Type, Type> entry : map.entrySet()) {
                 entry.setValue(erase(entry.getValue()));
             }
         }
     }
 
     /**
-     * Replaces the given {@code formal} type
-     * with the type it stand for in this type resolver.
-     *
-     * @param formal  the array of types to resolve
-     * @return a resolved type
-     */
-    private Type resolve(Type formal) {
-        if (formal instanceof Class) {
-            return formal;
-        }
-        if (formal instanceof GenericArrayType) {
-            Type comp = ((GenericArrayType)formal).getGenericComponentType();
-            comp = resolve(comp);
-            return (comp instanceof Class)
-                    ? Array.newInstance((Class<?>)comp, 0).getClass()
-                    : GenericArrayTypeImpl.make(comp);
-        }
-        if (formal instanceof ParameterizedType) {
-            ParameterizedType fpt = (ParameterizedType)formal;
-            Type[] actuals = resolve(fpt.getActualTypeArguments());
-            return ParameterizedTypeImpl.make(
-                    (Class<?>)fpt.getRawType(), actuals, fpt.getOwnerType());
-        }
-        if (formal instanceof WildcardType) {
-            WildcardType fwt = (WildcardType)formal;
-            Type[] upper = resolve(fwt.getUpperBounds());
-            Type[] lower = resolve(fwt.getLowerBounds());
-            return new WildcardTypeImpl(upper, lower);
-        }
-        if (!(formal instanceof TypeVariable)) {
-            throw new IllegalArgumentException("Bad Type kind: " + formal.getClass());
-        }
-        Type actual = this.map.get((TypeVariable) formal);
-        if (actual == null || actual.equals(formal)) {
-            return formal;
-        }
-        actual = fixGenericArray(actual);
-        return resolve(actual);
-        // A variable can be bound to another variable that is itself bound
-        // to something.  For example, given:
-        // class Super<T> {...}
-        // class Mid<X> extends Super<T> {...}
-        // class Sub extends Mid<String>
-        // the variable T is bound to X, which is in turn bound to String.
-        // So if we have to resolve T, we need the tail recursion here.
-    }
-
-    /**
-     * Replaces all formal types in the given array
-     * with the types they stand for in this type resolver.
-     *
-     * @param formals  the array of types to resolve
-     * @return an array of resolved types
-     *
-     * @see #resolve(Type)
-     */
-    private Type[] resolve(Type[] formals) {
-        int length = formals.length;
-        Type[] actuals = new Type[length];
-        for (int i = 0; i < length; i++) {
-            actuals[i] = resolve(formals[i]);
-        }
-        return actuals;
-    }
-
-    /**
      * Replaces a {@link GenericArrayType GenericArrayType}
      * with plain array class where it is possible.
      * Bug <a href="http://bugs.sun.com/view_bug.do?bug_id=5041784">5041784</a>
--- a/jdk/src/share/classes/com/sun/beans/finder/MethodFinder.java	Fri Jul 27 22:39:44 2012 -0700
+++ b/jdk/src/share/classes/com/sun/beans/finder/MethodFinder.java	Mon Jul 30 13:35:10 2012 +0400
@@ -164,8 +164,10 @@
                             return findAccessibleMethod(m);
                         }
                         Type[] gpts = m.getGenericParameterTypes();
-                        if (Arrays.equals(params, TypeResolver.erase(TypeResolver.resolve(pt, gpts)))) {
-                            return findAccessibleMethod(m);
+                        if (params.length == gpts.length) {
+                            if (Arrays.equals(params, TypeResolver.erase(TypeResolver.resolve(pt, gpts)))) {
+                                return findAccessibleMethod(m);
+                            }
                         }
                     }
                 }
--- a/jdk/src/share/classes/java/beans/IndexedPropertyDescriptor.java	Fri Jul 27 22:39:44 2012 -0700
+++ b/jdk/src/share/classes/java/beans/IndexedPropertyDescriptor.java	Mon Jul 30 13:35:10 2012 +0400
@@ -181,20 +181,21 @@
                 // the Indexed readMethod was explicitly set to null.
                 return null;
             }
+            String nextMethodName = Introspector.GET_PREFIX + getBaseName();
             if (indexedReadMethodName == null) {
                 Class<?> type = getIndexedPropertyType0();
                 if (type == boolean.class || type == null) {
                     indexedReadMethodName = Introspector.IS_PREFIX + getBaseName();
                 } else {
-                    indexedReadMethodName = Introspector.GET_PREFIX + getBaseName();
+                    indexedReadMethodName = nextMethodName;
                 }
             }
 
             Class<?>[] args = { int.class };
             indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
-            if (indexedReadMethod == null) {
+            if ((indexedReadMethod == null) && !indexedReadMethodName.equals(nextMethodName)) {
                 // no "is" method, so look for a "get" method.
-                indexedReadMethodName = Introspector.GET_PREFIX + getBaseName();
+                indexedReadMethodName = nextMethodName;
                 indexedReadMethod = Introspector.findMethod(cls, indexedReadMethodName, 1, args);
             }
             setIndexedReadMethod0(indexedReadMethod);
--- a/jdk/src/share/classes/java/beans/Introspector.java	Fri Jul 27 22:39:44 2012 -0700
+++ b/jdk/src/share/classes/java/beans/Introspector.java	Mon Jul 30 13:35:10 2012 +0400
@@ -25,6 +25,7 @@
 
 package java.beans;
 
+import com.sun.beans.TypeResolver;
 import com.sun.beans.WeakCache;
 import com.sun.beans.finder.ClassFinder;
 
@@ -34,6 +35,7 @@
 import java.lang.ref.SoftReference;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
 
 import java.util.Map;
 import java.util.ArrayList;
@@ -951,44 +953,61 @@
                     continue;
                 }
 
-                Class<?>[] argTypes = FeatureDescriptor.getParameterTypes(beanClass, method);
-                Class<?> resultType = FeatureDescriptor.getReturnType(beanClass, method);
-
-                if (name.startsWith(ADD_PREFIX) && argTypes.length == 1 &&
-                    resultType == Void.TYPE &&
-                    Introspector.isSubclass(argTypes[0], eventListenerType)) {
-                    String listenerName = name.substring(3);
-                    if (listenerName.length() > 0 &&
-                        argTypes[0].getName().endsWith(listenerName)) {
-                        if (adds == null) {
-                            adds = new HashMap<>();
+                if (name.startsWith(ADD_PREFIX)) {
+                    Class<?> returnType = method.getReturnType();
+                    if (returnType == void.class) {
+                        Type[] parameterTypes = method.getGenericParameterTypes();
+                        if (parameterTypes.length == 1) {
+                            Class<?> type = TypeResolver.erase(TypeResolver.resolveInClass(beanClass, parameterTypes[0]));
+                            if (Introspector.isSubclass(type, eventListenerType)) {
+                                String listenerName = name.substring(3);
+                                if (listenerName.length() > 0 &&
+                                    type.getName().endsWith(listenerName)) {
+                                    if (adds == null) {
+                                        adds = new HashMap<>();
+                                    }
+                                    adds.put(listenerName, method);
+                                }
+                            }
                         }
-                        adds.put(listenerName, method);
                     }
                 }
-                else if (name.startsWith(REMOVE_PREFIX) && argTypes.length == 1 &&
-                         resultType == Void.TYPE &&
-                         Introspector.isSubclass(argTypes[0], eventListenerType)) {
-                    String listenerName = name.substring(6);
-                    if (listenerName.length() > 0 &&
-                        argTypes[0].getName().endsWith(listenerName)) {
-                        if (removes == null) {
-                            removes = new HashMap<>();
+                else if (name.startsWith(REMOVE_PREFIX)) {
+                    Class<?> returnType = method.getReturnType();
+                    if (returnType == void.class) {
+                        Type[] parameterTypes = method.getGenericParameterTypes();
+                        if (parameterTypes.length == 1) {
+                            Class<?> type = TypeResolver.erase(TypeResolver.resolveInClass(beanClass, parameterTypes[0]));
+                            if (Introspector.isSubclass(type, eventListenerType)) {
+                                String listenerName = name.substring(6);
+                                if (listenerName.length() > 0 &&
+                                    type.getName().endsWith(listenerName)) {
+                                    if (removes == null) {
+                                        removes = new HashMap<>();
+                                    }
+                                    removes.put(listenerName, method);
+                                }
+                            }
                         }
-                        removes.put(listenerName, method);
                     }
                 }
-                else if (name.startsWith(GET_PREFIX) && argTypes.length == 0 &&
-                         resultType.isArray() &&
-                         Introspector.isSubclass(resultType.getComponentType(),
-                                                 eventListenerType)) {
-                    String listenerName  = name.substring(3, name.length() - 1);
-                    if (listenerName.length() > 0 &&
-                        resultType.getComponentType().getName().endsWith(listenerName)) {
-                        if (gets == null) {
-                            gets = new HashMap<>();
+                else if (name.startsWith(GET_PREFIX)) {
+                    Class<?>[] parameterTypes = method.getParameterTypes();
+                    if (parameterTypes.length == 0) {
+                        Class<?> returnType = FeatureDescriptor.getReturnType(beanClass, method);
+                        if (returnType.isArray()) {
+                            Class<?> type = returnType.getComponentType();
+                            if (Introspector.isSubclass(type, eventListenerType)) {
+                                String listenerName  = name.substring(3, name.length() - 1);
+                                if (listenerName.length() > 0 &&
+                                    type.getName().endsWith(listenerName)) {
+                                    if (gets == null) {
+                                        gets = new HashMap<>();
+                                    }
+                                    gets.put(listenerName, method);
+                                }
+                            }
                         }
-                        gets.put(listenerName, method);
                     }
                 }
             }
@@ -1240,11 +1259,11 @@
     private boolean isEventHandler(Method m) {
         // We assume that a method is an event handler if it has a single
         // argument, whose type inherit from java.util.Event.
-        Class argTypes[] = FeatureDescriptor.getParameterTypes(beanClass, m);
+        Type argTypes[] = m.getGenericParameterTypes();
         if (argTypes.length != 1) {
             return false;
         }
-        return isSubclass(argTypes[0], EventObject.class);
+        return isSubclass(TypeResolver.erase(TypeResolver.resolveInClass(beanClass, argTypes[0])), EventObject.class);
     }
 
     /*
@@ -1296,24 +1315,25 @@
                 }
 
                 // make sure method signature matches.
-                Class params[] = FeatureDescriptor.getParameterTypes(start, method);
-                if (method.getName().equals(methodName) &&
-                    params.length == argCount) {
-                    if (args != null) {
-                        boolean different = false;
-                        if (argCount > 0) {
-                            for (int j = 0; j < argCount; j++) {
-                                if (params[j] != args[j]) {
-                                    different = true;
+                if (method.getName().equals(methodName)) {
+                    Type[] params = method.getGenericParameterTypes();
+                    if (params.length == argCount) {
+                        if (args != null) {
+                            boolean different = false;
+                            if (argCount > 0) {
+                                for (int j = 0; j < argCount; j++) {
+                                    if (TypeResolver.erase(TypeResolver.resolveInClass(start, params[j])) != args[j]) {
+                                        different = true;
+                                        continue;
+                                    }
+                                }
+                                if (different) {
                                     continue;
                                 }
                             }
-                            if (different) {
-                                continue;
-                            }
                         }
+                        return method;
                     }
-                    return method;
                 }
             }
         }
--- a/jdk/src/share/classes/java/beans/PropertyDescriptor.java	Fri Jul 27 22:39:44 2012 -0700
+++ b/jdk/src/share/classes/java/beans/PropertyDescriptor.java	Mon Jul 30 13:35:10 2012 +0400
@@ -210,12 +210,13 @@
                 // The read method was explicitly set to null.
                 return null;
             }
+            String nextMethodName = Introspector.GET_PREFIX + getBaseName();
             if (readMethodName == null) {
                 Class<?> type = getPropertyType0();
                 if (type == boolean.class || type == null) {
                     readMethodName = Introspector.IS_PREFIX + getBaseName();
                 } else {
-                    readMethodName = Introspector.GET_PREFIX + getBaseName();
+                    readMethodName = nextMethodName;
                 }
             }
 
@@ -225,8 +226,8 @@
             // methods.  If an "is" method exists, this is the official
             // reader method so look for this one first.
             readMethod = Introspector.findMethod(cls, readMethodName, 0);
-            if (readMethod == null) {
-                readMethodName = Introspector.GET_PREFIX + getBaseName();
+            if ((readMethod == null) && !readMethodName.equals(nextMethodName)) {
+                readMethodName = nextMethodName;
                 readMethod = Introspector.findMethod(cls, readMethodName, 0);
             }
             try {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/beans/Performance/Test7122740.java	Mon Jul 30 13:35:10 2012 +0400
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 7122740
+ * @summary Tests just a benchmark of PropertyDescriptor(String, Class) performance
+ * @author Sergey Malenkov
+ * @run main/manual Test7122740
+ */
+
+import java.beans.PropertyDescriptor;
+
+public class Test7122740 {
+    public static void main(String[] args) throws Exception {
+        long time = System.nanoTime();
+        for (int i = 0; i < 1000; i++) {
+            new PropertyDescriptor("name", PropertyDescriptor.class);
+            new PropertyDescriptor("value", Concrete.class);
+        }
+        time -= System.nanoTime();
+        System.out.println("Time (ms): " + (-time / 1000000));
+    }
+
+    public static class Abstract<T> {
+        private T value;
+        public T getValue() {
+            return this.value;
+        }
+        public void setValue(T value) {
+            this.value = value;
+        }
+    }
+
+    private static class Concrete extends Abstract<String> {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/beans/Performance/Test7184799.java	Mon Jul 30 13:35:10 2012 +0400
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 7184799
+ * @summary Tests just a benchmark of Introspector.getBeanInfo(Class) performance
+ * @author Sergey Malenkov
+ * @run main/manual Test7184799
+ */
+
+import java.beans.Introspector;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Test7184799 {
+    private static final Class[] TYPES = {
+            Class.class,
+            String.class,
+            Character.class,
+            Boolean.class,
+            Byte.class,
+            Short.class,
+            Integer.class,
+            Long.class,
+            Float.class,
+            Double.class,
+            Collection.class,
+            Set.class,
+            HashSet.class,
+            TreeSet.class,
+            LinkedHashSet.class,
+            Map.class,
+            HashMap.class,
+            TreeMap.class,
+            LinkedHashMap.class,
+            WeakHashMap.class,
+            ConcurrentHashMap.class,
+            Dictionary.class,
+            Exception.class,
+    };
+
+    public static void main(String[] args) throws Exception {
+        long time = System.nanoTime();
+        for (Class type : TYPES) {
+            Introspector.getBeanInfo(type);
+        }
+        time -= System.nanoTime();
+        System.out.println("Time (ms): " + (-time / 1000000));
+    }
+}