View Javadoc

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