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