Save This Page
Home » jdo2-model-2.3-ea-src » org.apache.jdo.impl.model.java.reflection » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    * 
    9    *     http://www.apache.org/licenses/LICENSE-2.0
   10    * 
   11    * Unless required by applicable law or agreed to in writing, software 
   12    * distributed under the License is distributed on an "AS IS" BASIS, 
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
   14    * See the License for the specific language governing permissions and 
   15    * limitations under the License.
   16    */
   17   
   18   package org.apache.jdo.impl.model.java.reflection;
   19   
   20   import java.beans.BeanInfo;
   21   import java.beans.Introspector;
   22   import java.beans.IntrospectionException;
   23   import java.beans.PropertyDescriptor;
   24   
   25   import java.lang.reflect.Method;
   26   import java.lang.reflect.Modifier;
   27   
   28   import java.security.AccessController;
   29   import java.security.PrivilegedAction;
   30   
   31   import java.util.ArrayList;
   32   import java.util.HashMap;
   33   import java.util.Iterator;
   34   import java.util.List;
   35   
   36   import org.apache.jdo.model.ModelFatalException;
   37   import org.apache.jdo.model.java.JavaMethod;
   38   import org.apache.jdo.model.java.JavaType;
   39   import org.apache.jdo.util.I18NHelper;
   40   
   41   /** 
   42    * Helper class to introspect a ReflectionJavaType representing a class to
   43    * find its properties.
   44    *
   45    * @author Michael Bouschen
   46    * @since JDO 2.0
   47    */
   48   public class ReflectionJavaTypeIntrospector
   49   {
   50       /** I18N support */
   51       private final static I18NHelper msg = I18NHelper.getInstance(
   52           "org.apache.jdo.impl.model.java.Bundle"); //NOI18N
   53       
   54       /** 
   55        * Adds declared properties to the specified ReflectionJavaType instance. 
   56        * @param beanClass the class to be introspected
   57        */
   58       public void addDeclaredJavaProperties(ReflectionJavaType beanClass) 
   59       {
   60           Class clazz = beanClass.getJavaClass();
   61           PropertyDescriptor[] descrs = 
   62               getPublicAndProtectedPropertyDescriptors(clazz);
   63           if (descrs != null) {
   64               for (int i = 0; i < descrs.length; i++) {
   65                   PropertyDescriptor descr = descrs[i];
   66                   if (descr == null) continue;
   67                   String name = descr.getName();
   68                   JavaType type = 
   69                       beanClass.getJavaTypeForClass(descr.getPropertyType());
   70                   Method getter = descr.getReadMethod();
   71                   JavaMethod javaGetter = (getter == null) ? null : 
   72                       beanClass.createJavaMethod(getter);
   73                   Method setter = descr.getWriteMethod();
   74                   JavaMethod javaSetter = (setter == null) ? null : 
   75                       beanClass.createJavaMethod(setter);
   76                   beanClass.createJavaProperty(name, javaGetter, 
   77                                                javaSetter, type);
   78               }
   79           }
   80       }
   81   
   82       // ===== Implementation using java.beans.Introspector =====
   83   
   84       /** 
   85        * Returns an array of PropertyDescriptor instances representing the
   86        * declared public properties of the specified class.
   87        * @param clazz the class to be introspected
   88        * @return array of PropertyDescriptor instances for declared public
   89        * properties.
   90        */
   91       private PropertyDescriptor[] getPublicPropertyDescriptors(Class clazz) {
   92           try {
   93               BeanInfo beanInfo = Introspector.getBeanInfo(
   94                   clazz, clazz.getSuperclass());
   95               return beanInfo.getPropertyDescriptors();
   96           }
   97           catch (IntrospectionException ex) {
   98               throw new ModelFatalException(msg.msg(
   99                   "ERR_CannotIntrospectClass", clazz.getName()), ex); //NOI18N
  100           }
  101       }
  102   
  103       // ===== Implementation using hand-written Introspector =====
  104   
  105       /**
  106        * Returns an array of PropertyDescriptor instances representing the
  107        * declared public and protected properties of the specified class.
  108        * @param clazz the class to be introspected
  109        * @return array of PropertyDescriptor instances for declared public and
  110        * protected properties.
  111        */
  112       private PropertyDescriptor[] getPublicAndProtectedPropertyDescriptors(
  113           Class clazz) {
  114           return new PropertyStore(clazz).getPropertyDescriptors();
  115       }
  116   
  117       /**
  118        * Helper class to introspect a class in order to find properties.
  119        * The class provides a public method {@link #getPropertyDescriptors()}
  120        * returning an array of PropertyDescriptors. Each PropertyDescriptor 
  121        * represents a public or protected property of the class specified as
  122        * constructor argument. This code is inspired by the implementation
  123        * of java.beans.Introspector class. 
  124        * <p>
  125        * Class PropertyStore uses the following algorithm to identify the
  126        * properties:
  127        * <ul>
  128        * <li>Iterate the declared non-static methods that are public or
  129        * protected.</li>
  130        * <li>A no-arg method returning a value and having a name staring with
  131        * "get" is a potential getter method of a property.</li>
  132        * <li>A no-arg method returning a boolean value and a name starting with
  133        * "is" is a potential getter method of a property.</li>
  134        * <li>A void method with a single argument and having a name starting
  135        * with "set" is a potential setter method of a property.</li>
  136        * <li>If there exsists an "is" and a "get" method, the "is" method is
  137        * used as the getter method. </li>
  138        * <li>If there is a getter method and multiple setter methods, it chooses
  139        * the setter where the argument has exactly the same type as the getter
  140        * return type.</li>
  141        * <li>If there no such matching getter method, none of the setter methods
  142        * correspond to a property.</li>
  143        * </ul>
  144        */
  145       static class PropertyStore extends HashMap {
  146   
  147           private static final String GET_PREFIX = "get";    //NOI18N
  148           private static final int    GET_PREFIX_LENGTH = 3;
  149           private static final String SET_PREFIX = "set";    //NOI18N
  150           private static final int    SET_PREFIX_LENGTH = 3;
  151           private static final String IS_PREFIX = "is";      //NOI18N
  152           private static final int    IS_PREFIX_LENGTH = 2;
  153   
  154           /** The declared method instances for the specified class. */
  155           private final Method[] declaredMethods;
  156   
  157           /** Constructor. */
  158           public PropertyStore(final Class clazz) {
  159               this.declaredMethods = (Method[]) AccessController.doPrivileged(
  160                   new PrivilegedAction() {
  161                       public Object run() {
  162                           return clazz.getDeclaredMethods();
  163                       }});
  164           }
  165   
  166           /**
  167            * Returns an array of PropertyDescriptors. Each PropertyDescriptor 
  168            * represents a public or protected property of the class specified as
  169            * constructor argument.
  170            * @return array of public and protected properties
  171            */
  172           public PropertyDescriptor[] getPropertyDescriptors() {
  173               // iterate all declared methods
  174               for (int i = 0; i < declaredMethods.length; i++) {
  175                   Method method = declaredMethods[i];
  176                   int mods = method.getModifiers();
  177                   
  178                   // we are only interested in non-static methods that are
  179                   // public or protected.
  180                   if (Modifier.isStatic(mods) || 
  181                       (!Modifier.isPublic(mods) && !Modifier.isProtected(mods))) {
  182                       continue;
  183                   }
  184   
  185                   String name = method.getName();
  186                   Class paramTypes[] = method.getParameterTypes();
  187                   Class resultType = method.getReturnType();
  188                   
  189                   switch (paramTypes.length) {
  190                   case 0:
  191                       // no args => possible getter
  192                       if (name.startsWith(GET_PREFIX) && 
  193                           resultType != void.class) {
  194                           String propName = Introspector.decapitalize(
  195                               name.substring(GET_PREFIX_LENGTH));
  196                           addGetter(propName, method);
  197                       }
  198                       else if (name.startsWith(IS_PREFIX) && 
  199                                resultType == boolean.class) {
  200                           String propName = Introspector.decapitalize(
  201                               name.substring(IS_PREFIX_LENGTH));
  202                           addGetter(propName, method);
  203                       }
  204                       break;
  205                   case 1:
  206                       // one arg => possible setter
  207                       if (name.startsWith(SET_PREFIX) && 
  208                           resultType == void.class) {
  209                           String propName = Introspector.decapitalize(
  210                               name.substring(GET_PREFIX_LENGTH));
  211                           addSetter(propName, method);
  212                       }
  213                       break;
  214                   }
  215               }
  216               
  217               // Now merge getters and setters
  218               List properties = processProperties();
  219               return (PropertyDescriptor[]) properties.toArray(
  220                   new PropertyDescriptor[properties.size()]);
  221           }
  222           
  223           /**
  224            * Adds a getter method to the methods list for the property with the
  225            * specified name.
  226            * @param propName the name of the property.
  227            * @param method the getter method.
  228            */
  229           private void addGetter(String propName, Method method) {
  230               try {
  231                   addPropertyDescriptor(
  232                       propName, new PropertyDescriptor(propName, method, null));
  233               }
  234               catch (IntrospectionException ex) {
  235                   throw new ModelFatalException(
  236                       msg.msg("ERR_CannotCreatePropertyDescriptor", //NOI18N
  237                               propName, method.getName()), ex); 
  238               }
  239           }
  240       
  241           /**
  242            * Adds a setter method to the methods list for the property with the
  243            * specified name.
  244            * @param propName the name of the property.
  245            * @param method the setter method.
  246            */
  247           private void addSetter(String propName, Method method) {
  248               try {
  249                   addPropertyDescriptor(
  250                       propName, new PropertyDescriptor(propName, null, method));
  251               }
  252               catch (IntrospectionException ex) {
  253                   throw new ModelFatalException(
  254                       msg.msg("ERR_CannotCreatePropertyDescriptor", //NOI18N 
  255                               propName, method.getName()), ex);
  256               }
  257           }
  258   
  259           /**
  260            * Adds a the specified (incomplete) PropertyDescriptor to the list of
  261            * PropertyDescriptor candidates managed by this PropertyStore. The
  262            * method initializes the list of PropertyDescriptors, in case it is
  263            * the first PropertyDescriptor for the property with the specified
  264            * name.
  265            * @param propName the name of the property.
  266            * @param pd new PropertyDescriptor.
  267            */
  268           private synchronized void addPropertyDescriptor(
  269               String propName, PropertyDescriptor pd) {
  270               if (pd == null) {
  271                   // nothing to be added
  272                   return;
  273               }
  274               
  275               List list = (List) get(propName);
  276               if (list == null) {
  277                   list = new ArrayList();
  278                   put(propName, list);
  279               }
  280               list.add(pd);
  281           }
  282           
  283           /**
  284            * The method returns a list of PropertyDescriptors for the properties
  285            * managed by this PropertyStore. It iterates the all properties
  286            * and analyzes the candidate PropertyDescriptors (by calling method 
  287            * {@link #processProperty(List)}.
  288            * @return list of PropertyDescriptors
  289            */
  290           private synchronized List processProperties() {
  291               List result = new ArrayList();
  292               for (Iterator i = values().iterator(); i.hasNext();) {
  293                   PropertyDescriptor pd = processProperty((List) i.next());
  294                   if (pd != null) {
  295                       result.add(pd);
  296                   }
  297               }
  298               return result;
  299           }
  300           
  301           /** 
  302            * The method analyzes the specified list of candidate
  303            * PropertyDescriptors and returns a single PropertyDescriptor
  304            * describing the property. It iterates the candidate list in order to
  305            * find a getter PropertyDescriptor. If there is such a
  306            * PropertyDescriptor it looks for a corresponding setter
  307            * PropertyDescriptor and updates the getter PropertyDescriptor with
  308            * the write method of the setter. It then returns the getter
  309            * PropertyDescriptor. If there is no getter PropertyDescriptor and a
  310            * single setter PropertyDescriptor it returns the setter
  311            * PropertyDescriptor. Otherwise it returns <code>null</code> which
  312            * means the list of candidate PropertyDescriptors does not qualify
  313            * for a valid property.
  314            * @param candidates the list of candidate PropertyDescriptors
  315            * @return a PropertyDescriptor describing a property or
  316            * <code>null</code> if the candidate PropertyDescriptors do not
  317            * qualify for a valid property.
  318            */
  319           private PropertyDescriptor processProperty(List candidates) {
  320               if (candidates == null)
  321                   return null;
  322               
  323               PropertyDescriptor getter = null;
  324               PropertyDescriptor setter = null;
  325               
  326               // First iteration: check getter methods 
  327               for (Iterator i = candidates.iterator(); i.hasNext();) {
  328                   PropertyDescriptor pd = (PropertyDescriptor) i.next();
  329                   if (pd.getReadMethod() != null) {
  330                       if (getter != null) {
  331                           // Found getter, but do not overwrite "is" getter
  332                           // stored before
  333                           String name = getter.getReadMethod().getName();
  334                           if (!name.startsWith(IS_PREFIX)) {
  335                               getter = pd;
  336                           }
  337                       }
  338                       else {
  339                           // Store getter
  340                           getter = pd;
  341                       }
  342                   }
  343               }
  344               
  345               // Second iteration: check setter methods. This cannot be combined
  346               // with the first iteration, because I need the property type of
  347               // the getter to find the corresponding setter.
  348               for (Iterator i = candidates.iterator(); i.hasNext();) {
  349                   PropertyDescriptor pd = (PropertyDescriptor) i.next();
  350                   if (pd.getWriteMethod() != null) {
  351                       if (getter != null) {
  352                           if (pd.getPropertyType() == getter.getPropertyType()) {
  353                               // Found setter that corresponds to getter => 
  354                               // store setter and stop iterating the candidates
  355                               setter = pd;
  356                               break;
  357                           }
  358                       }
  359                       else if (setter != null) {
  360                           // Found multiple setters w/o getter =>
  361                           // no property, remove stored setter
  362                           setter = null;
  363                           break;
  364                       }
  365                       else {
  366                           // Found setter => store it
  367                           setter = pd;
  368                       }
  369                   }
  370               }
  371               
  372               // check stored getter and setter
  373               if (getter != null) {
  374                   if (setter != null) {
  375                       // getter and setter => merge setter into getter and
  376                       // return getter
  377                       try {
  378                           getter.setWriteMethod(setter.getWriteMethod());
  379                       }
  380                       catch (IntrospectionException ex) {
  381                           throw new ModelFatalException(
  382                               msg.msg("ERR_CannotSetWriteMethod", //NOI18N
  383                                       getter.getName()), ex); 
  384                       }            
  385                   }
  386                   return getter;
  387               }
  388               return setter;
  389           }
  390       }
  391       
  392   }
  393   

Save This Page
Home » jdo2-model-2.3-ea-src » org.apache.jdo.impl.model.java.reflection » [javadoc | source]