Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/commons/beanutils/PropertyUtilsBean.java


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}