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