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 * https://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 (for example <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 (for example 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) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1201 1202 if (bean == null) { 1203 throw new IllegalArgumentException("No bean specified"); 1204 } 1205 if (name == null) { 1206 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1207 } 1208 1209 // Validate the syntax of the property name 1210 if (resolver.hasNested(name)) { 1211 throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); 1212 } 1213 if (resolver.isIndexed(name)) { 1214 throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); 1215 } 1216 if (resolver.isMapped(name)) { 1217 throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); 1218 } 1219 1220 // Handle DynaBean instances specially 1221 if (bean instanceof DynaBean) { 1222 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); 1223 if (descriptor == null) { 1224 throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'"); 1225 } 1226 return ((DynaBean) bean).get(name); 1227 } 1228 1229 // Retrieve the property getter method for the specified property 1230 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); 1231 if (descriptor == null) { 1232 throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'"); 1233 } 1234 final Method readMethod = getReadMethod(bean.getClass(), descriptor); 1235 if (readMethod == null) { 1236 throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'"); 1237 } 1238 1239 return invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); 1240 1241 } 1242 1243 /** 1244 * <p>Return an accessible property setter method for this property, 1245 * if there is one; otherwise return <code>null</code>.</p> 1246 * 1247 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 1248 * 1249 * @param clazz The class of the read method will be invoked on 1250 * @param descriptor Property descriptor to return a setter for 1251 * @return The write method 1252 * @since 1.9.1 1253 */ 1254 public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) { 1255 final BeanIntrospectionData data = getIntrospectionData(clazz); 1256 return MethodUtils.getAccessibleMethod(clazz, 1257 data.getWriteMethod(clazz, descriptor)); 1258 } 1259 1260 /** 1261 * <p>Return an accessible property setter method for this property, 1262 * if there is one; otherwise return <code>null</code>.</p> 1263 * 1264 * <p><em>Note:</em> This method does not work correctly with custom bean 1265 * introspection under certain circumstances. It may return {@code null} 1266 * even if a write method is defined for the property in question. Use 1267 * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the 1268 * correct result is returned.</p> 1269 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p> 1270 * 1271 * @param descriptor Property descriptor to return a setter for 1272 * @return The write method 1273 */ 1274 public Method getWriteMethod(final PropertyDescriptor descriptor) { 1275 1276 return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod()); 1277 1278 } 1279 1280 /** This just catches and wraps IllegalArgumentException. */ 1281 private Object invokeMethod(final Method method, final Object bean, final Object[] values) throws IllegalAccessException, InvocationTargetException { 1282 if (bean == null) { 1283 throw new IllegalArgumentException("No bean specified " + "- this should have been checked before reaching this method"); 1284 } 1285 try { 1286 return method.invoke(bean, values); 1287 } catch (final NullPointerException | IllegalArgumentException cause) { 1288 final StringBuilder valueString = new StringBuilder(); 1289 if (values != null) { 1290 for (int i = 0; i < values.length; i++) { 1291 if (i > 0) { 1292 valueString.append(", "); 1293 } 1294 if (values[i] == null) { 1295 valueString.append("<null>"); 1296 } else { 1297 valueString.append(values[i].getClass().getName()); 1298 } 1299 } 1300 } 1301 final StringBuilder expectedString = new StringBuilder(); 1302 final Class<?>[] parTypes = method.getParameterTypes(); 1303 if (parTypes != null) { 1304 for (int i = 0; i < parTypes.length; i++) { 1305 if (i > 0) { 1306 expectedString.append(", "); 1307 } 1308 expectedString.append(parTypes[i].getName()); 1309 } 1310 } 1311 throw new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " on bean class '" + 1312 bean.getClass() + "' - " + cause.getMessage() + " - had objects of type \"" + 1313 valueString.append("\" but expected signature \"").append(expectedString.toString()).append("\"").toString(), cause); 1314 1315 } 1316 } 1317 1318 /** 1319 * <p> 1320 * Return <code>true</code> if the specified property name identifies a readable property on the specified bean; otherwise, return <code>false</code>. 1321 * 1322 * @param bean Bean to be examined (may be a {@link DynaBean} 1323 * @param name Property name to be evaluated 1324 * @return <code>true</code> if the property is readable, otherwise <code>false</code> 1325 * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is <code>null</code> 1326 * @since BeanUtils 1.6 1327 */ 1328 public boolean isReadable(Object bean, String name) { 1329 1330 // Validate method parameters 1331 if (bean == null) { 1332 throw new IllegalArgumentException("No bean specified"); 1333 } 1334 if (name == null) { 1335 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1336 } 1337 1338 // Resolve nested references 1339 while (resolver.hasNested(name)) { 1340 final String next = resolver.next(name); 1341 Object nestedBean = null; 1342 try { 1343 nestedBean = getProperty(bean, next); 1344 } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 1345 return false; 1346 } 1347 if (nestedBean == null) { 1348 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'"); 1349 } 1350 bean = nestedBean; 1351 name = resolver.remove(name); 1352 } 1353 1354 // Remove any subscript from the final name value 1355 name = resolver.getProperty(name); 1356 1357 // Treat WrapDynaBean as special case - may be a write-only property 1358 // (see Jira issue# BEANUTILS-61) 1359 if (bean instanceof WrapDynaBean) { 1360 bean = ((WrapDynaBean) bean).getInstance(); 1361 } 1362 1363 // Return the requested result 1364 if (bean instanceof DynaBean) { 1365 // All DynaBean properties are readable 1366 return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null; 1367 } 1368 try { 1369 final PropertyDescriptor desc = getPropertyDescriptor(bean, name); 1370 if (desc == null) { 1371 return false; 1372 } 1373 Method readMethod = getReadMethod(bean.getClass(), desc); 1374 if (readMethod == null) { 1375 if (desc instanceof IndexedPropertyDescriptor) { 1376 readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod(); 1377 } else if (desc instanceof MappedPropertyDescriptor) { 1378 readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod(); 1379 } 1380 readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod); 1381 } 1382 return readMethod != null; 1383 } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 1384 return false; 1385 } 1386 1387 } 1388 1389 /** 1390 * <p> 1391 * Return <code>true</code> if the specified property name identifies a writeable property on the specified bean; otherwise, return <code>false</code>. 1392 * 1393 * @param bean Bean to be examined (may be a {@link DynaBean} 1394 * @param name Property name to be evaluated 1395 * @return <code>true</code> if the property is writeable, otherwise <code>false</code> 1396 * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is <code>null</code> 1397 * @since BeanUtils 1.6 1398 */ 1399 public boolean isWriteable(Object bean, String name) { 1400 1401 // Validate method parameters 1402 if (bean == null) { 1403 throw new IllegalArgumentException("No bean specified"); 1404 } 1405 if (name == null) { 1406 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1407 } 1408 1409 // Resolve nested references 1410 while (resolver.hasNested(name)) { 1411 final String next = resolver.next(name); 1412 Object nestedBean = null; 1413 try { 1414 nestedBean = getProperty(bean, next); 1415 } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 1416 return false; 1417 } 1418 if (nestedBean == null) { 1419 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'"); 1420 } 1421 bean = nestedBean; 1422 name = resolver.remove(name); 1423 } 1424 1425 // Remove any subscript from the final name value 1426 name = resolver.getProperty(name); 1427 1428 // Treat WrapDynaBean as special case - may be a read-only property 1429 // (see Jira issue# BEANUTILS-61) 1430 if (bean instanceof WrapDynaBean) { 1431 bean = ((WrapDynaBean) bean).getInstance(); 1432 } 1433 1434 // Return the requested result 1435 if (bean instanceof DynaBean) { 1436 // All DynaBean properties are writeable 1437 return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null; 1438 } 1439 try { 1440 final PropertyDescriptor desc = getPropertyDescriptor(bean, name); 1441 if (desc == null) { 1442 return false; 1443 } 1444 Method writeMethod = getWriteMethod(bean.getClass(), desc); 1445 if (writeMethod == null) { 1446 if (desc instanceof IndexedPropertyDescriptor) { 1447 writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod(); 1448 } else if (desc instanceof MappedPropertyDescriptor) { 1449 writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod(); 1450 } 1451 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod); 1452 } 1453 return writeMethod != null; 1454 } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 1455 return false; 1456 } 1457 1458 } 1459 1460 /** 1461 * Removes the specified <code>BeanIntrospector</code>. 1462 * 1463 * @param introspector the <code>BeanIntrospector</code> to be removed 1464 * @return <strong>true</strong> if the <code>BeanIntrospector</code> existed and could be removed, <strong>false</strong> otherwise 1465 * @since 1.9 1466 */ 1467 public boolean removeBeanIntrospector(final BeanIntrospector introspector) { 1468 return introspectors.remove(introspector); 1469 } 1470 1471 /** 1472 * Resets the {@link BeanIntrospector} objects registered at this instance. After this method was called, only the default {@code BeanIntrospector} is 1473 * registered. 1474 * 1475 * @since 1.9 1476 */ 1477 public final void resetBeanIntrospectors() { 1478 introspectors.clear(); 1479 introspectors.add(DefaultBeanIntrospector.INSTANCE); 1480 introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); 1481 introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS); 1482 } 1483 1484 /** 1485 * Set the value of the specified indexed property of the specified bean, with no type conversions. In addition to supporting the JavaBeans specification, 1486 * this method has been extended to support <code>List</code> objects as well. 1487 * 1488 * @param bean Bean whose property is to be set 1489 * @param name Simple property name of the property value to be set 1490 * @param index Index of the property value to be set 1491 * @param value Value to which the indexed property element is to be set 1492 * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property 1493 * @throws IllegalAccessException if the caller does not have access to the property accessor method 1494 * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is null 1495 * @throws InvocationTargetException if the property accessor method throws an exception 1496 * @throws NoSuchMethodException if an accessor method for this propety cannot be found 1497 */ 1498 public void setIndexedProperty(final Object bean, final String name, final int index, final Object value) 1499 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1500 1501 if (bean == null) { 1502 throw new IllegalArgumentException("No bean specified"); 1503 } 1504 if (name == null || name.length() == 0) { 1505 if (bean.getClass().isArray()) { 1506 Array.set(bean, index, value); 1507 return; 1508 } 1509 if (bean instanceof List) { 1510 final List<Object> list = toObjectList(bean); 1511 list.set(index, value); 1512 return; 1513 } 1514 } 1515 if (name == null) { 1516 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1517 } 1518 1519 // Handle DynaBean instances specially 1520 if (bean instanceof DynaBean) { 1521 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); 1522 if (descriptor == null) { 1523 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'"); 1524 } 1525 ((DynaBean) bean).set(name, index, value); 1526 return; 1527 } 1528 1529 // Retrieve the property descriptor for the specified property 1530 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); 1531 if (descriptor == null) { 1532 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'"); 1533 } 1534 1535 // Call the indexed setter method if there is one 1536 if (descriptor instanceof IndexedPropertyDescriptor) { 1537 Method writeMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod(); 1538 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod); 1539 if (writeMethod != null) { 1540 final Object[] subscript = new Object[2]; 1541 subscript[0] = Integer.valueOf(index); 1542 subscript[1] = value; 1543 try { 1544 if (log.isTraceEnabled()) { 1545 final String valueClassName = value == null ? "<null>" : value.getClass().getName(); 1546 log.trace("setSimpleProperty: Invoking method " + writeMethod + " with index=" + index + ", value=" + value + " (class " + 1547 valueClassName + ")"); 1548 } 1549 invokeMethod(writeMethod, bean, subscript); 1550 } catch (final InvocationTargetException e) { 1551 if (e.getTargetException() instanceof IndexOutOfBoundsException) { 1552 throw (IndexOutOfBoundsException) e.getTargetException(); 1553 } 1554 throw e; 1555 } 1556 return; 1557 } 1558 } 1559 1560 // Otherwise, the underlying property must be an array or a list 1561 final Method readMethod = getReadMethod(bean.getClass(), descriptor); 1562 if (readMethod == null) { 1563 throw new NoSuchMethodException("Property '" + name + "' has no getter method on bean class '" + bean.getClass() + "'"); 1564 } 1565 1566 // Call the property getter to get the array or list 1567 final Object array = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); 1568 if (!array.getClass().isArray()) { 1569 if (!(array instanceof List)) { 1570 throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'"); 1571 } 1572 // Modify the specified value in the List 1573 final List<Object> list = toObjectList(array); 1574 list.set(index, value); 1575 } else { 1576 // Modify the specified value in the array 1577 Array.set(array, index, value); 1578 } 1579 1580 } 1581 1582 /** 1583 * 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 1584 * included (in square brackets) as a suffix to the property name, or <code>IllegalArgumentException</code> will be thrown. In addition to supporting the 1585 * JavaBeans specification, this method has been extended to support <code>List</code> objects as well. 1586 * 1587 * @param bean Bean whose property is to be modified 1588 * @param name <code>propertyname[index]</code> of the property value to be modified 1589 * @param value Value to which the specified property element should be set 1590 * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property 1591 * @throws IllegalAccessException if the caller does not have access to the property accessor method 1592 * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is null 1593 * @throws InvocationTargetException if the property accessor method throws an exception 1594 * @throws NoSuchMethodException if an accessor method for this propety cannot be found 1595 */ 1596 public void setIndexedProperty(final Object bean, String name, final Object value) 1597 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1598 1599 if (bean == null) { 1600 throw new IllegalArgumentException("No bean specified"); 1601 } 1602 if (name == null) { 1603 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1604 } 1605 1606 // Identify the index of the requested individual property 1607 int index = -1; 1608 try { 1609 index = resolver.getIndex(name); 1610 } catch (final IllegalArgumentException e) { 1611 throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'"); 1612 } 1613 if (index < 0) { 1614 throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'"); 1615 } 1616 1617 // Isolate the name 1618 name = resolver.getProperty(name); 1619 1620 // Set the specified indexed property value 1621 setIndexedProperty(bean, name, index, value); 1622 1623 } 1624 1625 /** 1626 * 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 1627 * brackets) as a suffix to the property name, or <code>IllegalArgumentException</code> will be thrown. 1628 * 1629 * @param bean Bean whose property is to be set 1630 * @param name <code>propertyname(key)</code> of the property value to be set 1631 * @param value The property value to be set 1632 * @throws IllegalAccessException if the caller does not have access to the property accessor method 1633 * @throws InvocationTargetException if the property accessor method throws an exception 1634 * @throws NoSuchMethodException if an accessor method for this propety cannot be found 1635 */ 1636 public void setMappedProperty(final Object bean, String name, final Object value) 1637 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1638 1639 if (bean == null) { 1640 throw new IllegalArgumentException("No bean specified"); 1641 } 1642 if (name == null) { 1643 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1644 } 1645 1646 // Identify the key of the requested individual property 1647 String key = null; 1648 try { 1649 key = resolver.getKey(name); 1650 } catch (final IllegalArgumentException e) { 1651 throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'"); 1652 } 1653 if (key == null) { 1654 throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'"); 1655 } 1656 1657 // Isolate the name 1658 name = resolver.getProperty(name); 1659 1660 // Request the specified indexed property value 1661 setMappedProperty(bean, name, key, value); 1662 1663 } 1664 1665 /** 1666 * Set the value of the specified mapped property of the specified bean, with no type conversions. 1667 * 1668 * @param bean Bean whose property is to be set 1669 * @param name Mapped property name of the property value to be set 1670 * @param key Key of the property value to be set 1671 * @param value The property value to be set 1672 * @throws IllegalAccessException if the caller does not have access to the property accessor method 1673 * @throws InvocationTargetException if the property accessor method throws an exception 1674 * @throws NoSuchMethodException if an accessor method for this propety cannot be found 1675 */ 1676 public void setMappedProperty(final Object bean, final String name, final String key, final Object value) 1677 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1678 1679 if (bean == null) { 1680 throw new IllegalArgumentException("No bean specified"); 1681 } 1682 if (name == null) { 1683 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1684 } 1685 if (key == null) { 1686 throw new IllegalArgumentException("No key specified for property '" + name + "' on bean class '" + bean.getClass() + "'"); 1687 } 1688 1689 // Handle DynaBean instances specially 1690 if (bean instanceof DynaBean) { 1691 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); 1692 if (descriptor == null) { 1693 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'"); 1694 } 1695 ((DynaBean) bean).set(name, key, value); 1696 return; 1697 } 1698 1699 // Retrieve the property descriptor for the specified property 1700 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); 1701 if (descriptor == null) { 1702 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'"); 1703 } 1704 1705 if (descriptor instanceof MappedPropertyDescriptor) { 1706 // Call the keyed setter method if there is one 1707 Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor).getMappedWriteMethod(); 1708 mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod); 1709 if (mappedWriteMethod == null) { 1710 throw new NoSuchMethodException("Property '" + name + "' has no mapped setter method" + "on bean class '" + bean.getClass() + "'"); 1711 } 1712 final Object[] params = new Object[2]; 1713 params[0] = key; 1714 params[1] = value; 1715 if (log.isTraceEnabled()) { 1716 final String valueClassName = value == null ? "<null>" : value.getClass().getName(); 1717 log.trace("setSimpleProperty: Invoking method " + mappedWriteMethod + " with key=" + key + ", value=" + value + " (class " + valueClassName + 1718 ")"); 1719 } 1720 invokeMethod(mappedWriteMethod, bean, params); 1721 } else { 1722 /* means that the result has to be retrieved from a map */ 1723 final Method readMethod = getReadMethod(bean.getClass(), descriptor); 1724 if (readMethod == null) { 1725 throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'"); 1726 } 1727 final Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); 1728 /* test and fetch from the map */ 1729 if (invokeResult instanceof java.util.Map) { 1730 final java.util.Map<String, Object> map = toPropertyMap(invokeResult); 1731 map.put(key, value); 1732 } 1733 } 1734 1735 } 1736 1737 /** 1738 * Set the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions. 1739 * <p> 1740 * Example values for parameter "name" are: 1741 * <ul> 1742 * <li>"a" -- sets the value of property a of the specified bean</li> 1743 * <li>"a.b" -- gets the value of property a of the specified bean, then on that object sets the value of property b.</li> 1744 * <li>"a(key)" -- sets a value of mapped-property a on the specified bean. This effectively means bean.setA("key").</li> 1745 * <li>"a[3]" -- sets a value of indexed-property a on the specified bean. This effectively means bean.setA(3).</li> 1746 * </ul> 1747 * 1748 * @param bean Bean whose property is to be modified 1749 * @param name Possibly nested name of the property to be modified 1750 * @param value Value to which the property is to be set 1751 * @throws IllegalAccessException if the caller does not have access to the property accessor method 1752 * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is null 1753 * @throws IllegalArgumentException if a nested reference to a property returns null 1754 * @throws InvocationTargetException if the property accessor method throws an exception 1755 * @throws NoSuchMethodException if an accessor method for this propety cannot be found 1756 */ 1757 public void setNestedProperty(Object bean, String name, final Object value) 1758 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1759 1760 if (bean == null) { 1761 throw new IllegalArgumentException("No bean specified"); 1762 } 1763 if (name == null) { 1764 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1765 } 1766 1767 // Resolve nested references 1768 while (resolver.hasNested(name)) { 1769 final String next = resolver.next(name); 1770 Object nestedBean = null; 1771 if (bean instanceof Map) { 1772 nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next); 1773 } else if (resolver.isMapped(next)) { 1774 nestedBean = getMappedProperty(bean, next); 1775 } else if (resolver.isIndexed(next)) { 1776 nestedBean = getIndexedProperty(bean, next); 1777 } else { 1778 nestedBean = getSimpleProperty(bean, next); 1779 } 1780 if (nestedBean == null) { 1781 throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'"); 1782 } 1783 bean = nestedBean; 1784 name = resolver.remove(name); 1785 } 1786 1787 if (bean instanceof Map) { 1788 setPropertyOfMapBean(toPropertyMap(bean), name, value); 1789 } else if (resolver.isMapped(name)) { 1790 setMappedProperty(bean, name, value); 1791 } else if (resolver.isIndexed(name)) { 1792 setIndexedProperty(bean, name, value); 1793 } else { 1794 setSimpleProperty(bean, name, value); 1795 } 1796 1797 } 1798 1799 /** 1800 * Set the value of the specified property of the specified bean, no matter which property reference format is used, with no type conversions. 1801 * 1802 * @param bean Bean whose property is to be modified 1803 * @param name Possibly indexed and/or nested name of the property to be modified 1804 * @param value Value to which this property is to be set 1805 * @throws IllegalAccessException if the caller does not have access to the property accessor method 1806 * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is null 1807 * @throws InvocationTargetException if the property accessor method throws an exception 1808 * @throws NoSuchMethodException if an accessor method for this propety cannot be found 1809 */ 1810 public void setProperty(final Object bean, final String name, final Object value) 1811 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1812 1813 setNestedProperty(bean, name, value); 1814 1815 } 1816 1817 /** 1818 * 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 1819 * Map. 1820 * <p> 1821 * The standard implementation here is to: 1822 * <ul> 1823 * <li>call bean.set(propertyName) for all propertyName values.</li> 1824 * <li>throw an IllegalArgumentException if the property specifier contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially simple properties; 1825 * 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> 1826 * </ul> 1827 * <p> 1828 * The default behavior of beanutils 1.7.1 or later is for assigning to "a.b" to mean a.put(b, obj) always. However the behavior of beanutils version 1829 * 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 1830 * (ie the same as the behavior in the current version). In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is all <em>very</em> unfortunate] 1831 * <p> 1832 * Users who would like to customize the meaning of "a.b" in method setNestedProperty when a is a Map can create a custom subclass of this class and 1833 * override this method to implement the behavior of their choice, such as restoring the pre-1.4 behavior of this class if they wish. When overriding this 1834 * method, do not forget to deal with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName. 1835 * <p> 1836 * 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 1837 * to override their get/put methods to implement that behavior, and <em>not</em> to solve the problem by modifying the default behavior of the 1838 * PropertyUtilsBean class by overriding this method. 1839 * 1840 * @param bean Map bean 1841 * @param propertyName The property name 1842 * @param value the property value 1843 * @throws IllegalArgumentException when the propertyName is regarded as being invalid. 1844 * @throws IllegalAccessException just in case subclasses override this method to try to access real setter methods and find permission is denied. 1845 * @throws InvocationTargetException just in case subclasses override this method to try to access real setter methods, and find it throws an exception when 1846 * invoked. 1847 * 1848 * @throws NoSuchMethodException just in case subclasses override this method to try to access real setter methods, and want to fail if no simple method 1849 * is available. 1850 * @since 1.8.0 1851 */ 1852 protected void setPropertyOfMapBean(final Map<String, Object> bean, String propertyName, final Object value) 1853 throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1854 1855 if (resolver.isMapped(propertyName)) { 1856 final String name = resolver.getProperty(propertyName); 1857 if (name == null || name.length() == 0) { 1858 propertyName = resolver.getKey(propertyName); 1859 } 1860 } 1861 1862 if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) { 1863 throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName); 1864 } 1865 1866 bean.put(propertyName, value); 1867 } 1868 1869 /** 1870 * Configure the {@link Resolver} implementation used by BeanUtils. 1871 * <p> 1872 * The {@link Resolver} handles the <em>property name</em> expressions and the implementation in use effectively controls the dialect of the <em>expression 1873 * language</em> that BeanUtils recongnises. 1874 * <p> 1875 * {@link DefaultResolver} is the default implementation used. 1876 * 1877 * @param resolver The property expression resolver. 1878 * @since 1.8.0 1879 */ 1880 public void setResolver(final Resolver resolver) { 1881 if (resolver == null) { 1882 this.resolver = new DefaultResolver(); 1883 } else { 1884 this.resolver = resolver; 1885 } 1886 } 1887 1888 /** 1889 * Set the value of the specified simple property of the specified bean, with no type conversions. 1890 * 1891 * @param bean Bean whose property is to be modified 1892 * @param name Name of the property to be modified 1893 * @param value Value to which the property should be set 1894 * @throws IllegalAccessException if the caller does not have access to the property accessor method 1895 * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is null 1896 * @throws IllegalArgumentException if the property name is nested or indexed 1897 * @throws InvocationTargetException if the property accessor method throws an exception 1898 * @throws NoSuchMethodException if an accessor method for this propety cannot be found 1899 */ 1900 public void setSimpleProperty(final Object bean, final String name, final Object value) 1901 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { 1902 if (bean == null) { 1903 throw new IllegalArgumentException("No bean specified"); 1904 } 1905 if (name == null) { 1906 throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); 1907 } 1908 // Validate the syntax of the property name 1909 if (resolver.hasNested(name)) { 1910 throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); 1911 } 1912 if (resolver.isIndexed(name)) { 1913 throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); 1914 } 1915 if (resolver.isMapped(name)) { 1916 throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); 1917 } 1918 // Handle DynaBean instances specially 1919 if (bean instanceof DynaBean) { 1920 final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name); 1921 if (descriptor == null) { 1922 throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'"); 1923 } 1924 ((DynaBean) bean).set(name, value); 1925 return; 1926 } 1927 // Retrieve the property setter method for the specified property 1928 final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name); 1929 if (descriptor == null) { 1930 throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'"); 1931 } 1932 final Method writeMethod = getWriteMethod(bean.getClass(), descriptor); 1933 if (writeMethod == null) { 1934 throw new NoSuchMethodException("Property '" + name + "' has no setter method in class '" + bean.getClass() + "'"); 1935 } 1936 // Call the property setter method 1937 final Object[] values = new Object[1]; 1938 values[0] = value; 1939 if (log.isTraceEnabled()) { 1940 final String valueClassName = value == null ? "<null>" : value.getClass().getName(); 1941 log.trace("setSimpleProperty: Invoking method " + writeMethod + " with value " + value + " (class " + valueClassName + ")"); 1942 } 1943 invokeMethod(writeMethod, bean, values); 1944 } 1945}