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