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