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