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