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 16:23:23 +0100 (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
1061 descriptorsCache.put(beanClass, descriptors);
1062 return (descriptors);
1063
1064 }
1065
1066
1067 /**
1068 * <p>Retrieve the property descriptors for the specified bean,
1069 * introspecting and caching them the first time a particular bean class
1070 * is encountered.</p>
1071 *
1072 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1073 *
1074 * @param bean Bean for which property descriptors are requested
1075 * @return the property descriptors
1076 *
1077 * @exception IllegalArgumentException if <code>bean</code> is null
1078 */
1079 public PropertyDescriptor[] getPropertyDescriptors(Object bean) {
1080
1081 if (bean == null) {
1082 throw new IllegalArgumentException("No bean specified");
1083 }
1084 return (getPropertyDescriptors(bean.getClass()));
1085
1086 }
1087
1088
1089 /**
1090 * <p>Return the Java Class repesenting the property editor class that has
1091 * been registered for this property (if any). This method follows the
1092 * same name resolution rules used by <code>getPropertyDescriptor()</code>,
1093 * so if the last element of a name reference is indexed, the property
1094 * editor for the underlying property's class is returned.</p>
1095 *
1096 * <p>Note that <code>null</code> will be returned if there is no property,
1097 * or if there is no registered property editor class. Because this
1098 * return value is ambiguous, you should determine the existence of the
1099 * property itself by other means.</p>
1100 *
1101 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1102 *
1103 * @param bean Bean for which a property descriptor is requested
1104 * @param name Possibly indexed and/or nested name of the property for
1105 * which a property descriptor is requested
1106 * @return the property editor class
1107 *
1108 * @exception IllegalAccessException if the caller does not have
1109 * access to the property accessor method
1110 * @exception IllegalArgumentException if <code>bean</code> or
1111 * <code>name</code> is null
1112 * @exception IllegalArgumentException if a nested reference to a
1113 * property returns null
1114 * @exception InvocationTargetException if the property accessor method
1115 * throws an exception
1116 * @exception NoSuchMethodException if an accessor method for this
1117 * propety cannot be found
1118 */
1119 public Class getPropertyEditorClass(Object bean, String name)
1120 throws IllegalAccessException, InvocationTargetException,
1121 NoSuchMethodException {
1122
1123 if (bean == null) {
1124 throw new IllegalArgumentException("No bean specified");
1125 }
1126 if (name == null) {
1127 throw new IllegalArgumentException("No name specified for bean class '" +
1128 bean.getClass() + "'");
1129 }
1130
1131 PropertyDescriptor descriptor =
1132 getPropertyDescriptor(bean, name);
1133 if (descriptor != null) {
1134 return (descriptor.getPropertyEditorClass());
1135 } else {
1136 return (null);
1137 }
1138
1139 }
1140
1141
1142 /**
1143 * Return the Java Class representing the property type of the specified
1144 * property, or <code>null</code> if there is no such property for the
1145 * specified bean. This method follows the same name resolution rules
1146 * used by <code>getPropertyDescriptor()</code>, so if the last element
1147 * of a name reference is indexed, the type of the property itself will
1148 * be returned. If the last (or only) element has no property with the
1149 * specified name, <code>null</code> is returned.
1150 *
1151 * @param bean Bean for which a property descriptor is requested
1152 * @param name Possibly indexed and/or nested name of the property for
1153 * which a property descriptor is requested
1154 * @return The property type
1155 *
1156 * @exception IllegalAccessException if the caller does not have
1157 * access to the property accessor method
1158 * @exception IllegalArgumentException if <code>bean</code> or
1159 * <code>name</code> is null
1160 * @exception IllegalArgumentException if a nested reference to a
1161 * property returns null
1162 * @exception InvocationTargetException if the property accessor method
1163 * throws an exception
1164 * @exception NoSuchMethodException if an accessor method for this
1165 * propety cannot be found
1166 */
1167 public Class getPropertyType(Object bean, String name)
1168 throws IllegalAccessException, InvocationTargetException,
1169 NoSuchMethodException {
1170
1171 if (bean == null) {
1172 throw new IllegalArgumentException("No bean specified");
1173 }
1174 if (name == null) {
1175 throw new IllegalArgumentException("No name specified for bean class '" +
1176 bean.getClass() + "'");
1177 }
1178
1179 // Resolve nested references
1180 while (resolver.hasNested(name)) {
1181 String next = resolver.next(name);
1182 Object nestedBean = getProperty(bean, next);
1183 if (nestedBean == null) {
1184 throw new NestedNullException
1185 ("Null property value for '" + next +
1186 "' on bean class '" + bean.getClass() + "'");
1187 }
1188 bean = nestedBean;
1189 name = resolver.remove(name);
1190 }
1191
1192 // Remove any subscript from the final name value
1193 name = resolver.getProperty(name);
1194
1195 // Special handling for DynaBeans
1196 if (bean instanceof DynaBean) {
1197 DynaProperty descriptor =
1198 ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1199 if (descriptor == null) {
1200 return (null);
1201 }
1202 Class type = descriptor.getType();
1203 if (type == null) {
1204 return (null);
1205 } else if (type.isArray()) {
1206 return (type.getComponentType());
1207 } else {
1208 return (type);
1209 }
1210 }
1211
1212 PropertyDescriptor descriptor =
1213 getPropertyDescriptor(bean, name);
1214 if (descriptor == null) {
1215 return (null);
1216 } else if (descriptor instanceof IndexedPropertyDescriptor) {
1217 return (((IndexedPropertyDescriptor) descriptor).
1218 getIndexedPropertyType());
1219 } else if (descriptor instanceof MappedPropertyDescriptor) {
1220 return (((MappedPropertyDescriptor) descriptor).
1221 getMappedPropertyType());
1222 } else {
1223 return (descriptor.getPropertyType());
1224 }
1225
1226 }
1227
1228
1229 /**
1230 * <p>Return an accessible property getter method for this property,
1231 * if there is one; otherwise return <code>null</code>.</p>
1232 *
1233 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1234 *
1235 * @param descriptor Property descriptor to return a getter for
1236 * @return The read method
1237 */
1238 public Method getReadMethod(PropertyDescriptor descriptor) {
1239
1240 return (MethodUtils.getAccessibleMethod(descriptor.getReadMethod()));
1241
1242 }
1243
1244
1245 /**
1246 * <p>Return an accessible property getter method for this property,
1247 * if there is one; otherwise return <code>null</code>.</p>
1248 *
1249 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1250 *
1251 * @param clazz The class of the read method will be invoked on
1252 * @param descriptor Property descriptor to return a getter for
1253 * @return The read method
1254 */
1255 Method getReadMethod(Class clazz, PropertyDescriptor descriptor) {
1256 return (MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod()));
1257 }
1258
1259
1260 /**
1261 * Return the value of the specified simple property of the specified
1262 * bean, with no type conversions.
1263 *
1264 * @param bean Bean whose property is to be extracted
1265 * @param name Name of the property to be extracted
1266 * @return The property value
1267 *
1268 * @exception IllegalAccessException if the caller does not have
1269 * access to the property accessor method
1270 * @exception IllegalArgumentException if <code>bean</code> or
1271 * <code>name</code> is null
1272 * @exception IllegalArgumentException if the property name
1273 * is nested or indexed
1274 * @exception InvocationTargetException if the property accessor method
1275 * throws an exception
1276 * @exception NoSuchMethodException if an accessor method for this
1277 * propety cannot be found
1278 */
1279 public Object getSimpleProperty(Object bean, String name)
1280 throws IllegalAccessException, InvocationTargetException,
1281 NoSuchMethodException {
1282
1283 if (bean == null) {
1284 throw new IllegalArgumentException("No bean specified");
1285 }
1286 if (name == null) {
1287 throw new IllegalArgumentException("No name specified for bean class '" +
1288 bean.getClass() + "'");
1289 }
1290
1291 // Validate the syntax of the property name
1292 if (resolver.hasNested(name)) {
1293 throw new IllegalArgumentException
1294 ("Nested property names are not allowed: Property '" +
1295 name + "' on bean class '" + bean.getClass() + "'");
1296 } else if (resolver.isIndexed(name)) {
1297 throw new IllegalArgumentException
1298 ("Indexed property names are not allowed: Property '" +
1299 name + "' on bean class '" + bean.getClass() + "'");
1300 } else if (resolver.isMapped(name)) {
1301 throw new IllegalArgumentException
1302 ("Mapped property names are not allowed: Property '" +
1303 name + "' on bean class '" + bean.getClass() + "'");
1304 }
1305
1306 // Handle DynaBean instances specially
1307 if (bean instanceof DynaBean) {
1308 DynaProperty descriptor =
1309 ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1310 if (descriptor == null) {
1311 throw new NoSuchMethodException("Unknown property '" +
1312 name + "' on dynaclass '" +
1313 ((DynaBean) bean).getDynaClass() + "'" );
1314 }
1315 return (((DynaBean) bean).get(name));
1316 }
1317
1318 // Retrieve the property getter method for the specified property
1319 PropertyDescriptor descriptor =
1320 getPropertyDescriptor(bean, name);
1321 if (descriptor == null) {
1322 throw new NoSuchMethodException("Unknown property '" +
1323 name + "' on class '" + bean.getClass() + "'" );
1324 }
1325 Method readMethod = getReadMethod(bean.getClass(), descriptor);
1326 if (readMethod == null) {
1327 throw new NoSuchMethodException("Property '" + name +
1328 "' has no getter method in class '" + bean.getClass() + "'");
1329 }
1330
1331 // Call the property getter and return the value
1332 Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
1333 return (value);
1334
1335 }
1336
1337
1338 /**
1339 * <p>Return an accessible property setter method for this property,
1340 * if there is one; otherwise return <code>null</code>.</p>
1341 *
1342 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1343 *
1344 * @param descriptor Property descriptor to return a setter for
1345 * @return The write method
1346 */
1347 public Method getWriteMethod(PropertyDescriptor descriptor) {
1348
1349 return (MethodUtils.getAccessibleMethod(descriptor.getWriteMethod()));
1350
1351 }
1352
1353
1354 /**
1355 * <p>Return an accessible property setter method for this property,
1356 * if there is one; otherwise return <code>null</code>.</p>
1357 *
1358 * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
1359 *
1360 * @param clazz The class of the read method will be invoked on
1361 * @param descriptor Property descriptor to return a setter for
1362 * @return The write method
1363 */
1364 Method getWriteMethod(Class clazz, PropertyDescriptor descriptor) {
1365 return (MethodUtils.getAccessibleMethod(clazz, descriptor.getWriteMethod()));
1366 }
1367
1368
1369 /**
1370 * <p>Return <code>true</code> if the specified property name identifies
1371 * a readable property on the specified bean; otherwise, return
1372 * <code>false</code>.
1373 *
1374 * @param bean Bean to be examined (may be a {@link DynaBean}
1375 * @param name Property name to be evaluated
1376 * @return <code>true</code> if the property is readable,
1377 * otherwise <code>false</code>
1378 *
1379 * @exception IllegalArgumentException if <code>bean</code>
1380 * or <code>name</code> is <code>null</code>
1381 *
1382 * @since BeanUtils 1.6
1383 */
1384 public boolean isReadable(Object bean, String name) {
1385
1386 // Validate method parameters
1387 if (bean == null) {
1388 throw new IllegalArgumentException("No bean specified");
1389 }
1390 if (name == null) {
1391 throw new IllegalArgumentException("No name specified for bean class '" +
1392 bean.getClass() + "'");
1393 }
1394
1395 // Resolve nested references
1396 while (resolver.hasNested(name)) {
1397 String next = resolver.next(name);
1398 Object nestedBean = null;
1399 try {
1400 nestedBean = getProperty(bean, next);
1401 } catch (IllegalAccessException e) {
1402 return false;
1403 } catch (InvocationTargetException e) {
1404 return false;
1405 } catch (NoSuchMethodException e) {
1406 return false;
1407 }
1408 if (nestedBean == null) {
1409 throw new NestedNullException
1410 ("Null property value for '" + next +
1411 "' on bean class '" + bean.getClass() + "'");
1412 }
1413 bean = nestedBean;
1414 name = resolver.remove(name);
1415 }
1416
1417 // Remove any subscript from the final name value
1418 name = resolver.getProperty(name);
1419
1420 // Treat WrapDynaBean as special case - may be a write-only property
1421 // (see Jira issue# BEANUTILS-61)
1422 if (bean instanceof WrapDynaBean) {
1423 bean = ((WrapDynaBean)bean).getInstance();
1424 }
1425
1426 // Return the requested result
1427 if (bean instanceof DynaBean) {
1428 // All DynaBean properties are readable
1429 return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
1430 } else {
1431 try {
1432 PropertyDescriptor desc =
1433 getPropertyDescriptor(bean, name);
1434 if (desc != null) {
1435 Method readMethod = getReadMethod(bean.getClass(), desc);
1436 if (readMethod == null) {
1437 if (desc instanceof IndexedPropertyDescriptor) {
1438 readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
1439 } else if (desc instanceof MappedPropertyDescriptor) {
1440 readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
1441 }
1442 readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
1443 }
1444 return (readMethod != null);
1445 } else {
1446 return (false);
1447 }
1448 } catch (IllegalAccessException e) {
1449 return (false);
1450 } catch (InvocationTargetException e) {
1451 return (false);
1452 } catch (NoSuchMethodException e) {
1453 return (false);
1454 }
1455 }
1456
1457 }
1458
1459
1460 /**
1461 * <p>Return <code>true</code> if the specified property name identifies
1462 * a writeable property on the specified bean; otherwise, return
1463 * <code>false</code>.
1464 *
1465 * @param bean Bean to be examined (may be a {@link DynaBean}
1466 * @param name Property name to be evaluated
1467 * @return <code>true</code> if the property is writeable,
1468 * otherwise <code>false</code>
1469 *
1470 * @exception IllegalArgumentException if <code>bean</code>
1471 * or <code>name</code> is <code>null</code>
1472 *
1473 * @since BeanUtils 1.6
1474 */
1475 public boolean isWriteable(Object bean, String name) {
1476
1477 // Validate method parameters
1478 if (bean == null) {
1479 throw new IllegalArgumentException("No bean specified");
1480 }
1481 if (name == null) {
1482 throw new IllegalArgumentException("No name specified for bean class '" +
1483 bean.getClass() + "'");
1484 }
1485
1486 // Resolve nested references
1487 while (resolver.hasNested(name)) {
1488 String next = resolver.next(name);
1489 Object nestedBean = null;
1490 try {
1491 nestedBean = getProperty(bean, next);
1492 } catch (IllegalAccessException e) {
1493 return false;
1494 } catch (InvocationTargetException e) {
1495 return false;
1496 } catch (NoSuchMethodException e) {
1497 return false;
1498 }
1499 if (nestedBean == null) {
1500 throw new NestedNullException
1501 ("Null property value for '" + next +
1502 "' on bean class '" + bean.getClass() + "'");
1503 }
1504 bean = nestedBean;
1505 name = resolver.remove(name);
1506 }
1507
1508 // Remove any subscript from the final name value
1509 name = resolver.getProperty(name);
1510
1511 // Treat WrapDynaBean as special case - may be a read-only property
1512 // (see Jira issue# BEANUTILS-61)
1513 if (bean instanceof WrapDynaBean) {
1514 bean = ((WrapDynaBean)bean).getInstance();
1515 }
1516
1517 // Return the requested result
1518 if (bean instanceof DynaBean) {
1519 // All DynaBean properties are writeable
1520 return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
1521 } else {
1522 try {
1523 PropertyDescriptor desc =
1524 getPropertyDescriptor(bean, name);
1525 if (desc != null) {
1526 Method writeMethod = getWriteMethod(bean.getClass(), desc);
1527 if (writeMethod == null) {
1528 if (desc instanceof IndexedPropertyDescriptor) {
1529 writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
1530 } else if (desc instanceof MappedPropertyDescriptor) {
1531 writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
1532 }
1533 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
1534 }
1535 return (writeMethod != null);
1536 } else {
1537 return (false);
1538 }
1539 } catch (IllegalAccessException e) {
1540 return (false);
1541 } catch (InvocationTargetException e) {
1542 return (false);
1543 } catch (NoSuchMethodException e) {
1544 return (false);
1545 }
1546 }
1547
1548 }
1549
1550
1551 /**
1552 * Set the value of the specified indexed property of the specified
1553 * bean, with no type conversions. The zero-relative index of the
1554 * required value must be included (in square brackets) as a suffix to
1555 * the property name, or <code>IllegalArgumentException</code> will be
1556 * thrown. In addition to supporting the JavaBeans specification, this
1557 * method has been extended to support <code>List</code> objects as well.
1558 *
1559 * @param bean Bean whose property is to be modified
1560 * @param name <code>propertyname[index]</code> of the property value
1561 * to be modified
1562 * @param value Value to which the specified property element
1563 * should be set
1564 *
1565 * @exception IndexOutOfBoundsException if the specified index
1566 * is outside the valid range for the underlying property
1567 * @exception IllegalAccessException if the caller does not have
1568 * access to the property accessor method
1569 * @exception IllegalArgumentException if <code>bean</code> or
1570 * <code>name</code> is null
1571 * @exception InvocationTargetException if the property accessor method
1572 * throws an exception
1573 * @exception NoSuchMethodException if an accessor method for this
1574 * propety cannot be found
1575 */
1576 public void setIndexedProperty(Object bean, String name,
1577 Object value)
1578 throws IllegalAccessException, InvocationTargetException,
1579 NoSuchMethodException {
1580
1581 if (bean == null) {
1582 throw new IllegalArgumentException("No bean specified");
1583 }
1584 if (name == null) {
1585 throw new IllegalArgumentException("No name specified for bean class '" +
1586 bean.getClass() + "'");
1587 }
1588
1589 // Identify the index of the requested individual property
1590 int index = -1;
1591 try {
1592 index = resolver.getIndex(name);
1593 } catch (IllegalArgumentException e) {
1594 throw new IllegalArgumentException("Invalid indexed property '" +
1595 name + "' on bean class '" + bean.getClass() + "'");
1596 }
1597 if (index < 0) {
1598 throw new IllegalArgumentException("Invalid indexed property '" +
1599 name + "' on bean class '" + bean.getClass() + "'");
1600 }
1601
1602 // Isolate the name
1603 name = resolver.getProperty(name);
1604
1605 // Set the specified indexed property value
1606 setIndexedProperty(bean, name, index, value);
1607
1608 }
1609
1610
1611 /**
1612 * Set the value of the specified indexed property of the specified
1613 * bean, with no type conversions. In addition to supporting the JavaBeans
1614 * specification, this method has been extended to support
1615 * <code>List</code> objects as well.
1616 *
1617 * @param bean Bean whose property is to be set
1618 * @param name Simple property name of the property value to be set
1619 * @param index Index of the property value to be set
1620 * @param value Value to which the indexed property element is to be set
1621 *
1622 * @exception IndexOutOfBoundsException if the specified index
1623 * is outside the valid range for the underlying property
1624 * @exception IllegalAccessException if the caller does not have
1625 * access to the property accessor method
1626 * @exception IllegalArgumentException if <code>bean</code> or
1627 * <code>name</code> is null
1628 * @exception InvocationTargetException if the property accessor method
1629 * throws an exception
1630 * @exception NoSuchMethodException if an accessor method for this
1631 * propety cannot be found
1632 */
1633 public void setIndexedProperty(Object bean, String name,
1634 int index, Object value)
1635 throws IllegalAccessException, InvocationTargetException,
1636 NoSuchMethodException {
1637
1638 if (bean == null) {
1639 throw new IllegalArgumentException("No bean specified");
1640 }
1641 if (name == null || name.length() == 0) {
1642 if (bean.getClass().isArray()) {
1643 Array.set(bean, index, value);
1644 return;
1645 } else if (bean instanceof List) {
1646 ((List)bean).set(index, value);
1647 return;
1648 }
1649 }
1650 if (name == null) {
1651 throw new IllegalArgumentException("No name specified for bean class '" +
1652 bean.getClass() + "'");
1653 }
1654
1655 // Handle DynaBean instances specially
1656 if (bean instanceof DynaBean) {
1657 DynaProperty descriptor =
1658 ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1659 if (descriptor == null) {
1660 throw new NoSuchMethodException("Unknown property '" +
1661 name + "' on bean class '" + bean.getClass() + "'");
1662 }
1663 ((DynaBean) bean).set(name, index, value);
1664 return;
1665 }
1666
1667 // Retrieve the property descriptor for the specified property
1668 PropertyDescriptor descriptor =
1669 getPropertyDescriptor(bean, name);
1670 if (descriptor == null) {
1671 throw new NoSuchMethodException("Unknown property '" +
1672 name + "' on bean class '" + bean.getClass() + "'");
1673 }
1674
1675 // Call the indexed setter method if there is one
1676 if (descriptor instanceof IndexedPropertyDescriptor) {
1677 Method writeMethod = ((IndexedPropertyDescriptor) descriptor).
1678 getIndexedWriteMethod();
1679 writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
1680 if (writeMethod != null) {
1681 Object[] subscript = new Object[2];
1682 subscript[0] = new Integer(index);
1683 subscript[1] = value;
1684 try {
1685 if (log.isTraceEnabled()) {
1686 String valueClassName =
1687 value == null ? "<null>"
1688 : value.getClass().getName();
1689 log.trace("setSimpleProperty: Invoking method "
1690 + writeMethod +" with index=" + index
1691 + ", value=" + value
1692 + " (class " + valueClassName+ ")");
1693 }
1694 invokeMethod(writeMethod, bean, subscript);
1695 } catch (InvocationTargetException e) {
1696 if (e.getTargetException() instanceof
1697 IndexOutOfBoundsException) {
1698 throw (IndexOutOfBoundsException)
1699 e.getTargetException();
1700 } else {
1701 throw e;
1702 }
1703 }
1704 return;
1705 }
1706 }
1707
1708 // Otherwise, the underlying property must be an array or a list
1709 Method readMethod = getReadMethod(bean.getClass(), descriptor);
1710 if (readMethod == null) {
1711 throw new NoSuchMethodException("Property '" + name +
1712 "' has no getter method on bean class '" + bean.getClass() + "'");
1713 }
1714
1715 // Call the property getter to get the array or list
1716 Object array = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
1717 if (!array.getClass().isArray()) {
1718 if (array instanceof List) {
1719 // Modify the specified value in the List
1720 ((List) array).set(index, value);
1721 } else {
1722 throw new IllegalArgumentException("Property '" + name +
1723 "' is not indexed on bean class '" + bean.getClass() + "'");
1724 }
1725 } else {
1726 // Modify the specified value in the array
1727 Array.set(array, index, value);
1728 }
1729
1730 }
1731
1732
1733 /**
1734 * Set the value of the specified mapped property of the
1735 * specified bean, with no type conversions. The key of the
1736 * value to set must be included (in brackets) as a suffix to
1737 * the property name, or <code>IllegalArgumentException</code> will be
1738 * thrown.
1739 *
1740 * @param bean Bean whose property is to be set
1741 * @param name <code>propertyname(key)</code> of the property value
1742 * to be set
1743 * @param value The property value to be set
1744 *
1745 * @exception IllegalAccessException if the caller does not have
1746 * access to the property accessor method
1747 * @exception InvocationTargetException if the property accessor method
1748 * throws an exception
1749 * @exception NoSuchMethodException if an accessor method for this
1750 * propety cannot be found
1751 */
1752 public void setMappedProperty(Object bean, String name,
1753 Object value)
1754 throws IllegalAccessException, InvocationTargetException,
1755 NoSuchMethodException {
1756
1757 if (bean == null) {
1758 throw new IllegalArgumentException("No bean specified");
1759 }
1760 if (name == null) {
1761 throw new IllegalArgumentException("No name specified for bean class '" +
1762 bean.getClass() + "'");
1763 }
1764
1765 // Identify the key of the requested individual property
1766 String key = null;
1767 try {
1768 key = resolver.getKey(name);
1769 } catch (IllegalArgumentException e) {
1770 throw new IllegalArgumentException
1771 ("Invalid mapped property '" + name +
1772 "' on bean class '" + bean.getClass() + "'");
1773 }
1774 if (key == null) {
1775 throw new IllegalArgumentException
1776 ("Invalid mapped property '" + name +
1777 "' on bean class '" + bean.getClass() + "'");
1778 }
1779
1780 // Isolate the name
1781 name = resolver.getProperty(name);
1782
1783 // Request the specified indexed property value
1784 setMappedProperty(bean, name, key, value);
1785
1786 }
1787
1788
1789 /**
1790 * Set the value of the specified mapped property of the specified
1791 * bean, with no type conversions.
1792 *
1793 * @param bean Bean whose property is to be set
1794 * @param name Mapped property name of the property value to be set
1795 * @param key Key of the property value to be set
1796 * @param value The property value to be set
1797 *
1798 * @exception IllegalAccessException if the caller does not have
1799 * access to the property accessor method
1800 * @exception InvocationTargetException if the property accessor method
1801 * throws an exception
1802 * @exception NoSuchMethodException if an accessor method for this
1803 * propety cannot be found
1804 */
1805 public void setMappedProperty(Object bean, String name,
1806 String key, Object value)
1807 throws IllegalAccessException, InvocationTargetException,
1808 NoSuchMethodException {
1809
1810 if (bean == null) {
1811 throw new IllegalArgumentException("No bean specified");
1812 }
1813 if (name == null) {
1814 throw new IllegalArgumentException("No name specified for bean class '" +
1815 bean.getClass() + "'");
1816 }
1817 if (key == null) {
1818 throw new IllegalArgumentException("No key specified for property '" +
1819 name + "' on bean class '" + bean.getClass() + "'");
1820 }
1821
1822 // Handle DynaBean instances specially
1823 if (bean instanceof DynaBean) {
1824 DynaProperty descriptor =
1825 ((DynaBean) bean).getDynaClass().getDynaProperty(name);
1826 if (descriptor == null) {
1827 throw new NoSuchMethodException("Unknown property '" +
1828 name + "' on bean class '" + bean.getClass() + "'");
1829 }
1830 ((DynaBean) bean).set(name, key, value);
1831 return;
1832 }
1833
1834 // Retrieve the property descriptor for the specified property
1835 PropertyDescriptor descriptor =
1836 getPropertyDescriptor(bean, name);
1837 if (descriptor == null) {
1838 throw new NoSuchMethodException("Unknown property '" +
1839 name + "' on bean class '" + bean.getClass() + "'");
1840 }
1841
1842 if (descriptor instanceof MappedPropertyDescriptor) {
1843 // Call the keyed setter method if there is one
1844 Method mappedWriteMethod =
1845 ((MappedPropertyDescriptor) descriptor).
1846 getMappedWriteMethod();
1847 mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
1848 if (mappedWriteMethod != null) {
1849 Object[] params = new Object[2];
1850 params[0] = key;
1851 params[1] = value;
1852 if (log.isTraceEnabled()) {
1853 String valueClassName =
1854 value == null ? "<null>" : value.getClass().getName();
1855 log.trace("setSimpleProperty: Invoking method "
1856 + mappedWriteMethod + " with key=" + key
1857 + ", value=" + value
1858 + " (class " + valueClassName +")");
1859 }
1860 invokeMethod(mappedWriteMethod, bean, params);
1861 } else {
1862 throw new NoSuchMethodException
1863 ("Property '" + name + "' has no mapped setter method" +
1864 "on bean class '" + bean.getClass() + "'");
1865 }
1866 } else {
1867 /* means that the result has to be retrieved from a map */
1868 Method readMethod = getReadMethod(bean.getClass(), descriptor);
1869 if (readMethod != null) {
1870 Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
1871 /* test and fetch from the map */
1872 if (invokeResult instanceof java.util.Map) {
1873 ((java.util.Map)invokeResult).put(key, value);
1874 }
1875 } else {
1876 throw new NoSuchMethodException("Property '" + name +
1877 "' has no mapped getter method on bean class '" +
1878 bean.getClass() + "'");
1879 }
1880 }
1881
1882 }
1883
1884
1885 /**
1886 * Set the value of the (possibly nested) property of the specified
1887 * name, for the specified bean, with no type conversions.
1888 * <p>
1889 * Example values for parameter "name" are:
1890 * <ul>
1891 * <li> "a" -- sets the value of property a of the specified bean </li>
1892 * <li> "a.b" -- gets the value of property a of the specified bean,
1893 * then on that object sets the value of property b.</li>
1894 * <li> "a(key)" -- sets a value of mapped-property a on the specified
1895 * bean. This effectively means bean.setA("key").</li>
1896 * <li> "a[3]" -- sets a value of indexed-property a on the specified
1897 * bean. This effectively means bean.setA(3).</li>
1898 * </ul>
1899 *
1900 * @param bean Bean whose property is to be modified
1901 * @param name Possibly nested name of the property to be modified
1902 * @param value Value to which the property is to be set
1903 *
1904 * @exception IllegalAccessException if the caller does not have
1905 * access to the property accessor method
1906 * @exception IllegalArgumentException if <code>bean</code> or
1907 * <code>name</code> is null
1908 * @exception IllegalArgumentException if a nested reference to a
1909 * property returns null
1910 * @exception InvocationTargetException if the property accessor method
1911 * throws an exception
1912 * @exception NoSuchMethodException if an accessor method for this
1913 * propety cannot be found
1914 */
1915 public void setNestedProperty(Object bean,
1916 String name, Object value)
1917 throws IllegalAccessException, InvocationTargetException,
1918 NoSuchMethodException {
1919
1920 if (bean == null) {
1921 throw new IllegalArgumentException("No bean specified");
1922 }
1923 if (name == null) {
1924 throw new IllegalArgumentException("No name specified for bean class '" +
1925 bean.getClass() + "'");
1926 }
1927
1928 // Resolve nested references
1929 while (resolver.hasNested(name)) {
1930 String next = resolver.next(name);
1931 Object nestedBean = null;
1932 if (bean instanceof Map) {
1933 nestedBean = getPropertyOfMapBean((Map)bean, next);
1934 } else if (resolver.isMapped(next)) {
1935 nestedBean = getMappedProperty(bean, next);
1936 } else if (resolver.isIndexed(next)) {
1937 nestedBean = getIndexedProperty(bean, next);
1938 } else {
1939 nestedBean = getSimpleProperty(bean, next);
1940 }
1941 if (nestedBean == null) {
1942 throw new NestedNullException
1943 ("Null property value for '" + name +
1944 "' on bean class '" + bean.getClass() + "'");
1945 }
1946 bean = nestedBean;
1947 name = resolver.remove(name);
1948 }
1949
1950 if (bean instanceof Map) {
1951 setPropertyOfMapBean((Map) bean, name, value);
1952 } else if (resolver.isMapped(name)) {
1953 setMappedProperty(bean, name, value);
1954 } else if (resolver.isIndexed(name)) {
1955 setIndexedProperty(bean, name, value);
1956 } else {
1957 setSimpleProperty(bean, name, value);
1958 }
1959
1960 }
1961
1962 /**
1963 * This method is called by method setNestedProperty when the current bean
1964 * is found to be a Map object, and defines how to deal with setting
1965 * a property on a Map.
1966 * <p>
1967 * The standard implementation here is to:
1968 * <ul>
1969 * <li>call bean.set(propertyName) for all propertyName values.</li>
1970 * <li>throw an IllegalArgumentException if the property specifier
1971 * contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially
1972 * simple properties; mapping and indexing operations do not make sense
1973 * when accessing a map (even thought the returned object may be a Map
1974 * or an Array).</li>
1975 * </ul>
1976 * <p>
1977 * The default behaviour of beanutils 1.7.1 or later is for assigning to
1978 * "a.b" to mean a.put(b, obj) always. However the behaviour of beanutils
1979 * version 1.6.0, 1.6.1, 1.7.0 was for "a.b" to mean a.setB(obj) if such
1980 * a method existed, and a.put(b, obj) otherwise. In version 1.5 it meant
1981 * a.put(b, obj) always (ie the same as the behaviour in the current version).
1982 * In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is
1983 * all <i>very</i> unfortunate]
1984 * <p>
1985 * Users who would like to customise the meaning of "a.b" in method
1986 * setNestedProperty when a is a Map can create a custom subclass of
1987 * this class and override this method to implement the behaviour of
1988 * their choice, such as restoring the pre-1.4 behaviour of this class
1989 * if they wish. When overriding this method, do not forget to deal
1990 * with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
1991 * <p>
1992 * Note, however, that the recommended solution for objects that
1993 * implement Map but want their simple properties to come first is
1994 * for <i>those</i> objects to override their get/put methods to implement
1995 * that behaviour, and <i>not</i> to solve the problem by modifying the
1996 * default behaviour of the PropertyUtilsBean class by overriding this
1997 * method.
1998 *
1999 * @param bean Map bean
2000 * @param propertyName The property name
2001 * @param value the property value
2002 *
2003 * @throws IllegalArgumentException when the propertyName is regarded as
2004 * being invalid.
2005 *
2006 * @throws IllegalAccessException just in case subclasses override this
2007 * method to try to access real setter methods and find permission is denied.
2008 *
2009 * @throws InvocationTargetException just in case subclasses override this
2010 * method to try to access real setter methods, and find it throws an
2011 * exception when invoked.
2012 *
2013 * @throws NoSuchMethodException just in case subclasses override this
2014 * method to try to access real setter methods, and want to fail if
2015 * no simple method is available.
2016 * @since 1.8.0
2017 */
2018 protected void setPropertyOfMapBean(Map bean, String propertyName, Object value)
2019 throws IllegalArgumentException, IllegalAccessException,
2020 InvocationTargetException, NoSuchMethodException {
2021
2022 if (resolver.isMapped(propertyName)) {
2023 String name = resolver.getProperty(propertyName);
2024 if (name == null || name.length() == 0) {
2025 propertyName = resolver.getKey(propertyName);
2026 }
2027 }
2028
2029 if (resolver.isIndexed(propertyName) ||
2030 resolver.isMapped(propertyName)) {
2031 throw new IllegalArgumentException(
2032 "Indexed or mapped properties are not supported on"
2033 + " objects of type Map: " + propertyName);
2034 }
2035
2036 bean.put(propertyName, value);
2037 }
2038
2039
2040
2041 /**
2042 * Set the value of the specified property of the specified bean,
2043 * no matter which property reference format is used, with no
2044 * type conversions.
2045 *
2046 * @param bean Bean whose property is to be modified
2047 * @param name Possibly indexed and/or nested name of the property
2048 * to be modified
2049 * @param value Value to which this property is to be set
2050 *
2051 * @exception IllegalAccessException if the caller does not have
2052 * access to the property accessor method
2053 * @exception IllegalArgumentException if <code>bean</code> or
2054 * <code>name</code> is null
2055 * @exception InvocationTargetException if the property accessor method
2056 * throws an exception
2057 * @exception NoSuchMethodException if an accessor method for this
2058 * propety cannot be found
2059 */
2060 public void setProperty(Object bean, String name, Object value)
2061 throws IllegalAccessException, InvocationTargetException,
2062 NoSuchMethodException {
2063
2064 setNestedProperty(bean, name, value);
2065
2066 }
2067
2068
2069 /**
2070 * Set the value of the specified simple property of the specified bean,
2071 * with no type conversions.
2072 *
2073 * @param bean Bean whose property is to be modified
2074 * @param name Name of the property to be modified
2075 * @param value Value to which the property should be set
2076 *
2077 * @exception IllegalAccessException if the caller does not have
2078 * access to the property accessor method
2079 * @exception IllegalArgumentException if <code>bean</code> or
2080 * <code>name</code> is null
2081 * @exception IllegalArgumentException if the property name is
2082 * nested or indexed
2083 * @exception InvocationTargetException if the property accessor method
2084 * throws an exception
2085 * @exception NoSuchMethodException if an accessor method for this
2086 * propety cannot be found
2087 */
2088 public void setSimpleProperty(Object bean,
2089 String name, Object value)
2090 throws IllegalAccessException, InvocationTargetException,
2091 NoSuchMethodException {
2092
2093 if (bean == null) {
2094 throw new IllegalArgumentException("No bean specified");
2095 }
2096 if (name == null) {
2097 throw new IllegalArgumentException("No name specified for bean class '" +
2098 bean.getClass() + "'");
2099 }
2100
2101 // Validate the syntax of the property name
2102 if (resolver.hasNested(name)) {
2103 throw new IllegalArgumentException
2104 ("Nested property names are not allowed: Property '" +
2105 name + "' on bean class '" + bean.getClass() + "'");
2106 } else if (resolver.isIndexed(name)) {
2107 throw new IllegalArgumentException
2108 ("Indexed property names are not allowed: Property '" +
2109 name + "' on bean class '" + bean.getClass() + "'");
2110 } else if (resolver.isMapped(name)) {
2111 throw new IllegalArgumentException
2112 ("Mapped property names are not allowed: Property '" +
2113 name + "' on bean class '" + bean.getClass() + "'");
2114 }
2115
2116 // Handle DynaBean instances specially
2117 if (bean instanceof DynaBean) {
2118 DynaProperty descriptor =
2119 ((DynaBean) bean).getDynaClass().getDynaProperty(name);
2120 if (descriptor == null) {
2121 throw new NoSuchMethodException("Unknown property '" +
2122 name + "' on dynaclass '" +
2123 ((DynaBean) bean).getDynaClass() + "'" );
2124 }
2125 ((DynaBean) bean).set(name, value);
2126 return;
2127 }
2128
2129 // Retrieve the property setter method for the specified property
2130 PropertyDescriptor descriptor =
2131 getPropertyDescriptor(bean, name);
2132 if (descriptor == null) {
2133 throw new NoSuchMethodException("Unknown property '" +
2134 name + "' on class '" + bean.getClass() + "'" );
2135 }
2136 Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
2137 if (writeMethod == null) {
2138 throw new NoSuchMethodException("Property '" + name +
2139 "' has no setter method in class '" + bean.getClass() + "'");
2140 }
2141
2142 // Call the property setter method
2143 Object[] values = new Object[1];
2144 values[0] = value;
2145 if (log.isTraceEnabled()) {
2146 String valueClassName =
2147 value == null ? "<null>" : value.getClass().getName();
2148 log.trace("setSimpleProperty: Invoking method " + writeMethod
2149 + " with value " + value + " (class " + valueClassName + ")");
2150 }
2151 invokeMethod(writeMethod, bean, values);
2152
2153 }
2154
2155 /** This just catches and wraps IllegalArgumentException. */
2156 private Object invokeMethod(
2157 Method method,
2158 Object bean,
2159 Object[] values)
2160 throws
2161 IllegalAccessException,
2162 InvocationTargetException {
2163 if(bean == null) {
2164 throw new IllegalArgumentException("No bean specified " +
2165 "- this should have been checked before reaching this method");
2166 }
2167
2168 try {
2169
2170 return method.invoke(bean, values);
2171
2172 } catch (NullPointerException cause) {
2173 // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
2174 // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
2175 String valueString = "";
2176 if (values != null) {
2177 for (int i = 0; i < values.length; i++) {
2178 if (i>0) {
2179 valueString += ", " ;
2180 }
2181 if (values[i] == null) {
2182 valueString += "<null>";
2183 } else {
2184 valueString += (values[i]).getClass().getName();
2185 }
2186 }
2187 }
2188 String expectedString = "";
2189 Class[] parTypes = method.getParameterTypes();
2190 if (parTypes != null) {
2191 for (int i = 0; i < parTypes.length; i++) {
2192 if (i > 0) {
2193 expectedString += ", ";
2194 }
2195 expectedString += parTypes[i].getName();
2196 }
2197 }
2198 IllegalArgumentException e = new IllegalArgumentException(
2199 "Cannot invoke " + method.getDeclaringClass().getName() + "."
2200 + method.getName() + " on bean class '" + bean.getClass() +
2201 "' - " + cause.getMessage()
2202 // as per https://issues.apache.org/jira/browse/BEANUTILS-224
2203 + " - had objects of type \"" + valueString
2204 + "\" but expected signature \""
2205 + expectedString + "\""
2206 );
2207 if (!BeanUtils.initCause(e, cause)) {
2208 log.error("Method invocation failed", cause);
2209 }
2210 throw e;
2211 } catch (IllegalArgumentException cause) {
2212 String valueString = "";
2213 if (values != null) {
2214 for (int i = 0; i < values.length; i++) {
2215 if (i>0) {
2216 valueString += ", " ;
2217 }
2218 if (values[i] == null) {
2219 valueString += "<null>";
2220 } else {
2221 valueString += (values[i]).getClass().getName();
2222 }
2223 }
2224 }
2225 String expectedString = "";
2226 Class[] parTypes = method.getParameterTypes();
2227 if (parTypes != null) {
2228 for (int i = 0; i < parTypes.length; i++) {
2229 if (i > 0) {
2230 expectedString += ", ";
2231 }
2232 expectedString += parTypes[i].getName();
2233 }
2234 }
2235 IllegalArgumentException e = new IllegalArgumentException(
2236 "Cannot invoke " + method.getDeclaringClass().getName() + "."
2237 + method.getName() + " on bean class '" + bean.getClass() +
2238 "' - " + cause.getMessage()
2239 // as per https://issues.apache.org/jira/browse/BEANUTILS-224
2240 + " - had objects of type \"" + valueString
2241 + "\" but expected signature \""
2242 + expectedString + "\""
2243 );
2244 if (!BeanUtils.initCause(e, cause)) {
2245 log.error("Method invocation failed", cause);
2246 }
2247 throw e;
2248
2249 }
2250 }
2251 }