View Javadoc

    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.commons.beanutils;
    19
    20
    21  import java.beans.BeanInfo;
    22  import java.beans.IndexedPropertyDescriptor;
    23  import java.beans.IntrospectionException;
    24  import java.beans.Introspector;
    25  import java.beans.PropertyDescriptor;
    26  import java.lang.reflect.Array;
    27  import java.lang.reflect.InvocationTargetException;
    28  import java.lang.reflect.Method;
    29  import java.util.HashMap;
    30  import java.util.Iterator;
    31  import java.util.List;
    32  import java.util.Map;
    33
    34  import org.apache.commons.beanutils.expression.DefaultResolver;
    35  import org.apache.commons.beanutils.expression.Resolver;
    36  import org.apache.commons.collections.FastHashMap;
    37  import org.apache.commons.logging.Log;
    38  import org.apache.commons.logging.LogFactory;
    39
    40
    41  /***
    42   * Utility methods for using Java Reflection APIs to facilitate generic
    43   * property getter and setter operations on Java objects.  Much of this
    44   * code was originally included in <code>BeanUtils</code>, but has been
    45   * separated because of the volume of code involved.
    46   * <p>
    47   * In general, the objects that are examined and modified using these
    48   * methods are expected to conform to the property getter and setter method
    49   * naming conventions described in the JavaBeans Specification (Version 1.0.1).
    50   * No data type conversions are performed, and there are no usage of any
    51   * <code>PropertyEditor</code> classes that have been registered, although
    52   * a convenient way to access the registered classes themselves is included.
    53   * <p>
    54   * For the purposes of this class, five formats for referencing a particular
    55   * property value of a bean are defined, with the <i>default</i> layout of an
    56   * identifying String in parentheses. However the notation for these formats
    57   * and how they are resolved is now (since BeanUtils 1.8.0) controlled by
    58   * the configured {@link Resolver} implementation:
    59   * <ul>
    60   * <li><strong>Simple (<code>name</code>)</strong> - The specified
    61   *     <code>name</code> identifies an individual property of a particular
    62   *     JavaBean.  The name of the actual getter or setter method to be used
    63   *     is determined using standard JavaBeans instrospection, so that (unless
    64   *     overridden by a <code>BeanInfo</code> class, a property named "xyz"
    65   *     will have a getter method named <code>getXyz()</code> or (for boolean
    66   *     properties only) <code>isXyz()</code>, and a setter method named
    67   *     <code>setXyz()</code>.</li>
    68   * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first
    69   *     name element is used to select a property getter, as for simple
    70   *     references above.  The object returned for this property is then
    71   *     consulted, using the same approach, for a property getter for a
    72   *     property named <code>name2</code>, and so on.  The property value that
    73   *     is ultimately retrieved or modified is the one identified by the
    74   *     last name element.</li>
    75   * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying
    76   *     property value is assumed to be an array, or this JavaBean is assumed
    77   *     to have indexed property getter and setter methods.  The appropriate
    78   *     (zero-relative) entry in the array is selected.  <code>List</code>
    79   *     objects are now also supported for read/write.  You simply need to define
    80   *     a getter that returns the <code>List</code></li>
    81   * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean
    82   *     is assumed to have an property getter and setter methods with an
    83   *     additional attribute of type <code>java.lang.String</code>.</li>
    84   * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> -
    85   *     Combining mapped, nested, and indexed references is also
    86   *     supported.</li>
    87   * </ul>
    88   *
    89   * @author Craig R. McClanahan
    90   * @author Ralph Schaer
    91   * @author Chris Audley
    92   * @author Rey Francois
    93   * @author Gregor Rayman
    94   * @author Jan Sorensen
    95   * @author Scott Sanders
    96   * @author Erik Meade
    97   * @version $Revision: 557008 $ $Date: 2007-07-17 19:27:26 +0100 (Tue, 17 Jul 2007) $
    98   * @see Resolver
    99   * @see PropertyUtils
    100  * @since 1.7
    101  */
    102
    103 public class PropertyUtilsBean {
    104
    105     private Resolver resolver = new DefaultResolver();
    106
    107     // --------------------------------------------------------- Class Methods
    108
    109     /***
    110      * Return the PropertyUtils bean instance.
    111      * @return The PropertyUtils bean instance
    112      */
    113     protected static PropertyUtilsBean getInstance() {
    114         return BeanUtilsBean.getInstance().getPropertyUtils();
    115     }
    116
    117     // --------------------------------------------------------- Variables
    118
    119     /***
    120      * The cache of PropertyDescriptor arrays for beans we have already
    121      * introspected, keyed by the java.lang.Class of this object.
    122      */
    123     private FastHashMap descriptorsCache = null;
    124     private FastHashMap mappedDescriptorsCache = null;
    125     private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
    126     private static final Class[] LIST_CLASS_PARAMETER = new Class[] {java.util.List.class};
    127
    128     /*** Log instance */
    129     private Log log = LogFactory.getLog(PropertyUtils.class);
    130
    131     // ---------------------------------------------------------- Constructors
    132
    133     /*** Base constructor */
    134     public PropertyUtilsBean() {
    135         descriptorsCache = new FastHashMap();
    136         descriptorsCache.setFast(true);
    137         mappedDescriptorsCache = new FastHashMap();
    138         mappedDescriptorsCache.setFast(true);
    139     }
    140
    141
    142     // --------------------------------------------------------- Public Methods
    143
    144
    145     /***
    146      * Return the configured {@link Resolver} implementation used by BeanUtils.
    147      * <p>
    148      * The {@link Resolver} handles the <i>property name</i>
    149      * expressions and the implementation in use effectively
    150      * controls the dialect of the <i>expression language</i>
    151      * that BeanUtils recongnises.
    152      * <p>
    153      * {@link DefaultResolver} is the default implementation used.
    154      *
    155      * @return resolver The property expression resolver.
    156      */
    157     public Resolver getResolver() {
    158         return resolver;
    159     }
    160
    161     /***
    162      * Configure the {@link Resolver} implementation used by BeanUtils.
    163      * <p>
    164      * The {@link Resolver} handles the <i>property name</i>
    165      * expressions and the implementation in use effectively
    166      * controls the dialect of the <i>expression language</i>
    167      * that BeanUtils recongnises.
    168      * <p>
    169      * {@link DefaultResolver} is the default implementation used.
    170      *
    171      * @param resolver The property expression resolver.
    172      */
    173     public void setResolver(Resolver resolver) {
    174         if (resolver == null) {
    175             this.resolver = new DefaultResolver();
    176         } else {
    177             this.resolver = resolver;
    178         }
    179     }
    180
    181     /***
    182      * Clear any cached property descriptors information for all classes
    183      * loaded by any class loaders.  This is useful in cases where class
    184      * loaders are thrown away to implement class reloading.
    185      */
    186     public void clearDescriptors() {
    187
    188         descriptorsCache.clear();
    189         mappedDescriptorsCache.clear();
    190         Introspector.flushCaches();
    191
    192     }
    193
    194
    195     /***
    196      * <p>Copy property values from the "origin" bean to the "destination" bean
    197      * for all cases where the property names are the same (even though the
    198      * actual getter and setter methods might have been customized via
    199      * <code>BeanInfo</code> classes).  No conversions are performed on the
    200      * actual property values -- it is assumed that the values retrieved from
    201      * the origin bean are assignment-compatible with the types expected by
    202      * the destination bean.</p>
    203      *
    204      * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
    205      * to contain String-valued <strong>simple</strong> property names as the keys, pointing
    206      * at the corresponding property values that will be set in the destination
    207      * bean.<strong>Note</strong> that this method is intended to perform
    208      * a "shallow copy" of the properties and so complex properties
    209      * (for example, nested ones) will not be copied.</p>
    210      *
    211      * <p>Note, that this method will not copy a List to a List, or an Object[]
    212      * to an Object[]. It's specifically for copying JavaBean properties. </p>
    213      *
    214      * @param dest Destination bean whose properties are modified
    215      * @param orig Origin bean whose properties are retrieved
    216      *
    217      * @exception IllegalAccessException if the caller does not have
    218      *  access to the property accessor method
    219      * @exception IllegalArgumentException if the <code>dest</code> or
    220      *  <code>orig</code> argument is null
    221      * @exception InvocationTargetException if the property accessor method
    222      *  throws an exception
    223      * @exception NoSuchMethodException if an accessor method for this
    224      *  propety cannot be found
    225      */
    226     public void copyProperties(Object dest, Object orig)
    227             throws IllegalAccessException, InvocationTargetException,
    228             NoSuchMethodException {
    229
    230         if (dest == null) {
    231             throw new IllegalArgumentException
    232                     ("No destination bean specified");
    233         }
    234         if (orig == null) {
    235             throw new IllegalArgumentException("No origin bean specified");
    236         }
    237
    238         if (orig instanceof DynaBean) {
    239             DynaProperty[] origDescriptors =
    240                 ((DynaBean) orig).getDynaClass().getDynaProperties();
    241             for (int i = 0; i < origDescriptors.length; i++) {
    242                 String name = origDescriptors[i].getName();
    243                 if (isReadable(orig, name) && isWriteable(dest, name)) {
    244                     try {
    245                         Object value = ((DynaBean) orig).get(name);
    246                         if (dest instanceof DynaBean) {
    247                             ((DynaBean) dest).set(name, value);
    248                         } else {
    249                                 setSimpleProperty(dest, name, value);
    250                         }
    251                     } catch (NoSuchMethodException e) {
    252                         if (log.isDebugEnabled()) {
    253                             log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
    254                         }
    255                     }
    256                 }
    257             }
    258         } else if (orig instanceof Map) {
    259             Iterator names = ((Map) orig).keySet().iterator();
    260             while (names.hasNext()) {
    261                 String name = (String) names.next();
    262                 if (isWriteable(dest, name)) {
    263                     try {
    264                         Object value = ((Map) orig).get(name);
    265                         if (dest instanceof DynaBean) {
    266                             ((DynaBean) dest).set(name, value);
    267                         } else {
    268                             setSimpleProperty(dest, name, value);
    269                         }
    270                     } catch (NoSuchMethodException e) {
    271                         if (log.isDebugEnabled()) {
    272                             log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
    273                         }
    274                     }
    275                 }
    276             }
    277         } else /* if (orig is a standard JavaBean) */ {
    278             PropertyDescriptor[] origDescriptors =
    279                 getPropertyDescriptors(orig);
    280             for (int i = 0; i < origDescriptors.length; i++) {
    281                 String name = origDescriptors[i].getName();
    282                 if (isReadable(orig, name) && isWriteable(dest, name)) {
    283                     try {
    284                         Object value = getSimpleProperty(orig, name);
    285                         if (dest instanceof DynaBean) {
    286                             ((DynaBean) dest).set(name, value);
    287                         } else {
    288                                 setSimpleProperty(dest, name, value);
    289                         }
    290                     } catch (NoSuchMethodException e) {
    291                         if (log.isDebugEnabled()) {
    292                             log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
    293                         }
    294                     }
    295                 }
    296             }
    297         }
    298
    299     }
    300
    301
    302     /***
    303      * <p>Return the entire set of properties for which the specified bean
    304      * provides a read method.  This map contains the unconverted property
    305      * values for all properties for which a read method is provided
    306      * (i.e. where the <code>getReadMethod()</code> returns non-null).</p>
    307      *
    308      * <p><strong>FIXME</strong> - Does not account for mapped properties.</p>
    309      *
    310      * @param bean Bean whose properties are to be extracted
    311      * @return The set of properties for the bean
    312      *
    313      * @exception IllegalAccessException if the caller does not have
    314      *  access to the property accessor method
    315      * @exception IllegalArgumentException if <code>bean</code> is null
    316      * @exception InvocationTargetException if the property accessor method
    317      *  throws an exception
    318      * @exception NoSuchMethodException if an accessor method for this
    319      *  propety cannot be found
    320      */
    321     public Map describe(Object bean)
    322             throws IllegalAccessException, InvocationTargetException,
    323             NoSuchMethodException {
    324
    325         if (bean == null) {
    326             throw new IllegalArgumentException("No bean specified");
    327         }
    328         Map description = new HashMap();
    329         if (bean instanceof DynaBean) {
    330             DynaProperty[] descriptors =
    331                 ((DynaBean) bean).getDynaClass().getDynaProperties();
    332             for (int i = 0; i < descriptors.length; i++) {
    333                 String name = descriptors[i].getName();
    334                 description.put(name, getProperty(bean, name));
    335             }
    336         } else {
    337             PropertyDescriptor[] descriptors =
    338                 getPropertyDescriptors(bean);
    339             for (int i = 0; i < descriptors.length; i++) {
    340                 String name = descriptors[i].getName();
    341                 if (descriptors[i].getReadMethod() != null) {
    342                     description.put(name, getProperty(bean, name));
    343                 }
    344             }
    345         }
    346         return (description);
    347
    348     }
    349
    350
    351     /***
    352      * Return the value of the specified indexed property of the specified
    353      * bean, with no type conversions.  The zero-relative index of the
    354      * required value must be included (in square brackets) as a suffix to
    355      * the property name, or <code>IllegalArgumentException</code> will be
    356      * thrown.  In addition to supporting the JavaBeans specification, this
    357      * method has been extended to support <code>List</code> objects as well.
    358      *
    359      * @param bean Bean whose property is to be extracted
    360      * @param name <code>propertyname[index]</code> of the property value
    361      *  to be extracted
    362      * @return the indexed property value
    363      *
    364      * @exception IndexOutOfBoundsException if the specified index
    365      *  is outside the valid range for the underlying array or List
    366      * @exception IllegalAccessException if the caller does not have
    367      *  access to the property accessor method
    368      * @exception IllegalArgumentException if <code>bean</code> or
    369      *  <code>name</code> is null
    370      * @exception InvocationTargetException if the property accessor method
    371      *  throws an exception
    372      * @exception NoSuchMethodException if an accessor method for this
    373      *  propety cannot be found
    374      */
    375     public Object getIndexedProperty(Object bean, String name)
    376             throws IllegalAccessException, InvocationTargetException,
    377             NoSuchMethodException {
    378
    379         if (bean == null) {
    380             throw new IllegalArgumentException("No bean specified");
    381         }
    382         if (name == null) {
    383             throw new IllegalArgumentException("No name specified for bean class '" +
    384                     bean.getClass() + "'");
    385         }
    386
    387         // Identify the index of the requested individual property
    388         int index = -1;
    389         try {
    390             index = resolver.getIndex(name);
    391         } catch (IllegalArgumentException e) {
    392             throw new IllegalArgumentException("Invalid indexed property '" +
    393                     name + "' on bean class '" + bean.getClass() + "' " +
    394                     e.getMessage());
    395         }
    396         if (index < 0) {
    397             throw new IllegalArgumentException("Invalid indexed property '" +
    398                     name + "' on bean class '" + bean.getClass() + "'");
    399         }
    400
    401         // Isolate the name
    402         name = resolver.getProperty(name);
    403
    404         // Request the specified indexed property value
    405         return (getIndexedProperty(bean, name, index));
    406
    407     }
    408
    409
    410     /***
    411      * Return the value of the specified indexed property of the specified
    412      * bean, with no type conversions.  In addition to supporting the JavaBeans
    413      * specification, this method has been extended to support
    414      * <code>List</code> objects as well.
    415      *
    416      * @param bean Bean whose property is to be extracted
    417      * @param name Simple property name of the property value to be extracted
    418      * @param index Index of the property value to be extracted
    419      * @return the indexed property value
    420      *
    421      * @exception IndexOutOfBoundsException if the specified index
    422      *  is outside the valid range for the underlying property
    423      * @exception IllegalAccessException if the caller does not have
    424      *  access to the property accessor method
    425      * @exception IllegalArgumentException if <code>bean</code> or
    426      *  <code>name</code> is null
    427      * @exception InvocationTargetException if the property accessor method
    428      *  throws an exception
    429      * @exception NoSuchMethodException if an accessor method for this
    430      *  propety cannot be found
    431      */
    432     public Object getIndexedProperty(Object bean,
    433                                             String name, int index)
    434             throws IllegalAccessException, InvocationTargetException,
    435             NoSuchMethodException {
    436
    437         if (bean == null) {
    438             throw new IllegalArgumentException("No bean specified");
    439         }
    440         if (name == null || name.length() == 0) {
    441             if (bean.getClass().isArray()) {
    442                 return Array.get(bean, index);
    443             } else if (bean instanceof List) {
    444                 return ((List)bean).get(index);
    445             }
    446         }
    447         if (name == null) {
    448             throw new IllegalArgumentException("No name specified for bean class '" +
    449                     bean.getClass() + "'");
    450         }
    451
    452         // Handle DynaBean instances specially
    453         if (bean instanceof DynaBean) {
    454             DynaProperty descriptor =
    455                     ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    456             if (descriptor == null) {
    457                 throw new NoSuchMethodException("Unknown property '" +
    458                     name + "' on bean class '" + bean.getClass() + "'");
    459             }
    460             return (((DynaBean) bean).get(name, index));
    461         }
    462
    463         // Retrieve the property descriptor for the specified property
    464         PropertyDescriptor descriptor =
    465                 getPropertyDescriptor(bean, name);
    466         if (descriptor == null) {
    467             throw new NoSuchMethodException("Unknown property '" +
    468                     name + "' on bean class '" + bean.getClass() + "'");
    469         }
    470
    471         // Call the indexed getter method if there is one
    472         if (descriptor instanceof IndexedPropertyDescriptor) {
    473             Method readMethod = ((IndexedPropertyDescriptor) descriptor).
    474                     getIndexedReadMethod();
    475             readMethod = MethodUtils.getAccessibleMethod(readMethod);
    476             if (readMethod != null) {
    477                 Object[] subscript = new Object[1];
    478                 subscript[0] = new Integer(index);
    479                 try {
    480                     return (invokeMethod(readMethod,bean, subscript));
    481                 } catch (InvocationTargetException e) {
    482                     if (e.getTargetException() instanceof
    483                             IndexOutOfBoundsException) {
    484                         throw (IndexOutOfBoundsException)
    485                                 e.getTargetException();
    486                     } else {
    487                         throw e;
    488                     }
    489                 }
    490             }
    491         }
    492
    493         // Otherwise, the underlying property must be an array
    494         Method readMethod = getReadMethod(descriptor);
    495         if (readMethod == null) {
    496             throw new NoSuchMethodException("Property '" + name + "' has no " +
    497                     "getter method on bean class '" + bean.getClass() + "'");
    498         }
    499
    500         // Call the property getter and return the value
    501         Object value = invokeMethod(readMethod, bean, new Object[0]);
    502         if (!value.getClass().isArray()) {
    503             if (!(value instanceof java.util.List)) {
    504                 throw new IllegalArgumentException("Property '" + name +
    505                         "' is not indexed on bean class '" + bean.getClass() + "'");
    506             } else {
    507                 //get the List's value
    508                 return ((java.util.List) value).get(index);
    509             }
    510         } else {
    511             //get the array's value
    512             return (Array.get(value, index));
    513         }
    514
    515     }
    516
    517
    518     /***
    519      * Return the value of the specified mapped property of the
    520      * specified bean, with no type conversions.  The key of the
    521      * required value must be included (in brackets) as a suffix to
    522      * the property name, or <code>IllegalArgumentException</code> will be
    523      * thrown.
    524      *
    525      * @param bean Bean whose property is to be extracted
    526      * @param name <code>propertyname(key)</code> of the property value
    527      *  to be extracted
    528      * @return the mapped property value
    529      *
    530      * @exception IllegalAccessException if the caller does not have
    531      *  access to the property accessor method
    532      * @exception InvocationTargetException if the property accessor method
    533      *  throws an exception
    534      * @exception NoSuchMethodException if an accessor method for this
    535      *  propety cannot be found
    536      */
    537     public Object getMappedProperty(Object bean, String name)
    538             throws IllegalAccessException, InvocationTargetException,
    539             NoSuchMethodException {
    540
    541         if (bean == null) {
    542             throw new IllegalArgumentException("No bean specified");
    543         }
    544         if (name == null) {
    545             throw new IllegalArgumentException("No name specified for bean class '" +
    546                     bean.getClass() + "'");
    547         }
    548
    549         // Identify the key of the requested individual property
    550         String key  = null;
    551         try {
    552             key = resolver.getKey(name);
    553         } catch (IllegalArgumentException e) {
    554             throw new IllegalArgumentException
    555                     ("Invalid mapped property '" + name +
    556                     "' on bean class '" + bean.getClass() + "' " + e.getMessage());
    557         }
    558         if (key == null) {
    559             throw new IllegalArgumentException("Invalid mapped property '" +
    560                     name + "' on bean class '" + bean.getClass() + "'");
    561         }
    562
    563         // Isolate the name
    564         name = resolver.getProperty(name);
    565
    566         // Request the specified indexed property value
    567         return (getMappedProperty(bean, name, key));
    568
    569     }
    570
    571
    572     /***
    573      * Return the value of the specified mapped property of the specified
    574      * bean, with no type conversions.
    575      *
    576      * @param bean Bean whose property is to be extracted
    577      * @param name Mapped property name of the property value to be extracted
    578      * @param key Key of the property value to be extracted
    579      * @return the mapped property value
    580      *
    581      * @exception IllegalAccessException if the caller does not have
    582      *  access to the property accessor method
    583      * @exception InvocationTargetException if the property accessor method
    584      *  throws an exception
    585      * @exception NoSuchMethodException if an accessor method for this
    586      *  propety cannot be found
    587      */
    588     public Object getMappedProperty(Object bean,
    589                                            String name, String key)
    590             throws IllegalAccessException, InvocationTargetException,
    591             NoSuchMethodException {
    592
    593         if (bean == null) {
    594             throw new IllegalArgumentException("No bean specified");
    595         }
    596         if (name == null) {
    597             throw new IllegalArgumentException("No name specified for bean class '" +
    598                     bean.getClass() + "'");
    599         }
    600         if (key == null) {
    601             throw new IllegalArgumentException("No key specified for property '" +
    602                     name + "' on bean class " + bean.getClass() + "'");
    603         }
    604
    605         // Handle DynaBean instances specially
    606         if (bean instanceof DynaBean) {
    607             DynaProperty descriptor =
    608                     ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    609             if (descriptor == null) {
    610                 throw new NoSuchMethodException("Unknown property '" +
    611                         name + "'+ on bean class '" + bean.getClass() + "'");
    612             }
    613             return (((DynaBean) bean).get(name, key));
    614         }
    615
    616         Object result = null;
    617
    618         // Retrieve the property descriptor for the specified property
    619         PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    620         if (descriptor == null) {
    621             throw new NoSuchMethodException("Unknown property '" +
    622                     name + "'+ on bean class '" + bean.getClass() + "'");
    623         }
    624
    625         if (descriptor instanceof MappedPropertyDescriptor) {
    626             // Call the keyed getter method if there is one
    627             Method readMethod = ((MappedPropertyDescriptor) descriptor).
    628                     getMappedReadMethod();
    629             readMethod = MethodUtils.getAccessibleMethod(readMethod);
    630             if (readMethod != null) {
    631                 Object[] keyArray = new Object[1];
    632                 keyArray[0] = key;
    633                 result = invokeMethod(readMethod, bean, keyArray);
    634             } else {
    635                 throw new NoSuchMethodException("Property '" + name +
    636                         "' has no mapped getter method on bean class '" +
    637                         bean.getClass() + "'");
    638             }
    639         } else {
    640           /* means that the result has to be retrieved from a map */
    641           Method readMethod = getReadMethod(descriptor);
    642           if (readMethod != null) {
    643             Object invokeResult = invokeMethod(readMethod, bean, new Object[0]);
    644             /* test and fetch from the map */
    645             if (invokeResult instanceof java.util.Map) {
    646               result = ((java.util.Map)invokeResult).get(key);
    647             }
    648           } else {
    649             throw new NoSuchMethodException("Property '" + name +
    650                     "' has no mapped getter method on bean class '" +
    651                     bean.getClass() + "'");
    652           }
    653         }
    654         return result;
    655
    656     }
    657
    658
    659     /***
    660      * <p>Return the mapped property descriptors for this bean class.</p>
    661      *
    662      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    663      *
    664      * @param beanClass Bean class to be introspected
    665      * @return the mapped property descriptors
    666      * @deprecated This method should not be exposed
    667      */
    668     public FastHashMap getMappedPropertyDescriptors(Class beanClass) {
    669
    670         if (beanClass == null) {
    671             return null;
    672         }
    673
    674         // Look up any cached descriptors for this bean class
    675         return (FastHashMap) mappedDescriptorsCache.get(beanClass);
    676
    677     }
    678
    679
    680     /***
    681      * <p>Return the mapped property descriptors for this bean.</p>
    682      *
    683      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    684      *
    685      * @param bean Bean to be introspected
    686      * @return the mapped property descriptors
    687      * @deprecated This method should not be exposed
    688      */
    689     public FastHashMap getMappedPropertyDescriptors(Object bean) {
    690
    691         if (bean == null) {
    692             return null;
    693         }
    694         return (getMappedPropertyDescriptors(bean.getClass()));
    695
    696     }
    697
    698
    699     /***
    700      * Return the value of the (possibly nested) property of the specified
    701      * name, for the specified bean, with no type conversions.
    702      *
    703      * @param bean Bean whose property is to be extracted
    704      * @param name Possibly nested name of the property to be extracted
    705      * @return the nested property value
    706      *
    707      * @exception IllegalAccessException if the caller does not have
    708      *  access to the property accessor method
    709      * @exception IllegalArgumentException if <code>bean</code> or
    710      *  <code>name</code> is null
    711      * @exception NestedNullException if a nested reference to a
    712      *  property returns null
    713      * @exception InvocationTargetException
    714      * if the property accessor method throws an exception
    715      * @exception NoSuchMethodException if an accessor method for this
    716      *  propety cannot be found
    717      */
    718     public Object getNestedProperty(Object bean, String name)
    719             throws IllegalAccessException, InvocationTargetException,
    720             NoSuchMethodException {
    721
    722         if (bean == null) {
    723             throw new IllegalArgumentException("No bean specified");
    724         }
    725         if (name == null) {
    726             throw new IllegalArgumentException("No name specified for bean class '" +
    727                     bean.getClass() + "'");
    728         }
    729
    730         // Resolve nested references
    731         while (resolver.hasNested(name)) {
    732             String next = resolver.next(name);
    733             Object nestedBean = null;
    734             if (bean instanceof Map) {
    735                 nestedBean = getPropertyOfMapBean((Map) bean, next);
    736             } else if (resolver.isMapped(next)) {
    737                 nestedBean = getMappedProperty(bean, next);
    738             } else if (resolver.isIndexed(next)) {
    739                 nestedBean = getIndexedProperty(bean, next);
    740             } else {
    741                 nestedBean = getSimpleProperty(bean, next);
    742             }
    743             if (nestedBean == null) {
    744                 throw new NestedNullException
    745                         ("Null property value for '" + name +
    746                         "' on bean class '" + bean.getClass() + "'");
    747             }
    748             bean = nestedBean;
    749             name = resolver.remove(name);
    750         }
    751
    752         if (bean instanceof Map) {
    753             bean = getPropertyOfMapBean((Map) bean, name);
    754         } else if (resolver.isMapped(name)) {
    755             bean = getMappedProperty(bean, name);
    756         } else if (resolver.isIndexed(name)) {
    757             bean = getIndexedProperty(bean, name);
    758         } else {
    759             bean = getSimpleProperty(bean, name);
    760         }
    761         return bean;
    762
    763     }
    764
    765     /***
    766      * This method is called by getNestedProperty and setNestedProperty to
    767      * define what it means to get a property from an object which implements
    768      * Map. See setPropertyOfMapBean for more information.
    769      *
    770      * @param bean Map bean
    771      * @param propertyName The property name
    772      * @return the property value
    773      *
    774      * @throws IllegalArgumentException when the propertyName is regarded as
    775      * being invalid.
    776      *
    777      * @throws IllegalAccessException just in case subclasses override this
    778      * method to try to access real getter methods and find permission is denied.
    779      *
    780      * @throws InvocationTargetException just in case subclasses override this
    781      * method to try to access real getter methods, and find it throws an
    782      * exception when invoked.
    783      *
    784      * @throws NoSuchMethodException just in case subclasses override this
    785      * method to try to access real getter methods, and want to fail if
    786      * no simple method is available.
    787      */
    788     protected Object getPropertyOfMapBean(Map bean, String propertyName)
    789         throws IllegalArgumentException, IllegalAccessException,
    790         InvocationTargetException, NoSuchMethodException {
    791
    792         if (resolver.isMapped(propertyName)) {
    793             String name = resolver.getProperty(propertyName);
    794             if (name == null || name.length() == 0) {
    795                 propertyName = resolver.getKey(propertyName);
    796             }
    797         }
    798
    799         if (resolver.isIndexed(propertyName) ||
    800             resolver.isMapped(propertyName)) {
    801             throw new IllegalArgumentException(
    802                     "Indexed or mapped properties are not supported on"
    803                     + " objects of type Map: " + propertyName);
    804         }
    805
    806         return bean.get(propertyName);
    807     }
    808
    809
    810
    811     /***
    812      * Return the value of the specified property of the specified bean,
    813      * no matter which property reference format is used, with no
    814      * type conversions.
    815      *
    816      * @param bean Bean whose property is to be extracted
    817      * @param name Possibly indexed and/or nested name of the property
    818      *  to be extracted
    819      * @return the property value
    820      *
    821      * @exception IllegalAccessException if the caller does not have
    822      *  access to the property accessor method
    823      * @exception IllegalArgumentException if <code>bean</code> or
    824      *  <code>name</code> is null
    825      * @exception InvocationTargetException if the property accessor method
    826      *  throws an exception
    827      * @exception NoSuchMethodException if an accessor method for this
    828      *  propety cannot be found
    829      */
    830     public Object getProperty(Object bean, String name)
    831             throws IllegalAccessException, InvocationTargetException,
    832             NoSuchMethodException {
    833
    834         return (getNestedProperty(bean, name));
    835
    836     }
    837
    838
    839     /***
    840      * <p>Retrieve the property descriptor for the specified property of the
    841      * specified bean, or return <code>null</code> if there is no such
    842      * descriptor.  This method resolves indexed and nested property
    843      * references in the same manner as other methods in this class, except
    844      * that if the last (or only) name element is indexed, the descriptor
    845      * for the last resolved property itself is returned.</p>
    846      *
    847      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    848      *
    849      * @param bean Bean for which a property descriptor is requested
    850      * @param name Possibly indexed and/or nested name of the property for
    851      *  which a property descriptor is requested
    852      * @return the property descriptor
    853      *
    854      * @exception IllegalAccessException if the caller does not have
    855      *  access to the property accessor method
    856      * @exception IllegalArgumentException if <code>bean</code> or
    857      *  <code>name</code> is null
    858      * @exception IllegalArgumentException if a nested reference to a
    859      *  property returns null
    860      * @exception InvocationTargetException if the property accessor method
    861      *  throws an exception
    862      * @exception NoSuchMethodException if an accessor method for this
    863      *  propety cannot be found
    864      */
    865     public PropertyDescriptor getPropertyDescriptor(Object bean,
    866                                                            String name)
    867             throws IllegalAccessException, InvocationTargetException,
    868             NoSuchMethodException {
    869
    870         if (bean == null) {
    871             throw new IllegalArgumentException("No bean specified");
    872         }
    873         if (name == null) {
    874             throw new IllegalArgumentException("No name specified for bean class '" +
    875                     bean.getClass() + "'");
    876         }
    877
    878         // Resolve nested references
    879         while (resolver.hasNested(name)) {
    880             String next = resolver.next(name);
    881             Object nestedBean = null;
    882             if (bean instanceof Map) {
    883                 nestedBean = getPropertyOfMapBean((Map)bean, next);
    884             } else if (resolver.isMapped(next)) {
    885                 nestedBean = getMappedProperty(bean, next);
    886             } else if (resolver.isIndexed(next)) {
    887                 nestedBean = getIndexedProperty(bean, next);
    888             } else {
    889                 nestedBean = getSimpleProperty(bean, next);
    890             }
    891             if (nestedBean == null) {
    892                 throw new NestedNullException
    893                         ("Null property value for '" + name +
    894                         "' on bean class '" + bean.getClass() + "'");
    895             }
    896             bean = nestedBean;
    897             name = resolver.remove(name);
    898         }
    899
    900         // Remove any subscript from the final name value
    901         name = resolver.getProperty(name);
    902
    903         // Look up and return this property from our cache
    904         // creating and adding it to the cache if not found.
    905         if ((bean == null) || (name == null)) {
    906             return (null);
    907         }
    908
    909         PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
    910         if (descriptors != null) {
    911
    912             for (int i = 0; i < descriptors.length; i++) {
    913                 if (name.equals(descriptors[i].getName())) {
    914                     return (descriptors[i]);
    915                 }
    916             }
    917         }
    918
    919         PropertyDescriptor result = null;
    920         FastHashMap mappedDescriptors =
    921                 getMappedPropertyDescriptors(bean);
    922         if (mappedDescriptors == null) {
    923             mappedDescriptors = new FastHashMap();
    924             mappedDescriptors.setFast(true);
    925             mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors);
    926         }
    927         result = (PropertyDescriptor) mappedDescriptors.get(name);
    928         if (result == null) {
    929             // not found, try to create it
    930             try {
    931                 result = new MappedPropertyDescriptor(name, bean.getClass());
    932             } catch (IntrospectionException ie) {
    933                 /* Swallow IntrospectionException
    934                  * TODO: Why?
    935                  */
    936             }
    937             if (result != null) {
    938                 mappedDescriptors.put(name, result);
    939             }
    940         }
    941
    942         return result;
    943
    944     }
    945
    946
    947     /***
    948      * <p>Retrieve the property descriptors for the specified class,
    949      * introspecting and caching them the first time a particular bean class
    950      * is encountered.</p>
    951      *
    952      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    953      *
    954      * @param beanClass Bean class for which property descriptors are requested
    955      * @return the property descriptors
    956      *
    957      * @exception IllegalArgumentException if <code>beanClass</code> is null
    958      */
    959     public PropertyDescriptor[]
    960             getPropertyDescriptors(Class beanClass) {
    961
    962         if (beanClass == null) {
    963             throw new IllegalArgumentException("No bean class specified");
    964         }
    965
    966         // Look up any cached descriptors for this bean class
    967         PropertyDescriptor[] descriptors = null;
    968         descriptors =
    969                 (PropertyDescriptor[]) descriptorsCache.get(beanClass);
    970         if (descriptors != null) {
    971             return (descriptors);
    972         }
    973
    974         // Introspect the bean and cache the generated descriptors
    975         BeanInfo beanInfo = null;
    976         try {
    977             beanInfo = Introspector.getBeanInfo(beanClass);
    978         } catch (IntrospectionException e) {
    979             return (new PropertyDescriptor[0]);
    980         }
    981         descriptors = beanInfo.getPropertyDescriptors();
    982         if (descriptors == null) {
    983             descriptors = new PropertyDescriptor[0];
    984         }
    985
    986         // ----------------- Workaround for Bug 28358 --------- START ------------------
    987         //
    988         // The following code fixes an issue where IndexedPropertyDescriptor behaves
    989         // Differently in different versions of the JDK for 'indexed' properties which
    990         // use java.util.List (rather than an array).
    991         //
    992         // If you have a Bean with the following getters/setters for an indexed property:
    993         //
    994         //     public List getFoo()
    995         //     public Object getFoo(int index)
    996         //     public void setFoo(List foo)
    997         //     public void setFoo(int index, Object foo)
    998         //
    999         // then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod()
    1000         // behave as follows:
    1001         //
    1002         //     JDK 1.3.1_04: returns valid Method objects from these methods.
    1003         //     JDK 1.4.2_05: returns null from these methods.
    1004         //
    1005         for (int i = 0; i < descriptors.length; i++) {
    1006             if (descriptors[i] instanceof IndexedPropertyDescriptor) {
    1007                 IndexedPropertyDescriptor descriptor =  (IndexedPropertyDescriptor)descriptors[i];
    1008                 String propName = descriptor.getName().substring(0, 1).toUpperCase() +
    1009                                   descriptor.getName().substring(1);
    1010
    1011                 if (descriptor.getReadMethod() == null) {
    1012                     String methodName = descriptor.getIndexedReadMethod() != null
    1013                                         ? descriptor.getIndexedReadMethod().getName()
    1014                                         : "get" + propName;
    1015                     Method readMethod = MethodUtils.getMatchingAccessibleMethod(beanClass,
    1016                                                             methodName,
    1017                                                             EMPTY_CLASS_PARAMETERS);
    1018                     if (readMethod != null) {
    1019                         try {
    1020                             descriptor.setReadMethod(readMethod);
    1021                         } catch(Exception e) {
    1022                             log.error("Error setting indexed property read method", e);
    1023                         }
    1024                     }
    1025                 }
    1026                 if (descriptor.getWriteMethod() == null) {
    1027                     String methodName = descriptor.getIndexedWriteMethod() != null
    1028                                       ? descriptor.getIndexedWriteMethod().getName()
    1029                                       : "set" + propName;
    1030                     Method writeMethod = MethodUtils.getMatchingAccessibleMethod(beanClass,
    1031                                                             methodName,
    1032                                                             LIST_CLASS_PARAMETER);
    1033                     if (writeMethod == null) {
    1034                         Method[] methods = beanClass.getMethods();
    1035                         for (int j = 0; j < methods.length; j++) {
    1036                             if (methods[j].getName().equals(methodName)) {
    1037                                 Class[] parameterTypes = methods[j].getParameterTypes();
    1038                                 if (parameterTypes.length == 1 &&
    1039                                     List.class.isAssignableFrom(parameterTypes[0])) {
    1040                                     writeMethod = methods[j];
    1041                                     break;
    1042                                 }
    1043                             }
    1044                         }
    1045                     }
    1046                     if (writeMethod != null) {
    1047                         try {
    1048                             descriptor.setWriteMethod(writeMethod);
    1049                         } catch(Exception e) {
    1050                             log.error("Error setting indexed property write method", e);
    1051                         }
    1052                     }
    1053                 }
    1054             }
    1055         }
    1056         // ----------------- Workaround for Bug 28358 ---------- END -------------------
    1057
    1058         descriptorsCache.put(beanClass, descriptors);
    1059         return (descriptors);
    1060
    1061     }
    1062
    1063
    1064     /***
    1065      * <p>Retrieve the property descriptors for the specified bean,
    1066      * introspecting and caching them the first time a particular bean class
    1067      * is encountered.</p>
    1068      *
    1069      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1070      *
    1071      * @param bean Bean for which property descriptors are requested
    1072      * @return the property descriptors
    1073      *
    1074      * @exception IllegalArgumentException if <code>bean</code> is null
    1075      */
    1076     public PropertyDescriptor[] getPropertyDescriptors(Object bean) {
    1077
    1078         if (bean == null) {
    1079             throw new IllegalArgumentException("No bean specified");
    1080         }
    1081         return (getPropertyDescriptors(bean.getClass()));
    1082
    1083     }
    1084
    1085
    1086     /***
    1087      * <p>Return the Java Class repesenting the property editor class that has
    1088      * been registered for this property (if any).  This method follows the
    1089      * same name resolution rules used by <code>getPropertyDescriptor()</code>,
    1090      * so if the last element of a name reference is indexed, the property
    1091      * editor for the underlying property's class is returned.</p>
    1092      *
    1093      * <p>Note that <code>null</code> will be returned if there is no property,
    1094      * or if there is no registered property editor class.  Because this
    1095      * return value is ambiguous, you should determine the existence of the
    1096      * property itself by other means.</p>
    1097      *
    1098      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1099      *
    1100      * @param bean Bean for which a property descriptor is requested
    1101      * @param name Possibly indexed and/or nested name of the property for
    1102      *  which a property descriptor is requested
    1103      * @return the property editor class
    1104      *
    1105      * @exception IllegalAccessException if the caller does not have
    1106      *  access to the property accessor method
    1107      * @exception IllegalArgumentException if <code>bean</code> or
    1108      *  <code>name</code> is null
    1109      * @exception IllegalArgumentException if a nested reference to a
    1110      *  property returns null
    1111      * @exception InvocationTargetException if the property accessor method
    1112      *  throws an exception
    1113      * @exception NoSuchMethodException if an accessor method for this
    1114      *  propety cannot be found
    1115      */
    1116     public Class getPropertyEditorClass(Object bean, String name)
    1117             throws IllegalAccessException, InvocationTargetException,
    1118             NoSuchMethodException {
    1119
    1120         if (bean == null) {
    1121             throw new IllegalArgumentException("No bean specified");
    1122         }
    1123         if (name == null) {
    1124             throw new IllegalArgumentException("No name specified for bean class '" +
    1125                     bean.getClass() + "'");
    1126         }
    1127
    1128         PropertyDescriptor descriptor =
    1129                 getPropertyDescriptor(bean, name);
    1130         if (descriptor != null) {
    1131             return (descriptor.getPropertyEditorClass());
    1132         } else {
    1133             return (null);
    1134         }
    1135
    1136     }
    1137
    1138
    1139     /***
    1140      * Return the Java Class representing the property type of the specified
    1141      * property, or <code>null</code> if there is no such property for the
    1142      * specified bean.  This method follows the same name resolution rules
    1143      * used by <code>getPropertyDescriptor()</code>, so if the last element
    1144      * of a name reference is indexed, the type of the property itself will
    1145      * be returned.  If the last (or only) element has no property with the
    1146      * specified name, <code>null</code> is returned.
    1147      *
    1148      * @param bean Bean for which a property descriptor is requested
    1149      * @param name Possibly indexed and/or nested name of the property for
    1150      *  which a property descriptor is requested
    1151      * @return The property type
    1152      *
    1153      * @exception IllegalAccessException if the caller does not have
    1154      *  access to the property accessor method
    1155      * @exception IllegalArgumentException if <code>bean</code> or
    1156      *  <code>name</code> is null
    1157      * @exception IllegalArgumentException if a nested reference to a
    1158      *  property returns null
    1159      * @exception InvocationTargetException if the property accessor method
    1160      *  throws an exception
    1161      * @exception NoSuchMethodException if an accessor method for this
    1162      *  propety cannot be found
    1163      */
    1164     public Class getPropertyType(Object bean, String name)
    1165             throws IllegalAccessException, InvocationTargetException,
    1166             NoSuchMethodException {
    1167
    1168         if (bean == null) {
    1169             throw new IllegalArgumentException("No bean specified");
    1170         }
    1171         if (name == null) {
    1172             throw new IllegalArgumentException("No name specified for bean class '" +
    1173                     bean.getClass() + "'");
    1174         }
    1175
    1176         // Special handling for DynaBeans
    1177         if (bean instanceof DynaBean) {
    1178             DynaProperty descriptor =
    1179                     ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1180             if (descriptor == null) {
    1181                 return (null);
    1182             }
    1183             Class type = descriptor.getType();
    1184             if (type == null) {
    1185                 return (null);
    1186             } else if (type.isArray()) {
    1187                 return (type.getComponentType());
    1188             } else {
    1189                 return (type);
    1190             }
    1191         }
    1192
    1193         PropertyDescriptor descriptor =
    1194                 getPropertyDescriptor(bean, name);
    1195         if (descriptor == null) {
    1196             return (null);
    1197         } else if (descriptor instanceof IndexedPropertyDescriptor) {
    1198             return (((IndexedPropertyDescriptor) descriptor).
    1199                     getIndexedPropertyType());
    1200         } else if (descriptor instanceof MappedPropertyDescriptor) {
    1201             return (((MappedPropertyDescriptor) descriptor).
    1202                     getMappedPropertyType());
    1203         } else {
    1204             return (descriptor.getPropertyType());
    1205         }
    1206
    1207     }
    1208
    1209
    1210     /***
    1211      * <p>Return an accessible property getter method for this property,
    1212      * if there is one; otherwise return <code>null</code>.</p>
    1213      *
    1214      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1215      *
    1216      * @param descriptor Property descriptor to return a getter for
    1217      * @return The read method
    1218      */
    1219     public Method getReadMethod(PropertyDescriptor descriptor) {
    1220
    1221         return (MethodUtils.getAccessibleMethod(descriptor.getReadMethod()));
    1222
    1223     }
    1224
    1225
    1226     /***
    1227      * Return the value of the specified simple property of the specified
    1228      * bean, with no type conversions.
    1229      *
    1230      * @param bean Bean whose property is to be extracted
    1231      * @param name Name of the property to be extracted
    1232      * @return The property value
    1233      *
    1234      * @exception IllegalAccessException if the caller does not have
    1235      *  access to the property accessor method
    1236      * @exception IllegalArgumentException if <code>bean</code> or
    1237      *  <code>name</code> is null
    1238      * @exception IllegalArgumentException if the property name
    1239      *  is nested or indexed
    1240      * @exception InvocationTargetException if the property accessor method
    1241      *  throws an exception
    1242      * @exception NoSuchMethodException if an accessor method for this
    1243      *  propety cannot be found
    1244      */
    1245     public Object getSimpleProperty(Object bean, String name)
    1246             throws IllegalAccessException, InvocationTargetException,
    1247             NoSuchMethodException {
    1248
    1249         if (bean == null) {
    1250             throw new IllegalArgumentException("No bean specified");
    1251         }
    1252         if (name == null) {
    1253             throw new IllegalArgumentException("No name specified for bean class '" +
    1254                     bean.getClass() + "'");
    1255         }
    1256
    1257         // Validate the syntax of the property name
    1258         if (resolver.hasNested(name)) {
    1259             throw new IllegalArgumentException
    1260                     ("Nested property names are not allowed: Property '" +
    1261                     name + "' on bean class '" + bean.getClass() + "'");
    1262         } else if (resolver.isIndexed(name)) {
    1263             throw new IllegalArgumentException
    1264                     ("Indexed property names are not allowed: Property '" +
    1265                     name + "' on bean class '" + bean.getClass() + "'");
    1266         } else if (resolver.isMapped(name)) {
    1267             throw new IllegalArgumentException
    1268                     ("Mapped property names are not allowed: Property '" +
    1269                     name + "' on bean class '" + bean.getClass() + "'");
    1270         }
    1271
    1272         // Handle DynaBean instances specially
    1273         if (bean instanceof DynaBean) {
    1274             DynaProperty descriptor =
    1275                     ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1276             if (descriptor == null) {
    1277                 throw new NoSuchMethodException("Unknown property '" +
    1278                         name + "' on dynaclass '" +
    1279                         ((DynaBean) bean).getDynaClass() + "'" );
    1280             }
    1281             return (((DynaBean) bean).get(name));
    1282         }
    1283
    1284         // Retrieve the property getter method for the specified property
    1285         PropertyDescriptor descriptor =
    1286                 getPropertyDescriptor(bean, name);
    1287         if (descriptor == null) {
    1288             throw new NoSuchMethodException("Unknown property '" +
    1289                     name + "' on class '" + bean.getClass() + "'" );
    1290         }
    1291         Method readMethod = getReadMethod(descriptor);
    1292         if (readMethod == null) {
    1293             throw new NoSuchMethodException("Property '" + name +
    1294                     "' has no getter method in class '" + bean.getClass() + "'");
    1295         }
    1296
    1297         // Call the property getter and return the value
    1298         Object value = invokeMethod(readMethod, bean, new Object[0]);
    1299         return (value);
    1300
    1301     }
    1302
    1303
    1304     /***
    1305      * <p>Return an accessible property setter method for this property,
    1306      * if there is one; otherwise return <code>null</code>.</p>
    1307      *
    1308      * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1309      *
    1310      * @param descriptor Property descriptor to return a setter for
    1311      * @return The write method
    1312      */
    1313     public Method getWriteMethod(PropertyDescriptor descriptor) {
    1314
    1315         return (MethodUtils.getAccessibleMethod(descriptor.getWriteMethod()));
    1316
    1317     }
    1318
    1319
    1320     /***
    1321      * <p>Return <code>true</code> if the specified property name identifies
    1322      * a readable property on the specified bean; otherwise, return
    1323      * <code>false</code>.
    1324      *
    1325      * @param bean Bean to be examined (may be a {@link DynaBean}
    1326      * @param name Property name to be evaluated
    1327      * @return <code>true</code> if the property is readable,
    1328      * otherwise <code>false</code>
    1329      *
    1330      * @exception IllegalArgumentException if <code>bean</code>
    1331      *  or <code>name</code> is <code>null</code>
    1332      *
    1333      * @since BeanUtils 1.6
    1334      */
    1335     public boolean isReadable(Object bean, String name) {
    1336
    1337         // Validate method parameters
    1338         if (bean == null) {
    1339             throw new IllegalArgumentException("No bean specified");
    1340         }
    1341         if (name == null) {
    1342             throw new IllegalArgumentException("No name specified for bean class '" +
    1343                     bean.getClass() + "'");
    1344         }
    1345
    1346         // Treat WrapDynaBean as special case - may be a write-only property
    1347         // (see Jira issue# BEANUTILS-61)
    1348         if (bean instanceof WrapDynaBean) {
    1349             bean = ((WrapDynaBean)bean).getInstance();
    1350         }
    1351
    1352         // Return the requested result
    1353         if (bean instanceof DynaBean) {
    1354             // All DynaBean properties are readable
    1355             return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
    1356         } else {
    1357             try {
    1358                 PropertyDescriptor desc =
    1359                     getPropertyDescriptor(bean, name);
    1360                 if (desc != null) {
    1361                     Method readMethod = getReadMethod(desc);
    1362                     if (readMethod == null) {
    1363                         if (desc instanceof IndexedPropertyDescriptor) {
    1364                             readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
    1365                         } else if (desc instanceof MappedPropertyDescriptor) {
    1366                             readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
    1367                         }
    1368                         readMethod = MethodUtils.getAccessibleMethod(readMethod);
    1369                     }
    1370                     return (readMethod != null);
    1371                 } else {
    1372                     return (false);
    1373                 }
    1374             } catch (IllegalAccessException e) {
    1375                 return (false);
    1376             } catch (InvocationTargetException e) {
    1377                 return (false);
    1378             } catch (NoSuchMethodException e) {
    1379                 return (false);
    1380             }
    1381         }
    1382
    1383     }
    1384
    1385
    1386     /***
    1387      * <p>Return <code>true</code> if the specified property name identifies
    1388      * a writeable property on the specified bean; otherwise, return
    1389      * <code>false</code>.
    1390      *
    1391      * @param bean Bean to be examined (may be a {@link DynaBean}
    1392      * @param name Property name to be evaluated
    1393      * @return <code>true</code> if the property is writeable,
    1394      * otherwise <code>false</code>
    1395      *
    1396      * @exception IllegalArgumentException if <code>bean</code>
    1397      *  or <code>name</code> is <code>null</code>
    1398      *
    1399      * @since BeanUtils 1.6
    1400      */
    1401     public boolean isWriteable(Object bean, String name) {
    1402
    1403         // Validate method parameters
    1404         if (bean == null) {
    1405             throw new IllegalArgumentException("No bean specified");
    1406         }
    1407         if (name == null) {
    1408             throw new IllegalArgumentException("No name specified for bean class '" +
    1409                     bean.getClass() + "'");
    1410         }
    1411
    1412         // Treat WrapDynaBean as special case - may be a read-only property
    1413         // (see Jira issue# BEANUTILS-61)
    1414         if (bean instanceof WrapDynaBean) {
    1415             bean = ((WrapDynaBean)bean).getInstance();
    1416         }
    1417
    1418         // Return the requested result
    1419         if (bean instanceof DynaBean) {
    1420             // All DynaBean properties are writeable
    1421             return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
    1422         } else {
    1423             try {
    1424                 PropertyDescriptor desc =
    1425                     getPropertyDescriptor(bean, name);
    1426                 if (desc != null) {
    1427                     Method writeMethod = getWriteMethod(desc);
    1428                     if (writeMethod == null) {
    1429                         if (desc instanceof IndexedPropertyDescriptor) {
    1430                             writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
    1431                         } else if (desc instanceof MappedPropertyDescriptor) {
    1432                             writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
    1433                         }
    1434                         writeMethod = MethodUtils.getAccessibleMethod(writeMethod);
    1435                     }
    1436                     return (writeMethod != null);
    1437                 } else {
    1438                     return (false);
    1439                 }
    1440             } catch (IllegalAccessException e) {
    1441                 return (false);
    1442             } catch (InvocationTargetException e) {
    1443                 return (false);
    1444             } catch (NoSuchMethodException e) {
    1445                 return (false);
    1446             }
    1447         }
    1448
    1449     }
    1450
    1451
    1452     /***
    1453      * Set the value of the specified indexed property of the specified
    1454      * bean, with no type conversions.  The zero-relative index of the
    1455      * required value must be included (in square brackets) as a suffix to
    1456      * the property name, or <code>IllegalArgumentException</code> will be
    1457      * thrown.  In addition to supporting the JavaBeans specification, this
    1458      * method has been extended to support <code>List</code> objects as well.
    1459      *
    1460      * @param bean Bean whose property is to be modified
    1461      * @param name <code>propertyname[index]</code> of the property value
    1462      *  to be modified
    1463      * @param value Value to which the specified property element
    1464      *  should be set
    1465      *
    1466      * @exception IndexOutOfBoundsException if the specified index
    1467      *  is outside the valid range for the underlying property
    1468      * @exception IllegalAccessException if the caller does not have
    1469      *  access to the property accessor method
    1470      * @exception IllegalArgumentException if <code>bean</code> or
    1471      *  <code>name</code> is null
    1472      * @exception InvocationTargetException if the property accessor method
    1473      *  throws an exception
    1474      * @exception NoSuchMethodException if an accessor method for this
    1475      *  propety cannot be found
    1476      */
    1477     public void setIndexedProperty(Object bean, String name,
    1478                                           Object value)
    1479             throws IllegalAccessException, InvocationTargetException,
    1480             NoSuchMethodException {
    1481
    1482         if (bean == null) {
    1483             throw new IllegalArgumentException("No bean specified");
    1484         }
    1485         if (name == null) {
    1486             throw new IllegalArgumentException("No name specified for bean class '" +
    1487                     bean.getClass() + "'");
    1488         }
    1489
    1490         // Identify the index of the requested individual property
    1491         int index = -1;
    1492         try {
    1493             index = resolver.getIndex(name);
    1494         } catch (IllegalArgumentException e) {
    1495             throw new IllegalArgumentException("Invalid indexed property '" +
    1496                     name + "' on bean class '" + bean.getClass() + "'");
    1497         }
    1498         if (index < 0) {
    1499             throw new IllegalArgumentException("Invalid indexed property '" +
    1500                     name + "' on bean class '" + bean.getClass() + "'");
    1501         }
    1502
    1503         // Isolate the name
    1504         name = resolver.getProperty(name);
    1505
    1506         // Set the specified indexed property value
    1507         setIndexedProperty(bean, name, index, value);
    1508
    1509     }
    1510
    1511
    1512     /***
    1513      * Set the value of the specified indexed property of the specified
    1514      * bean, with no type conversions.  In addition to supporting the JavaBeans
    1515      * specification, this method has been extended to support
    1516      * <code>List</code> objects as well.
    1517      *
    1518      * @param bean Bean whose property is to be set
    1519      * @param name Simple property name of the property value to be set
    1520      * @param index Index of the property value to be set
    1521      * @param value Value to which the indexed property element is to be set
    1522      *
    1523      * @exception IndexOutOfBoundsException if the specified index
    1524      *  is outside the valid range for the underlying property
    1525      * @exception IllegalAccessException if the caller does not have
    1526      *  access to the property accessor method
    1527      * @exception IllegalArgumentException if <code>bean</code> or
    1528      *  <code>name</code> is null
    1529      * @exception InvocationTargetException if the property accessor method
    1530      *  throws an exception
    1531      * @exception NoSuchMethodException if an accessor method for this
    1532      *  propety cannot be found
    1533      */
    1534     public void setIndexedProperty(Object bean, String name,
    1535                                           int index, Object value)
    1536             throws IllegalAccessException, InvocationTargetException,
    1537             NoSuchMethodException {
    1538
    1539         if (bean == null) {
    1540             throw new IllegalArgumentException("No bean specified");
    1541         }
    1542         if (name == null || name.length() == 0) {
    1543             if (bean.getClass().isArray()) {
    1544                 Array.set(bean, index, value);
    1545                 return;
    1546             } else if (bean instanceof List) {
    1547                 ((List)bean).set(index, value);
    1548                 return;
    1549             }
    1550         }
    1551         if (name == null) {
    1552             throw new IllegalArgumentException("No name specified for bean class '" +
    1553                     bean.getClass() + "'");
    1554         }
    1555
    1556         // Handle DynaBean instances specially
    1557         if (bean instanceof DynaBean) {
    1558             DynaProperty descriptor =
    1559                     ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1560             if (descriptor == null) {
    1561                 throw new NoSuchMethodException("Unknown property '" +
    1562                         name + "' on bean class '" + bean.getClass() + "'");
    1563             }
    1564             ((DynaBean) bean).set(name, index, value);
    1565             return;
    1566         }
    1567
    1568         // Retrieve the property descriptor for the specified property
    1569         PropertyDescriptor descriptor =
    1570                 getPropertyDescriptor(bean, name);
    1571         if (descriptor == null) {
    1572             throw new NoSuchMethodException("Unknown property '" +
    1573                     name + "' on bean class '" + bean.getClass() + "'");
    1574         }
    1575
    1576         // Call the indexed setter method if there is one
    1577         if (descriptor instanceof IndexedPropertyDescriptor) {
    1578             Method writeMethod = ((IndexedPropertyDescriptor) descriptor).
    1579                     getIndexedWriteMethod();
    1580             writeMethod = MethodUtils.getAccessibleMethod(writeMethod);
    1581             if (writeMethod != null) {
    1582                 Object[] subscript = new Object[2];
    1583                 subscript[0] = new Integer(index);
    1584                 subscript[1] = value;
    1585                 try {
    1586                     if (log.isTraceEnabled()) {
    1587                         String valueClassName =
    1588                             value == null ? "<null>"
    1589                                           : value.getClass().getName();
    1590                         log.trace("setSimpleProperty: Invoking method "
    1591                                   + writeMethod +" with index=" + index
    1592                                   + ", value=" + value
    1593                                   + " (class " + valueClassName+ ")");
    1594                     }
    1595                     invokeMethod(writeMethod, bean, subscript);
    1596                 } catch (InvocationTargetException e) {
    1597                     if (e.getTargetException() instanceof
    1598                             IndexOutOfBoundsException) {
    1599                         throw (IndexOutOfBoundsException)
    1600                                 e.getTargetException();
    1601                     } else {
    1602                         throw e;
    1603                     }
    1604                 }
    1605                 return;
    1606             }
    1607         }
    1608
    1609         // Otherwise, the underlying property must be an array or a list
    1610         Method readMethod = getReadMethod(descriptor);
    1611         if (readMethod == null) {
    1612             throw new NoSuchMethodException("Property '" + name +
    1613                     "' has no getter method on bean class '" + bean.getClass() + "'");
    1614         }
    1615
    1616         // Call the property getter to get the array or list
    1617         Object array = invokeMethod(readMethod, bean, new Object[0]);
    1618         if (!array.getClass().isArray()) {
    1619             if (array instanceof List) {
    1620                 // Modify the specified value in the List
    1621                 ((List) array).set(index, value);
    1622             } else {
    1623                 throw new IllegalArgumentException("Property '" + name +
    1624                         "' is not indexed on bean class '" + bean.getClass() + "'");
    1625             }
    1626         } else {
    1627             // Modify the specified value in the array
    1628             Array.set(array, index, value);
    1629         }
    1630
    1631     }
    1632
    1633
    1634     /***
    1635      * Set the value of the specified mapped property of the
    1636      * specified bean, with no type conversions.  The key of the
    1637      * value to set must be included (in brackets) as a suffix to
    1638      * the property name, or <code>IllegalArgumentException</code> will be
    1639      * thrown.
    1640      *
    1641      * @param bean Bean whose property is to be set
    1642      * @param name <code>propertyname(key)</code> of the property value
    1643      *  to be set
    1644      * @param value The property value to be set
    1645      *
    1646      * @exception IllegalAccessException if the caller does not have
    1647      *  access to the property accessor method
    1648      * @exception InvocationTargetException if the property accessor method
    1649      *  throws an exception
    1650      * @exception NoSuchMethodException if an accessor method for this
    1651      *  propety cannot be found
    1652      */
    1653     public void setMappedProperty(Object bean, String name,
    1654                                          Object value)
    1655             throws IllegalAccessException, InvocationTargetException,
    1656             NoSuchMethodException {
    1657
    1658         if (bean == null) {
    1659             throw new IllegalArgumentException("No bean specified");
    1660         }
    1661         if (name == null) {
    1662             throw new IllegalArgumentException("No name specified for bean class '" +
    1663                     bean.getClass() + "'");
    1664         }
    1665
    1666         // Identify the key of the requested individual property
    1667         String key  = null;
    1668         try {
    1669             key = resolver.getKey(name);
    1670         } catch (IllegalArgumentException e) {
    1671             throw new IllegalArgumentException
    1672                     ("Invalid mapped property '" + name +
    1673                     "' on bean class '" + bean.getClass() + "'");
    1674         }
    1675         if (key == null) {
    1676             throw new IllegalArgumentException
    1677                     ("Invalid mapped property '" + name +
    1678                     "' on bean class '" + bean.getClass() + "'");
    1679         }
    1680
    1681         // Isolate the name
    1682         name = resolver.getProperty(name);
    1683
    1684         // Request the specified indexed property value
    1685         setMappedProperty(bean, name, key, value);
    1686
    1687     }
    1688
    1689
    1690     /***
    1691      * Set the value of the specified mapped property of the specified
    1692      * bean, with no type conversions.
    1693      *
    1694      * @param bean Bean whose property is to be set
    1695      * @param name Mapped property name of the property value to be set
    1696      * @param key Key of the property value to be set
    1697      * @param value The property value to be set
    1698      *
    1699      * @exception IllegalAccessException if the caller does not have
    1700      *  access to the property accessor method
    1701      * @exception InvocationTargetException if the property accessor method
    1702      *  throws an exception
    1703      * @exception NoSuchMethodException if an accessor method for this
    1704      *  propety cannot be found
    1705      */
    1706     public void setMappedProperty(Object bean, String name,
    1707                                          String key, Object value)
    1708             throws IllegalAccessException, InvocationTargetException,
    1709             NoSuchMethodException {
    1710
    1711         if (bean == null) {
    1712             throw new IllegalArgumentException("No bean specified");
    1713         }
    1714         if (name == null) {
    1715             throw new IllegalArgumentException("No name specified for bean class '" +
    1716                     bean.getClass() + "'");
    1717         }
    1718         if (key == null) {
    1719             throw new IllegalArgumentException("No key specified for property '" +
    1720                     name + "' on bean class '" + bean.getClass() + "'");
    1721         }
    1722
    1723         // Handle DynaBean instances specially
    1724         if (bean instanceof DynaBean) {
    1725             DynaProperty descriptor =
    1726                     ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1727             if (descriptor == null) {
    1728                 throw new NoSuchMethodException("Unknown property '" +
    1729                         name + "' on bean class '" + bean.getClass() + "'");
    1730             }
    1731             ((DynaBean) bean).set(name, key, value);
    1732             return;
    1733         }
    1734
    1735         // Retrieve the property descriptor for the specified property
    1736         PropertyDescriptor descriptor =
    1737                 getPropertyDescriptor(bean, name);
    1738         if (descriptor == null) {
    1739             throw new NoSuchMethodException("Unknown property '" +
    1740                     name + "' on bean class '" + bean.getClass() + "'");
    1741         }
    1742
    1743         if (descriptor instanceof MappedPropertyDescriptor) {
    1744             // Call the keyed setter method if there is one
    1745             Method mappedWriteMethod =
    1746                     ((MappedPropertyDescriptor) descriptor).
    1747                     getMappedWriteMethod();
    1748             mappedWriteMethod = MethodUtils.getAccessibleMethod(mappedWriteMethod);
    1749             if (mappedWriteMethod != null) {
    1750                 Object[] params = new Object[2];
    1751                 params[0] = key;
    1752                 params[1] = value;
    1753                 if (log.isTraceEnabled()) {
    1754                     String valueClassName =
    1755                         value == null ? "<null>" : value.getClass().getName();
    1756                     log.trace("setSimpleProperty: Invoking method "
    1757                               + mappedWriteMethod + " with key=" + key
    1758                               + ", value=" + value
    1759                               + " (class " + valueClassName +")");
    1760                 }
    1761                 invokeMethod(mappedWriteMethod, bean, params);
    1762             } else {
    1763                 throw new NoSuchMethodException
    1764                     ("Property '" + name + "' has no mapped setter method" +
    1765                      "on bean class '" + bean.getClass() + "'");
    1766             }
    1767         } else {
    1768           /* means that the result has to be retrieved from a map */
    1769           Method readMethod = getReadMethod(descriptor);
    1770           if (readMethod != null) {
    1771             Object invokeResult = invokeMethod(readMethod, bean, new Object[0]);
    1772             /* test and fetch from the map */
    1773             if (invokeResult instanceof java.util.Map) {
    1774               ((java.util.Map)invokeResult).put(key, value);
    1775             }
    1776           } else {
    1777             throw new NoSuchMethodException("Property '" + name +
    1778                     "' has no mapped getter method on bean class '" +
    1779                     bean.getClass() + "'");
    1780           }
    1781         }
    1782
    1783     }
    1784
    1785
    1786     /***
    1787      * Set the value of the (possibly nested) property of the specified
    1788      * name, for the specified bean, with no type conversions.
    1789      * <p>
    1790      * Example values for parameter "name" are:
    1791      * <ul>
    1792      * <li> "a" -- sets the value of property a of the specified bean </li>
    1793      * <li> "a.b" -- gets the value of property a of the specified bean,
    1794      * then on that object sets the value of property b.</li>
    1795      * <li> "a(key)" -- sets a value of mapped-property a on the specified
    1796      * bean. This effectively means bean.setA("key").</li>
    1797      * <li> "a[3]" -- sets a value of indexed-property a on the specified
    1798      * bean. This effectively means bean.setA(3).</li>
    1799      * </ul>
    1800      *
    1801      * @param bean Bean whose property is to be modified
    1802      * @param name Possibly nested name of the property to be modified
    1803      * @param value Value to which the property is to be set
    1804      *
    1805      * @exception IllegalAccessException if the caller does not have
    1806      *  access to the property accessor method
    1807      * @exception IllegalArgumentException if <code>bean</code> or
    1808      *  <code>name</code> is null
    1809      * @exception IllegalArgumentException if a nested reference to a
    1810      *  property returns null
    1811      * @exception InvocationTargetException if the property accessor method
    1812      *  throws an exception
    1813      * @exception NoSuchMethodException if an accessor method for this
    1814      *  propety cannot be found
    1815      */
    1816     public void setNestedProperty(Object bean,
    1817                                          String name, Object value)
    1818             throws IllegalAccessException, InvocationTargetException,
    1819             NoSuchMethodException {
    1820
    1821         if (bean == null) {
    1822             throw new IllegalArgumentException("No bean specified");
    1823         }
    1824         if (name == null) {
    1825             throw new IllegalArgumentException("No name specified for bean class '" +
    1826                     bean.getClass() + "'");
    1827         }
    1828
    1829         // Resolve nested references
    1830         while (resolver.hasNested(name)) {
    1831             String next = resolver.next(name);
    1832             Object nestedBean = null;
    1833             if (bean instanceof Map) {
    1834                 nestedBean = getPropertyOfMapBean((Map)bean, next);
    1835             } else if (resolver.isMapped(next)) {
    1836                 nestedBean = getMappedProperty(bean, next);
    1837             } else if (resolver.isIndexed(next)) {
    1838                 nestedBean = getIndexedProperty(bean, next);
    1839             } else {
    1840                 nestedBean = getSimpleProperty(bean, next);
    1841             }
    1842             if (nestedBean == null) {
    1843                 throw new NestedNullException
    1844                         ("Null property value for '" + name +
    1845                          "' on bean class '" + bean.getClass() + "'");
    1846             }
    1847             bean = nestedBean;
    1848             name = resolver.remove(name);
    1849         }
    1850
    1851         if (bean instanceof Map) {
    1852             setPropertyOfMapBean((Map) bean, name, value);
    1853         } else if (resolver.isMapped(name)) {
    1854             setMappedProperty(bean, name, value);
    1855         } else if (resolver.isIndexed(name)) {
    1856             setIndexedProperty(bean, name, value);
    1857         } else {
    1858             setSimpleProperty(bean, name, value);
    1859         }
    1860
    1861     }
    1862
    1863     /***
    1864      * This method is called by method setNestedProperty when the current bean
    1865      * is found to be a Map object, and defines how to deal with setting
    1866      * a property on a Map.
    1867      * <p>
    1868      * The standard implementation here is to:
    1869      * <ul>
    1870      * <li>call bean.set(propertyName) for all propertyName values.</li>
    1871      * <li>throw an IllegalArgumentException if the property specifier
    1872      * contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially
    1873      * simple properties; mapping and indexing operations do not make sense
    1874      * when accessing a map (even thought the returned object may be a Map
    1875      * or an Array).</li>
    1876      * </ul>
    1877      * <p>
    1878      * The default behaviour of beanutils 1.7.1 or later is for assigning to
    1879      * "a.b" to mean a.put(b, obj) always. However the behaviour of beanutils
    1880      * version 1.6.0, 1.6.1, 1.7.0 was for "a.b" to mean a.setB(obj) if such
    1881      * a method existed, and a.put(b, obj) otherwise. In version 1.5 it meant
    1882      * a.put(b, obj) always (ie the same as the behaviour in the current version).
    1883      * In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is
    1884      * all <i>very</i> unfortunate]
    1885      * <p>
    1886      * Users who would like to customise the meaning of "a.b" in method
    1887      * setNestedProperty when a is a Map can create a custom subclass of
    1888      * this class and override this method to implement the behaviour of
    1889      * their choice, such as restoring the pre-1.4 behaviour of this class
    1890      * if they wish. When overriding this method, do not forget to deal
    1891      * with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
    1892      * <p>
    1893      * Note, however, that the recommended solution for objects that
    1894      * implement Map but want their simple properties to come first is
    1895      * for <i>those</i> objects to override their get/put methods to implement
    1896      * that behaviour, and <i>not</i> to solve the problem by modifying the
    1897      * default behaviour of the PropertyUtilsBean class by overriding this
    1898      * method.
    1899      *
    1900      * @param bean Map bean
    1901      * @param propertyName The property name
    1902      * @param value the property value
    1903      *
    1904      * @throws IllegalArgumentException when the propertyName is regarded as
    1905      * being invalid.
    1906      *
    1907      * @throws IllegalAccessException just in case subclasses override this
    1908      * method to try to access real setter methods and find permission is denied.
    1909      *
    1910      * @throws InvocationTargetException just in case subclasses override this
    1911      * method to try to access real setter methods, and find it throws an
    1912      * exception when invoked.
    1913      *
    1914      * @throws NoSuchMethodException just in case subclasses override this
    1915      * method to try to access real setter methods, and want to fail if
    1916      * no simple method is available.
    1917      */
    1918     protected void setPropertyOfMapBean(Map bean, String propertyName, Object value)
    1919         throws IllegalArgumentException, IllegalAccessException,
    1920         InvocationTargetException, NoSuchMethodException {
    1921
    1922         if (resolver.isMapped(propertyName)) {
    1923             String name = resolver.getProperty(propertyName);
    1924             if (name == null || name.length() == 0) {
    1925                 propertyName = resolver.getKey(propertyName);
    1926             }
    1927         }
    1928
    1929         if (resolver.isIndexed(propertyName) ||
    1930             resolver.isMapped(propertyName)) {
    1931             throw new IllegalArgumentException(
    1932                     "Indexed or mapped properties are not supported on"
    1933                     + " objects of type Map: " + propertyName);
    1934         }
    1935
    1936         bean.put(propertyName, value);
    1937     }
    1938
    1939
    1940
    1941     /***
    1942      * Set the value of the specified property of the specified bean,
    1943      * no matter which property reference format is used, with no
    1944      * type conversions.
    1945      *
    1946      * @param bean Bean whose property is to be modified
    1947      * @param name Possibly indexed and/or nested name of the property
    1948      *  to be modified
    1949      * @param value Value to which this property is to be set
    1950      *
    1951      * @exception IllegalAccessException if the caller does not have
    1952      *  access to the property accessor method
    1953      * @exception IllegalArgumentException if <code>bean</code> or
    1954      *  <code>name</code> is null
    1955      * @exception InvocationTargetException if the property accessor method
    1956      *  throws an exception
    1957      * @exception NoSuchMethodException if an accessor method for this
    1958      *  propety cannot be found
    1959      */
    1960     public void setProperty(Object bean, String name, Object value)
    1961             throws IllegalAccessException, InvocationTargetException,
    1962             NoSuchMethodException {
    1963
    1964         setNestedProperty(bean, name, value);
    1965
    1966     }
    1967
    1968
    1969     /***
    1970      * Set the value of the specified simple property of the specified bean,
    1971      * with no type conversions.
    1972      *
    1973      * @param bean Bean whose property is to be modified
    1974      * @param name Name of the property to be modified
    1975      * @param value Value to which the property should be set
    1976      *
    1977      * @exception IllegalAccessException if the caller does not have
    1978      *  access to the property accessor method
    1979      * @exception IllegalArgumentException if <code>bean</code> or
    1980      *  <code>name</code> is null
    1981      * @exception IllegalArgumentException if the property name is
    1982      *  nested or indexed
    1983      * @exception InvocationTargetException if the property accessor method
    1984      *  throws an exception
    1985      * @exception NoSuchMethodException if an accessor method for this
    1986      *  propety cannot be found
    1987      */
    1988     public void setSimpleProperty(Object bean,
    1989                                          String name, Object value)
    1990             throws IllegalAccessException, InvocationTargetException,
    1991             NoSuchMethodException {
    1992
    1993         if (bean == null) {
    1994             throw new IllegalArgumentException("No bean specified");
    1995         }
    1996         if (name == null) {
    1997             throw new IllegalArgumentException("No name specified for bean class '" +
    1998                     bean.getClass() + "'");
    1999         }
    2000
    2001         // Validate the syntax of the property name
    2002         if (resolver.hasNested(name)) {
    2003             throw new IllegalArgumentException
    2004                     ("Nested property names are not allowed: Property '" +
    2005                     name + "' on bean class '" + bean.getClass() + "'");
    2006         } else if (resolver.isIndexed(name)) {
    2007             throw new IllegalArgumentException
    2008                     ("Indexed property names are not allowed: Property '" +
    2009                     name + "' on bean class '" + bean.getClass() + "'");
    2010         } else if (resolver.isMapped(name)) {
    2011             throw new IllegalArgumentException
    2012                     ("Mapped property names are not allowed: Property '" +
    2013                     name + "' on bean class '" + bean.getClass() + "'");
    2014         }
    2015
    2016         // Handle DynaBean instances specially
    2017         if (bean instanceof DynaBean) {
    2018             DynaProperty descriptor =
    2019                     ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    2020             if (descriptor == null) {
    2021                 throw new NoSuchMethodException("Unknown property '" +
    2022                         name + "' on dynaclass '" +
    2023                         ((DynaBean) bean).getDynaClass() + "'" );
    2024             }
    2025             ((DynaBean) bean).set(name, value);
    2026             return;
    2027         }
    2028
    2029         // Retrieve the property setter method for the specified property
    2030         PropertyDescriptor descriptor =
    2031                 getPropertyDescriptor(bean, name);
    2032         if (descriptor == null) {
    2033             throw new NoSuchMethodException("Unknown property '" +
    2034                     name + "' on class '" + bean.getClass() + "'" );
    2035         }
    2036         Method writeMethod = getWriteMethod(descriptor);
    2037         if (writeMethod == null) {
    2038             throw new NoSuchMethodException("Property '" + name +
    2039                     "' has no setter method in class '" + bean.getClass() + "'");
    2040         }
    2041
    2042         // Call the property setter method
    2043         Object[] values = new Object[1];
    2044         values[0] = value;
    2045         if (log.isTraceEnabled()) {
    2046             String valueClassName =
    2047                 value == null ? "<null>" : value.getClass().getName();
    2048             log.trace("setSimpleProperty: Invoking method " + writeMethod
    2049                       + " with value " + value + " (class " + valueClassName + ")");
    2050         }
    2051         invokeMethod(writeMethod, bean, values);
    2052
    2053     }
    2054
    2055     /*** This just catches and wraps IllegalArgumentException. */
    2056     private Object invokeMethod(
    2057                         Method method,
    2058                         Object bean,
    2059                         Object[] values)
    2060                             throws
    2061                                 IllegalAccessException,
    2062                                 InvocationTargetException {
    2063         try {
    2064
    2065             return method.invoke(bean, values);
    2066
    2067         } catch (IllegalArgumentException cause) {
    2068             if(bean == null) {
    2069                 throw new IllegalArgumentException("No bean specified " +
    2070                     "- this should have been checked before reaching this method");
    2071             }
    2072             String valueString = "";
    2073             if (values != null) {
    2074                 for (int i = 0; i < values.length; i++) {
    2075                     if (i>0) {
    2076                         valueString += ", " ;
    2077                     }
    2078                     valueString += (values[i]).getClass().getName();
    2079                 }
    2080             }
    2081             String expectedString = "";
    2082             Class[] parTypes = method.getParameterTypes();
    2083             if (parTypes != null) {
    2084                 for (int i = 0; i < parTypes.length; i++) {
    2085                     if (i > 0) {
    2086                         expectedString += ", ";
    2087                     }
    2088                     expectedString += parTypes[i].getName();
    2089                 }
    2090             }
    2091             IllegalArgumentException e = new IllegalArgumentException(
    2092                 "Cannot invoke " + method.getDeclaringClass().getName() + "."
    2093                 + method.getName() + " on bean class '" + bean.getClass() +
    2094                 "' - " + cause.getMessage()
    2095                 // as per https://issues.apache.org/jira/browse/BEANUTILS-224
    2096                 + " - had objects of type \"" + valueString
    2097                 + "\" but expected signature \""
    2098                 +   expectedString + "\""
    2099                 );
    2100             if (!BeanUtils.initCause(e, cause)) {
    2101                 log.error("Method invocation failed", cause);
    2102             }
    2103             throw e;
    2104
    2105         }
    2106     }
    2107 }