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