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