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 1547898 2013-12-04 20:33:06Z 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<?>, PropertyDescriptor[]> 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<?>, PropertyDescriptor[]>(); 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 PropertyDescriptor[] descriptors = getPropertyDescriptors(bean); 951 if (descriptors != null) { 952 953 for (int i = 0; i < descriptors.length; i++) { 954 if (name.equals(descriptors[i].getName())) { 955 return (descriptors[i]); 956 } 957 } 958 } 959 960 PropertyDescriptor result = null; 961 FastHashMap mappedDescriptors = 962 getMappedPropertyDescriptors(bean); 963 if (mappedDescriptors == null) { 964 mappedDescriptors = new FastHashMap(); 965 mappedDescriptors.setFast(true); 966 mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors); 967 } 968 result = (PropertyDescriptor) mappedDescriptors.get(name); 969 if (result == null) { 970 // not found, try to create it 971 try { 972 result = new MappedPropertyDescriptor(name, bean.getClass()); 973 } catch (IntrospectionException ie) { 974 /* Swallow IntrospectionException 975 * TODO: Why? 976 */ 977 } 978 if (result != null) { 979 mappedDescriptors.put(name, result); 980 } 981 } 982 983 return result; 984 985 } 986 987 988 /** 989 * <p>Retrieve the property descriptors for the specified class, 990 * introspecting and caching them the first time a particular bean class 991 * is encountered.</p> 992 * 993 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 994 * 995 * @param beanClass Bean class for which property descriptors are requested 996 * @return the property descriptors 997 * 998 * @exception IllegalArgumentException if <code>beanClass</code> is null 999 */ 1000 public PropertyDescriptor[] 1001 getPropertyDescriptors(Class<?> beanClass) { 1002 1003 if (beanClass == null) { 1004 throw new IllegalArgumentException("No bean class specified"); 1005 } 1006 1007 // Look up any cached descriptors for this bean class 1008 PropertyDescriptor[] descriptors = null; 1009 descriptors = 1010 descriptorsCache.get(beanClass); 1011 if (descriptors != null) { 1012 return (descriptors); 1013 } 1014 1015 descriptors = fetchPropertyDescriptors(beanClass); 1016 descriptorsCache.put(beanClass, descriptors); 1017 return (descriptors); 1018 1019 } 1020 1021 1022 /** 1023 * <p>Retrieve the property descriptors for the specified bean, 1024 * introspecting and caching them the first time a particular bean class 1025 * is encountered.</p> 1026 * 1027 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 1028 * 1029 * @param bean Bean for which property descriptors are requested 1030 * @return the property descriptors 1031 * 1032 * @exception IllegalArgumentException if <code>bean</code> is null 1033 */ 1034 public PropertyDescriptor[] getPropertyDescriptors(Object bean) { 1035 1036 if (bean == null) { 1037 throw new IllegalArgumentException("No bean specified"); 1038 } 1039 return (getPropertyDescriptors(bean.getClass())); 1040 1041 } 1042 1043 1044 /** 1045 * <p>Return the Java Class repesenting the property editor class that has 1046 * been registered for this property (if any). This method follows the 1047 * same name resolution rules used by <code>getPropertyDescriptor()</code>, 1048 * so if the last element of a name reference is indexed, the property 1049 * editor for the underlying property's class is returned.</p> 1050 * 1051 * <p>Note that <code>null</code> will be returned if there is no property, 1052 * or if there is no registered property editor class. Because this 1053 * return value is ambiguous, you should determine the existence of the 1054 * property itself by other means.</p> 1055 * 1056 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 1057 * 1058 * @param bean Bean for which a property descriptor is requested 1059 * @param name Possibly indexed and/or nested name of the property for 1060 * which a property descriptor is requested 1061 * @return the property editor class 1062 * 1063 * @exception IllegalAccessException if the caller does not have 1064 * access to the property accessor method 1065 * @exception IllegalArgumentException if <code>bean</code> or 1066 * <code>name</code> is null 1067 * @exception IllegalArgumentException if a nested reference to a 1068 * property returns null 1069 * @exception InvocationTargetException if the property accessor method 1070 * throws an exception 1071 * @exception NoSuchMethodException if an accessor method for this 1072 * propety cannot be found 1073 */ 1074 public Class<?> getPropertyEditorClass(Object bean, String name) 1075 throws IllegalAccessException, InvocationTargetException, 1076 NoSuchMethodException { 1077 1078 if (bean == null) { 1079 throw new IllegalArgumentException("No bean specified"); 1080 } 1081 if (name == null) { 1082 throw new IllegalArgumentException("No name specified for bean class '" + 1083 bean.getClass() + "'"); 1084 } 1085 1086 PropertyDescriptor descriptor = 1087 getPropertyDescriptor(bean, name); 1088 if (descriptor != null) { 1089 return (descriptor.getPropertyEditorClass()); 1090 } else { 1091 return (null); 1092 } 1093 1094 } 1095 1096 1097 /** 1098 * Return the Java Class representing the property type of the specified 1099 * property, or <code>null</code> if there is no such property for the 1100 * specified bean. This method follows the same name resolution rules 1101 * used by <code>getPropertyDescriptor()</code>, so if the last element 1102 * of a name reference is indexed, the type of the property itself will 1103 * be returned. If the last (or only) element has no property with the 1104 * specified name, <code>null</code> is returned. 1105 * 1106 * @param bean Bean for which a property descriptor is requested 1107 * @param name Possibly indexed and/or nested name of the property for 1108 * which a property descriptor is requested 1109 * @return The property type 1110 * 1111 * @exception IllegalAccessException if the caller does not have 1112 * access to the property accessor method 1113 * @exception IllegalArgumentException if <code>bean</code> or 1114 * <code>name</code> is null 1115 * @exception IllegalArgumentException if a nested reference to a 1116 * property returns null 1117 * @exception InvocationTargetException if the property accessor method 1118 * throws an exception 1119 * @exception NoSuchMethodException if an accessor method for this 1120 * propety cannot be found 1121 */ 1122 public Class<?> getPropertyType(Object bean, String name) 1123 throws IllegalAccessException, InvocationTargetException, 1124 NoSuchMethodException { 1125 1126 if (bean == null) { 1127 throw new IllegalArgumentException("No bean specified"); 1128 } 1129 if (name == null) { 1130 throw new IllegalArgumentException("No name specified for bean class '" + 1131 bean.getClass() + "'"); 1132 } 1133 1134 // Resolve nested references 1135 while (resolver.hasNested(name)) { 1136 String next = resolver.next(name); 1137 Object nestedBean = getProperty(bean, next); 1138 if (nestedBean == null) { 1139 throw new NestedNullException 1140 ("Null property value for '" + next + 1141 "' on bean class '" + bean.getClass() + "'"); 1142 } 1143 bean = nestedBean; 1144 name = resolver.remove(name); 1145 } 1146 1147 // Remove any subscript from the final name value 1148 name = resolver.getProperty(name); 1149 1150 // Special handling for DynaBeans 1151 if (bean instanceof DynaBean) { 1152 DynaProperty descriptor = 1153 ((DynaBean) bean).getDynaClass().getDynaProperty(name); 1154 if (descriptor == null) { 1155 return (null); 1156 } 1157 Class<?> type = descriptor.getType(); 1158 if (type == null) { 1159 return (null); 1160 } else if (type.isArray()) { 1161 return (type.getComponentType()); 1162 } else { 1163 return (type); 1164 } 1165 } 1166 1167 PropertyDescriptor descriptor = 1168 getPropertyDescriptor(bean, name); 1169 if (descriptor == null) { 1170 return (null); 1171 } else if (descriptor instanceof IndexedPropertyDescriptor) { 1172 return (((IndexedPropertyDescriptor) descriptor). 1173 getIndexedPropertyType()); 1174 } else if (descriptor instanceof MappedPropertyDescriptor) { 1175 return (((MappedPropertyDescriptor) descriptor). 1176 getMappedPropertyType()); 1177 } else { 1178 return (descriptor.getPropertyType()); 1179 } 1180 1181 } 1182 1183 1184 /** 1185 * <p>Return an accessible property getter method for this property, 1186 * if there is one; otherwise return <code>null</code>.</p> 1187 * 1188 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 1189 * 1190 * @param descriptor Property descriptor to return a getter for 1191 * @return The read method 1192 */ 1193 public Method getReadMethod(PropertyDescriptor descriptor) { 1194 1195 return (MethodUtils.getAccessibleMethod(descriptor.getReadMethod())); 1196 1197 } 1198 1199 1200 /** 1201 * <p>Return an accessible property getter method for this property, 1202 * if there is one; otherwise return <code>null</code>.</p> 1203 * 1204 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 1205 * 1206 * @param clazz The class of the read method will be invoked on 1207 * @param descriptor Property descriptor to return a getter for 1208 * @return The read method 1209 */ 1210 Method getReadMethod(Class<?> clazz, PropertyDescriptor descriptor) { 1211 return (MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod())); 1212 } 1213 1214 1215 /** 1216 * Return the value of the specified simple property of the specified 1217 * bean, with no type conversions. 1218 * 1219 * @param bean Bean whose property is to be extracted 1220 * @param name Name of the property to be extracted 1221 * @return The property value 1222 * 1223 * @exception IllegalAccessException if the caller does not have 1224 * access to the property accessor method 1225 * @exception IllegalArgumentException if <code>bean</code> or 1226 * <code>name</code> is null 1227 * @exception IllegalArgumentException if the property name 1228 * is nested or indexed 1229 * @exception InvocationTargetException if the property accessor method 1230 * throws an exception 1231 * @exception NoSuchMethodException if an accessor method for this 1232 * propety cannot be found 1233 */ 1234 public Object getSimpleProperty(Object bean, String name) 1235 throws IllegalAccessException, InvocationTargetException, 1236 NoSuchMethodException { 1237 1238 if (bean == null) { 1239 throw new IllegalArgumentException("No bean specified"); 1240 } 1241 if (name == null) { 1242 throw new IllegalArgumentException("No name specified for bean class '" + 1243 bean.getClass() + "'"); 1244 } 1245 1246 // Validate the syntax of the property name 1247 if (resolver.hasNested(name)) { 1248 throw new IllegalArgumentException 1249 ("Nested property names are not allowed: Property '" + 1250 name + "' on bean class '" + bean.getClass() + "'"); 1251 } else if (resolver.isIndexed(name)) { 1252 throw new IllegalArgumentException 1253 ("Indexed property names are not allowed: Property '" + 1254 name + "' on bean class '" + bean.getClass() + "'"); 1255 } else if (resolver.isMapped(name)) { 1256 throw new IllegalArgumentException 1257 ("Mapped property names are not allowed: Property '" + 1258 name + "' on bean class '" + bean.getClass() + "'"); 1259 } 1260 1261 // Handle DynaBean instances specially 1262 if (bean instanceof DynaBean) { 1263 DynaProperty descriptor = 1264 ((DynaBean) bean).getDynaClass().getDynaProperty(name); 1265 if (descriptor == null) { 1266 throw new NoSuchMethodException("Unknown property '" + 1267 name + "' on dynaclass '" + 1268 ((DynaBean) bean).getDynaClass() + "'" ); 1269 } 1270 return (((DynaBean) bean).get(name)); 1271 } 1272 1273 // Retrieve the property getter method for the specified property 1274 PropertyDescriptor descriptor = 1275 getPropertyDescriptor(bean, name); 1276 if (descriptor == null) { 1277 throw new NoSuchMethodException("Unknown property '" + 1278 name + "' on class '" + bean.getClass() + "'" ); 1279 } 1280 Method readMethod = getReadMethod(bean.getClass(), descriptor); 1281 if (readMethod == null) { 1282 throw new NoSuchMethodException("Property '" + name + 1283 "' has no getter method in class '" + bean.getClass() + "'"); 1284 } 1285 1286 // Call the property getter and return the value 1287 Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); 1288 return (value); 1289 1290 } 1291 1292 1293 /** 1294 * <p>Return an accessible property setter method for this property, 1295 * if there is one; otherwise return <code>null</code>.</p> 1296 * 1297 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 1298 * 1299 * @param descriptor Property descriptor to return a setter for 1300 * @return The write method 1301 */ 1302 public Method getWriteMethod(PropertyDescriptor descriptor) { 1303 1304 return (MethodUtils.getAccessibleMethod(descriptor.getWriteMethod())); 1305 1306 } 1307 1308 1309 /** 1310 * <p>Return an accessible property setter method for this property, 1311 * if there is one; otherwise return <code>null</code>.</p> 1312 * 1313 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 1314 * 1315 * @param clazz The class of the read method will be invoked on 1316 * @param descriptor Property descriptor to return a setter for 1317 * @return The write method 1318 */ 1319 Method getWriteMethod(Class<?> clazz, PropertyDescriptor descriptor) { 1320 return (MethodUtils.getAccessibleMethod(clazz, descriptor.getWriteMethod())); 1321 } 1322 1323 1324 /** 1325 * <p>Return <code>true</code> if the specified property name identifies 1326 * a readable property on the specified bean; otherwise, return 1327 * <code>false</code>. 1328 * 1329 * @param bean Bean to be examined (may be a {@link DynaBean} 1330 * @param name Property name to be evaluated 1331 * @return <code>true</code> if the property is readable, 1332 * otherwise <code>false</code> 1333 * 1334 * @exception IllegalArgumentException if <code>bean</code> 1335 * or <code>name</code> is <code>null</code> 1336 * 1337 * @since BeanUtils 1.6 1338 */ 1339 public boolean isReadable(Object bean, String name) { 1340 1341 // Validate method parameters 1342 if (bean == null) { 1343 throw new IllegalArgumentException("No bean specified"); 1344 } 1345 if (name == null) { 1346 throw new IllegalArgumentException("No name specified for bean class '" + 1347 bean.getClass() + "'"); 1348 } 1349 1350 // Resolve nested references 1351 while (resolver.hasNested(name)) { 1352 String next = resolver.next(name); 1353 Object nestedBean = null; 1354 try { 1355 nestedBean = getProperty(bean, next); 1356 } catch (IllegalAccessException e) { 1357 return false; 1358 } catch (InvocationTargetException e) { 1359 return false; 1360 } catch (NoSuchMethodException e) { 1361 return false; 1362 } 1363 if (nestedBean == null) { 1364 throw new NestedNullException 1365 ("Null property value for '" + next + 1366 "' on bean class '" + bean.getClass() + "'"); 1367 } 1368 bean = nestedBean; 1369 name = resolver.remove(name); 1370 } 1371 1372 // Remove any subscript from the final name value 1373 name = resolver.getProperty(name); 1374 1375 // Treat WrapDynaBean as special case - may be a write-only property 1376 // (see Jira issue# BEANUTILS-61) 1377 if (bean instanceof WrapDynaBean) { 1378 bean = ((WrapDynaBean)bean).getInstance(); 1379 } 1380 1381 // Return the requested result 1382 if (bean instanceof DynaBean) { 1383 // All DynaBean properties are readable 1384 return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null); 1385 } else { 1386 try { 1387 PropertyDescriptor desc = 1388 getPropertyDescriptor(bean, name); 1389 if (desc != null) { 1390 Method readMethod = getReadMethod(bean.getClass(), desc); 1391 if (readMethod == null) { 1392 if (desc instanceof IndexedPropertyDescriptor) { 1393 readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod(); 1394 } else if (desc instanceof MappedPropertyDescriptor) { 1395 readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod(); 1396 } 1397 readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod); 1398 } 1399 return (readMethod != null); 1400 } else { 1401 return (false); 1402 } 1403 } catch (IllegalAccessException e) { 1404 return (false); 1405 } catch (InvocationTargetException e) { 1406 return (false); 1407 } catch (NoSuchMethodException e) { 1408 return (false); 1409 } 1410 } 1411 1412 } 1413 1414 1415 /** 1416 * <p>Return <code>true</code> if the specified property name identifies 1417 * a writeable property on the specified bean; otherwise, return 1418 * <code>false</code>. 1419 * 1420 * @param bean Bean to be examined (may be a {@link DynaBean} 1421 * @param name Property name to be evaluated 1422 * @return <code>true</code> if the property is writeable, 1423 * otherwise <code>false</code> 1424 * 1425 * @exception IllegalArgumentException if <code>bean</code> 1426 * or <code>name</code> is <code>null</code> 1427 * 1428 * @since BeanUtils 1.6 1429 */ 1430 public boolean isWriteable(Object bean, String name) { 1431 1432 // Validate method parameters 1433 if (bean == null) { 1434 throw new IllegalArgumentException("No bean specified"); 1435 } 1436 if (name == null) { 1437 throw new IllegalArgumentException("No name specified for bean class '" + 1438 bean.getClass() + "'"); 1439 } 1440 1441 // Resolve nested references 1442 while (resolver.hasNested(name)) { 1443 String next = resolver.next(name); 1444 Object nestedBean = null; 1445 try { 1446 nestedBean = getProperty(bean, next); 1447 } catch (IllegalAccessException e) { 1448 return false; 1449 } catch (InvocationTargetException e) { 1450 return false; 1451 } catch (NoSuchMethodException e) { 1452 return false; 1453 } 1454 if (nestedBean == null) { 1455 throw new NestedNullException 1456 ("Null property value for '" + next + 1457 "' on bean class '" + bean.getClass() + "'"); 1458 } 1459 bean = nestedBean; 1460 name = resolver.remove(name); 1461 } 1462 1463 // Remove any subscript from the final name value 1464 name = resolver.getProperty(name); 1465 1466 // Treat WrapDynaBean as special case - may be a read-only property 1467 // (see Jira issue# BEANUTILS-61) 1468 if (bean instanceof WrapDynaBean) { 1469 bean = ((WrapDynaBean)bean).getInstance(); 1470 } 1471 1472 // Return the requested result 1473 if (bean instanceof DynaBean) { 1474 // All DynaBean properties are writeable 1475 return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null); 1476 } else { 1477 try { 1478 PropertyDescriptor desc = 1479 getPropertyDescriptor(bean, name); 1480 if (desc != null) { 1481 Method writeMethod = getWriteMethod(bean.getClass(), desc); 1482 if (writeMethod == null) { 1483 if (desc instanceof IndexedPropertyDescriptor) { 1484 writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod(); 1485 } else if (desc instanceof MappedPropertyDescriptor) { 1486 writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod(); 1487 } 1488 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod); 1489 } 1490 return (writeMethod != null); 1491 } else { 1492 return (false); 1493 } 1494 } catch (IllegalAccessException e) { 1495 return (false); 1496 } catch (InvocationTargetException e) { 1497 return (false); 1498 } catch (NoSuchMethodException e) { 1499 return (false); 1500 } 1501 } 1502 1503 } 1504 1505 1506 /** 1507 * Set the value of the specified indexed property of the specified 1508 * bean, with no type conversions. The zero-relative index of the 1509 * required value must be included (in square brackets) as a suffix to 1510 * the property name, or <code>IllegalArgumentException</code> will be 1511 * thrown. In addition to supporting the JavaBeans specification, this 1512 * method has been extended to support <code>List</code> objects as well. 1513 * 1514 * @param bean Bean whose property is to be modified 1515 * @param name <code>propertyname[index]</code> of the property value 1516 * to be modified 1517 * @param value Value to which the specified property element 1518 * should be set 1519 * 1520 * @exception IndexOutOfBoundsException if the specified index 1521 * is outside the valid range for the underlying property 1522 * @exception IllegalAccessException if the caller does not have 1523 * access to the property accessor method 1524 * @exception IllegalArgumentException if <code>bean</code> or 1525 * <code>name</code> is null 1526 * @exception InvocationTargetException if the property accessor method 1527 * throws an exception 1528 * @exception NoSuchMethodException if an accessor method for this 1529 * propety cannot be found 1530 */ 1531 public void setIndexedProperty(Object bean, String name, 1532 Object value) 1533 throws IllegalAccessException, InvocationTargetException, 1534 NoSuchMethodException { 1535 1536 if (bean == null) { 1537 throw new IllegalArgumentException("No bean specified"); 1538 } 1539 if (name == null) { 1540 throw new IllegalArgumentException("No name specified for bean class '" + 1541 bean.getClass() + "'"); 1542 } 1543 1544 // Identify the index of the requested individual property 1545 int index = -1; 1546 try { 1547 index = resolver.getIndex(name); 1548 } catch (IllegalArgumentException e) { 1549 throw new IllegalArgumentException("Invalid indexed property '" + 1550 name + "' on bean class '" + bean.getClass() + "'"); 1551 } 1552 if (index < 0) { 1553 throw new IllegalArgumentException("Invalid indexed property '" + 1554 name + "' on bean class '" + bean.getClass() + "'"); 1555 } 1556 1557 // Isolate the name 1558 name = resolver.getProperty(name); 1559 1560 // Set the specified indexed property value 1561 setIndexedProperty(bean, name, index, value); 1562 1563 } 1564 1565 1566 /** 1567 * Set the value of the specified indexed property of the specified 1568 * bean, with no type conversions. In addition to supporting the JavaBeans 1569 * specification, this method has been extended to support 1570 * <code>List</code> objects as well. 1571 * 1572 * @param bean Bean whose property is to be set 1573 * @param name Simple property name of the property value to be set 1574 * @param index Index of the property value to be set 1575 * @param value Value to which the indexed property element is to be set 1576 * 1577 * @exception IndexOutOfBoundsException if the specified index 1578 * is outside the valid range for the underlying property 1579 * @exception IllegalAccessException if the caller does not have 1580 * access to the property accessor method 1581 * @exception IllegalArgumentException if <code>bean</code> or 1582 * <code>name</code> is null 1583 * @exception InvocationTargetException if the property accessor method 1584 * throws an exception 1585 * @exception NoSuchMethodException if an accessor method for this 1586 * propety cannot be found 1587 */ 1588 public void setIndexedProperty(Object bean, String name, 1589 int index, Object value) 1590 throws IllegalAccessException, InvocationTargetException, 1591 NoSuchMethodException { 1592 1593 if (bean == null) { 1594 throw new IllegalArgumentException("No bean specified"); 1595 } 1596 if (name == null || name.length() == 0) { 1597 if (bean.getClass().isArray()) { 1598 Array.set(bean, index, value); 1599 return; 1600 } else if (bean instanceof List) { 1601 List<Object> list = toObjectList(bean); 1602 list.set(index, value); 1603 return; 1604 } 1605 } 1606 if (name == null) { 1607 throw new IllegalArgumentException("No name specified for bean class '" + 1608 bean.getClass() + "'"); 1609 } 1610 1611 // Handle DynaBean instances specially 1612 if (bean instanceof DynaBean) { 1613 DynaProperty descriptor = 1614 ((DynaBean) bean).getDynaClass().getDynaProperty(name); 1615 if (descriptor == null) { 1616 throw new NoSuchMethodException("Unknown property '" + 1617 name + "' on bean class '" + bean.getClass() + "'"); 1618 } 1619 ((DynaBean) bean).set(name, index, value); 1620 return; 1621 } 1622 1623 // Retrieve the property descriptor for the specified property 1624 PropertyDescriptor descriptor = 1625 getPropertyDescriptor(bean, name); 1626 if (descriptor == null) { 1627 throw new NoSuchMethodException("Unknown property '" + 1628 name + "' on bean class '" + bean.getClass() + "'"); 1629 } 1630 1631 // Call the indexed setter method if there is one 1632 if (descriptor instanceof IndexedPropertyDescriptor) { 1633 Method writeMethod = ((IndexedPropertyDescriptor) descriptor). 1634 getIndexedWriteMethod(); 1635 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod); 1636 if (writeMethod != null) { 1637 Object[] subscript = new Object[2]; 1638 subscript[0] = new Integer(index); 1639 subscript[1] = value; 1640 try { 1641 if (log.isTraceEnabled()) { 1642 String valueClassName = 1643 value == null ? "<null>" 1644 : value.getClass().getName(); 1645 log.trace("setSimpleProperty: Invoking method " 1646 + writeMethod +" with index=" + index 1647 + ", value=" + value 1648 + " (class " + valueClassName+ ")"); 1649 } 1650 invokeMethod(writeMethod, bean, subscript); 1651 } catch (InvocationTargetException e) { 1652 if (e.getTargetException() instanceof 1653 IndexOutOfBoundsException) { 1654 throw (IndexOutOfBoundsException) 1655 e.getTargetException(); 1656 } else { 1657 throw e; 1658 } 1659 } 1660 return; 1661 } 1662 } 1663 1664 // Otherwise, the underlying property must be an array or a list 1665 Method readMethod = getReadMethod(bean.getClass(), descriptor); 1666 if (readMethod == null) { 1667 throw new NoSuchMethodException("Property '" + name + 1668 "' has no getter method on bean class '" + bean.getClass() + "'"); 1669 } 1670 1671 // Call the property getter to get the array or list 1672 Object array = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); 1673 if (!array.getClass().isArray()) { 1674 if (array instanceof List) { 1675 // Modify the specified value in the List 1676 List<Object> list = toObjectList(array); 1677 list.set(index, value); 1678 } else { 1679 throw new IllegalArgumentException("Property '" + name + 1680 "' is not indexed on bean class '" + bean.getClass() + "'"); 1681 } 1682 } else { 1683 // Modify the specified value in the array 1684 Array.set(array, index, value); 1685 } 1686 1687 } 1688 1689 1690 /** 1691 * Set the value of the specified mapped property of the 1692 * specified bean, with no type conversions. The key of the 1693 * value to set must be included (in brackets) as a suffix to 1694 * the property name, or <code>IllegalArgumentException</code> will be 1695 * thrown. 1696 * 1697 * @param bean Bean whose property is to be set 1698 * @param name <code>propertyname(key)</code> of the property value 1699 * to be set 1700 * @param value The property value to be set 1701 * 1702 * @exception IllegalAccessException if the caller does not have 1703 * access to the property accessor method 1704 * @exception InvocationTargetException if the property accessor method 1705 * throws an exception 1706 * @exception NoSuchMethodException if an accessor method for this 1707 * propety cannot be found 1708 */ 1709 public void setMappedProperty(Object bean, String name, 1710 Object value) 1711 throws IllegalAccessException, InvocationTargetException, 1712 NoSuchMethodException { 1713 1714 if (bean == null) { 1715 throw new IllegalArgumentException("No bean specified"); 1716 } 1717 if (name == null) { 1718 throw new IllegalArgumentException("No name specified for bean class '" + 1719 bean.getClass() + "'"); 1720 } 1721 1722 // Identify the key of the requested individual property 1723 String key = null; 1724 try { 1725 key = resolver.getKey(name); 1726 } catch (IllegalArgumentException e) { 1727 throw new IllegalArgumentException 1728 ("Invalid mapped property '" + name + 1729 "' on bean class '" + bean.getClass() + "'"); 1730 } 1731 if (key == null) { 1732 throw new IllegalArgumentException 1733 ("Invalid mapped property '" + name + 1734 "' on bean class '" + bean.getClass() + "'"); 1735 } 1736 1737 // Isolate the name 1738 name = resolver.getProperty(name); 1739 1740 // Request the specified indexed property value 1741 setMappedProperty(bean, name, key, value); 1742 1743 } 1744 1745 1746 /** 1747 * Set the value of the specified mapped property of the specified 1748 * bean, with no type conversions. 1749 * 1750 * @param bean Bean whose property is to be set 1751 * @param name Mapped property name of the property value to be set 1752 * @param key Key of the property value to be set 1753 * @param value The property value to be set 1754 * 1755 * @exception IllegalAccessException if the caller does not have 1756 * access to the property accessor method 1757 * @exception InvocationTargetException if the property accessor method 1758 * throws an exception 1759 * @exception NoSuchMethodException if an accessor method for this 1760 * propety cannot be found 1761 */ 1762 public void setMappedProperty(Object bean, String name, 1763 String key, Object value) 1764 throws IllegalAccessException, InvocationTargetException, 1765 NoSuchMethodException { 1766 1767 if (bean == null) { 1768 throw new IllegalArgumentException("No bean specified"); 1769 } 1770 if (name == null) { 1771 throw new IllegalArgumentException("No name specified for bean class '" + 1772 bean.getClass() + "'"); 1773 } 1774 if (key == null) { 1775 throw new IllegalArgumentException("No key specified for property '" + 1776 name + "' on bean class '" + bean.getClass() + "'"); 1777 } 1778 1779 // Handle DynaBean instances specially 1780 if (bean instanceof DynaBean) { 1781 DynaProperty descriptor = 1782 ((DynaBean) bean).getDynaClass().getDynaProperty(name); 1783 if (descriptor == null) { 1784 throw new NoSuchMethodException("Unknown property '" + 1785 name + "' on bean class '" + bean.getClass() + "'"); 1786 } 1787 ((DynaBean) bean).set(name, key, value); 1788 return; 1789 } 1790 1791 // Retrieve the property descriptor for the specified property 1792 PropertyDescriptor descriptor = 1793 getPropertyDescriptor(bean, name); 1794 if (descriptor == null) { 1795 throw new NoSuchMethodException("Unknown property '" + 1796 name + "' on bean class '" + bean.getClass() + "'"); 1797 } 1798 1799 if (descriptor instanceof MappedPropertyDescriptor) { 1800 // Call the keyed setter method if there is one 1801 Method mappedWriteMethod = 1802 ((MappedPropertyDescriptor) descriptor). 1803 getMappedWriteMethod(); 1804 mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod); 1805 if (mappedWriteMethod != null) { 1806 Object[] params = new Object[2]; 1807 params[0] = key; 1808 params[1] = value; 1809 if (log.isTraceEnabled()) { 1810 String valueClassName = 1811 value == null ? "<null>" : value.getClass().getName(); 1812 log.trace("setSimpleProperty: Invoking method " 1813 + mappedWriteMethod + " with key=" + key 1814 + ", value=" + value 1815 + " (class " + valueClassName +")"); 1816 } 1817 invokeMethod(mappedWriteMethod, bean, params); 1818 } else { 1819 throw new NoSuchMethodException 1820 ("Property '" + name + "' has no mapped setter method" + 1821 "on bean class '" + bean.getClass() + "'"); 1822 } 1823 } else { 1824 /* means that the result has to be retrieved from a map */ 1825 Method readMethod = getReadMethod(bean.getClass(), descriptor); 1826 if (readMethod != null) { 1827 Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); 1828 /* test and fetch from the map */ 1829 if (invokeResult instanceof java.util.Map) { 1830 java.util.Map<String, Object> map = toPropertyMap(invokeResult); 1831 map.put(key, value); 1832 } 1833 } else { 1834 throw new NoSuchMethodException("Property '" + name + 1835 "' has no mapped getter method on bean class '" + 1836 bean.getClass() + "'"); 1837 } 1838 } 1839 1840 } 1841 1842 1843 /** 1844 * Set the value of the (possibly nested) property of the specified 1845 * name, for the specified bean, with no type conversions. 1846 * <p> 1847 * Example values for parameter "name" are: 1848 * <ul> 1849 * <li> "a" -- sets the value of property a of the specified bean </li> 1850 * <li> "a.b" -- gets the value of property a of the specified bean, 1851 * then on that object sets the value of property b.</li> 1852 * <li> "a(key)" -- sets a value of mapped-property a on the specified 1853 * bean. This effectively means bean.setA("key").</li> 1854 * <li> "a[3]" -- sets a value of indexed-property a on the specified 1855 * bean. This effectively means bean.setA(3).</li> 1856 * </ul> 1857 * 1858 * @param bean Bean whose property is to be modified 1859 * @param name Possibly nested name of the property to be modified 1860 * @param value Value to which the property is to be set 1861 * 1862 * @exception IllegalAccessException if the caller does not have 1863 * access to the property accessor method 1864 * @exception IllegalArgumentException if <code>bean</code> or 1865 * <code>name</code> is null 1866 * @exception IllegalArgumentException if a nested reference to a 1867 * property returns null 1868 * @exception InvocationTargetException if the property accessor method 1869 * throws an exception 1870 * @exception NoSuchMethodException if an accessor method for this 1871 * propety cannot be found 1872 */ 1873 public void setNestedProperty(Object bean, 1874 String name, Object value) 1875 throws IllegalAccessException, InvocationTargetException, 1876 NoSuchMethodException { 1877 1878 if (bean == null) { 1879 throw new IllegalArgumentException("No bean specified"); 1880 } 1881 if (name == null) { 1882 throw new IllegalArgumentException("No name specified for bean class '" + 1883 bean.getClass() + "'"); 1884 } 1885 1886 // Resolve nested references 1887 while (resolver.hasNested(name)) { 1888 String next = resolver.next(name); 1889 Object nestedBean = null; 1890 if (bean instanceof Map) { 1891 nestedBean = getPropertyOfMapBean((Map<?, ?>)bean, next); 1892 } else if (resolver.isMapped(next)) { 1893 nestedBean = getMappedProperty(bean, next); 1894 } else if (resolver.isIndexed(next)) { 1895 nestedBean = getIndexedProperty(bean, next); 1896 } else { 1897 nestedBean = getSimpleProperty(bean, next); 1898 } 1899 if (nestedBean == null) { 1900 throw new NestedNullException 1901 ("Null property value for '" + name + 1902 "' on bean class '" + bean.getClass() + "'"); 1903 } 1904 bean = nestedBean; 1905 name = resolver.remove(name); 1906 } 1907 1908 if (bean instanceof Map) { 1909 setPropertyOfMapBean(toPropertyMap(bean), name, value); 1910 } else if (resolver.isMapped(name)) { 1911 setMappedProperty(bean, name, value); 1912 } else if (resolver.isIndexed(name)) { 1913 setIndexedProperty(bean, name, value); 1914 } else { 1915 setSimpleProperty(bean, name, value); 1916 } 1917 1918 } 1919 1920 /** 1921 * This method is called by method setNestedProperty when the current bean 1922 * is found to be a Map object, and defines how to deal with setting 1923 * a property on a Map. 1924 * <p> 1925 * The standard implementation here is to: 1926 * <ul> 1927 * <li>call bean.set(propertyName) for all propertyName values.</li> 1928 * <li>throw an IllegalArgumentException if the property specifier 1929 * contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially 1930 * simple properties; mapping and indexing operations do not make sense 1931 * when accessing a map (even thought the returned object may be a Map 1932 * or an Array).</li> 1933 * </ul> 1934 * <p> 1935 * The default behaviour of beanutils 1.7.1 or later is for assigning to 1936 * "a.b" to mean a.put(b, obj) always. However the behaviour of beanutils 1937 * version 1.6.0, 1.6.1, 1.7.0 was for "a.b" to mean a.setB(obj) if such 1938 * a method existed, and a.put(b, obj) otherwise. In version 1.5 it meant 1939 * a.put(b, obj) always (ie the same as the behaviour in the current version). 1940 * In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is 1941 * all <i>very</i> unfortunate] 1942 * <p> 1943 * Users who would like to customise the meaning of "a.b" in method 1944 * setNestedProperty when a is a Map can create a custom subclass of 1945 * this class and override this method to implement the behaviour of 1946 * their choice, such as restoring the pre-1.4 behaviour of this class 1947 * if they wish. When overriding this method, do not forget to deal 1948 * with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName. 1949 * <p> 1950 * Note, however, that the recommended solution for objects that 1951 * implement Map but want their simple properties to come first is 1952 * for <i>those</i> objects to override their get/put methods to implement 1953 * that behaviour, and <i>not</i> to solve the problem by modifying the 1954 * default behaviour of the PropertyUtilsBean class by overriding this 1955 * method. 1956 * 1957 * @param bean Map bean 1958 * @param propertyName The property name 1959 * @param value the property value 1960 * 1961 * @throws IllegalArgumentException when the propertyName is regarded as 1962 * being invalid. 1963 * 1964 * @throws IllegalAccessException just in case subclasses override this 1965 * method to try to access real setter methods and find permission is denied. 1966 * 1967 * @throws InvocationTargetException just in case subclasses override this 1968 * method to try to access real setter methods, and find it throws an 1969 * exception when invoked. 1970 * 1971 * @throws NoSuchMethodException just in case subclasses override this 1972 * method to try to access real setter methods, and want to fail if 1973 * no simple method is available. 1974 * @since 1.8.0 1975 */ 1976 protected void setPropertyOfMapBean(Map<String, Object> bean, String propertyName, Object value) 1977 throws IllegalArgumentException, IllegalAccessException, 1978 InvocationTargetException, NoSuchMethodException { 1979 1980 if (resolver.isMapped(propertyName)) { 1981 String name = resolver.getProperty(propertyName); 1982 if (name == null || name.length() == 0) { 1983 propertyName = resolver.getKey(propertyName); 1984 } 1985 } 1986 1987 if (resolver.isIndexed(propertyName) || 1988 resolver.isMapped(propertyName)) { 1989 throw new IllegalArgumentException( 1990 "Indexed or mapped properties are not supported on" 1991 + " objects of type Map: " + propertyName); 1992 } 1993 1994 bean.put(propertyName, value); 1995 } 1996 1997 1998 1999 /** 2000 * Set the value of the specified property of the specified bean, 2001 * no matter which property reference format is used, with no 2002 * type conversions. 2003 * 2004 * @param bean Bean whose property is to be modified 2005 * @param name Possibly indexed and/or nested name of the property 2006 * to be modified 2007 * @param value Value to which this property is to be set 2008 * 2009 * @exception IllegalAccessException if the caller does not have 2010 * access to the property accessor method 2011 * @exception IllegalArgumentException if <code>bean</code> or 2012 * <code>name</code> is null 2013 * @exception InvocationTargetException if the property accessor method 2014 * throws an exception 2015 * @exception NoSuchMethodException if an accessor method for this 2016 * propety cannot be found 2017 */ 2018 public void setProperty(Object bean, String name, Object value) 2019 throws IllegalAccessException, InvocationTargetException, 2020 NoSuchMethodException { 2021 2022 setNestedProperty(bean, name, value); 2023 2024 } 2025 2026 2027 /** 2028 * Set the value of the specified simple property of the specified bean, 2029 * with no type conversions. 2030 * 2031 * @param bean Bean whose property is to be modified 2032 * @param name Name of the property to be modified 2033 * @param value Value to which the property should be set 2034 * 2035 * @exception IllegalAccessException if the caller does not have 2036 * access to the property accessor method 2037 * @exception IllegalArgumentException if <code>bean</code> or 2038 * <code>name</code> is null 2039 * @exception IllegalArgumentException if the property name is 2040 * nested or indexed 2041 * @exception InvocationTargetException if the property accessor method 2042 * throws an exception 2043 * @exception NoSuchMethodException if an accessor method for this 2044 * propety cannot be found 2045 */ 2046 public void setSimpleProperty(Object bean, 2047 String name, Object value) 2048 throws IllegalAccessException, InvocationTargetException, 2049 NoSuchMethodException { 2050 2051 if (bean == null) { 2052 throw new IllegalArgumentException("No bean specified"); 2053 } 2054 if (name == null) { 2055 throw new IllegalArgumentException("No name specified for bean class '" + 2056 bean.getClass() + "'"); 2057 } 2058 2059 // Validate the syntax of the property name 2060 if (resolver.hasNested(name)) { 2061 throw new IllegalArgumentException 2062 ("Nested property names are not allowed: Property '" + 2063 name + "' on bean class '" + bean.getClass() + "'"); 2064 } else if (resolver.isIndexed(name)) { 2065 throw new IllegalArgumentException 2066 ("Indexed property names are not allowed: Property '" + 2067 name + "' on bean class '" + bean.getClass() + "'"); 2068 } else if (resolver.isMapped(name)) { 2069 throw new IllegalArgumentException 2070 ("Mapped property names are not allowed: Property '" + 2071 name + "' on bean class '" + bean.getClass() + "'"); 2072 } 2073 2074 // Handle DynaBean instances specially 2075 if (bean instanceof DynaBean) { 2076 DynaProperty descriptor = 2077 ((DynaBean) bean).getDynaClass().getDynaProperty(name); 2078 if (descriptor == null) { 2079 throw new NoSuchMethodException("Unknown property '" + 2080 name + "' on dynaclass '" + 2081 ((DynaBean) bean).getDynaClass() + "'" ); 2082 } 2083 ((DynaBean) bean).set(name, value); 2084 return; 2085 } 2086 2087 // Retrieve the property setter method for the specified property 2088 PropertyDescriptor descriptor = 2089 getPropertyDescriptor(bean, name); 2090 if (descriptor == null) { 2091 throw new NoSuchMethodException("Unknown property '" + 2092 name + "' on class '" + bean.getClass() + "'" ); 2093 } 2094 Method writeMethod = getWriteMethod(bean.getClass(), descriptor); 2095 if (writeMethod == null) { 2096 throw new NoSuchMethodException("Property '" + name + 2097 "' has no setter method in class '" + bean.getClass() + "'"); 2098 } 2099 2100 // Call the property setter method 2101 Object[] values = new Object[1]; 2102 values[0] = value; 2103 if (log.isTraceEnabled()) { 2104 String valueClassName = 2105 value == null ? "<null>" : value.getClass().getName(); 2106 log.trace("setSimpleProperty: Invoking method " + writeMethod 2107 + " with value " + value + " (class " + valueClassName + ")"); 2108 } 2109 invokeMethod(writeMethod, bean, values); 2110 2111 } 2112 2113 /** This just catches and wraps IllegalArgumentException. */ 2114 private Object invokeMethod( 2115 Method method, 2116 Object bean, 2117 Object[] values) 2118 throws 2119 IllegalAccessException, 2120 InvocationTargetException { 2121 if(bean == null) { 2122 throw new IllegalArgumentException("No bean specified " + 2123 "- this should have been checked before reaching this method"); 2124 } 2125 2126 try { 2127 2128 return method.invoke(bean, values); 2129 2130 } catch (NullPointerException cause) { 2131 // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is 2132 // null for a primitive value (JDK 1.5+ throw IllegalArgumentException) 2133 String valueString = ""; 2134 if (values != null) { 2135 for (int i = 0; i < values.length; i++) { 2136 if (i>0) { 2137 valueString += ", " ; 2138 } 2139 if (values[i] == null) { 2140 valueString += "<null>"; 2141 } else { 2142 valueString += (values[i]).getClass().getName(); 2143 } 2144 } 2145 } 2146 String expectedString = ""; 2147 Class<?>[] parTypes = method.getParameterTypes(); 2148 if (parTypes != null) { 2149 for (int i = 0; i < parTypes.length; i++) { 2150 if (i > 0) { 2151 expectedString += ", "; 2152 } 2153 expectedString += parTypes[i].getName(); 2154 } 2155 } 2156 IllegalArgumentException e = new IllegalArgumentException( 2157 "Cannot invoke " + method.getDeclaringClass().getName() + "." 2158 + method.getName() + " on bean class '" + bean.getClass() + 2159 "' - " + cause.getMessage() 2160 // as per https://issues.apache.org/jira/browse/BEANUTILS-224 2161 + " - had objects of type \"" + valueString 2162 + "\" but expected signature \"" 2163 + expectedString + "\"" 2164 ); 2165 if (!BeanUtils.initCause(e, cause)) { 2166 log.error("Method invocation failed", cause); 2167 } 2168 throw e; 2169 } catch (IllegalArgumentException cause) { 2170 String valueString = ""; 2171 if (values != null) { 2172 for (int i = 0; i < values.length; i++) { 2173 if (i>0) { 2174 valueString += ", " ; 2175 } 2176 if (values[i] == null) { 2177 valueString += "<null>"; 2178 } else { 2179 valueString += (values[i]).getClass().getName(); 2180 } 2181 } 2182 } 2183 String expectedString = ""; 2184 Class<?>[] parTypes = method.getParameterTypes(); 2185 if (parTypes != null) { 2186 for (int i = 0; i < parTypes.length; i++) { 2187 if (i > 0) { 2188 expectedString += ", "; 2189 } 2190 expectedString += parTypes[i].getName(); 2191 } 2192 } 2193 IllegalArgumentException e = new IllegalArgumentException( 2194 "Cannot invoke " + method.getDeclaringClass().getName() + "." 2195 + method.getName() + " on bean class '" + bean.getClass() + 2196 "' - " + cause.getMessage() 2197 // as per https://issues.apache.org/jira/browse/BEANUTILS-224 2198 + " - had objects of type \"" + valueString 2199 + "\" but expected signature \"" 2200 + expectedString + "\"" 2201 ); 2202 if (!BeanUtils.initCause(e, cause)) { 2203 log.error("Method invocation failed", cause); 2204 } 2205 throw e; 2206 2207 } 2208 } 2209 2210 /** 2211 * Performs introspection on the specified class. This method invokes all {@code BeanIntrospector} objects that were 2212 * added to this instance. 2213 * 2214 * @param beanClass the class to be inspected 2215 * @return an array with all property descriptors found 2216 */ 2217 private PropertyDescriptor[] fetchPropertyDescriptors(Class<?> beanClass) { 2218 DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass); 2219 2220 for (BeanIntrospector bi : introspectors) { 2221 try { 2222 bi.introspect(ictx); 2223 } catch (IntrospectionException iex) { 2224 log.error("Exception during introspection", iex); 2225 } 2226 } 2227 2228 return ictx.getPropertyDescriptors(); 2229 } 2230 2231 /** 2232 * Converts an object to a list of objects. This method is used when dealing 2233 * with indexed properties. It assumes that indexed properties are stored as 2234 * lists of objects. 2235 * 2236 * @param obj the object to be converted 2237 * @return the resulting list of objects 2238 */ 2239 private static List<Object> toObjectList(Object obj) { 2240 @SuppressWarnings("unchecked") 2241 // indexed properties are stored in lists of objects 2242 List<Object> list = (List<Object>) obj; 2243 return list; 2244 } 2245 2246 /** 2247 * Converts an object to a map with property values. This method is used 2248 * when dealing with mapped properties. It assumes that mapped properties 2249 * are stored in a Map<String, Object>. 2250 * 2251 * @param obj the object to be converted 2252 * @return the resulting properties map 2253 */ 2254 private static Map<String, Object> toPropertyMap(Object obj) { 2255 @SuppressWarnings("unchecked") 2256 // mapped properties are stores in maps of type <String, Object> 2257 Map<String, Object> map = (Map<String, Object>) obj; 2258 return map; 2259 } 2260}