1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
99
100 protected static PropertyUtilsBean getInstance() {
101 return BeanUtilsBean.getInstance().getPropertyUtils();
102 }
103
104
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
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
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
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
211 if (isWriteable(dest, name)) {
212 Object value = ((Map) orig).get(name);
213 setSimpleProperty(dest, name, value);
214 }
215 }
216 }
217 } else
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
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
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
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
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
390 PropertyDescriptor descriptor =
391 getPropertyDescriptor(bean, name);
392 if (descriptor == null) {
393 throw new NoSuchMethodException("Unknown property '" +
394 name + "'");
395 }
396
397
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
419 Method readMethod = getReadMethod(descriptor);
420 if (readMethod == null) {
421 throw new NoSuchMethodException("Property '" + name +
422 "' has no getter method");
423 }
424
425
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
433 return ((java.util.List) value).get(index);
434 }
435 } else {
436
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
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
481 String key = name.substring(delim + 1, delim2);
482 name = name.substring(0, delim);
483
484
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
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
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
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
554 Method readMethod = descriptor.getReadMethod();
555 if (readMethod != null) {
556 Object invokeResult = invokeMethod(readMethod, bean, new Object[0]);
557
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
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
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
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
796
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
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
838
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
852 ++bracketCount;
853 break;
854
855 case PropertyUtils.MAPPED_DELIM2:
856 case PropertyUtils.INDEXED_DELIM2:
857
858 --bracketCount;
859 break;
860 }
861 }
862
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
886 PropertyDescriptor descriptors[] = null;
887 descriptors =
888 (PropertyDescriptor[]) descriptorsCache.get(beanClass);
889 if (descriptors != null) {
890 return (descriptors);
891 }
892
893
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
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
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
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
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
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
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
1177 if (bean instanceof DynaBean) {
1178
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
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
1230 if (bean instanceof DynaBean) {
1231
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
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
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
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
1366 PropertyDescriptor descriptor =
1367 getPropertyDescriptor(bean, name);
1368 if (descriptor == null) {
1369 throw new NoSuchMethodException("Unknown property '" +
1370 name + "'");
1371 }
1372
1373
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
1406 Method readMethod = descriptor.getReadMethod();
1407 if (readMethod == null) {
1408 throw new NoSuchMethodException("Property '" + name +
1409 "' has no getter method");
1410 }
1411
1412
1413 Object array = invokeMethod(readMethod, bean, new Object[0]);
1414 if (!array.getClass().isArray()) {
1415 if (array instanceof List) {
1416
1417 ((List) array).set(index, value);
1418 } else {
1419 throw new IllegalArgumentException("Property '" + name +
1420 "' is not indexed");
1421 }
1422 } else {
1423
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
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
1470 String key = name.substring(delim + 1, delim2);
1471 name = name.substring(0, delim);
1472
1473
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
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
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
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
1555 Method readMethod = descriptor.getReadMethod();
1556 if (readMethod != null) {
1557 Object invokeResult = invokeMethod(readMethod, bean, new Object[0]);
1558
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
1634 PropertyDescriptor descriptor =
1635 getPropertyDescriptor(bean, name);
1636 if (descriptor == null) {
1637
1638 ((Map) bean).put(name, value);
1639 } else {
1640
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
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
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
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
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 }