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