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