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