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