You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jdo-commits@db.apache.org by mb...@apache.org on 2005/07/15 12:47:04 UTC

svn commit: r219174 - in /incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java: Bundle.properties reflection/ReflectionJavaTypeIntrospector.java

Author: mbo
Date: Fri Jul 15 03:46:58 2005
New Revision: 219174

URL: http://svn.apache.org/viewcvs?rev=219174&view=rev
Log:
Add support for protected properties to reflection based JavaModel

Modified:
    incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/Bundle.properties
    incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/reflection/ReflectionJavaTypeIntrospector.java

Modified: incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/Bundle.properties
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/Bundle.properties?rev=219174&r1=219173&r2=219174&view=diff
==============================================================================
--- incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/Bundle.properties (original)
+++ incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/Bundle.properties Fri Jul 15 03:46:58 2005
@@ -95,3 +95,11 @@
 #NOI18N
 # {0} - Class
 ERR_CannotIntrospectClass=Cannot introspect class ''{0}''.
+#NOI18N
+# {0} - Method name
+# {1} - Property name
+ERR_CannotCreatePropertyDescriptor=Cannot create PropertyDescriptor for property ''{0}'' using method ''{1}''.
+#NOI18N
+# {0} - Method name
+# {1} - Property name
+ERR_CannotSetWriteMethod=Cannot set write method of PropertyDescriptor for property ''{0}''.

Modified: incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/reflection/ReflectionJavaTypeIntrospector.java
URL: http://svn.apache.org/viewcvs/incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/reflection/ReflectionJavaTypeIntrospector.java?rev=219174&r1=219173&r2=219174&view=diff
==============================================================================
--- incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/reflection/ReflectionJavaTypeIntrospector.java (original)
+++ incubator/jdo/trunk/core20/src/java/org/apache/jdo/impl/model/java/reflection/ReflectionJavaTypeIntrospector.java Fri Jul 15 03:46:58 2005
@@ -22,6 +22,15 @@
 import java.beans.PropertyDescriptor;
 
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
 
 import org.apache.jdo.model.ModelFatalException;
 import org.apache.jdo.model.java.JavaMethod;
@@ -29,8 +38,8 @@
 import org.apache.jdo.util.I18NHelper;
 
 /** 
- * Helper clas to intropsect a ReflectionJavaType representing a class for
- * properties. 
+ * Helper class to introspect a ReflectionJavaType representing a class to
+ * find its properties.
  *
  * @author Michael Bouschen
  * @since JDO 2.0
@@ -38,45 +47,346 @@
 public class ReflectionJavaTypeIntrospector
 {
     /** I18N support */
-    private final static I18NHelper msg =  
-        I18NHelper.getInstance("org.apache.jdo.impl.model.java.Bundle"); //NOI18N
+    private final static I18NHelper msg = I18NHelper.getInstance(
+        "org.apache.jdo.impl.model.java.Bundle"); //NOI18N
     
     /** 
-     * Returns a map of properties for the specified class. The key is the
-     * property name, value is the JavaProperty instance.
+     * Adds declared properties to the specified ReflectionJavaType instance. 
      * @param beanClass the class to be introspected
-     * @return a map of JavaProperty instances
      */
     public void addDeclaredJavaProperties(ReflectionJavaType beanClass) 
     {
         Class clazz = beanClass.getJavaClass();
+        PropertyDescriptor[] descrs = 
+            getPublicAndProtectedPropertyDescriptors(clazz);
+        if (descrs != null) {
+            for (int i = 0; i < descrs.length; i++) {
+                PropertyDescriptor descr = descrs[i];
+                if (descr == null) continue;
+                String name = descr.getName();
+                JavaType type = 
+                    beanClass.getJavaTypeForClass(descr.getPropertyType());
+                Method getter = descr.getReadMethod();
+                JavaMethod javaGetter = (getter == null) ? null : 
+                    beanClass.createJavaMethod(getter);
+                Method setter = descr.getWriteMethod();
+                JavaMethod javaSetter = (setter == null) ? null : 
+                    beanClass.createJavaMethod(setter);
+                beanClass.createJavaProperty(name, javaGetter, 
+                                             javaSetter, type);
+            }
+        }
+    }
+
+    // ===== Implementation using java.beans.Introspector =====
+
+    /** 
+     * Returns an array of PropertyDescriptor instances representing the
+     * declared public properties of the specified class.
+     * @param clazz the class to be introspected
+     * @return array of PropertyDescriptor instances for declared public
+     * properties.
+     */
+    public PropertyDescriptor[] getPublicPropertyDescriptors(Class clazz) {
         try {
             BeanInfo beanInfo = Introspector.getBeanInfo(
                 clazz, clazz.getSuperclass());
-            PropertyDescriptor[] descrs = 
-                beanInfo.getPropertyDescriptors();
-            if (descrs != null) {
-                for (int i = 0; i < descrs.length; i++) {
-                    PropertyDescriptor descr = descrs[i];
-                    if (descr == null) continue;
-                    String name = descr.getName();
-                    JavaType type = 
-                        beanClass.getJavaTypeForClass(descr.getPropertyType());
-                    Method getter = descr.getReadMethod();
-                    JavaMethod javaGetter = (getter == null) ? null : 
-                        beanClass.createJavaMethod(getter);
-                    Method setter = descr.getWriteMethod();
-                    JavaMethod javaSetter = (setter == null) ? null : 
-                        beanClass.createJavaMethod(setter);
-                    beanClass.createJavaProperty(name, javaGetter, 
-                                                 javaSetter, type);
-                }
-            }
+            return beanInfo.getPropertyDescriptors();
         }
         catch (IntrospectionException ex) {
             throw new ModelFatalException(msg.msg(
-                "ERR_CannotIntrospectClass", beanClass.getName()), ex); //NOI18N
+                "ERR_CannotIntrospectClass", clazz.getName()), ex); //NOI18N
+        }
+    }
+
+    // ===== Implementation using hand-written Introspector =====
+
+    /**
+     * Returns an array of PropertyDescriptor instances representing the
+     * declared public and protected properties of the specified class.
+     * @param clazz the class to be introspected
+     * @return array of PropertyDescriptor instances for declared public and
+     * protected properties.
+     */
+    public PropertyDescriptor[] getPublicAndProtectedPropertyDescriptors(
+        Class clazz) {
+        return new PropertyStore(clazz).getPropertyDescriptors();
+    }
+
+    /**
+     * Helper class to introspect a class in order to find properties.
+     * The class provides a public method {@link #getPropertyDescriptors()}
+     * returning an array of PropertyDescriptors. Each PropertyDescriptor 
+     * represents a public or protected property of the class specified as
+     * constructor argument. This code is inpired by the implementation
+     * of java.beans.Introspector class. 
+     * <p>
+     * Class PropertyStore uses the following algorithm to identify the
+     * properties:
+     * <ul>
+     * <li>Iterate the declared non-static methods that are public or
+     * protected.</li>
+     * <li>A no-arg method returning a value and having a name staring with
+     * "get" is a potential getter method of a property.</li>
+     * <li>A no-arg method returning a boolean value and a name starting with
+     * "is" is a potential getter method of a property.</li>
+     * <li>A void method with a single argument and having a name starting
+     * with "set" is a potential setter method of a property.</li>
+     * <li>If there exsists an "is" and a "get" method, the "is" method is
+     * used as the getter method. </li>
+     * <li>If there is a getter method and multiple setter methods, it chooses
+     * the setter where the argument has exactly the same type as the getter
+     * return type.</li>
+     * <li>If there no such matching getter method, none of the setter methods
+     * correspond to a property.</li>
+     * </ul>
+     */
+    static class PropertyStore extends HashMap {
+
+        private static final String GET_PREFIX = "get";    //NOI18N
+        private static final int    GET_PREFIX_LENGTH = 3; //NOI18N
+        private static final String SET_PREFIX = "set";    //NOI18N
+        private static final int    SET_PREFIX_LENGTH = 3; //NOI18N
+        private static final String IS_PREFIX = "is";      //NOI18N
+        private static final int    IS_PREFIX_LENGTH = 2;  //NOI18N
+
+        /** The declared method instances for the specified class. */
+        private final Method[] declaredMethods;
+
+        /** Constructor. */
+        public PropertyStore(final Class clazz) {
+            this.declaredMethods = (Method[]) AccessController.doPrivileged(
+                new PrivilegedAction() {
+                    public Object run() {
+                        return clazz.getDeclaredMethods();
+                    }});
+        }
+
+        /**
+         * Returns an array of PropertyDescriptors. Each PropertyDescriptor 
+         * represents a public or protected property of the class specified as
+         * constructor argument.
+         * @return array of public and protected properties
+         */
+        public PropertyDescriptor[] getPropertyDescriptors() {
+            // iterate all declared methods
+            for (int i = 0; i < declaredMethods.length; i++) {
+                Method method = declaredMethods[i];
+                int mods = method.getModifiers();
+                
+                // we are only interested in non-static methods that are
+                // public or protected.
+                if (Modifier.isStatic(mods) || 
+                    (!Modifier.isPublic(mods) && !Modifier.isProtected(mods))) {
+                    continue;
+                }
+
+                String name = method.getName();
+                Class paramTypes[] = method.getParameterTypes();
+                Class resultType = method.getReturnType();
+                
+                switch (paramTypes.length) {
+                case 0:
+                    // no args => possible getter
+                    if (name.startsWith(GET_PREFIX) && 
+                        resultType != void.class) {
+                        String propName = Introspector.decapitalize(
+                            name.substring(GET_PREFIX_LENGTH));
+                        addGetter(propName, method);
+                    }
+                    else if (name.startsWith(IS_PREFIX) && 
+                             resultType == boolean.class) {
+                        String propName = Introspector.decapitalize(
+                            name.substring(IS_PREFIX_LENGTH));
+                        addGetter(propName, method);
+                    }
+                    break;
+                case 1:
+                    // one arg => possible setter
+                    if (name.startsWith(SET_PREFIX) && 
+                        resultType == void.class) {
+                        String propName = Introspector.decapitalize(
+                            name.substring(GET_PREFIX_LENGTH));
+                        addSetter(propName, method);
+                    }
+                    break;
+                }
+            }
+            
+            // Now merge getters and setters
+            List properties = processProperties();
+            return (PropertyDescriptor[]) properties.toArray(
+                new PropertyDescriptor[properties.size()]);
+        }
+        
+        /**
+         * Adds a getter method to the methods list for the property with the
+         * specified name.
+         * @param propName the name of the property.
+         * @param method the getter method.
+         */
+        private void addGetter(String propName, Method method) {
+            try {
+                addPropertyDescriptor(
+                    propName, new PropertyDescriptor(propName, method, null));
+            }
+            catch (IntrospectionException ex) {
+                throw new ModelFatalException(
+                    msg.msg("ERR_CannotCreatePropertyDescriptor", //NOI18N
+                            propName, method.getName()), ex); 
+            }
+        }
+    
+        /**
+         * Adds a setter method to the methods list for the property with the
+         * specified name.
+         * @param propName the name of the property.
+         * @param method the setter method.
+         */
+        private void addSetter(String propName, Method method) {
+            try {
+                addPropertyDescriptor(
+                    propName, new PropertyDescriptor(propName, null, method));
+            }
+            catch (IntrospectionException ex) {
+                throw new ModelFatalException(
+                    msg.msg("ERR_CannotCreatePropertyDescriptor", //NOI18N 
+                            propName, method.getName()), ex);
+            }
+        }
+
+        /**
+         * Adds a the specified (incomplete) PropertyDescriptor to the list of
+         * PropertyDescriptor candidates managed by this PropertyStore. The
+         * method initializes the list of PropertyDescriptors, in case it is
+         * the first PropertyDescriptor for the property with the specified
+         * name.
+         * @param propName the name of the property.
+         * @param pd new PropertyDescriptor.
+         */
+        private synchronized void addPropertyDescriptor(
+            String propName, PropertyDescriptor pd) {
+            if (pd == null) {
+                // nothing to be added
+                return;
+            }
+            
+            List list = (List) get(propName);
+            if (list == null) {
+                list = new ArrayList();
+                put(propName, list);
+            }
+            list.add(pd);
+        }
+        
+        /**
+         * The method returns a list of PropertyDescriptors for the properties
+         * managed by this PropertyStore. It iterates the all properties
+         * and analyzes the candidate PropertyDescriptors (by calling method 
+         * {@link #processProperty(List)}.
+         * @return list of PropertyDescriptors
+         */
+        private synchronized List processProperties() {
+            List result = new ArrayList();
+            for (Iterator i = values().iterator(); i.hasNext();) {
+                PropertyDescriptor pd = processProperty((List) i.next());
+                if (pd != null) {
+                    result.add(pd);
+                }
+            }
+            return result;
+        }
+        
+        /** 
+         * The method analyzes the specified list of candidate
+         * PropertyDescriptors and returns a single PropertyDescriptor
+         * describing the property. It iterates the candidate list in order to
+         * find a getter PropertyDescriptor. If there is such a
+         * PropertyDescriptor it looks for a corresponding setter
+         * PropertyDescriptor and updates the getter PropertyDescriptor with
+         * the write method of the setter. It then returns the getter
+         * PropertyDescriptor. If there is no getter PropertyDescriptor and a
+         * single setter PropertyDescriptor it returns the setter
+         * PropertyDescriptor. Otherwise it returns <code>null</code> which
+         * means the list of candidate PropertyDescriptors does not qualify
+         * for a valid property.
+         * @param candidates the list of candidate PropertyDescriptors
+         * @return a PropertyDescriptor describing a property or
+         * <code>null</code> if the candidate PropertyDescriptors do not
+         * qualify for a valid property.
+         */
+        private PropertyDescriptor processProperty(List candidates) {
+            if (candidates == null)
+                return null;
+            
+            PropertyDescriptor getter = null;
+            PropertyDescriptor setter = null;
+            
+            // First iteration: check getter methods 
+            for (Iterator i = candidates.iterator(); i.hasNext();) {
+                PropertyDescriptor pd = (PropertyDescriptor) i.next();
+                if (pd.getReadMethod() != null) {
+                    if (getter != null) {
+                        // Found getter, but do not overwrite "is" getter
+                        // stored before
+                        String name = getter.getReadMethod().getName();
+                        if (!name.startsWith(IS_PREFIX)) {
+                            getter = pd;
+                        }
+                    }
+                    else {
+                        // Store getter
+                        getter = pd;
+                    }
+                }
+            }
+            
+            // Second iteration: check setter methods. This cannot be combined
+            // with the first iteration, because I need the property type of
+            // the getter to find the corresponding setter.
+            for (Iterator i = candidates.iterator(); i.hasNext();) {
+                PropertyDescriptor pd = (PropertyDescriptor) i.next();
+                if (pd.getWriteMethod() != null) {
+                    if (getter != null) {
+                        if (pd.getPropertyType() == getter.getPropertyType()) {
+                            // Found setter that corresponds to getter => 
+                            // store setter and stop iterating the candidates
+                            setter = pd;
+                            break;
+                        }
+                    }
+                    else if (setter != null) {
+                        // Found multiple setters w/o getter =>
+                        // no property, remove stored setter
+                        setter = null;
+                        break;
+                    }
+                    else {
+                        // Found setter => store it
+                        setter = pd;
+                    }
+                }
+            }
+            
+            // check stored getter and setter
+            if (getter != null) {
+                if (setter != null) {
+                    // getter and setter => merge setter into getter and
+                    // return getter
+                    try {
+                        getter.setWriteMethod(setter.getWriteMethod());
+                    }
+                    catch (IntrospectionException ex) {
+                        throw new ModelFatalException(
+                            msg.msg("ERR_CannotSetWriteMethod", //NOI18N
+                                    getter.getName()), ex); 
+                    }            
+                }
+                return getter;
+            }
+            return setter;
         }
     }
+
 }