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