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