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