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