001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 019 package org.apache.commons.beanutils; 020 021 022 import java.beans.IndexedPropertyDescriptor; 023 import java.beans.PropertyDescriptor; 024 import java.lang.reflect.Array; 025 import java.lang.reflect.InvocationTargetException; 026 import java.lang.reflect.Method; 027 import java.util.ArrayList; 028 import java.util.Collection; 029 import java.util.HashMap; 030 import java.util.Iterator; 031 import java.util.Map; 032 033 import org.apache.commons.beanutils.expression.Resolver; 034 import org.apache.commons.logging.Log; 035 import org.apache.commons.logging.LogFactory; 036 037 038 /** 039 * <p>JavaBean property population methods.</p> 040 * 041 * <p>This class provides implementations for the utility methods in 042 * {@link BeanUtils}. 043 * Different instances can be used to isolate caches between classloaders 044 * and to vary the value converters registered.</p> 045 * 046 * @author Craig R. McClanahan 047 * @author Ralph Schaer 048 * @author Chris Audley 049 * @author Rey Francois 050 * @author Gregor Rayman 051 * @version $Revision: 834031 $ $Date: 2009-11-09 12:26:52 +0000 (Mon, 09 Nov 2009) $ 052 * @see BeanUtils 053 * @since 1.7 054 */ 055 056 public class BeanUtilsBean { 057 058 059 // ------------------------------------------------------ Private Class Variables 060 061 /** 062 * Contains <code>BeanUtilsBean</code> instances indexed by context classloader. 063 */ 064 private static final ContextClassLoaderLocal 065 BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal() { 066 // Creates the default instance used when the context classloader is unavailable 067 protected Object initialValue() { 068 return new BeanUtilsBean(); 069 } 070 }; 071 072 /** 073 * Gets the instance which provides the functionality for {@link BeanUtils}. 074 * This is a pseudo-singleton - an single instance is provided per (thread) context classloader. 075 * This mechanism provides isolation for web apps deployed in the same container. 076 * 077 * @return The (pseudo-singleton) BeanUtils bean instance 078 */ 079 public static BeanUtilsBean getInstance() { 080 return (BeanUtilsBean) BEANS_BY_CLASSLOADER.get(); 081 } 082 083 /** 084 * Sets the instance which provides the functionality for {@link BeanUtils}. 085 * This is a pseudo-singleton - an single instance is provided per (thread) context classloader. 086 * This mechanism provides isolation for web apps deployed in the same container. 087 * 088 * @param newInstance The (pseudo-singleton) BeanUtils bean instance 089 */ 090 public static void setInstance(BeanUtilsBean newInstance) { 091 BEANS_BY_CLASSLOADER.set(newInstance); 092 } 093 094 // --------------------------------------------------------- Attributes 095 096 /** 097 * Logging for this instance 098 */ 099 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 // --------------------------------------------------------- Constuctors 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 // --------------------------------------------------------- Public Methods 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 // Validate existence of the specified beans 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 // Copy the properties, converting as necessary 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 // Need to check isReadable() for WrapDynaBean 257 // (see Jira issue# BEANUTILS-61) 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 entries = ((Map) orig).entrySet().iterator(); 266 while (entries.hasNext()) { 267 Map.Entry entry = (Map.Entry) entries.next(); 268 String name = (String)entry.getKey(); 269 if (getPropertyUtils().isWriteable(dest, name)) { 270 copyProperty(dest, name, entry.getValue()); 271 } 272 } 273 } else /* if (orig is a standard JavaBean) */ { 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; // No point in trying to set an object's class 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 // Should not happen 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 // Trace logging (if enabled) 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 // Resolve any nested expression to get the actual target bean 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; // Skip this property setter 368 } 369 } 370 if (log.isTraceEnabled()) { 371 log.trace(" Target bean = " + target); 372 log.trace(" Target name = " + name); 373 } 374 375 // Declare local variables we will require 376 String propName = resolver.getProperty(name); // Simple name of target property 377 Class type = null; // Java type of target property 378 int index = resolver.getIndex(name); // Indexed subscript value (if any) 379 String key = resolver.getKey(name); // Mapped key value (if any) 380 381 // Calculate the target property type 382 if (target instanceof DynaBean) { 383 DynaClass dynaClass = ((DynaBean) target).getDynaClass(); 384 DynaProperty dynaProperty = dynaClass.getDynaProperty(propName); 385 if (dynaProperty == null) { 386 return; // Skip this property setter 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; // Skip this property setter 396 } 397 } catch (NoSuchMethodException e) { 398 return; // Skip this property setter 399 } 400 type = descriptor.getPropertyType(); 401 if (type == null) { 402 // Most likely an indexed setter on a POJB only 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 // Convert the specified value to the required type and store it 416 if (index >= 0) { // Destination must be indexed 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) { // Destination must be mapped 426 // Maps do not know what the preferred data type is, 427 // so perform no conversions at all 428 // FIXME - should we create or support a TypedMap? 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 { // Destination must be simple 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 // return (Collections.EMPTY_MAP); 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 Class clazz = bean.getClass(); 511 for (int i = 0; i < descriptors.length; i++) { 512 String name = descriptors[i].getName(); 513 if (getPropertyUtils().getReadMethod(clazz, descriptors[i]) != null) { 514 description.put(name, getProperty(bean, name)); 515 } 516 } 517 } 518 return (description); 519 520 } 521 522 523 /** 524 * Return the value of the specified array property of the specified 525 * bean, as a String array. 526 * 527 * @param bean Bean whose property is to be extracted 528 * @param name Name of the property to be extracted 529 * @return The array property value 530 * 531 * @exception IllegalAccessException if the caller does not have 532 * access to the property accessor method 533 * @exception InvocationTargetException if the property accessor method 534 * throws an exception 535 * @exception NoSuchMethodException if an accessor method for this 536 * property cannot be found 537 */ 538 public String[] getArrayProperty(Object bean, String name) 539 throws IllegalAccessException, InvocationTargetException, 540 NoSuchMethodException { 541 542 Object value = getPropertyUtils().getProperty(bean, name); 543 if (value == null) { 544 return (null); 545 } else if (value instanceof Collection) { 546 ArrayList values = new ArrayList(); 547 Iterator items = ((Collection) value).iterator(); 548 while (items.hasNext()) { 549 Object item = items.next(); 550 if (item == null) { 551 values.add((String) null); 552 } else { 553 // convert to string using convert utils 554 values.add(getConvertUtils().convert(item)); 555 } 556 } 557 return ((String[]) values.toArray(new String[values.size()])); 558 } else if (value.getClass().isArray()) { 559 int n = Array.getLength(value); 560 String[] results = new String[n]; 561 for (int i = 0; i < n; i++) { 562 Object item = Array.get(value, i); 563 if (item == null) { 564 results[i] = null; 565 } else { 566 // convert to string using convert utils 567 results[i] = getConvertUtils().convert(item); 568 } 569 } 570 return (results); 571 } else { 572 String[] results = new String[1]; 573 results[0] = getConvertUtils().convert(value); 574 return (results); 575 } 576 577 } 578 579 580 /** 581 * Return the value of the specified indexed property of the specified 582 * bean, as a String. The zero-relative index of the 583 * required value must be included (in square brackets) as a suffix to 584 * the property name, or <code>IllegalArgumentException</code> will be 585 * thrown. 586 * 587 * @param bean Bean whose property is to be extracted 588 * @param name <code>propertyname[index]</code> of the property value 589 * to be extracted 590 * @return The indexed property's value, converted to a String 591 * 592 * @exception IllegalAccessException if the caller does not have 593 * access to the property accessor method 594 * @exception InvocationTargetException if the property accessor method 595 * throws an exception 596 * @exception NoSuchMethodException if an accessor method for this 597 * property cannot be found 598 */ 599 public String getIndexedProperty(Object bean, String name) 600 throws IllegalAccessException, InvocationTargetException, 601 NoSuchMethodException { 602 603 Object value = getPropertyUtils().getIndexedProperty(bean, name); 604 return (getConvertUtils().convert(value)); 605 606 } 607 608 609 /** 610 * Return the value of the specified indexed property of the specified 611 * bean, as a String. The index is specified as a method parameter and 612 * must *not* be included in the property name expression 613 * 614 * @param bean Bean whose property is to be extracted 615 * @param name Simple property name of the property value to be extracted 616 * @param index Index of the property value to be extracted 617 * @return The indexed property's value, converted to a String 618 * 619 * @exception IllegalAccessException if the caller does not have 620 * access to the property accessor method 621 * @exception InvocationTargetException if the property accessor method 622 * throws an exception 623 * @exception NoSuchMethodException if an accessor method for this 624 * property cannot be found 625 */ 626 public String getIndexedProperty(Object bean, 627 String name, int index) 628 throws IllegalAccessException, InvocationTargetException, 629 NoSuchMethodException { 630 631 Object value = getPropertyUtils().getIndexedProperty(bean, name, index); 632 return (getConvertUtils().convert(value)); 633 634 } 635 636 637 /** 638 * Return the value of the specified indexed property of the specified 639 * bean, as a String. The String-valued key of the required value 640 * must be included (in parentheses) as a suffix to 641 * the property name, or <code>IllegalArgumentException</code> will be 642 * thrown. 643 * 644 * @param bean Bean whose property is to be extracted 645 * @param name <code>propertyname(index)</code> of the property value 646 * to be extracted 647 * @return The mapped property's value, converted to a String 648 * 649 * @exception IllegalAccessException if the caller does not have 650 * access to the property accessor method 651 * @exception InvocationTargetException if the property accessor method 652 * throws an exception 653 * @exception NoSuchMethodException if an accessor method for this 654 * property cannot be found 655 */ 656 public String getMappedProperty(Object bean, String name) 657 throws IllegalAccessException, InvocationTargetException, 658 NoSuchMethodException { 659 660 Object value = getPropertyUtils().getMappedProperty(bean, name); 661 return (getConvertUtils().convert(value)); 662 663 } 664 665 666 /** 667 * Return the value of the specified mapped property of the specified 668 * bean, as a String. The key is specified as a method parameter and 669 * must *not* be included in the property name expression 670 * 671 * @param bean Bean whose property is to be extracted 672 * @param name Simple property name of the property value to be extracted 673 * @param key Lookup key of the property value to be extracted 674 * @return The mapped property's value, converted to a String 675 * 676 * @exception IllegalAccessException if the caller does not have 677 * access to the property accessor method 678 * @exception InvocationTargetException if the property accessor method 679 * throws an exception 680 * @exception NoSuchMethodException if an accessor method for this 681 * property cannot be found 682 */ 683 public String getMappedProperty(Object bean, 684 String name, String key) 685 throws IllegalAccessException, InvocationTargetException, 686 NoSuchMethodException { 687 688 Object value = getPropertyUtils().getMappedProperty(bean, name, key); 689 return (getConvertUtils().convert(value)); 690 691 } 692 693 694 /** 695 * Return the value of the (possibly nested) property of the specified 696 * name, for the specified bean, as a String. 697 * 698 * @param bean Bean whose property is to be extracted 699 * @param name Possibly nested name of the property to be extracted 700 * @return The nested property's value, converted to a String 701 * 702 * @exception IllegalAccessException if the caller does not have 703 * access to the property accessor method 704 * @exception IllegalArgumentException if a nested reference to a 705 * property returns null 706 * @exception InvocationTargetException if the property accessor method 707 * throws an exception 708 * @exception NoSuchMethodException if an accessor method for this 709 * property cannot be found 710 */ 711 public String getNestedProperty(Object bean, String name) 712 throws IllegalAccessException, InvocationTargetException, 713 NoSuchMethodException { 714 715 Object value = getPropertyUtils().getNestedProperty(bean, name); 716 return (getConvertUtils().convert(value)); 717 718 } 719 720 721 /** 722 * Return the value of the specified property of the specified bean, 723 * no matter which property reference format is used, as a String. 724 * 725 * @param bean Bean whose property is to be extracted 726 * @param name Possibly indexed and/or nested name of the property 727 * to be extracted 728 * @return The property's value, converted to a String 729 * 730 * @exception IllegalAccessException if the caller does not have 731 * access to the property accessor method 732 * @exception InvocationTargetException if the property accessor method 733 * throws an exception 734 * @exception NoSuchMethodException if an accessor method for this 735 * property cannot be found 736 */ 737 public String getProperty(Object bean, String name) 738 throws IllegalAccessException, InvocationTargetException, 739 NoSuchMethodException { 740 741 return (getNestedProperty(bean, name)); 742 743 } 744 745 746 /** 747 * Return the value of the specified simple property of the specified 748 * bean, converted to a String. 749 * 750 * @param bean Bean whose property is to be extracted 751 * @param name Name of the property to be extracted 752 * @return The property's value, converted to a String 753 * 754 * @exception IllegalAccessException if the caller does not have 755 * access to the property accessor method 756 * @exception InvocationTargetException if the property accessor method 757 * throws an exception 758 * @exception NoSuchMethodException if an accessor method for this 759 * property cannot be found 760 */ 761 public String getSimpleProperty(Object bean, String name) 762 throws IllegalAccessException, InvocationTargetException, 763 NoSuchMethodException { 764 765 Object value = getPropertyUtils().getSimpleProperty(bean, name); 766 return (getConvertUtils().convert(value)); 767 768 } 769 770 771 /** 772 * <p>Populate the JavaBeans properties of the specified bean, based on 773 * the specified name/value pairs. This method uses Java reflection APIs 774 * to identify corresponding "property setter" method names, and deals 775 * with setter arguments of type <code>String</code>, <code>boolean</code>, 776 * <code>int</code>, <code>long</code>, <code>float</code>, and 777 * <code>double</code>. In addition, array setters for these types (or the 778 * corresponding primitive types) can also be identified.</p> 779 * 780 * <p>The particular setter method to be called for each property is 781 * determined using the usual JavaBeans introspection mechanisms. Thus, 782 * you may identify custom setter methods using a BeanInfo class that is 783 * associated with the class of the bean itself. If no such BeanInfo 784 * class is available, the standard method name conversion ("set" plus 785 * the capitalized name of the property in question) is used.</p> 786 * 787 * <p><strong>NOTE</strong>: It is contrary to the JavaBeans Specification 788 * to have more than one setter method (with different argument 789 * signatures) for the same property.</p> 790 * 791 * <p><strong>WARNING</strong> - The logic of this method is customized 792 * for extracting String-based request parameters from an HTTP request. 793 * It is probably not what you want for general property copying with 794 * type conversion. For that purpose, check out the 795 * <code>copyProperties()</code> method instead.</p> 796 * 797 * @param bean JavaBean whose properties are being populated 798 * @param properties Map keyed by property name, with the 799 * corresponding (String or String[]) value(s) to be set 800 * 801 * @exception IllegalAccessException if the caller does not have 802 * access to the property accessor method 803 * @exception InvocationTargetException if the property accessor method 804 * throws an exception 805 */ 806 public void populate(Object bean, Map properties) 807 throws IllegalAccessException, InvocationTargetException { 808 809 // Do nothing unless both arguments have been specified 810 if ((bean == null) || (properties == null)) { 811 return; 812 } 813 if (log.isDebugEnabled()) { 814 log.debug("BeanUtils.populate(" + bean + ", " + 815 properties + ")"); 816 } 817 818 // Loop through the property name/value pairs to be set 819 Iterator entries = properties.entrySet().iterator(); 820 while (entries.hasNext()) { 821 822 // Identify the property name and value(s) to be assigned 823 Map.Entry entry = (Map.Entry)entries.next(); 824 String name = (String) entry.getKey(); 825 if (name == null) { 826 continue; 827 } 828 829 // Perform the assignment for this property 830 setProperty(bean, name, entry.getValue()); 831 832 } 833 834 } 835 836 837 /** 838 * <p>Set the specified property value, performing type conversions as 839 * required to conform to the type of the destination property.</p> 840 * 841 * <p>If the property is read only then the method returns 842 * without throwing an exception.</p> 843 * 844 * <p>If <code>null</code> is passed into a property expecting a primitive value, 845 * then this will be converted as if it were a <code>null</code> string.</p> 846 * 847 * <p><strong>WARNING</strong> - The logic of this method is customized 848 * to meet the needs of <code>populate()</code>, and is probably not what 849 * you want for general property copying with type conversion. For that 850 * purpose, check out the <code>copyProperty()</code> method instead.</p> 851 * 852 * <p><strong>WARNING</strong> - PLEASE do not modify the behavior of this 853 * method without consulting with the Struts developer community. There 854 * are some subtleties to its functionality that are not documented in the 855 * Javadoc description above, yet are vital to the way that Struts utilizes 856 * this method.</p> 857 * 858 * @param bean Bean on which setting is to be performed 859 * @param name Property name (can be nested/indexed/mapped/combo) 860 * @param value Value to be set 861 * 862 * @exception IllegalAccessException if the caller does not have 863 * access to the property accessor method 864 * @exception InvocationTargetException if the property accessor method 865 * throws an exception 866 */ 867 public void setProperty(Object bean, String name, Object value) 868 throws IllegalAccessException, InvocationTargetException { 869 870 // Trace logging (if enabled) 871 if (log.isTraceEnabled()) { 872 StringBuffer sb = new StringBuffer(" setProperty("); 873 sb.append(bean); 874 sb.append(", "); 875 sb.append(name); 876 sb.append(", "); 877 if (value == null) { 878 sb.append("<NULL>"); 879 } else if (value instanceof String) { 880 sb.append((String) value); 881 } else if (value instanceof String[]) { 882 String[] values = (String[]) value; 883 sb.append('['); 884 for (int i = 0; i < values.length; i++) { 885 if (i > 0) { 886 sb.append(','); 887 } 888 sb.append(values[i]); 889 } 890 sb.append(']'); 891 } else { 892 sb.append(value.toString()); 893 } 894 sb.append(')'); 895 log.trace(sb.toString()); 896 } 897 898 // Resolve any nested expression to get the actual target bean 899 Object target = bean; 900 Resolver resolver = getPropertyUtils().getResolver(); 901 while (resolver.hasNested(name)) { 902 try { 903 target = getPropertyUtils().getProperty(target, resolver.next(name)); 904 name = resolver.remove(name); 905 } catch (NoSuchMethodException e) { 906 return; // Skip this property setter 907 } 908 } 909 if (log.isTraceEnabled()) { 910 log.trace(" Target bean = " + target); 911 log.trace(" Target name = " + name); 912 } 913 914 // Declare local variables we will require 915 String propName = resolver.getProperty(name); // Simple name of target property 916 Class type = null; // Java type of target property 917 int index = resolver.getIndex(name); // Indexed subscript value (if any) 918 String key = resolver.getKey(name); // Mapped key value (if any) 919 920 // Calculate the property type 921 if (target instanceof DynaBean) { 922 DynaClass dynaClass = ((DynaBean) target).getDynaClass(); 923 DynaProperty dynaProperty = dynaClass.getDynaProperty(propName); 924 if (dynaProperty == null) { 925 return; // Skip this property setter 926 } 927 type = dynaProperty.getType(); 928 } else if (target instanceof Map) { 929 type = Object.class; 930 } else if (target != null && target.getClass().isArray() && index >= 0) { 931 type = Array.get(target, index).getClass(); 932 } else { 933 PropertyDescriptor descriptor = null; 934 try { 935 descriptor = 936 getPropertyUtils().getPropertyDescriptor(target, name); 937 if (descriptor == null) { 938 return; // Skip this property setter 939 } 940 } catch (NoSuchMethodException e) { 941 return; // Skip this property setter 942 } 943 if (descriptor instanceof MappedPropertyDescriptor) { 944 if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) { 945 if (log.isDebugEnabled()) { 946 log.debug("Skipping read-only property"); 947 } 948 return; // Read-only, skip this property setter 949 } 950 type = ((MappedPropertyDescriptor) descriptor). 951 getMappedPropertyType(); 952 } else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) { 953 if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) { 954 if (log.isDebugEnabled()) { 955 log.debug("Skipping read-only property"); 956 } 957 return; // Read-only, skip this property setter 958 } 959 type = ((IndexedPropertyDescriptor) descriptor). 960 getIndexedPropertyType(); 961 } else if (key != null) { 962 if (descriptor.getReadMethod() == null) { 963 if (log.isDebugEnabled()) { 964 log.debug("Skipping read-only property"); 965 } 966 return; // Read-only, skip this property setter 967 } 968 type = (value == null) ? Object.class : value.getClass(); 969 } else { 970 if (descriptor.getWriteMethod() == null) { 971 if (log.isDebugEnabled()) { 972 log.debug("Skipping read-only property"); 973 } 974 return; // Read-only, skip this property setter 975 } 976 type = descriptor.getPropertyType(); 977 } 978 } 979 980 // Convert the specified value to the required type 981 Object newValue = null; 982 if (type.isArray() && (index < 0)) { // Scalar value into array 983 if (value == null) { 984 String[] values = new String[1]; 985 values[0] = null; 986 newValue = getConvertUtils().convert(values, type); 987 } else if (value instanceof String) { 988 newValue = getConvertUtils().convert(value, type); 989 } else if (value instanceof String[]) { 990 newValue = getConvertUtils().convert((String[]) value, type); 991 } else { 992 newValue = convert(value, type); 993 } 994 } else if (type.isArray()) { // Indexed value into array 995 if (value instanceof String || value == null) { 996 newValue = getConvertUtils().convert((String) value, 997 type.getComponentType()); 998 } else if (value instanceof String[]) { 999 newValue = getConvertUtils().convert(((String[]) value)[0], 1000 type.getComponentType()); 1001 } else { 1002 newValue = convert(value, type.getComponentType()); 1003 } 1004 } else { // Value into scalar 1005 if (value instanceof String) { 1006 newValue = getConvertUtils().convert((String) value, type); 1007 } else if (value instanceof String[]) { 1008 newValue = getConvertUtils().convert(((String[]) value)[0], 1009 type); 1010 } else { 1011 newValue = convert(value, type); 1012 } 1013 } 1014 1015 // Invoke the setter method 1016 try { 1017 getPropertyUtils().setProperty(target, name, newValue); 1018 } catch (NoSuchMethodException e) { 1019 throw new InvocationTargetException 1020 (e, "Cannot set " + propName); 1021 } 1022 1023 } 1024 1025 /** 1026 * Gets the <code>ConvertUtilsBean</code> instance used to perform the conversions. 1027 * 1028 * @return The ConvertUtils bean instance 1029 */ 1030 public ConvertUtilsBean getConvertUtils() { 1031 return convertUtilsBean; 1032 } 1033 1034 /** 1035 * Gets the <code>PropertyUtilsBean</code> instance used to access properties. 1036 * 1037 * @return The ConvertUtils bean instance 1038 */ 1039 public PropertyUtilsBean getPropertyUtils() { 1040 return propertyUtilsBean; 1041 } 1042 1043 /** 1044 * If we're running on JDK 1.4 or later, initialize the cause for the given throwable. 1045 * 1046 * @param throwable The throwable. 1047 * @param cause The cause of the throwable. 1048 * @return true if the cause was initialized, otherwise false. 1049 * @since 1.8.0 1050 */ 1051 public boolean initCause(Throwable throwable, Throwable cause) { 1052 if (INIT_CAUSE_METHOD != null && cause != null) { 1053 try { 1054 INIT_CAUSE_METHOD.invoke(throwable, new Object[] { cause }); 1055 return true; 1056 } catch (Throwable e) { 1057 return false; // can't initialize cause 1058 } 1059 } 1060 return false; 1061 } 1062 1063 /** 1064 * <p>Convert the value to an object of the specified class (if 1065 * possible).</p> 1066 * 1067 * @param value Value to be converted (may be null) 1068 * @param type Class of the value to be converted to 1069 * @return The converted value 1070 * 1071 * @exception ConversionException if thrown by an underlying Converter 1072 * @since 1.8.0 1073 */ 1074 protected Object convert(Object value, Class type) { 1075 Converter converter = getConvertUtils().lookup(type); 1076 if (converter != null) { 1077 log.trace(" USING CONVERTER " + converter); 1078 return converter.convert(type, value); 1079 } else { 1080 return value; 1081 } 1082 } 1083 1084 /** 1085 * Returns a <code>Method<code> allowing access to 1086 * {@link Throwable#initCause(Throwable)} method of {@link Throwable}, 1087 * or <code>null</code> if the method 1088 * does not exist. 1089 * 1090 * @return A <code>Method<code> for <code>Throwable.initCause</code>, or 1091 * <code>null</code> if unavailable. 1092 */ 1093 private static Method getInitCauseMethod() { 1094 try { 1095 Class[] paramsClasses = new Class[] { Throwable.class }; 1096 return Throwable.class.getMethod("initCause", paramsClasses); 1097 } catch (NoSuchMethodException e) { 1098 Log log = LogFactory.getLog(BeanUtils.class); 1099 if (log.isWarnEnabled()) { 1100 log.warn("Throwable does not have initCause() method in JDK 1.3"); 1101 } 1102 return null; 1103 } catch (Throwable e) { 1104 Log log = LogFactory.getLog(BeanUtils.class); 1105 if (log.isWarnEnabled()) { 1106 log.warn("Error getting the Throwable initCause() method", e); 1107 } 1108 return null; 1109 } 1110 } 1111 }