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