View Javadoc

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