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