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