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 11:23:23 -0400 (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            try {
1061                IntrospectorUtils.setNonSyntheticReadWriteMethods(beanClass, descriptors);
1062            } catch (NoSuchMethodException e) {
1063                log.warn("unable to read/write methods for synthetic methods", e);
1064            } catch (IntrospectionException e) {
1065                log.warn("unable to read/write methods for synthetic methods", e);
1066            }
1067    
1068            descriptorsCache.put(beanClass, descriptors);
1069            return (descriptors);
1070    
1071        }
1072    
1073    
1074        /**
1075         * <p>Retrieve the property descriptors for the specified bean,
1076         * introspecting and caching them the first time a particular bean class
1077         * is encountered.</p>
1078         *
1079         * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1080         *
1081         * @param bean Bean for which property descriptors are requested
1082         * @return the property descriptors
1083         *
1084         * @exception IllegalArgumentException if <code>bean</code> is null
1085         */
1086        public PropertyDescriptor[] getPropertyDescriptors(Object bean) {
1087    
1088            if (bean == null) {
1089                throw new IllegalArgumentException("No bean specified");
1090            }
1091            return (getPropertyDescriptors(bean.getClass()));
1092    
1093        }
1094    
1095    
1096        /**
1097         * <p>Return the Java Class repesenting the property editor class that has
1098         * been registered for this property (if any).  This method follows the
1099         * same name resolution rules used by <code>getPropertyDescriptor()</code>,
1100         * so if the last element of a name reference is indexed, the property
1101         * editor for the underlying property's class is returned.</p>
1102         *
1103         * <p>Note that <code>null</code> will be returned if there is no property,
1104         * or if there is no registered property editor class.  Because this
1105         * return value is ambiguous, you should determine the existence of the
1106         * property itself by other means.</p>
1107         *
1108         * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1109         *
1110         * @param bean Bean for which a property descriptor is requested
1111         * @param name Possibly indexed and/or nested name of the property for
1112         *  which a property descriptor is requested
1113         * @return the property editor class
1114         *
1115         * @exception IllegalAccessException if the caller does not have
1116         *  access to the property accessor method
1117         * @exception IllegalArgumentException if <code>bean</code> or
1118         *  <code>name</code> is null
1119         * @exception IllegalArgumentException if a nested reference to a
1120         *  property returns null
1121         * @exception InvocationTargetException if the property accessor method
1122         *  throws an exception
1123         * @exception NoSuchMethodException if an accessor method for this
1124         *  propety cannot be found
1125         */
1126        public Class getPropertyEditorClass(Object bean, String name)
1127                throws IllegalAccessException, InvocationTargetException,
1128                NoSuchMethodException {
1129    
1130            if (bean == null) {
1131                throw new IllegalArgumentException("No bean specified");
1132            }
1133            if (name == null) {
1134                throw new IllegalArgumentException("No name specified for bean class '" +
1135                        bean.getClass() + "'");
1136            }
1137    
1138            PropertyDescriptor descriptor =
1139                    getPropertyDescriptor(bean, name);
1140            if (descriptor != null) {
1141                return (descriptor.getPropertyEditorClass());
1142            } else {
1143                return (null);
1144            }
1145    
1146        }
1147    
1148    
1149        /**
1150         * Return the Java Class representing the property type of the specified
1151         * property, or <code>null</code> if there is no such property for the
1152         * specified bean.  This method follows the same name resolution rules
1153         * used by <code>getPropertyDescriptor()</code>, so if the last element
1154         * of a name reference is indexed, the type of the property itself will
1155         * be returned.  If the last (or only) element has no property with the
1156         * specified name, <code>null</code> is returned.
1157         *
1158         * @param bean Bean for which a property descriptor is requested
1159         * @param name Possibly indexed and/or nested name of the property for
1160         *  which a property descriptor is requested
1161         * @return The property type
1162         *
1163         * @exception IllegalAccessException if the caller does not have
1164         *  access to the property accessor method
1165         * @exception IllegalArgumentException if <code>bean</code> or
1166         *  <code>name</code> is null
1167         * @exception IllegalArgumentException if a nested reference to a
1168         *  property returns null
1169         * @exception InvocationTargetException if the property accessor method
1170         *  throws an exception
1171         * @exception NoSuchMethodException if an accessor method for this
1172         *  propety cannot be found
1173         */
1174        public Class getPropertyType(Object bean, String name)
1175                throws IllegalAccessException, InvocationTargetException,
1176                NoSuchMethodException {
1177    
1178            if (bean == null) {
1179                throw new IllegalArgumentException("No bean specified");
1180            }
1181            if (name == null) {
1182                throw new IllegalArgumentException("No name specified for bean class '" +
1183                        bean.getClass() + "'");
1184            }
1185    
1186            // Resolve nested references
1187            while (resolver.hasNested(name)) {
1188                String next = resolver.next(name);
1189                Object nestedBean = getProperty(bean, next);
1190                if (nestedBean == null) {
1191                    throw new NestedNullException
1192                            ("Null property value for '" + next +
1193                            "' on bean class '" + bean.getClass() + "'");
1194                }
1195                bean = nestedBean;
1196                name = resolver.remove(name);
1197            }
1198    
1199            // Remove any subscript from the final name value
1200            name = resolver.getProperty(name);
1201    
1202            // Special handling for DynaBeans
1203            if (bean instanceof DynaBean) {
1204                DynaProperty descriptor =
1205                        ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1206                if (descriptor == null) {
1207                    return (null);
1208                }
1209                Class type = descriptor.getType();
1210                if (type == null) {
1211                    return (null);
1212                } else if (type.isArray()) {
1213                    return (type.getComponentType());
1214                } else {
1215                    return (type);
1216                }
1217            }
1218    
1219            PropertyDescriptor descriptor =
1220                    getPropertyDescriptor(bean, name);
1221            if (descriptor == null) {
1222                return (null);
1223            } else if (descriptor instanceof IndexedPropertyDescriptor) {
1224                return (((IndexedPropertyDescriptor) descriptor).
1225                        getIndexedPropertyType());
1226            } else if (descriptor instanceof MappedPropertyDescriptor) {
1227                return (((MappedPropertyDescriptor) descriptor).
1228                        getMappedPropertyType());
1229            } else {
1230                return (descriptor.getPropertyType());
1231            }
1232    
1233        }
1234    
1235    
1236        /**
1237         * <p>Return an accessible property getter method for this property,
1238         * if there is one; otherwise return <code>null</code>.</p>
1239         *
1240         * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1241         *
1242         * @param descriptor Property descriptor to return a getter for
1243         * @return The read method
1244         */
1245        public Method getReadMethod(PropertyDescriptor descriptor) {
1246    
1247            return (MethodUtils.getAccessibleMethod(descriptor.getReadMethod()));
1248    
1249        }
1250    
1251    
1252        /**
1253         * <p>Return an accessible property getter method for this property,
1254         * if there is one; otherwise return <code>null</code>.</p>
1255         *
1256         * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1257         *
1258         * @param clazz The class of the read method will be invoked on
1259         * @param descriptor Property descriptor to return a getter for
1260         * @return The read method
1261         */
1262        Method getReadMethod(Class clazz, PropertyDescriptor descriptor) {
1263            return (MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod()));
1264        }
1265    
1266    
1267        /**
1268         * Return the value of the specified simple property of the specified
1269         * bean, with no type conversions.
1270         *
1271         * @param bean Bean whose property is to be extracted
1272         * @param name Name of the property to be extracted
1273         * @return The property value
1274         *
1275         * @exception IllegalAccessException if the caller does not have
1276         *  access to the property accessor method
1277         * @exception IllegalArgumentException if <code>bean</code> or
1278         *  <code>name</code> is null
1279         * @exception IllegalArgumentException if the property name
1280         *  is nested or indexed
1281         * @exception InvocationTargetException if the property accessor method
1282         *  throws an exception
1283         * @exception NoSuchMethodException if an accessor method for this
1284         *  propety cannot be found
1285         */
1286        public Object getSimpleProperty(Object bean, String name)
1287                throws IllegalAccessException, InvocationTargetException,
1288                NoSuchMethodException {
1289    
1290            if (bean == null) {
1291                throw new IllegalArgumentException("No bean specified");
1292            }
1293            if (name == null) {
1294                throw new IllegalArgumentException("No name specified for bean class '" +
1295                        bean.getClass() + "'");
1296            }
1297    
1298            // Validate the syntax of the property name
1299            if (resolver.hasNested(name)) {
1300                throw new IllegalArgumentException
1301                        ("Nested property names are not allowed: Property '" +
1302                        name + "' on bean class '" + bean.getClass() + "'");
1303            } else if (resolver.isIndexed(name)) {
1304                throw new IllegalArgumentException
1305                        ("Indexed property names are not allowed: Property '" +
1306                        name + "' on bean class '" + bean.getClass() + "'");
1307            } else if (resolver.isMapped(name)) {
1308                throw new IllegalArgumentException
1309                        ("Mapped property names are not allowed: Property '" +
1310                        name + "' on bean class '" + bean.getClass() + "'");
1311            }
1312    
1313            // Handle DynaBean instances specially
1314            if (bean instanceof DynaBean) {
1315                DynaProperty descriptor =
1316                        ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1317                if (descriptor == null) {
1318                    throw new NoSuchMethodException("Unknown property '" +
1319                            name + "' on dynaclass '" + 
1320                            ((DynaBean) bean).getDynaClass() + "'" );
1321                }
1322                return (((DynaBean) bean).get(name));
1323            }
1324    
1325            // Retrieve the property getter method for the specified property
1326            PropertyDescriptor descriptor =
1327                    getPropertyDescriptor(bean, name);
1328            if (descriptor == null) {
1329                throw new NoSuchMethodException("Unknown property '" +
1330                        name + "' on class '" + bean.getClass() + "'" );
1331            }
1332            Method readMethod = getReadMethod(bean.getClass(), descriptor);
1333            if (readMethod == null) {
1334                throw new NoSuchMethodException("Property '" + name +
1335                        "' has no getter method in class '" + bean.getClass() + "'");
1336            }
1337    
1338            // Call the property getter and return the value
1339            Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
1340            return (value);
1341    
1342        }
1343    
1344    
1345        /**
1346         * <p>Return an accessible property setter method for this property,
1347         * if there is one; otherwise return <code>null</code>.</p>
1348         *
1349         * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1350         *
1351         * @param descriptor Property descriptor to return a setter for
1352         * @return The write method
1353         */
1354        public Method getWriteMethod(PropertyDescriptor descriptor) {
1355    
1356            return (MethodUtils.getAccessibleMethod(descriptor.getWriteMethod()));
1357    
1358        }
1359    
1360    
1361        /**
1362         * <p>Return an accessible property setter method for this property,
1363         * if there is one; otherwise return <code>null</code>.</p>
1364         *
1365         * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1366         *
1367         * @param clazz The class of the read method will be invoked on
1368         * @param descriptor Property descriptor to return a setter for
1369         * @return The write method
1370         */
1371        Method getWriteMethod(Class clazz, PropertyDescriptor descriptor) {
1372            return (MethodUtils.getAccessibleMethod(clazz, descriptor.getWriteMethod()));
1373        }
1374    
1375    
1376        /**
1377         * <p>Return <code>true</code> if the specified property name identifies
1378         * a readable property on the specified bean; otherwise, return
1379         * <code>false</code>.
1380         *
1381         * @param bean Bean to be examined (may be a {@link DynaBean}
1382         * @param name Property name to be evaluated
1383         * @return <code>true</code> if the property is readable,
1384         * otherwise <code>false</code>
1385         *
1386         * @exception IllegalArgumentException if <code>bean</code>
1387         *  or <code>name</code> is <code>null</code>
1388         *
1389         * @since BeanUtils 1.6
1390         */
1391        public boolean isReadable(Object bean, String name) {
1392    
1393            // Validate method parameters
1394            if (bean == null) {
1395                throw new IllegalArgumentException("No bean specified");
1396            }
1397            if (name == null) {
1398                throw new IllegalArgumentException("No name specified for bean class '" +
1399                        bean.getClass() + "'");
1400            }
1401    
1402            // Resolve nested references
1403            while (resolver.hasNested(name)) {
1404                String next = resolver.next(name);
1405                Object nestedBean = null; 
1406                try {
1407                    nestedBean = getProperty(bean, next);
1408                } catch (IllegalAccessException e) {
1409                    return false;
1410                } catch (InvocationTargetException e) {
1411                    return false;
1412                } catch (NoSuchMethodException e) {
1413                    return false;
1414                }
1415                if (nestedBean == null) {
1416                    throw new NestedNullException
1417                            ("Null property value for '" + next +
1418                            "' on bean class '" + bean.getClass() + "'");
1419                }
1420                bean = nestedBean;
1421                name = resolver.remove(name);
1422            }
1423    
1424            // Remove any subscript from the final name value
1425            name = resolver.getProperty(name);
1426    
1427            // Treat WrapDynaBean as special case - may be a write-only property
1428            // (see Jira issue# BEANUTILS-61)
1429            if (bean instanceof WrapDynaBean) {
1430                bean = ((WrapDynaBean)bean).getInstance();
1431            }
1432    
1433            // Return the requested result
1434            if (bean instanceof DynaBean) {
1435                // All DynaBean properties are readable
1436                return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
1437            } else {
1438                try {
1439                    PropertyDescriptor desc =
1440                        getPropertyDescriptor(bean, name);
1441                    if (desc != null) {
1442                        Method readMethod = getReadMethod(bean.getClass(), desc);
1443                        if (readMethod == null) {
1444                            if (desc instanceof IndexedPropertyDescriptor) {
1445                                readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
1446                            } else if (desc instanceof MappedPropertyDescriptor) {
1447                                readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
1448                            }
1449                            readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
1450                        }
1451                        return (readMethod != null);
1452                    } else {
1453                        return (false);
1454                    }
1455                } catch (IllegalAccessException e) {
1456                    return (false);
1457                } catch (InvocationTargetException e) {
1458                    return (false);
1459                } catch (NoSuchMethodException e) {
1460                    return (false);
1461                }
1462            }
1463    
1464        }
1465    
1466    
1467        /**
1468         * <p>Return <code>true</code> if the specified property name identifies
1469         * a writeable property on the specified bean; otherwise, return
1470         * <code>false</code>.
1471         *
1472         * @param bean Bean to be examined (may be a {@link DynaBean}
1473         * @param name Property name to be evaluated
1474         * @return <code>true</code> if the property is writeable,
1475         * otherwise <code>false</code>
1476         *
1477         * @exception IllegalArgumentException if <code>bean</code>
1478         *  or <code>name</code> is <code>null</code>
1479         *
1480         * @since BeanUtils 1.6
1481         */
1482        public boolean isWriteable(Object bean, String name) {
1483    
1484            // Validate method parameters
1485            if (bean == null) {
1486                throw new IllegalArgumentException("No bean specified");
1487            }
1488            if (name == null) {
1489                throw new IllegalArgumentException("No name specified for bean class '" +
1490                        bean.getClass() + "'");
1491            }
1492    
1493            // Resolve nested references
1494            while (resolver.hasNested(name)) {
1495                String next = resolver.next(name);
1496                Object nestedBean = null; 
1497                try {
1498                    nestedBean = getProperty(bean, next);
1499                } catch (IllegalAccessException e) {
1500                    return false;
1501                } catch (InvocationTargetException e) {
1502                    return false;
1503                } catch (NoSuchMethodException e) {
1504                    return false;
1505                }
1506                if (nestedBean == null) {
1507                    throw new NestedNullException
1508                            ("Null property value for '" + next +
1509                            "' on bean class '" + bean.getClass() + "'");
1510                }
1511                bean = nestedBean;
1512                name = resolver.remove(name);
1513            }
1514    
1515            // Remove any subscript from the final name value
1516            name = resolver.getProperty(name);
1517    
1518            // Treat WrapDynaBean as special case - may be a read-only property
1519            // (see Jira issue# BEANUTILS-61)
1520            if (bean instanceof WrapDynaBean) {
1521                bean = ((WrapDynaBean)bean).getInstance();
1522            }
1523    
1524            // Return the requested result
1525            if (bean instanceof DynaBean) {
1526                // All DynaBean properties are writeable
1527                return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
1528            } else {
1529                try {
1530                    PropertyDescriptor desc =
1531                        getPropertyDescriptor(bean, name);
1532                    if (desc != null) {
1533                        Method writeMethod = getWriteMethod(bean.getClass(), desc);
1534                        if (writeMethod == null) {
1535                            if (desc instanceof IndexedPropertyDescriptor) {
1536                                writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
1537                            } else if (desc instanceof MappedPropertyDescriptor) {
1538                                writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
1539                            }
1540                            writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
1541                        }
1542                        return (writeMethod != null);
1543                    } else {
1544                        return (false);
1545                    }
1546                } catch (IllegalAccessException e) {
1547                    return (false);
1548                } catch (InvocationTargetException e) {
1549                    return (false);
1550                } catch (NoSuchMethodException e) {
1551                    return (false);
1552                }
1553            }
1554    
1555        }
1556    
1557    
1558        /**
1559         * Set the value of the specified indexed property of the specified
1560         * bean, with no type conversions.  The zero-relative index of the
1561         * required value must be included (in square brackets) as a suffix to
1562         * the property name, or <code>IllegalArgumentException</code> will be
1563         * thrown.  In addition to supporting the JavaBeans specification, this
1564         * method has been extended to support <code>List</code> objects as well.
1565         *
1566         * @param bean Bean whose property is to be modified
1567         * @param name <code>propertyname[index]</code> of the property value
1568         *  to be modified
1569         * @param value Value to which the specified property element
1570         *  should be set
1571         *
1572         * @exception IndexOutOfBoundsException if the specified index
1573         *  is outside the valid range for the underlying property
1574         * @exception IllegalAccessException if the caller does not have
1575         *  access to the property accessor method
1576         * @exception IllegalArgumentException if <code>bean</code> or
1577         *  <code>name</code> is null
1578         * @exception InvocationTargetException if the property accessor method
1579         *  throws an exception
1580         * @exception NoSuchMethodException if an accessor method for this
1581         *  propety cannot be found
1582         */
1583        public void setIndexedProperty(Object bean, String name,
1584                                              Object value)
1585                throws IllegalAccessException, InvocationTargetException,
1586                NoSuchMethodException {
1587    
1588            if (bean == null) {
1589                throw new IllegalArgumentException("No bean specified");
1590            }
1591            if (name == null) {
1592                throw new IllegalArgumentException("No name specified for bean class '" +
1593                        bean.getClass() + "'");
1594            }
1595    
1596            // Identify the index of the requested individual property
1597            int index = -1;
1598            try {
1599                index = resolver.getIndex(name);
1600            } catch (IllegalArgumentException e) {
1601                throw new IllegalArgumentException("Invalid indexed property '" +
1602                        name + "' on bean class '" + bean.getClass() + "'");
1603            }
1604            if (index < 0) {
1605                throw new IllegalArgumentException("Invalid indexed property '" +
1606                        name + "' on bean class '" + bean.getClass() + "'");
1607            }
1608    
1609            // Isolate the name
1610            name = resolver.getProperty(name);
1611    
1612            // Set the specified indexed property value
1613            setIndexedProperty(bean, name, index, value);
1614    
1615        }
1616    
1617    
1618        /**
1619         * Set the value of the specified indexed property of the specified
1620         * bean, with no type conversions.  In addition to supporting the JavaBeans
1621         * specification, this method has been extended to support
1622         * <code>List</code> objects as well.
1623         *
1624         * @param bean Bean whose property is to be set
1625         * @param name Simple property name of the property value to be set
1626         * @param index Index of the property value to be set
1627         * @param value Value to which the indexed property element is to be set
1628         *
1629         * @exception IndexOutOfBoundsException if the specified index
1630         *  is outside the valid range for the underlying property
1631         * @exception IllegalAccessException if the caller does not have
1632         *  access to the property accessor method
1633         * @exception IllegalArgumentException if <code>bean</code> or
1634         *  <code>name</code> is null
1635         * @exception InvocationTargetException if the property accessor method
1636         *  throws an exception
1637         * @exception NoSuchMethodException if an accessor method for this
1638         *  propety cannot be found
1639         */
1640        public void setIndexedProperty(Object bean, String name,
1641                                              int index, Object value)
1642                throws IllegalAccessException, InvocationTargetException,
1643                NoSuchMethodException {
1644    
1645            if (bean == null) {
1646                throw new IllegalArgumentException("No bean specified");
1647            }
1648            if (name == null || name.length() == 0) {
1649                if (bean.getClass().isArray()) {
1650                    Array.set(bean, index, value);
1651                    return;
1652                } else if (bean instanceof List) {
1653                    ((List)bean).set(index, value);   
1654                    return;
1655                }
1656            }
1657            if (name == null) {
1658                throw new IllegalArgumentException("No name specified for bean class '" +
1659                        bean.getClass() + "'");
1660            }
1661    
1662            // Handle DynaBean instances specially
1663            if (bean instanceof DynaBean) {
1664                DynaProperty descriptor =
1665                        ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1666                if (descriptor == null) {
1667                    throw new NoSuchMethodException("Unknown property '" +
1668                            name + "' on bean class '" + bean.getClass() + "'");
1669                }
1670                ((DynaBean) bean).set(name, index, value);
1671                return;
1672            }
1673    
1674            // Retrieve the property descriptor for the specified property
1675            PropertyDescriptor descriptor =
1676                    getPropertyDescriptor(bean, name);
1677            if (descriptor == null) {
1678                throw new NoSuchMethodException("Unknown property '" +
1679                        name + "' on bean class '" + bean.getClass() + "'");
1680            }
1681    
1682            // Call the indexed setter method if there is one
1683            if (descriptor instanceof IndexedPropertyDescriptor) {
1684                Method writeMethod = ((IndexedPropertyDescriptor) descriptor).
1685                        getIndexedWriteMethod();
1686                writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
1687                if (writeMethod != null) {
1688                    Object[] subscript = new Object[2];
1689                    subscript[0] = new Integer(index);
1690                    subscript[1] = value;
1691                    try {
1692                        if (log.isTraceEnabled()) {
1693                            String valueClassName =
1694                                value == null ? "<null>" 
1695                                              : value.getClass().getName();
1696                            log.trace("setSimpleProperty: Invoking method "
1697                                      + writeMethod +" with index=" + index
1698                                      + ", value=" + value
1699                                      + " (class " + valueClassName+ ")");
1700                        }
1701                        invokeMethod(writeMethod, bean, subscript);
1702                    } catch (InvocationTargetException e) {
1703                        if (e.getTargetException() instanceof
1704                                IndexOutOfBoundsException) {
1705                            throw (IndexOutOfBoundsException)
1706                                    e.getTargetException();
1707                        } else {
1708                            throw e;
1709                        }
1710                    }
1711                    return;
1712                }
1713            }
1714    
1715            // Otherwise, the underlying property must be an array or a list
1716            Method readMethod = getReadMethod(bean.getClass(), descriptor);
1717            if (readMethod == null) {
1718                throw new NoSuchMethodException("Property '" + name +
1719                        "' has no getter method on bean class '" + bean.getClass() + "'");
1720            }
1721    
1722            // Call the property getter to get the array or list
1723            Object array = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
1724            if (!array.getClass().isArray()) {
1725                if (array instanceof List) {
1726                    // Modify the specified value in the List
1727                    ((List) array).set(index, value);
1728                } else {
1729                    throw new IllegalArgumentException("Property '" + name +
1730                            "' is not indexed on bean class '" + bean.getClass() + "'");
1731                }
1732            } else {
1733                // Modify the specified value in the array
1734                Array.set(array, index, value);
1735            }
1736    
1737        }
1738    
1739    
1740        /**
1741         * Set the value of the specified mapped property of the
1742         * specified bean, with no type conversions.  The key of the
1743         * value to set must be included (in brackets) as a suffix to
1744         * the property name, or <code>IllegalArgumentException</code> will be
1745         * thrown.
1746         *
1747         * @param bean Bean whose property is to be set
1748         * @param name <code>propertyname(key)</code> of the property value
1749         *  to be set
1750         * @param value The property value to be set
1751         *
1752         * @exception IllegalAccessException if the caller does not have
1753         *  access to the property accessor method
1754         * @exception InvocationTargetException if the property accessor method
1755         *  throws an exception
1756         * @exception NoSuchMethodException if an accessor method for this
1757         *  propety cannot be found
1758         */
1759        public void setMappedProperty(Object bean, String name,
1760                                             Object value)
1761                throws IllegalAccessException, InvocationTargetException,
1762                NoSuchMethodException {
1763    
1764            if (bean == null) {
1765                throw new IllegalArgumentException("No bean specified");
1766            }
1767            if (name == null) {
1768                throw new IllegalArgumentException("No name specified for bean class '" +
1769                        bean.getClass() + "'");
1770            }
1771    
1772            // Identify the key of the requested individual property
1773            String key  = null;
1774            try {
1775                key = resolver.getKey(name);
1776            } catch (IllegalArgumentException e) {
1777                throw new IllegalArgumentException
1778                        ("Invalid mapped property '" + name + 
1779                        "' on bean class '" + bean.getClass() + "'");
1780            }
1781            if (key == null) {
1782                throw new IllegalArgumentException
1783                        ("Invalid mapped property '" + name + 
1784                        "' on bean class '" + bean.getClass() + "'");
1785            }
1786    
1787            // Isolate the name
1788            name = resolver.getProperty(name);
1789    
1790            // Request the specified indexed property value
1791            setMappedProperty(bean, name, key, value);
1792    
1793        }
1794    
1795    
1796        /**
1797         * Set the value of the specified mapped property of the specified
1798         * bean, with no type conversions.
1799         *
1800         * @param bean Bean whose property is to be set
1801         * @param name Mapped property name of the property value to be set
1802         * @param key Key of the property value to be set
1803         * @param value The property value to be set
1804         *
1805         * @exception IllegalAccessException if the caller does not have
1806         *  access to the property accessor method
1807         * @exception InvocationTargetException if the property accessor method
1808         *  throws an exception
1809         * @exception NoSuchMethodException if an accessor method for this
1810         *  propety cannot be found
1811         */
1812        public void setMappedProperty(Object bean, String name,
1813                                             String key, Object value)
1814                throws IllegalAccessException, InvocationTargetException,
1815                NoSuchMethodException {
1816    
1817            if (bean == null) {
1818                throw new IllegalArgumentException("No bean specified");
1819            }
1820            if (name == null) {
1821                throw new IllegalArgumentException("No name specified for bean class '" +
1822                        bean.getClass() + "'");
1823            }
1824            if (key == null) {
1825                throw new IllegalArgumentException("No key specified for property '" +
1826                        name + "' on bean class '" + bean.getClass() + "'");
1827            }
1828    
1829            // Handle DynaBean instances specially
1830            if (bean instanceof DynaBean) {
1831                DynaProperty descriptor =
1832                        ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1833                if (descriptor == null) {
1834                    throw new NoSuchMethodException("Unknown property '" +
1835                            name + "' on bean class '" + bean.getClass() + "'");
1836                }
1837                ((DynaBean) bean).set(name, key, value);
1838                return;
1839            }
1840    
1841            // Retrieve the property descriptor for the specified property
1842            PropertyDescriptor descriptor =
1843                    getPropertyDescriptor(bean, name);
1844            if (descriptor == null) {
1845                throw new NoSuchMethodException("Unknown property '" +
1846                        name + "' on bean class '" + bean.getClass() + "'");
1847            }
1848    
1849            if (descriptor instanceof MappedPropertyDescriptor) {
1850                // Call the keyed setter method if there is one
1851                Method mappedWriteMethod =
1852                        ((MappedPropertyDescriptor) descriptor).
1853                        getMappedWriteMethod();
1854                mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
1855                if (mappedWriteMethod != null) {
1856                    Object[] params = new Object[2];
1857                    params[0] = key;
1858                    params[1] = value;
1859                    if (log.isTraceEnabled()) {
1860                        String valueClassName =
1861                            value == null ? "<null>" : value.getClass().getName();
1862                        log.trace("setSimpleProperty: Invoking method "
1863                                  + mappedWriteMethod + " with key=" + key
1864                                  + ", value=" + value
1865                                  + " (class " + valueClassName +")");
1866                    }
1867                    invokeMethod(mappedWriteMethod, bean, params);
1868                } else {
1869                    throw new NoSuchMethodException
1870                        ("Property '" + name + "' has no mapped setter method" +
1871                         "on bean class '" + bean.getClass() + "'");
1872                }
1873            } else {
1874              /* means that the result has to be retrieved from a map */
1875              Method readMethod = getReadMethod(bean.getClass(), descriptor);
1876              if (readMethod != null) {
1877                Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
1878                /* test and fetch from the map */
1879                if (invokeResult instanceof java.util.Map) {
1880                  ((java.util.Map)invokeResult).put(key, value);
1881                }
1882              } else {
1883                throw new NoSuchMethodException("Property '" + name +
1884                        "' has no mapped getter method on bean class '" +
1885                        bean.getClass() + "'");
1886              }
1887            }
1888    
1889        }
1890    
1891    
1892        /**
1893         * Set the value of the (possibly nested) property of the specified
1894         * name, for the specified bean, with no type conversions.
1895         * <p>
1896         * Example values for parameter "name" are:
1897         * <ul>
1898         * <li> "a" -- sets the value of property a of the specified bean </li>
1899         * <li> "a.b" -- gets the value of property a of the specified bean,
1900         * then on that object sets the value of property b.</li>
1901         * <li> "a(key)" -- sets a value of mapped-property a on the specified
1902         * bean. This effectively means bean.setA("key").</li>
1903         * <li> "a[3]" -- sets a value of indexed-property a on the specified
1904         * bean. This effectively means bean.setA(3).</li>
1905         * </ul>
1906         *
1907         * @param bean Bean whose property is to be modified
1908         * @param name Possibly nested name of the property to be modified
1909         * @param value Value to which the property is to be set
1910         *
1911         * @exception IllegalAccessException if the caller does not have
1912         *  access to the property accessor method
1913         * @exception IllegalArgumentException if <code>bean</code> or
1914         *  <code>name</code> is null
1915         * @exception IllegalArgumentException if a nested reference to a
1916         *  property returns null
1917         * @exception InvocationTargetException if the property accessor method
1918         *  throws an exception
1919         * @exception NoSuchMethodException if an accessor method for this
1920         *  propety cannot be found
1921         */
1922        public void setNestedProperty(Object bean,
1923                                             String name, Object value)
1924                throws IllegalAccessException, InvocationTargetException,
1925                NoSuchMethodException {
1926    
1927            if (bean == null) {
1928                throw new IllegalArgumentException("No bean specified");
1929            }
1930            if (name == null) {
1931                throw new IllegalArgumentException("No name specified for bean class '" +
1932                        bean.getClass() + "'");
1933            }
1934    
1935            // Resolve nested references
1936            while (resolver.hasNested(name)) {
1937                String next = resolver.next(name);
1938                Object nestedBean = null;
1939                if (bean instanceof Map) {
1940                    nestedBean = getPropertyOfMapBean((Map)bean, next);
1941                } else if (resolver.isMapped(next)) {
1942                    nestedBean = getMappedProperty(bean, next);
1943                } else if (resolver.isIndexed(next)) {
1944                    nestedBean = getIndexedProperty(bean, next);
1945                } else {
1946                    nestedBean = getSimpleProperty(bean, next);
1947                }
1948                if (nestedBean == null) {
1949                    throw new NestedNullException
1950                            ("Null property value for '" + name +
1951                             "' on bean class '" + bean.getClass() + "'");
1952                }
1953                bean = nestedBean;
1954                name = resolver.remove(name);
1955            }
1956    
1957            if (bean instanceof Map) {
1958                setPropertyOfMapBean((Map) bean, name, value);
1959            } else if (resolver.isMapped(name)) {
1960                setMappedProperty(bean, name, value);
1961            } else if (resolver.isIndexed(name)) {
1962                setIndexedProperty(bean, name, value);
1963            } else {
1964                setSimpleProperty(bean, name, value);
1965            }
1966    
1967        }
1968    
1969        /**
1970         * This method is called by method setNestedProperty when the current bean
1971         * is found to be a Map object, and defines how to deal with setting
1972         * a property on a Map.
1973         * <p>
1974         * The standard implementation here is to:
1975         * <ul>
1976         * <li>call bean.set(propertyName) for all propertyName values.</li>
1977         * <li>throw an IllegalArgumentException if the property specifier
1978         * contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially
1979         * simple properties; mapping and indexing operations do not make sense
1980         * when accessing a map (even thought the returned object may be a Map
1981         * or an Array).</li>
1982         * </ul>
1983         * <p>
1984         * The default behaviour of beanutils 1.7.1 or later is for assigning to
1985         * "a.b" to mean a.put(b, obj) always. However the behaviour of beanutils 
1986         * version 1.6.0, 1.6.1, 1.7.0 was for "a.b" to mean a.setB(obj) if such
1987         * a method existed, and a.put(b, obj) otherwise. In version 1.5 it meant
1988         * a.put(b, obj) always (ie the same as the behaviour in the current version).
1989         * In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is 
1990         * all <i>very</i> unfortunate]
1991         * <p>
1992         * Users who would like to customise the meaning of "a.b" in method 
1993         * setNestedProperty when a is a Map can create a custom subclass of
1994         * this class and override this method to implement the behaviour of 
1995         * their choice, such as restoring the pre-1.4 behaviour of this class
1996         * if they wish. When overriding this method, do not forget to deal 
1997         * with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
1998         * <p>
1999         * Note, however, that the recommended solution for objects that
2000         * implement Map but want their simple properties to come first is
2001         * for <i>those</i> objects to override their get/put methods to implement
2002         * that behaviour, and <i>not</i> to solve the problem by modifying the
2003         * default behaviour of the PropertyUtilsBean class by overriding this
2004         * method.
2005         *
2006         * @param bean Map bean
2007         * @param propertyName The property name
2008         * @param value the property value
2009         * 
2010         * @throws IllegalArgumentException when the propertyName is regarded as
2011         * being invalid.
2012         * 
2013         * @throws IllegalAccessException just in case subclasses override this
2014         * method to try to access real setter methods and find permission is denied.
2015         * 
2016         * @throws InvocationTargetException just in case subclasses override this
2017         * method to try to access real setter methods, and find it throws an
2018         * exception when invoked.
2019         * 
2020         * @throws NoSuchMethodException just in case subclasses override this
2021         * method to try to access real setter methods, and want to fail if
2022         * no simple method is available.
2023         * @since 1.8.0
2024         */
2025        protected void setPropertyOfMapBean(Map bean, String propertyName, Object value)
2026            throws IllegalArgumentException, IllegalAccessException, 
2027            InvocationTargetException, NoSuchMethodException {
2028    
2029            if (resolver.isMapped(propertyName)) {
2030                String name = resolver.getProperty(propertyName);
2031                if (name == null || name.length() == 0) {
2032                    propertyName = resolver.getKey(propertyName);
2033                }
2034            }
2035    
2036            if (resolver.isIndexed(propertyName) ||
2037                resolver.isMapped(propertyName)) {
2038                throw new IllegalArgumentException(
2039                        "Indexed or mapped properties are not supported on"
2040                        + " objects of type Map: " + propertyName);
2041            }
2042    
2043            bean.put(propertyName, value);
2044        }
2045    
2046    
2047    
2048        /**
2049         * Set the value of the specified property of the specified bean,
2050         * no matter which property reference format is used, with no
2051         * type conversions.
2052         *
2053         * @param bean Bean whose property is to be modified
2054         * @param name Possibly indexed and/or nested name of the property
2055         *  to be modified
2056         * @param value Value to which this property is to be set
2057         *
2058         * @exception IllegalAccessException if the caller does not have
2059         *  access to the property accessor method
2060         * @exception IllegalArgumentException if <code>bean</code> or
2061         *  <code>name</code> is null
2062         * @exception InvocationTargetException if the property accessor method
2063         *  throws an exception
2064         * @exception NoSuchMethodException if an accessor method for this
2065         *  propety cannot be found
2066         */
2067        public void setProperty(Object bean, String name, Object value)
2068                throws IllegalAccessException, InvocationTargetException,
2069                NoSuchMethodException {
2070    
2071            setNestedProperty(bean, name, value);
2072    
2073        }
2074    
2075    
2076        /**
2077         * Set the value of the specified simple property of the specified bean,
2078         * with no type conversions.
2079         *
2080         * @param bean Bean whose property is to be modified
2081         * @param name Name of the property to be modified
2082         * @param value Value to which the property should be set
2083         *
2084         * @exception IllegalAccessException if the caller does not have
2085         *  access to the property accessor method
2086         * @exception IllegalArgumentException if <code>bean</code> or
2087         *  <code>name</code> is null
2088         * @exception IllegalArgumentException if the property name is
2089         *  nested or indexed
2090         * @exception InvocationTargetException if the property accessor method
2091         *  throws an exception
2092         * @exception NoSuchMethodException if an accessor method for this
2093         *  propety cannot be found
2094         */
2095        public void setSimpleProperty(Object bean,
2096                                             String name, Object value)
2097                throws IllegalAccessException, InvocationTargetException,
2098                NoSuchMethodException {
2099    
2100            if (bean == null) {
2101                throw new IllegalArgumentException("No bean specified");
2102            }
2103            if (name == null) {
2104                throw new IllegalArgumentException("No name specified for bean class '" +
2105                        bean.getClass() + "'");
2106            }
2107    
2108            // Validate the syntax of the property name
2109            if (resolver.hasNested(name)) {
2110                throw new IllegalArgumentException
2111                        ("Nested property names are not allowed: Property '" +
2112                        name + "' on bean class '" + bean.getClass() + "'");
2113            } else if (resolver.isIndexed(name)) {
2114                throw new IllegalArgumentException
2115                        ("Indexed property names are not allowed: Property '" +
2116                        name + "' on bean class '" + bean.getClass() + "'");
2117            } else if (resolver.isMapped(name)) {
2118                throw new IllegalArgumentException
2119                        ("Mapped property names are not allowed: Property '" +
2120                        name + "' on bean class '" + bean.getClass() + "'");
2121            }
2122    
2123            // Handle DynaBean instances specially
2124            if (bean instanceof DynaBean) {
2125                DynaProperty descriptor =
2126                        ((DynaBean) bean).getDynaClass().getDynaProperty(name);
2127                if (descriptor == null) {
2128                    throw new NoSuchMethodException("Unknown property '" +
2129                            name + "' on dynaclass '" + 
2130                            ((DynaBean) bean).getDynaClass() + "'" );
2131                }
2132                ((DynaBean) bean).set(name, value);
2133                return;
2134            }
2135    
2136            // Retrieve the property setter method for the specified property
2137            PropertyDescriptor descriptor =
2138                    getPropertyDescriptor(bean, name);
2139            if (descriptor == null) {
2140                throw new NoSuchMethodException("Unknown property '" +
2141                        name + "' on class '" + bean.getClass() + "'" );
2142            }
2143            Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
2144            if (writeMethod == null) {
2145                throw new NoSuchMethodException("Property '" + name +
2146                        "' has no setter method in class '" + bean.getClass() + "'");
2147            }
2148    
2149            // Call the property setter method
2150            Object[] values = new Object[1];
2151            values[0] = value;
2152            if (log.isTraceEnabled()) {
2153                String valueClassName =
2154                    value == null ? "<null>" : value.getClass().getName();
2155                log.trace("setSimpleProperty: Invoking method " + writeMethod
2156                          + " with value " + value + " (class " + valueClassName + ")");
2157            }
2158            invokeMethod(writeMethod, bean, values);
2159    
2160        }
2161        
2162        /** This just catches and wraps IllegalArgumentException. */
2163        private Object invokeMethod(
2164                            Method method, 
2165                            Object bean, 
2166                            Object[] values) 
2167                                throws
2168                                    IllegalAccessException,
2169                                    InvocationTargetException {
2170            if(bean == null) {
2171                throw new IllegalArgumentException("No bean specified " +
2172                    "- this should have been checked before reaching this method");
2173            }
2174    
2175            try {
2176                
2177                return method.invoke(bean, values);
2178            
2179            } catch (NullPointerException cause) {
2180                // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
2181                // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
2182                String valueString = "";
2183                if (values != null) {
2184                    for (int i = 0; i < values.length; i++) {
2185                        if (i>0) {
2186                            valueString += ", " ;
2187                        }
2188                        if (values[i] == null) {
2189                            valueString += "<null>";
2190                        } else {
2191                            valueString += (values[i]).getClass().getName();
2192                        }
2193                    }
2194                }
2195                String expectedString = "";
2196                Class[] parTypes = method.getParameterTypes();
2197                if (parTypes != null) {
2198                    for (int i = 0; i < parTypes.length; i++) {
2199                        if (i > 0) {
2200                            expectedString += ", ";
2201                        }
2202                        expectedString += parTypes[i].getName();
2203                    }
2204                }
2205                IllegalArgumentException e = new IllegalArgumentException(
2206                    "Cannot invoke " + method.getDeclaringClass().getName() + "." 
2207                    + method.getName() + " on bean class '" + bean.getClass() +
2208                    "' - " + cause.getMessage()
2209                    // as per https://issues.apache.org/jira/browse/BEANUTILS-224
2210                    + " - had objects of type \"" + valueString
2211                    + "\" but expected signature \""
2212                    +   expectedString + "\""
2213                    );
2214                if (!BeanUtils.initCause(e, cause)) {
2215                    log.error("Method invocation failed", cause);
2216                }
2217                throw e;
2218            } catch (IllegalArgumentException cause) {
2219                String valueString = "";
2220                if (values != null) {
2221                    for (int i = 0; i < values.length; i++) {
2222                        if (i>0) {
2223                            valueString += ", " ;
2224                        }
2225                        if (values[i] == null) {
2226                            valueString += "<null>";
2227                        } else {
2228                            valueString += (values[i]).getClass().getName();
2229                        }
2230                    }
2231                }
2232                String expectedString = "";
2233                Class[] parTypes = method.getParameterTypes();
2234                if (parTypes != null) {
2235                    for (int i = 0; i < parTypes.length; i++) {
2236                        if (i > 0) {
2237                            expectedString += ", ";
2238                        }
2239                        expectedString += parTypes[i].getName();
2240                    }
2241                }
2242                IllegalArgumentException e = new IllegalArgumentException(
2243                    "Cannot invoke " + method.getDeclaringClass().getName() + "." 
2244                    + method.getName() + " on bean class '" + bean.getClass() +
2245                    "' - " + cause.getMessage()
2246                    // as per https://issues.apache.org/jira/browse/BEANUTILS-224
2247                    + " - had objects of type \"" + valueString
2248                    + "\" but expected signature \""
2249                    +   expectedString + "\""
2250                    );
2251                if (!BeanUtils.initCause(e, cause)) {
2252                    log.error("Method invocation failed", cause);
2253                }
2254                throw e;
2255                
2256            }
2257        }
2258    }