1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.beanutils;
20
21
22 import java.beans.IndexedPropertyDescriptor;
23 import java.beans.PropertyDescriptor;
24 import java.lang.reflect.Array;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.Map;
32
33 import org.apache.commons.beanutils.expression.Resolver;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37
38 /***
39 * <p>JavaBean property population methods.</p>
40 *
41 * <p>This class provides implementations for the utility methods in
42 * {@link BeanUtils}.
43 * Different instances can be used to isolate caches between classloaders
44 * and to vary the value converters registered.</p>
45 *
46 * @author Craig R. McClanahan
47 * @author Ralph Schaer
48 * @author Chris Audley
49 * @author Rey Francois
50 * @author Gregor Rayman
51 * @version $Revision: 556229 $ $Date: 2007-07-14 07:11:19 +0100 (Sat, 14 Jul 2007) $
52 * @see BeanUtils
53 * @since 1.7
54 */
55
56 public class BeanUtilsBean {
57
58
59
60
61 /***
62 * Contains <code>BeanUtilsBean</code> instances indexed by context classloader.
63 */
64 private static final ContextClassLoaderLocal
65 BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal() {
66
67 protected Object initialValue() {
68 return new BeanUtilsBean();
69 }
70 };
71
72 /***
73 * Gets the instance which provides the functionality for {@link BeanUtils}.
74 * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
75 * This mechanism provides isolation for web apps deployed in the same container.
76 *
77 * @return The (pseudo-singleton) BeanUtils bean instance
78 */
79 public static BeanUtilsBean getInstance() {
80 return (BeanUtilsBean) BEANS_BY_CLASSLOADER.get();
81 }
82
83 /***
84 * Sets the instance which provides the functionality for {@link BeanUtils}.
85 * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
86 * This mechanism provides isolation for web apps deployed in the same container.
87 *
88 * @param newInstance The (pseudo-singleton) BeanUtils bean instance
89 */
90 public static void setInstance(BeanUtilsBean newInstance) {
91 BEANS_BY_CLASSLOADER.set(newInstance);
92 }
93
94
95
96 /***
97 * Logging for this instance
98 */
99 private Log log = LogFactory.getLog(BeanUtils.class);
100
101 /*** Used to perform conversions between object types when setting properties */
102 private ConvertUtilsBean convertUtilsBean;
103
104 /*** Used to access properties*/
105 private PropertyUtilsBean propertyUtilsBean;
106
107 /*** A reference to Throwable's initCause method, or null if it's not there in this JVM */
108 private static final Method INIT_CAUSE_METHOD = getInitCauseMethod();
109
110
111
112 /***
113 * <p>Constructs an instance using new property
114 * and conversion instances.</p>
115 */
116 public BeanUtilsBean() {
117 this(new ConvertUtilsBean(), new PropertyUtilsBean());
118 }
119
120 /***
121 * <p>Constructs an instance using given conversion instances
122 * and new {@link PropertyUtilsBean} instance.</p>
123 *
124 * @param convertUtilsBean use this <code>ConvertUtilsBean</code>
125 * to perform conversions from one object to another
126 *
127 * @since 1.8.0
128 */
129 public BeanUtilsBean(ConvertUtilsBean convertUtilsBean) {
130 this(convertUtilsBean, new PropertyUtilsBean());
131 }
132
133 /***
134 * <p>Constructs an instance using given property and conversion instances.</p>
135 *
136 * @param convertUtilsBean use this <code>ConvertUtilsBean</code>
137 * to perform conversions from one object to another
138 * @param propertyUtilsBean use this <code>PropertyUtilsBean</code>
139 * to access properties
140 */
141 public BeanUtilsBean(
142 ConvertUtilsBean convertUtilsBean,
143 PropertyUtilsBean propertyUtilsBean) {
144
145 this.convertUtilsBean = convertUtilsBean;
146 this.propertyUtilsBean = propertyUtilsBean;
147 }
148
149
150
151 /***
152 * <p>Clone a bean based on the available property getters and setters,
153 * even if the bean class itself does not implement Cloneable.</p>
154 *
155 * <p>
156 * <strong>Note:</strong> this method creates a <strong>shallow</strong> clone.
157 * In other words, any objects referred to by the bean are shared with the clone
158 * rather than being cloned in turn.
159 * </p>
160 *
161 * @param bean Bean to be cloned
162 * @return the cloned bean
163 *
164 * @exception IllegalAccessException if the caller does not have
165 * access to the property accessor method
166 * @exception InstantiationException if a new instance of the bean's
167 * class cannot be instantiated
168 * @exception InvocationTargetException if the property accessor method
169 * throws an exception
170 * @exception NoSuchMethodException if an accessor method for this
171 * property cannot be found
172 */
173 public Object cloneBean(Object bean)
174 throws IllegalAccessException, InstantiationException,
175 InvocationTargetException, NoSuchMethodException {
176
177 if (log.isDebugEnabled()) {
178 log.debug("Cloning bean: " + bean.getClass().getName());
179 }
180 Object newBean = null;
181 if (bean instanceof DynaBean) {
182 newBean = ((DynaBean) bean).getDynaClass().newInstance();
183 } else {
184 newBean = bean.getClass().newInstance();
185 }
186 getPropertyUtils().copyProperties(newBean, bean);
187 return (newBean);
188
189 }
190
191
192 /***
193 * <p>Copy property values from the origin bean to the destination bean
194 * for all cases where the property names are the same. For each
195 * property, a conversion is attempted as necessary. All combinations of
196 * standard JavaBeans and DynaBeans as origin and destination are
197 * supported. Properties that exist in the origin bean, but do not exist
198 * in the destination bean (or are read-only in the destination bean) are
199 * silently ignored.</p>
200 *
201 * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
202 * to contain String-valued <strong>simple</strong> property names as the keys, pointing at
203 * the corresponding property values that will be converted (if necessary)
204 * and set in the destination bean. <strong>Note</strong> that this method
205 * is intended to perform a "shallow copy" of the properties and so complex
206 * properties (for example, nested ones) will not be copied.</p>
207 *
208 * <p>This method differs from <code>populate()</code>, which
209 * was primarily designed for populating JavaBeans from the map of request
210 * parameters retrieved on an HTTP request, is that no scalar->indexed
211 * or indexed->scalar manipulations are performed. If the origin property
212 * is indexed, the destination property must be also.</p>
213 *
214 * <p>If you know that no type conversions are required, the
215 * <code>copyProperties()</code> method in {@link PropertyUtils} will
216 * execute faster than this method.</p>
217 *
218 * <p><strong>FIXME</strong> - Indexed and mapped properties that do not
219 * have getter and setter methods for the underlying array or Map are not
220 * copied by this method.</p>
221 *
222 * @param dest Destination bean whose properties are modified
223 * @param orig Origin bean whose properties are retrieved
224 *
225 * @exception IllegalAccessException if the caller does not have
226 * access to the property accessor method
227 * @exception IllegalArgumentException if the <code>dest</code> or
228 * <code>orig</code> argument is null or if the <code>dest</code>
229 * property type is different from the source type and the relevant
230 * converter has not been registered.
231 * @exception InvocationTargetException if the property accessor method
232 * throws an exception
233 */
234 public void copyProperties(Object dest, Object orig)
235 throws IllegalAccessException, InvocationTargetException {
236
237
238 if (dest == null) {
239 throw new IllegalArgumentException
240 ("No destination bean specified");
241 }
242 if (orig == null) {
243 throw new IllegalArgumentException("No origin bean specified");
244 }
245 if (log.isDebugEnabled()) {
246 log.debug("BeanUtils.copyProperties(" + dest + ", " +
247 orig + ")");
248 }
249
250
251 if (orig instanceof DynaBean) {
252 DynaProperty[] origDescriptors =
253 ((DynaBean) orig).getDynaClass().getDynaProperties();
254 for (int i = 0; i < origDescriptors.length; i++) {
255 String name = origDescriptors[i].getName();
256
257
258 if (getPropertyUtils().isReadable(orig, name) &&
259 getPropertyUtils().isWriteable(dest, name)) {
260 Object value = ((DynaBean) orig).get(name);
261 copyProperty(dest, name, value);
262 }
263 }
264 } else if (orig instanceof Map) {
265 Iterator names = ((Map) orig).keySet().iterator();
266 while (names.hasNext()) {
267 String name = (String) names.next();
268 if (getPropertyUtils().isWriteable(dest, name)) {
269 Object value = ((Map) orig).get(name);
270 copyProperty(dest, name, value);
271 }
272 }
273 } else
274 PropertyDescriptor[] origDescriptors =
275 getPropertyUtils().getPropertyDescriptors(orig);
276 for (int i = 0; i < origDescriptors.length; i++) {
277 String name = origDescriptors[i].getName();
278 if ("class".equals(name)) {
279 continue;
280 }
281 if (getPropertyUtils().isReadable(orig, name) &&
282 getPropertyUtils().isWriteable(dest, name)) {
283 try {
284 Object value =
285 getPropertyUtils().getSimpleProperty(orig, name);
286 copyProperty(dest, name, value);
287 } catch (NoSuchMethodException e) {
288
289 }
290 }
291 }
292 }
293
294 }
295
296
297 /***
298 * <p>Copy the specified property value to the specified destination bean,
299 * performing any type conversion that is required. If the specified
300 * bean does not have a property of the specified name, or the property
301 * is read only on the destination bean, return without
302 * doing anything. If you have custom destination property types, register
303 * {@link Converter}s for them by calling the <code>register()</code>
304 * method of {@link ConvertUtils}.</p>
305 *
306 * <p><strong>IMPLEMENTATION RESTRICTIONS</strong>:</p>
307 * <ul>
308 * <li>Does not support destination properties that are indexed,
309 * but only an indexed setter (as opposed to an array setter)
310 * is available.</li>
311 * <li>Does not support destination properties that are mapped,
312 * but only a keyed setter (as opposed to a Map setter)
313 * is available.</li>
314 * <li>The desired property type of a mapped setter cannot be
315 * determined (since Maps support any data type), so no conversion
316 * will be performed.</li>
317 * </ul>
318 *
319 * @param bean Bean on which setting is to be performed
320 * @param name Property name (can be nested/indexed/mapped/combo)
321 * @param value Value to be set
322 *
323 * @exception IllegalAccessException if the caller does not have
324 * access to the property accessor method
325 * @exception InvocationTargetException if the property accessor method
326 * throws an exception
327 */
328 public void copyProperty(Object bean, String name, Object value)
329 throws IllegalAccessException, InvocationTargetException {
330
331
332 if (log.isTraceEnabled()) {
333 StringBuffer sb = new StringBuffer(" copyProperty(");
334 sb.append(bean);
335 sb.append(", ");
336 sb.append(name);
337 sb.append(", ");
338 if (value == null) {
339 sb.append("<NULL>");
340 } else if (value instanceof String) {
341 sb.append((String) value);
342 } else if (value instanceof String[]) {
343 String[] values = (String[]) value;
344 sb.append('[');
345 for (int i = 0; i < values.length; i++) {
346 if (i > 0) {
347 sb.append(',');
348 }
349 sb.append(values[i]);
350 }
351 sb.append(']');
352 } else {
353 sb.append(value.toString());
354 }
355 sb.append(')');
356 log.trace(sb.toString());
357 }
358
359
360 Object target = bean;
361 Resolver resolver = getPropertyUtils().getResolver();
362 while (resolver.hasNested(name)) {
363 try {
364 target = getPropertyUtils().getProperty(target, resolver.next(name));
365 name = resolver.remove(name);
366 } catch (NoSuchMethodException e) {
367 return;
368 }
369 }
370 if (log.isTraceEnabled()) {
371 log.trace(" Target bean = " + target);
372 log.trace(" Target name = " + name);
373 }
374
375
376 String propName = resolver.getProperty(name);
377 Class type = null;
378 int index = resolver.getIndex(name);
379 String key = resolver.getKey(name);
380
381
382 if (target instanceof DynaBean) {
383 DynaClass dynaClass = ((DynaBean) target).getDynaClass();
384 DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
385 if (dynaProperty == null) {
386 return;
387 }
388 type = dynaProperty.getType();
389 } else {
390 PropertyDescriptor descriptor = null;
391 try {
392 descriptor =
393 getPropertyUtils().getPropertyDescriptor(target, name);
394 if (descriptor == null) {
395 return;
396 }
397 } catch (NoSuchMethodException e) {
398 return;
399 }
400 type = descriptor.getPropertyType();
401 if (type == null) {
402
403 if (log.isTraceEnabled()) {
404 log.trace(" target type for property '" +
405 propName + "' is null, so skipping ths setter");
406 }
407 return;
408 }
409 }
410 if (log.isTraceEnabled()) {
411 log.trace(" target propName=" + propName + ", type=" +
412 type + ", index=" + index + ", key=" + key);
413 }
414
415
416 if (index >= 0) {
417 value = convert(value, type.getComponentType());
418 try {
419 getPropertyUtils().setIndexedProperty(target, propName,
420 index, value);
421 } catch (NoSuchMethodException e) {
422 throw new InvocationTargetException
423 (e, "Cannot set " + propName);
424 }
425 } else if (key != null) {
426
427
428
429 try {
430 getPropertyUtils().setMappedProperty(target, propName,
431 key, value);
432 } catch (NoSuchMethodException e) {
433 throw new InvocationTargetException
434 (e, "Cannot set " + propName);
435 }
436 } else {
437 value = convert(value, type);
438 try {
439 getPropertyUtils().setSimpleProperty(target, propName, value);
440 } catch (NoSuchMethodException e) {
441 throw new InvocationTargetException
442 (e, "Cannot set " + propName);
443 }
444 }
445
446 }
447
448
449 /***
450 * <p>Return the entire set of properties for which the specified bean
451 * provides a read method. This map contains the to <code>String</code>
452 * converted property values for all properties for which a read method
453 * is provided (i.e. where the getReadMethod() returns non-null).</p>
454 *
455 * <p>This map can be fed back to a call to
456 * <code>BeanUtils.populate()</code> to reconsitute the same set of
457 * properties, modulo differences for read-only and write-only
458 * properties, but only if there are no indexed properties.</p>
459 *
460 * <p><strong>Warning:</strong> if any of the bean property implementations
461 * contain (directly or indirectly) a call to this method then
462 * a stack overflow may result. For example:
463 * <code><pre>
464 * class MyBean
465 * {
466 * public Map getParameterMap()
467 * {
468 * BeanUtils.describe(this);
469 * }
470 * }
471 * </pre></code>
472 * will result in an infinite regression when <code>getParametersMap</code>
473 * is called. It is recommended that such methods are given alternative
474 * names (for example, <code>parametersMap</code>).
475 * </p>
476 * @param bean Bean whose properties are to be extracted
477 * @return Map of property descriptors
478 *
479 * @exception IllegalAccessException if the caller does not have
480 * access to the property accessor method
481 * @exception InvocationTargetException if the property accessor method
482 * throws an exception
483 * @exception NoSuchMethodException if an accessor method for this
484 * property cannot be found
485 */
486 public Map describe(Object bean)
487 throws IllegalAccessException, InvocationTargetException,
488 NoSuchMethodException {
489
490 if (bean == null) {
491
492 return (new java.util.HashMap());
493 }
494
495 if (log.isDebugEnabled()) {
496 log.debug("Describing bean: " + bean.getClass().getName());
497 }
498
499 Map description = new HashMap();
500 if (bean instanceof DynaBean) {
501 DynaProperty[] descriptors =
502 ((DynaBean) bean).getDynaClass().getDynaProperties();
503 for (int i = 0; i < descriptors.length; i++) {
504 String name = descriptors[i].getName();
505 description.put(name, getProperty(bean, name));
506 }
507 } else {
508 PropertyDescriptor[] descriptors =
509 getPropertyUtils().getPropertyDescriptors(bean);
510 for (int i = 0; i < descriptors.length; i++) {
511 String name = descriptors[i].getName();
512 if (getPropertyUtils().getReadMethod(descriptors[i]) != null) {
513 description.put(name, getProperty(bean, name));
514 }
515 }
516 }
517 return (description);
518
519 }
520
521
522 /***
523 * Return the value of the specified array property of the specified
524 * bean, as a String array.
525 *
526 * @param bean Bean whose property is to be extracted
527 * @param name Name of the property to be extracted
528 * @return The array 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 * property cannot be found
536 */
537 public String[] getArrayProperty(Object bean, String name)
538 throws IllegalAccessException, InvocationTargetException,
539 NoSuchMethodException {
540
541 Object value = getPropertyUtils().getProperty(bean, name);
542 if (value == null) {
543 return (null);
544 } else if (value instanceof Collection) {
545 ArrayList values = new ArrayList();
546 Iterator items = ((Collection) value).iterator();
547 while (items.hasNext()) {
548 Object item = items.next();
549 if (item == null) {
550 values.add((String) null);
551 } else {
552
553 values.add(getConvertUtils().convert(item));
554 }
555 }
556 return ((String[]) values.toArray(new String[values.size()]));
557 } else if (value.getClass().isArray()) {
558 int n = Array.getLength(value);
559 String[] results = new String[n];
560 for (int i = 0; i < n; i++) {
561 Object item = Array.get(value, i);
562 if (item == null) {
563 results[i] = null;
564 } else {
565
566 results[i] = getConvertUtils().convert(item);
567 }
568 }
569 return (results);
570 } else {
571 String[] results = new String[1];
572 results[0] = getConvertUtils().convert(value);
573 return (results);
574 }
575
576 }
577
578
579 /***
580 * Return the value of the specified indexed property of the specified
581 * bean, as a String. The zero-relative index of the
582 * required value must be included (in square brackets) as a suffix to
583 * the property name, or <code>IllegalArgumentException</code> will be
584 * thrown.
585 *
586 * @param bean Bean whose property is to be extracted
587 * @param name <code>propertyname[index]</code> of the property value
588 * to be extracted
589 * @return The indexed property's value, converted to a String
590 *
591 * @exception IllegalAccessException if the caller does not have
592 * access to the property accessor method
593 * @exception InvocationTargetException if the property accessor method
594 * throws an exception
595 * @exception NoSuchMethodException if an accessor method for this
596 * property cannot be found
597 */
598 public String getIndexedProperty(Object bean, String name)
599 throws IllegalAccessException, InvocationTargetException,
600 NoSuchMethodException {
601
602 Object value = getPropertyUtils().getIndexedProperty(bean, name);
603 return (getConvertUtils().convert(value));
604
605 }
606
607
608 /***
609 * Return the value of the specified indexed property of the specified
610 * bean, as a String. The index is specified as a method parameter and
611 * must *not* be included in the property name expression
612 *
613 * @param bean Bean whose property is to be extracted
614 * @param name Simple property name of the property value to be extracted
615 * @param index Index of the property value to be extracted
616 * @return The indexed property's value, converted to a String
617 *
618 * @exception IllegalAccessException if the caller does not have
619 * access to the property accessor method
620 * @exception InvocationTargetException if the property accessor method
621 * throws an exception
622 * @exception NoSuchMethodException if an accessor method for this
623 * property cannot be found
624 */
625 public String getIndexedProperty(Object bean,
626 String name, int index)
627 throws IllegalAccessException, InvocationTargetException,
628 NoSuchMethodException {
629
630 Object value = getPropertyUtils().getIndexedProperty(bean, name, index);
631 return (getConvertUtils().convert(value));
632
633 }
634
635
636 /***
637 * Return the value of the specified indexed property of the specified
638 * bean, as a String. The String-valued key of the required value
639 * must be included (in parentheses) as a suffix to
640 * the property name, or <code>IllegalArgumentException</code> will be
641 * thrown.
642 *
643 * @param bean Bean whose property is to be extracted
644 * @param name <code>propertyname(index)</code> of the property value
645 * to be extracted
646 * @return The mapped property's value, converted to a String
647 *
648 * @exception IllegalAccessException if the caller does not have
649 * access to the property accessor method
650 * @exception InvocationTargetException if the property accessor method
651 * throws an exception
652 * @exception NoSuchMethodException if an accessor method for this
653 * property cannot be found
654 */
655 public String getMappedProperty(Object bean, String name)
656 throws IllegalAccessException, InvocationTargetException,
657 NoSuchMethodException {
658
659 Object value = getPropertyUtils().getMappedProperty(bean, name);
660 return (getConvertUtils().convert(value));
661
662 }
663
664
665 /***
666 * Return the value of the specified mapped property of the specified
667 * bean, as a String. The key is specified as a method parameter and
668 * must *not* be included in the property name expression
669 *
670 * @param bean Bean whose property is to be extracted
671 * @param name Simple property name of the property value to be extracted
672 * @param key Lookup key of the property value to be extracted
673 * @return The mapped property's value, converted to a String
674 *
675 * @exception IllegalAccessException if the caller does not have
676 * access to the property accessor method
677 * @exception InvocationTargetException if the property accessor method
678 * throws an exception
679 * @exception NoSuchMethodException if an accessor method for this
680 * property cannot be found
681 */
682 public String getMappedProperty(Object bean,
683 String name, String key)
684 throws IllegalAccessException, InvocationTargetException,
685 NoSuchMethodException {
686
687 Object value = getPropertyUtils().getMappedProperty(bean, name, key);
688 return (getConvertUtils().convert(value));
689
690 }
691
692
693 /***
694 * Return the value of the (possibly nested) property of the specified
695 * name, for the specified bean, as a String.
696 *
697 * @param bean Bean whose property is to be extracted
698 * @param name Possibly nested name of the property to be extracted
699 * @return The nested property's value, converted to a String
700 *
701 * @exception IllegalAccessException if the caller does not have
702 * access to the property accessor method
703 * @exception IllegalArgumentException if a nested reference to a
704 * property returns null
705 * @exception InvocationTargetException if the property accessor method
706 * throws an exception
707 * @exception NoSuchMethodException if an accessor method for this
708 * property cannot be found
709 */
710 public String getNestedProperty(Object bean, String name)
711 throws IllegalAccessException, InvocationTargetException,
712 NoSuchMethodException {
713
714 Object value = getPropertyUtils().getNestedProperty(bean, name);
715 return (getConvertUtils().convert(value));
716
717 }
718
719
720 /***
721 * Return the value of the specified property of the specified bean,
722 * no matter which property reference format is used, as a String.
723 *
724 * @param bean Bean whose property is to be extracted
725 * @param name Possibly indexed and/or nested name of the property
726 * to be extracted
727 * @return The property's value, converted to a String
728 *
729 * @exception IllegalAccessException if the caller does not have
730 * access to the property accessor method
731 * @exception InvocationTargetException if the property accessor method
732 * throws an exception
733 * @exception NoSuchMethodException if an accessor method for this
734 * property cannot be found
735 */
736 public String getProperty(Object bean, String name)
737 throws IllegalAccessException, InvocationTargetException,
738 NoSuchMethodException {
739
740 return (getNestedProperty(bean, name));
741
742 }
743
744
745 /***
746 * Return the value of the specified simple property of the specified
747 * bean, converted to a String.
748 *
749 * @param bean Bean whose property is to be extracted
750 * @param name Name of the property to be extracted
751 * @return The property's value, converted to a String
752 *
753 * @exception IllegalAccessException if the caller does not have
754 * access to the property accessor method
755 * @exception InvocationTargetException if the property accessor method
756 * throws an exception
757 * @exception NoSuchMethodException if an accessor method for this
758 * property cannot be found
759 */
760 public String getSimpleProperty(Object bean, String name)
761 throws IllegalAccessException, InvocationTargetException,
762 NoSuchMethodException {
763
764 Object value = getPropertyUtils().getSimpleProperty(bean, name);
765 return (getConvertUtils().convert(value));
766
767 }
768
769
770 /***
771 * <p>Populate the JavaBeans properties of the specified bean, based on
772 * the specified name/value pairs. This method uses Java reflection APIs
773 * to identify corresponding "property setter" method names, and deals
774 * with setter arguments of type <code>String</code>, <code>boolean</code>,
775 * <code>int</code>, <code>long</code>, <code>float</code>, and
776 * <code>double</code>. In addition, array setters for these types (or the
777 * corresponding primitive types) can also be identified.</p>
778 *
779 * <p>The particular setter method to be called for each property is
780 * determined using the usual JavaBeans introspection mechanisms. Thus,
781 * you may identify custom setter methods using a BeanInfo class that is
782 * associated with the class of the bean itself. If no such BeanInfo
783 * class is available, the standard method name conversion ("set" plus
784 * the capitalized name of the property in question) is used.</p>
785 *
786 * <p><strong>NOTE</strong>: It is contrary to the JavaBeans Specification
787 * to have more than one setter method (with different argument
788 * signatures) for the same property.</p>
789 *
790 * <p><strong>WARNING</strong> - The logic of this method is customized
791 * for extracting String-based request parameters from an HTTP request.
792 * It is probably not what you want for general property copying with
793 * type conversion. For that purpose, check out the
794 * <code>copyProperties()</code> method instead.</p>
795 *
796 * @param bean JavaBean whose properties are being populated
797 * @param properties Map keyed by property name, with the
798 * corresponding (String or String[]) value(s) to be set
799 *
800 * @exception IllegalAccessException if the caller does not have
801 * access to the property accessor method
802 * @exception InvocationTargetException if the property accessor method
803 * throws an exception
804 */
805 public void populate(Object bean, Map properties)
806 throws IllegalAccessException, InvocationTargetException {
807
808
809 if ((bean == null) || (properties == null)) {
810 return;
811 }
812 if (log.isDebugEnabled()) {
813 log.debug("BeanUtils.populate(" + bean + ", " +
814 properties + ")");
815 }
816
817
818 Iterator names = properties.keySet().iterator();
819 while (names.hasNext()) {
820
821
822 String name = (String) names.next();
823 if (name == null) {
824 continue;
825 }
826 Object value = properties.get(name);
827
828
829 setProperty(bean, name, value);
830
831 }
832
833 }
834
835
836 /***
837 * <p>Set the specified property value, performing type conversions as
838 * required to conform to the type of the destination property.</p>
839 *
840 * <p>If the property is read only then the method returns
841 * without throwing an exception.</p>
842 *
843 * <p>If <code>null</code> is passed into a property expecting a primitive value,
844 * then this will be converted as if it were a <code>null</code> string.</p>
845 *
846 * <p><strong>WARNING</strong> - The logic of this method is customized
847 * to meet the needs of <code>populate()</code>, and is probably not what
848 * you want for general property copying with type conversion. For that
849 * purpose, check out the <code>copyProperty()</code> method instead.</p>
850 *
851 * <p><strong>WARNING</strong> - PLEASE do not modify the behavior of this
852 * method without consulting with the Struts developer community. There
853 * are some subtleties to its functionality that are not documented in the
854 * Javadoc description above, yet are vital to the way that Struts utilizes
855 * this method.</p>
856 *
857 * @param bean Bean on which setting is to be performed
858 * @param name Property name (can be nested/indexed/mapped/combo)
859 * @param value Value to be set
860 *
861 * @exception IllegalAccessException if the caller does not have
862 * access to the property accessor method
863 * @exception InvocationTargetException if the property accessor method
864 * throws an exception
865 */
866 public void setProperty(Object bean, String name, Object value)
867 throws IllegalAccessException, InvocationTargetException {
868
869
870 if (log.isTraceEnabled()) {
871 StringBuffer sb = new StringBuffer(" setProperty(");
872 sb.append(bean);
873 sb.append(", ");
874 sb.append(name);
875 sb.append(", ");
876 if (value == null) {
877 sb.append("<NULL>");
878 } else if (value instanceof String) {
879 sb.append((String) value);
880 } else if (value instanceof String[]) {
881 String[] values = (String[]) value;
882 sb.append('[');
883 for (int i = 0; i < values.length; i++) {
884 if (i > 0) {
885 sb.append(',');
886 }
887 sb.append(values[i]);
888 }
889 sb.append(']');
890 } else {
891 sb.append(value.toString());
892 }
893 sb.append(')');
894 log.trace(sb.toString());
895 }
896
897
898 Object target = bean;
899 Resolver resolver = getPropertyUtils().getResolver();
900 while (resolver.hasNested(name)) {
901 try {
902 target = getPropertyUtils().getProperty(target, resolver.next(name));
903 name = resolver.remove(name);
904 } catch (NoSuchMethodException e) {
905 return;
906 }
907 }
908 if (log.isTraceEnabled()) {
909 log.trace(" Target bean = " + target);
910 log.trace(" Target name = " + name);
911 }
912
913
914 String propName = resolver.getProperty(name);
915 Class type = null;
916 int index = resolver.getIndex(name);
917 String key = resolver.getKey(name);
918
919
920 if (target instanceof DynaBean) {
921 DynaClass dynaClass = ((DynaBean) target).getDynaClass();
922 DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
923 if (dynaProperty == null) {
924 return;
925 }
926 type = dynaProperty.getType();
927 } else {
928 PropertyDescriptor descriptor = null;
929 try {
930 descriptor =
931 getPropertyUtils().getPropertyDescriptor(target, name);
932 if (descriptor == null) {
933 return;
934 }
935 } catch (NoSuchMethodException e) {
936 return;
937 }
938 if (descriptor instanceof MappedPropertyDescriptor) {
939 if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) {
940 if (log.isDebugEnabled()) {
941 log.debug("Skipping read-only property");
942 }
943 return;
944 }
945 type = ((MappedPropertyDescriptor) descriptor).
946 getMappedPropertyType();
947 } else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) {
948 if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) {
949 if (log.isDebugEnabled()) {
950 log.debug("Skipping read-only property");
951 }
952 return;
953 }
954 type = ((IndexedPropertyDescriptor) descriptor).
955 getIndexedPropertyType();
956 } else if (key != null) {
957 if (descriptor.getReadMethod() == null) {
958 if (log.isDebugEnabled()) {
959 log.debug("Skipping read-only property");
960 }
961 return;
962 }
963 type = (value == null) ? Object.class : value.getClass();
964 } else {
965 if (descriptor.getWriteMethod() == null) {
966 if (log.isDebugEnabled()) {
967 log.debug("Skipping read-only property");
968 }
969 return;
970 }
971 type = descriptor.getPropertyType();
972 }
973 }
974
975
976 Object newValue = null;
977 if (type.isArray() && (index < 0)) {
978 if (value == null) {
979 String[] values = new String[1];
980 values[0] = (String) value;
981 newValue = getConvertUtils().convert((String[]) values, type);
982 } else if (value instanceof String) {
983 newValue = getConvertUtils().convert(value, type);
984 } else if (value instanceof String[]) {
985 newValue = getConvertUtils().convert((String[]) value, type);
986 } else {
987 newValue = convert(value, type);
988 }
989 } else if (type.isArray()) {
990 if (value instanceof String || value == null) {
991 newValue = getConvertUtils().convert((String) value,
992 type.getComponentType());
993 } else if (value instanceof String[]) {
994 newValue = getConvertUtils().convert(((String[]) value)[0],
995 type.getComponentType());
996 } else {
997 newValue = convert(value, type.getComponentType());
998 }
999 } else {
1000 if ((value instanceof String) || (value == null)) {
1001 newValue = getConvertUtils().convert((String) value, type);
1002 } else if (value instanceof String[]) {
1003 newValue = getConvertUtils().convert(((String[]) value)[0],
1004 type);
1005 } else {
1006 newValue = convert(value, type);
1007 }
1008 }
1009
1010
1011 try {
1012 if (index >= 0) {
1013 getPropertyUtils().setIndexedProperty(target, propName,
1014 index, newValue);
1015 } else if (key != null) {
1016 getPropertyUtils().setMappedProperty(target, propName,
1017 key, newValue);
1018 } else {
1019 getPropertyUtils().setProperty(target, propName, newValue);
1020 }
1021 } catch (NoSuchMethodException e) {
1022 throw new InvocationTargetException
1023 (e, "Cannot set " + propName);
1024 }
1025
1026 }
1027
1028 /***
1029 * Gets the <code>ConvertUtilsBean</code> instance used to perform the conversions.
1030 *
1031 * @return The ConvertUtils bean instance
1032 */
1033 public ConvertUtilsBean getConvertUtils() {
1034 return convertUtilsBean;
1035 }
1036
1037 /***
1038 * Gets the <code>PropertyUtilsBean</code> instance used to access properties.
1039 *
1040 * @return The ConvertUtils bean instance
1041 */
1042 public PropertyUtilsBean getPropertyUtils() {
1043 return propertyUtilsBean;
1044 }
1045
1046 /***
1047 * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
1048 *
1049 * @param throwable The throwable.
1050 * @param cause The cause of the throwable.
1051 * @return true if the cause was initialized, otherwise false.
1052 */
1053 public boolean initCause(Throwable throwable, Throwable cause) {
1054 if (INIT_CAUSE_METHOD != null && cause != null) {
1055 try {
1056 INIT_CAUSE_METHOD.invoke(throwable, new Object[] { cause });
1057 return true;
1058 } catch (Throwable e) {
1059 return false;
1060 }
1061 }
1062 return false;
1063 }
1064
1065 /***
1066 * <p>Convert the value to an object of the specified class (if
1067 * possible).</p>
1068 *
1069 * @param value Value to be converted (may be null)
1070 * @param type Class of the value to be converted to
1071 * @return The converted value
1072 *
1073 * @exception ConversionException if thrown by an underlying Converter
1074 */
1075 protected Object convert(Object value, Class type) {
1076 Converter converter = getConvertUtils().lookup(type);
1077 if (converter != null) {
1078 log.trace(" USING CONVERTER " + converter);
1079 return converter.convert(type, value);
1080 } else {
1081 return value;
1082 }
1083 }
1084
1085 /***
1086 * Returns a <code>Method<code> allowing access to
1087 * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
1088 * or <code>null</code> if the method
1089 * does not exist.
1090 *
1091 * @return A <code>Method<code> for <code>Throwable.initCause</code>, or
1092 * <code>null</code> if unavailable.
1093 */
1094 private static Method getInitCauseMethod() {
1095 try {
1096 Class[] paramsClasses = new Class[] { Throwable.class };
1097 return Throwable.class.getMethod("initCause", paramsClasses);
1098 } catch (NoSuchMethodException e) {
1099 Log log = LogFactory.getLog(BeanUtils.class);
1100 if (log.isWarnEnabled()) {
1101 log.warn("Throwable does not have initCause() method in JDK 1.3");
1102 }
1103 return null;
1104 } catch (Throwable e) {
1105 Log log = LogFactory.getLog(BeanUtils.class);
1106 if (log.isWarnEnabled()) {
1107 log.warn("Error getting the Throwable initCause() method", e);
1108 }
1109 return null;
1110 }
1111 }
1112 }