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