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    
019    package org.apache.commons.beanutils;
020    
021    
022    import java.beans.IndexedPropertyDescriptor;
023    import java.beans.PropertyDescriptor;
024    import java.lang.reflect.Array;
025    import java.lang.reflect.InvocationTargetException;
026    import java.lang.reflect.Method;
027    import java.util.ArrayList;
028    import java.util.Collection;
029    import java.util.HashMap;
030    import java.util.Iterator;
031    import java.util.Map;
032    
033    import org.apache.commons.beanutils.expression.Resolver;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    
037    
038    /**
039     * <p>JavaBean property population methods.</p>
040     *
041     * <p>This class provides implementations for the utility methods in
042     * {@link BeanUtils}.
043     * Different instances can be used to isolate caches between classloaders
044     * and to vary the value converters registered.</p>
045     *
046     * @author Craig R. McClanahan
047     * @author Ralph Schaer
048     * @author Chris Audley
049     * @author Rey Francois
050     * @author Gregor Rayman
051     * @version $Revision: 834031 $ $Date: 2009-11-09 12:26:52 +0000 (Mon, 09 Nov 2009) $
052     * @see BeanUtils
053     * @since 1.7
054     */
055    
056    public class BeanUtilsBean {
057    
058    
059        // ------------------------------------------------------ Private Class Variables
060    
061        /** 
062         * Contains <code>BeanUtilsBean</code> instances indexed by context classloader.
063         */
064        private static final ContextClassLoaderLocal 
065                BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal() {
066                            // Creates the default instance used when the context classloader is unavailable
067                            protected Object initialValue() {
068                                return new BeanUtilsBean();
069                            }
070                        };
071        
072        /** 
073         * Gets the instance which provides the functionality for {@link BeanUtils}.
074         * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
075         * This mechanism provides isolation for web apps deployed in the same container.
076         *
077         * @return The (pseudo-singleton) BeanUtils bean instance
078         */
079        public static BeanUtilsBean getInstance() {
080            return (BeanUtilsBean) BEANS_BY_CLASSLOADER.get();
081        }
082    
083        /** 
084         * Sets the instance which provides the functionality for {@link BeanUtils}.
085         * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
086         * This mechanism provides isolation for web apps deployed in the same container.
087         * 
088         * @param newInstance The (pseudo-singleton) BeanUtils bean instance
089         */
090        public static void setInstance(BeanUtilsBean newInstance) {
091            BEANS_BY_CLASSLOADER.set(newInstance);
092        }
093    
094        // --------------------------------------------------------- Attributes
095    
096        /**
097         * Logging for this instance
098         */
099        private Log log = LogFactory.getLog(BeanUtils.class);
100        
101        /** Used to perform conversions between object types when setting properties */
102        private ConvertUtilsBean convertUtilsBean;
103        
104        /** Used to access properties*/
105        private PropertyUtilsBean propertyUtilsBean;
106    
107        /** A reference to Throwable's initCause method, or null if it's not there in this JVM */
108        private static final Method INIT_CAUSE_METHOD = getInitCauseMethod();
109    
110        // --------------------------------------------------------- Constuctors
111    
112        /** 
113         * <p>Constructs an instance using new property 
114         * and conversion instances.</p>
115         */
116        public BeanUtilsBean() {
117            this(new ConvertUtilsBean(), new PropertyUtilsBean());
118        }
119    
120        /** 
121         * <p>Constructs an instance using given conversion instances
122         * and new {@link PropertyUtilsBean} instance.</p>
123         *
124         * @param convertUtilsBean use this <code>ConvertUtilsBean</code> 
125         * to perform conversions from one object to another
126         *
127         * @since 1.8.0
128         */
129        public BeanUtilsBean(ConvertUtilsBean convertUtilsBean) {
130            this(convertUtilsBean, new PropertyUtilsBean());
131        }
132    
133        /** 
134         * <p>Constructs an instance using given property and conversion instances.</p>
135         *
136         * @param convertUtilsBean use this <code>ConvertUtilsBean</code> 
137         * to perform conversions from one object to another
138         * @param propertyUtilsBean use this <code>PropertyUtilsBean</code>
139         * to access properties
140         */
141        public BeanUtilsBean(
142                                ConvertUtilsBean convertUtilsBean, 
143                                PropertyUtilsBean propertyUtilsBean) {
144                                
145            this.convertUtilsBean = convertUtilsBean;
146            this.propertyUtilsBean = propertyUtilsBean;
147        }
148    
149        // --------------------------------------------------------- Public Methods
150    
151        /**
152         * <p>Clone a bean based on the available property getters and setters,
153         * even if the bean class itself does not implement Cloneable.</p>
154         *
155         * <p>
156         * <strong>Note:</strong> this method creates a <strong>shallow</strong> clone.
157         * In other words, any objects referred to by the bean are shared with the clone
158         * rather than being cloned in turn.
159         * </p>
160         *
161         * @param bean Bean to be cloned
162         * @return the cloned bean
163         *
164         * @exception IllegalAccessException if the caller does not have
165         *  access to the property accessor method
166         * @exception InstantiationException if a new instance of the bean's
167         *  class cannot be instantiated
168         * @exception InvocationTargetException if the property accessor method
169         *  throws an exception
170         * @exception NoSuchMethodException if an accessor method for this
171         *  property cannot be found
172         */
173        public Object cloneBean(Object bean)
174                throws IllegalAccessException, InstantiationException,
175                InvocationTargetException, NoSuchMethodException {
176    
177            if (log.isDebugEnabled()) {
178                log.debug("Cloning bean: " + bean.getClass().getName());
179            }
180            Object newBean = null;
181            if (bean instanceof DynaBean) {
182                newBean = ((DynaBean) bean).getDynaClass().newInstance();
183            } else {
184                newBean = bean.getClass().newInstance();
185            }
186            getPropertyUtils().copyProperties(newBean, bean);
187            return (newBean);
188    
189        }
190    
191    
192        /**
193         * <p>Copy property values from the origin bean to the destination bean
194         * for all cases where the property names are the same.  For each
195         * property, a conversion is attempted as necessary.  All combinations of
196         * standard JavaBeans and DynaBeans as origin and destination are
197         * supported.  Properties that exist in the origin bean, but do not exist
198         * in the destination bean (or are read-only in the destination bean) are
199         * silently ignored.</p>
200         *
201         * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
202         * to contain String-valued <strong>simple</strong> property names as the keys, pointing at
203         * the corresponding property values that will be converted (if necessary)
204         * and set in the destination bean. <strong>Note</strong> that this method
205         * is intended to perform a "shallow copy" of the properties and so complex
206         * properties (for example, nested ones) will not be copied.</p>
207         *
208         * <p>This method differs from <code>populate()</code>, which
209         * was primarily designed for populating JavaBeans from the map of request
210         * parameters retrieved on an HTTP request, is that no scalar->indexed
211         * or indexed->scalar manipulations are performed.  If the origin property
212         * is indexed, the destination property must be also.</p>
213         *
214         * <p>If you know that no type conversions are required, the
215         * <code>copyProperties()</code> method in {@link PropertyUtils} will
216         * execute faster than this method.</p>
217         *
218         * <p><strong>FIXME</strong> - Indexed and mapped properties that do not
219         * have getter and setter methods for the underlying array or Map are not
220         * copied by this method.</p>
221         *
222         * @param dest Destination bean whose properties are modified
223         * @param orig Origin bean whose properties are retrieved
224         *
225         * @exception IllegalAccessException if the caller does not have
226         *  access to the property accessor method
227         * @exception IllegalArgumentException if the <code>dest</code> or
228         *  <code>orig</code> argument is null or if the <code>dest</code> 
229         *  property type is different from the source type and the relevant
230         *  converter has not been registered.
231         * @exception InvocationTargetException if the property accessor method
232         *  throws an exception
233         */
234        public void copyProperties(Object dest, Object orig)
235            throws IllegalAccessException, InvocationTargetException {
236    
237            // Validate existence of the specified beans
238            if (dest == null) {
239                throw new IllegalArgumentException
240                        ("No destination bean specified");
241            }
242            if (orig == null) {
243                throw new IllegalArgumentException("No origin bean specified");
244            }
245            if (log.isDebugEnabled()) {
246                log.debug("BeanUtils.copyProperties(" + dest + ", " +
247                          orig + ")");
248            }
249    
250            // Copy the properties, converting as necessary
251            if (orig instanceof DynaBean) {
252                DynaProperty[] origDescriptors =
253                    ((DynaBean) orig).getDynaClass().getDynaProperties();
254                for (int i = 0; i < origDescriptors.length; i++) {
255                    String name = origDescriptors[i].getName();
256                    // Need to check isReadable() for WrapDynaBean
257                    // (see Jira issue# BEANUTILS-61)
258                    if (getPropertyUtils().isReadable(orig, name) &&
259                        getPropertyUtils().isWriteable(dest, name)) {
260                        Object value = ((DynaBean) orig).get(name);
261                        copyProperty(dest, name, value);
262                    }
263                }
264            } else if (orig instanceof Map) {
265                Iterator entries = ((Map) orig).entrySet().iterator();
266                while (entries.hasNext()) {
267                    Map.Entry entry = (Map.Entry) entries.next();
268                    String name = (String)entry.getKey();
269                    if (getPropertyUtils().isWriteable(dest, name)) {
270                        copyProperty(dest, name, entry.getValue());
271                    }
272                }
273            } else /* if (orig is a standard JavaBean) */ {
274                PropertyDescriptor[] origDescriptors =
275                    getPropertyUtils().getPropertyDescriptors(orig);
276                for (int i = 0; i < origDescriptors.length; i++) {
277                    String name = origDescriptors[i].getName();
278                    if ("class".equals(name)) {
279                        continue; // No point in trying to set an object's class
280                    }
281                    if (getPropertyUtils().isReadable(orig, name) &&
282                        getPropertyUtils().isWriteable(dest, name)) {
283                        try {
284                            Object value =
285                                getPropertyUtils().getSimpleProperty(orig, name);
286                            copyProperty(dest, name, value);
287                        } catch (NoSuchMethodException e) {
288                            // Should not happen
289                        }
290                    }
291                }
292            }
293    
294        }
295    
296    
297        /**
298         * <p>Copy the specified property value to the specified destination bean,
299         * performing any type conversion that is required.  If the specified
300         * bean does not have a property of the specified name, or the property
301         * is read only on the destination bean, return without
302         * doing anything.  If you have custom destination property types, register
303         * {@link Converter}s for them by calling the <code>register()</code>
304         * method of {@link ConvertUtils}.</p>
305         *
306         * <p><strong>IMPLEMENTATION RESTRICTIONS</strong>:</p>
307         * <ul>
308         * <li>Does not support destination properties that are indexed,
309         *     but only an indexed setter (as opposed to an array setter)
310         *     is available.</li>
311         * <li>Does not support destination properties that are mapped,
312         *     but only a keyed setter (as opposed to a Map setter)
313         *     is available.</li>
314         * <li>The desired property type of a mapped setter cannot be
315         *     determined (since Maps support any data type), so no conversion
316         *     will be performed.</li>
317         * </ul>
318         *
319         * @param bean Bean on which setting is to be performed
320         * @param name Property name (can be nested/indexed/mapped/combo)
321         * @param value Value to be set
322         *
323         * @exception IllegalAccessException if the caller does not have
324         *  access to the property accessor method
325         * @exception InvocationTargetException if the property accessor method
326         *  throws an exception
327         */
328        public void copyProperty(Object bean, String name, Object value)
329            throws IllegalAccessException, InvocationTargetException {
330    
331            // Trace logging (if enabled)
332            if (log.isTraceEnabled()) {
333                StringBuffer sb = new StringBuffer("  copyProperty(");
334                sb.append(bean);
335                sb.append(", ");
336                sb.append(name);
337                sb.append(", ");
338                if (value == null) {
339                    sb.append("<NULL>");
340                } else if (value instanceof String) {
341                    sb.append((String) value);
342                } else if (value instanceof String[]) {
343                    String[] values = (String[]) value;
344                    sb.append('[');
345                    for (int i = 0; i < values.length; i++) {
346                        if (i > 0) {
347                            sb.append(',');
348                        }
349                        sb.append(values[i]);
350                    }
351                    sb.append(']');
352                } else {
353                    sb.append(value.toString());
354                }
355                sb.append(')');
356                log.trace(sb.toString());
357            }
358    
359            // Resolve any nested expression to get the actual target bean
360            Object target = bean;
361            Resolver resolver = getPropertyUtils().getResolver();
362            while (resolver.hasNested(name)) {
363                try {
364                    target = getPropertyUtils().getProperty(target, resolver.next(name));
365                    name = resolver.remove(name);
366                } catch (NoSuchMethodException e) {
367                    return; // Skip this property setter
368                }
369            }
370            if (log.isTraceEnabled()) {
371                log.trace("    Target bean = " + target);
372                log.trace("    Target name = " + name);
373            }
374    
375            // Declare local variables we will require
376            String propName = resolver.getProperty(name); // Simple name of target property
377            Class type = null;                            // Java type of target property
378            int index  = resolver.getIndex(name);         // Indexed subscript value (if any)
379            String key = resolver.getKey(name);           // Mapped key value (if any)
380    
381            // Calculate the target property type
382            if (target instanceof DynaBean) {
383                DynaClass dynaClass = ((DynaBean) target).getDynaClass();
384                DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
385                if (dynaProperty == null) {
386                    return; // Skip this property setter
387                }
388                type = dynaProperty.getType();
389            } else {
390                PropertyDescriptor descriptor = null;
391                try {
392                    descriptor =
393                        getPropertyUtils().getPropertyDescriptor(target, name);
394                    if (descriptor == null) {
395                        return; // Skip this property setter
396                    }
397                } catch (NoSuchMethodException e) {
398                    return; // Skip this property setter
399                }
400                type = descriptor.getPropertyType();
401                if (type == null) {
402                    // Most likely an indexed setter on a POJB only
403                    if (log.isTraceEnabled()) {
404                        log.trace("    target type for property '" +
405                                  propName + "' is null, so skipping ths setter");
406                    }
407                    return;
408                }
409            }
410            if (log.isTraceEnabled()) {
411                log.trace("    target propName=" + propName + ", type=" +
412                          type + ", index=" + index + ", key=" + key);
413            }
414    
415            // Convert the specified value to the required type and store it
416            if (index >= 0) {                    // Destination must be indexed
417                value = convert(value, type.getComponentType());
418                try {
419                    getPropertyUtils().setIndexedProperty(target, propName,
420                                                     index, value);
421                } catch (NoSuchMethodException e) {
422                    throw new InvocationTargetException
423                        (e, "Cannot set " + propName);
424                }
425            } else if (key != null) {            // Destination must be mapped
426                // Maps do not know what the preferred data type is,
427                // so perform no conversions at all
428                // FIXME - should we create or support a TypedMap?
429                try {
430                    getPropertyUtils().setMappedProperty(target, propName,
431                                                    key, value);
432                } catch (NoSuchMethodException e) {
433                    throw new InvocationTargetException
434                        (e, "Cannot set " + propName);
435                }
436            } else {                             // Destination must be simple
437                value = convert(value, type);
438                try {
439                    getPropertyUtils().setSimpleProperty(target, propName, value);
440                } catch (NoSuchMethodException e) {
441                    throw new InvocationTargetException
442                        (e, "Cannot set " + propName);
443                }
444            }
445    
446        }
447    
448    
449        /**
450         * <p>Return the entire set of properties for which the specified bean
451         * provides a read method. This map contains the to <code>String</code>
452         * converted property values for all properties for which a read method
453         * is provided (i.e. where the getReadMethod() returns non-null).</p>
454         *
455         * <p>This map can be fed back to a call to
456         * <code>BeanUtils.populate()</code> to reconsitute the same set of
457         * properties, modulo differences for read-only and write-only
458         * properties, but only if there are no indexed properties.</p>
459         *
460         * <p><strong>Warning:</strong> if any of the bean property implementations
461         * contain (directly or indirectly) a call to this method then 
462         * a stack overflow may result. For example:
463         * <code><pre>
464         * class MyBean
465         * {
466         *    public Map getParameterMap()
467         *    {
468         *         BeanUtils.describe(this);
469         *    }
470         * }
471         * </pre></code>
472         * will result in an infinite regression when <code>getParametersMap</code>
473         * is called. It is recommended that such methods are given alternative
474         * names (for example, <code>parametersMap</code>).
475         * </p>
476         * @param bean Bean whose properties are to be extracted
477         * @return Map of property descriptors
478         *
479         * @exception IllegalAccessException if the caller does not have
480         *  access to the property accessor method
481         * @exception InvocationTargetException if the property accessor method
482         *  throws an exception
483         * @exception NoSuchMethodException if an accessor method for this
484         *  property cannot be found
485         */
486        public Map describe(Object bean)
487                throws IllegalAccessException, InvocationTargetException,
488                NoSuchMethodException {
489    
490            if (bean == null) {
491            //            return (Collections.EMPTY_MAP);
492                return (new java.util.HashMap());
493            }
494            
495            if (log.isDebugEnabled()) {
496                log.debug("Describing bean: " + bean.getClass().getName());
497            }
498                
499            Map description = new HashMap();
500            if (bean instanceof DynaBean) {
501                DynaProperty[] descriptors =
502                    ((DynaBean) bean).getDynaClass().getDynaProperties();
503                for (int i = 0; i < descriptors.length; i++) {
504                    String name = descriptors[i].getName();
505                    description.put(name, getProperty(bean, name));
506                }
507            } else {
508                PropertyDescriptor[] descriptors =
509                    getPropertyUtils().getPropertyDescriptors(bean);
510                Class clazz = bean.getClass();
511                for (int i = 0; i < descriptors.length; i++) {
512                    String name = descriptors[i].getName();
513                    if (getPropertyUtils().getReadMethod(clazz, descriptors[i]) != null) {
514                        description.put(name, getProperty(bean, name));
515                    }
516                }
517            }
518            return (description);
519    
520        }
521    
522    
523        /**
524         * Return the value of the specified array property of the specified
525         * bean, as a String array.
526         *
527         * @param bean Bean whose property is to be extracted
528         * @param name Name of the property to be extracted
529         * @return The array property value
530         *
531         * @exception IllegalAccessException if the caller does not have
532         *  access to the property accessor method
533         * @exception InvocationTargetException if the property accessor method
534         *  throws an exception
535         * @exception NoSuchMethodException if an accessor method for this
536         *  property cannot be found
537         */
538        public String[] getArrayProperty(Object bean, String name)
539                throws IllegalAccessException, InvocationTargetException,
540                NoSuchMethodException {
541    
542            Object value = getPropertyUtils().getProperty(bean, name);
543            if (value == null) {
544                return (null);
545            } else if (value instanceof Collection) {
546                ArrayList values = new ArrayList();
547                Iterator items = ((Collection) value).iterator();
548                while (items.hasNext()) {
549                    Object item = items.next();
550                    if (item == null) {
551                        values.add((String) null);
552                    } else {
553                        // convert to string using convert utils
554                        values.add(getConvertUtils().convert(item));
555                    }
556                }
557                return ((String[]) values.toArray(new String[values.size()]));
558            } else if (value.getClass().isArray()) {
559                int n = Array.getLength(value);
560                String[] results = new String[n];
561                for (int i = 0; i < n; i++) {
562                    Object item = Array.get(value, i);
563                    if (item == null) {
564                        results[i] = null;
565                    } else {
566                        // convert to string using convert utils
567                        results[i] = getConvertUtils().convert(item);
568                    }
569                }
570                return (results);
571            } else {
572                String[] results = new String[1];
573                results[0] = getConvertUtils().convert(value);
574                return (results);
575            }
576    
577        }
578    
579    
580        /**
581         * Return the value of the specified indexed property of the specified
582         * bean, as a String.  The zero-relative index of the
583         * required value must be included (in square brackets) as a suffix to
584         * the property name, or <code>IllegalArgumentException</code> will be
585         * thrown.
586         *
587         * @param bean Bean whose property is to be extracted
588         * @param name <code>propertyname[index]</code> of the property value
589         *  to be extracted
590         * @return The indexed property's value, converted to a String
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         *  property cannot be found
598         */
599        public String getIndexedProperty(Object bean, String name)
600                throws IllegalAccessException, InvocationTargetException,
601                NoSuchMethodException {
602    
603            Object value = getPropertyUtils().getIndexedProperty(bean, name);
604            return (getConvertUtils().convert(value));
605    
606        }
607    
608    
609        /**
610         * Return the value of the specified indexed property of the specified
611         * bean, as a String.  The index is specified as a method parameter and
612         * must *not* be included in the property name expression
613         *
614         * @param bean Bean whose property is to be extracted
615         * @param name Simple property name of the property value to be extracted
616         * @param index Index of the property value to be extracted
617         * @return The indexed property's value, converted to a String
618         *
619         * @exception IllegalAccessException if the caller does not have
620         *  access to the property accessor method
621         * @exception InvocationTargetException if the property accessor method
622         *  throws an exception
623         * @exception NoSuchMethodException if an accessor method for this
624         *  property cannot be found
625         */
626        public String getIndexedProperty(Object bean,
627                                                String name, int index)
628                throws IllegalAccessException, InvocationTargetException,
629                NoSuchMethodException {
630    
631            Object value = getPropertyUtils().getIndexedProperty(bean, name, index);
632            return (getConvertUtils().convert(value));
633    
634        }
635    
636    
637        /**
638         * Return the value of the specified indexed property of the specified
639         * bean, as a String.  The String-valued key of the required value
640         * must be included (in parentheses) as a suffix to
641         * the property name, or <code>IllegalArgumentException</code> will be
642         * thrown.
643         *
644         * @param bean Bean whose property is to be extracted
645         * @param name <code>propertyname(index)</code> of the property value
646         *  to be extracted
647         * @return The mapped property's value, converted to a String
648         *
649         * @exception IllegalAccessException if the caller does not have
650         *  access to the property accessor method
651         * @exception InvocationTargetException if the property accessor method
652         *  throws an exception
653         * @exception NoSuchMethodException if an accessor method for this
654         *  property cannot be found
655         */
656        public String getMappedProperty(Object bean, String name)
657                throws IllegalAccessException, InvocationTargetException,
658                NoSuchMethodException {
659    
660            Object value = getPropertyUtils().getMappedProperty(bean, name);
661            return (getConvertUtils().convert(value));
662    
663        }
664    
665    
666        /**
667         * Return the value of the specified mapped property of the specified
668         * bean, as a String.  The key is specified as a method parameter and
669         * must *not* be included in the property name expression
670         *
671         * @param bean Bean whose property is to be extracted
672         * @param name Simple property name of the property value to be extracted
673         * @param key Lookup key of the property value to be extracted
674         * @return The mapped property's value, converted to a String
675         *
676         * @exception IllegalAccessException if the caller does not have
677         *  access to the property accessor method
678         * @exception InvocationTargetException if the property accessor method
679         *  throws an exception
680         * @exception NoSuchMethodException if an accessor method for this
681         *  property cannot be found
682         */
683        public String getMappedProperty(Object bean,
684                                               String name, String key)
685                throws IllegalAccessException, InvocationTargetException,
686                NoSuchMethodException {
687    
688            Object value = getPropertyUtils().getMappedProperty(bean, name, key);
689            return (getConvertUtils().convert(value));
690    
691        }
692    
693    
694        /**
695         * Return the value of the (possibly nested) property of the specified
696         * name, for the specified bean, as a String.
697         *
698         * @param bean Bean whose property is to be extracted
699         * @param name Possibly nested name of the property to be extracted
700         * @return The nested property's value, converted to a String
701         *
702         * @exception IllegalAccessException if the caller does not have
703         *  access to the property accessor method
704         * @exception IllegalArgumentException if a nested reference to a
705         *  property returns null
706         * @exception InvocationTargetException if the property accessor method
707         *  throws an exception
708         * @exception NoSuchMethodException if an accessor method for this
709         *  property cannot be found
710         */
711        public String getNestedProperty(Object bean, String name)
712                throws IllegalAccessException, InvocationTargetException,
713                NoSuchMethodException {
714    
715            Object value = getPropertyUtils().getNestedProperty(bean, name);
716            return (getConvertUtils().convert(value));
717    
718        }
719    
720    
721        /**
722         * Return the value of the specified property of the specified bean,
723         * no matter which property reference format is used, as a String.
724         *
725         * @param bean Bean whose property is to be extracted
726         * @param name Possibly indexed and/or nested name of the property
727         *  to be extracted
728         * @return The property's value, converted to a String
729         *
730         * @exception IllegalAccessException if the caller does not have
731         *  access to the property accessor method
732         * @exception InvocationTargetException if the property accessor method
733         *  throws an exception
734         * @exception NoSuchMethodException if an accessor method for this
735         *  property cannot be found
736         */
737        public String getProperty(Object bean, String name)
738                throws IllegalAccessException, InvocationTargetException,
739                NoSuchMethodException {
740    
741            return (getNestedProperty(bean, name));
742    
743        }
744    
745    
746        /**
747         * Return the value of the specified simple property of the specified
748         * bean, converted to a String.
749         *
750         * @param bean Bean whose property is to be extracted
751         * @param name Name of the property to be extracted
752         * @return The property's value, converted to a String
753         *
754         * @exception IllegalAccessException if the caller does not have
755         *  access to the property accessor method
756         * @exception InvocationTargetException if the property accessor method
757         *  throws an exception
758         * @exception NoSuchMethodException if an accessor method for this
759         *  property cannot be found
760         */
761        public String getSimpleProperty(Object bean, String name)
762                throws IllegalAccessException, InvocationTargetException,
763                NoSuchMethodException {
764    
765            Object value = getPropertyUtils().getSimpleProperty(bean, name);
766            return (getConvertUtils().convert(value));
767    
768        }
769    
770    
771        /**
772         * <p>Populate the JavaBeans properties of the specified bean, based on
773         * the specified name/value pairs.  This method uses Java reflection APIs
774         * to identify corresponding "property setter" method names, and deals
775         * with setter arguments of type <code>String</code>, <code>boolean</code>,
776         * <code>int</code>, <code>long</code>, <code>float</code>, and
777         * <code>double</code>.  In addition, array setters for these types (or the
778         * corresponding primitive types) can also be identified.</p>
779         * 
780         * <p>The particular setter method to be called for each property is
781         * determined using the usual JavaBeans introspection mechanisms.  Thus,
782         * you may identify custom setter methods using a BeanInfo class that is
783         * associated with the class of the bean itself.  If no such BeanInfo
784         * class is available, the standard method name conversion ("set" plus
785         * the capitalized name of the property in question) is used.</p>
786         * 
787         * <p><strong>NOTE</strong>:  It is contrary to the JavaBeans Specification
788         * to have more than one setter method (with different argument
789         * signatures) for the same property.</p>
790         *
791         * <p><strong>WARNING</strong> - The logic of this method is customized
792         * for extracting String-based request parameters from an HTTP request.
793         * It is probably not what you want for general property copying with
794         * type conversion.  For that purpose, check out the
795         * <code>copyProperties()</code> method instead.</p>
796         *
797         * @param bean JavaBean whose properties are being populated
798         * @param properties Map keyed by property name, with the
799         *  corresponding (String or String[]) value(s) to be set
800         *
801         * @exception IllegalAccessException if the caller does not have
802         *  access to the property accessor method
803         * @exception InvocationTargetException if the property accessor method
804         *  throws an exception
805         */
806        public void populate(Object bean, Map properties)
807            throws IllegalAccessException, InvocationTargetException {
808    
809            // Do nothing unless both arguments have been specified
810            if ((bean == null) || (properties == null)) {
811                return;
812            }
813            if (log.isDebugEnabled()) {
814                log.debug("BeanUtils.populate(" + bean + ", " +
815                        properties + ")");
816            }
817    
818            // Loop through the property name/value pairs to be set
819            Iterator entries = properties.entrySet().iterator();
820            while (entries.hasNext()) {
821    
822                // Identify the property name and value(s) to be assigned
823                Map.Entry entry = (Map.Entry)entries.next();
824                String name = (String) entry.getKey();
825                if (name == null) {
826                    continue;
827                }
828    
829                // Perform the assignment for this property
830                setProperty(bean, name, entry.getValue());
831    
832            }
833    
834        }
835    
836    
837        /**
838         * <p>Set the specified property value, performing type conversions as
839         * required to conform to the type of the destination property.</p>
840         *
841         * <p>If the property is read only then the method returns 
842         * without throwing an exception.</p>
843         *
844         * <p>If <code>null</code> is passed into a property expecting a primitive value,
845         * then this will be converted as if it were a <code>null</code> string.</p>
846         *
847         * <p><strong>WARNING</strong> - The logic of this method is customized
848         * to meet the needs of <code>populate()</code>, and is probably not what
849         * you want for general property copying with type conversion.  For that
850         * purpose, check out the <code>copyProperty()</code> method instead.</p>
851         *
852         * <p><strong>WARNING</strong> - PLEASE do not modify the behavior of this
853         * method without consulting with the Struts developer community.  There
854         * are some subtleties to its functionality that are not documented in the
855         * Javadoc description above, yet are vital to the way that Struts utilizes
856         * this method.</p>
857         *
858         * @param bean Bean on which setting is to be performed
859         * @param name Property name (can be nested/indexed/mapped/combo)
860         * @param value Value to be set
861         *
862         * @exception IllegalAccessException if the caller does not have
863         *  access to the property accessor method
864         * @exception InvocationTargetException if the property accessor method
865         *  throws an exception
866         */
867        public void setProperty(Object bean, String name, Object value)
868            throws IllegalAccessException, InvocationTargetException {
869    
870            // Trace logging (if enabled)
871            if (log.isTraceEnabled()) {
872                StringBuffer sb = new StringBuffer("  setProperty(");
873                sb.append(bean);
874                sb.append(", ");
875                sb.append(name);
876                sb.append(", ");
877                if (value == null) {
878                    sb.append("<NULL>");
879                } else if (value instanceof String) {
880                    sb.append((String) value);
881                } else if (value instanceof String[]) {
882                    String[] values = (String[]) value;
883                    sb.append('[');
884                    for (int i = 0; i < values.length; i++) {
885                        if (i > 0) {
886                            sb.append(',');
887                        }
888                        sb.append(values[i]);
889                    }
890                    sb.append(']');
891                } else {
892                    sb.append(value.toString());
893                }
894                sb.append(')');
895                log.trace(sb.toString());
896            }
897    
898            // Resolve any nested expression to get the actual target bean
899            Object target = bean;
900            Resolver resolver = getPropertyUtils().getResolver();
901            while (resolver.hasNested(name)) {
902                try {
903                    target = getPropertyUtils().getProperty(target, resolver.next(name));
904                    name = resolver.remove(name);
905                } catch (NoSuchMethodException e) {
906                    return; // Skip this property setter
907                }
908            }
909            if (log.isTraceEnabled()) {
910                log.trace("    Target bean = " + target);
911                log.trace("    Target name = " + name);
912            }
913    
914            // Declare local variables we will require
915            String propName = resolver.getProperty(name); // Simple name of target property
916            Class type = null;                            // Java type of target property
917            int index  = resolver.getIndex(name);         // Indexed subscript value (if any)
918            String key = resolver.getKey(name);           // Mapped key value (if any)
919    
920            // Calculate the property type
921            if (target instanceof DynaBean) {
922                DynaClass dynaClass = ((DynaBean) target).getDynaClass();
923                DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
924                if (dynaProperty == null) {
925                    return; // Skip this property setter
926                }
927                type = dynaProperty.getType();
928            } else if (target instanceof Map) {
929                type = Object.class;
930            } else if (target != null && target.getClass().isArray() && index >= 0) {
931                type = Array.get(target, index).getClass();
932            } else {
933                PropertyDescriptor descriptor = null;
934                try {
935                    descriptor =
936                        getPropertyUtils().getPropertyDescriptor(target, name);
937                    if (descriptor == null) {
938                        return; // Skip this property setter
939                    }
940                } catch (NoSuchMethodException e) {
941                    return; // Skip this property setter
942                }
943                if (descriptor instanceof MappedPropertyDescriptor) {
944                    if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) {
945                        if (log.isDebugEnabled()) {
946                            log.debug("Skipping read-only property");
947                        }
948                        return; // Read-only, skip this property setter
949                    }
950                    type = ((MappedPropertyDescriptor) descriptor).
951                        getMappedPropertyType();
952                } else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) {
953                    if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) {
954                        if (log.isDebugEnabled()) {
955                            log.debug("Skipping read-only property");
956                        }
957                        return; // Read-only, skip this property setter
958                    }
959                    type = ((IndexedPropertyDescriptor) descriptor).
960                        getIndexedPropertyType();
961                } else if (key != null) {
962                    if (descriptor.getReadMethod() == null) {
963                        if (log.isDebugEnabled()) {
964                            log.debug("Skipping read-only property");
965                        }
966                        return; // Read-only, skip this property setter
967                    }
968                    type = (value == null) ? Object.class : value.getClass();
969                } else {
970                    if (descriptor.getWriteMethod() == null) {
971                        if (log.isDebugEnabled()) {
972                            log.debug("Skipping read-only property");
973                        }
974                        return; // Read-only, skip this property setter
975                    }
976                    type = descriptor.getPropertyType();
977                }
978            }
979    
980            // Convert the specified value to the required type
981            Object newValue = null;
982            if (type.isArray() && (index < 0)) { // Scalar value into array
983                if (value == null) {
984                    String[] values = new String[1];
985                    values[0] = null;
986                    newValue = getConvertUtils().convert(values, type);
987                } else if (value instanceof String) {
988                    newValue = getConvertUtils().convert(value, type);
989                } else if (value instanceof String[]) {
990                    newValue = getConvertUtils().convert((String[]) value, type);
991                } else {
992                    newValue = convert(value, type);
993                }
994            } else if (type.isArray()) {         // Indexed value into array
995                if (value instanceof String || value == null) {
996                    newValue = getConvertUtils().convert((String) value,
997                                                    type.getComponentType());
998                } else if (value instanceof String[]) {
999                    newValue = getConvertUtils().convert(((String[]) value)[0],
1000                                                    type.getComponentType());
1001                } else {
1002                    newValue = convert(value, type.getComponentType());
1003                }
1004            } else {                             // Value into scalar
1005                if (value instanceof String) {
1006                    newValue = getConvertUtils().convert((String) value, type);
1007                } else if (value instanceof String[]) {
1008                    newValue = getConvertUtils().convert(((String[]) value)[0],
1009                                                    type);
1010                } else {
1011                    newValue = convert(value, type);
1012                }
1013            }
1014    
1015            // Invoke the setter method
1016            try {
1017              getPropertyUtils().setProperty(target, name, newValue);
1018            } catch (NoSuchMethodException e) {
1019                throw new InvocationTargetException
1020                    (e, "Cannot set " + propName);
1021            }
1022    
1023        }
1024        
1025        /** 
1026         * Gets the <code>ConvertUtilsBean</code> instance used to perform the conversions.
1027         *
1028         * @return The ConvertUtils bean instance
1029         */
1030        public ConvertUtilsBean getConvertUtils() {
1031            return convertUtilsBean;
1032        }
1033        
1034        /**
1035         * Gets the <code>PropertyUtilsBean</code> instance used to access properties.
1036         *
1037         * @return The ConvertUtils bean instance
1038         */
1039        public PropertyUtilsBean getPropertyUtils() {
1040            return propertyUtilsBean;
1041        }
1042    
1043        /** 
1044         * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
1045         * 
1046         * @param  throwable The throwable.
1047         * @param  cause     The cause of the throwable.
1048         * @return  true if the cause was initialized, otherwise false.
1049         * @since 1.8.0
1050         */
1051        public boolean initCause(Throwable throwable, Throwable cause) {
1052            if (INIT_CAUSE_METHOD != null && cause != null) {
1053                try {
1054                    INIT_CAUSE_METHOD.invoke(throwable, new Object[] { cause });
1055                    return true;
1056                } catch (Throwable e) {
1057                    return false; // can't initialize cause
1058                }
1059            }
1060            return false;
1061        }
1062    
1063        /**
1064         * <p>Convert the value to an object of the specified class (if
1065         * possible).</p>
1066         *
1067         * @param value Value to be converted (may be null)
1068         * @param type Class of the value to be converted to
1069         * @return The converted value
1070         *
1071         * @exception ConversionException if thrown by an underlying Converter
1072         * @since 1.8.0
1073         */
1074        protected Object convert(Object value, Class type) {
1075            Converter converter = getConvertUtils().lookup(type);
1076            if (converter != null) {
1077                log.trace("        USING CONVERTER " + converter);
1078                return converter.convert(type, value);
1079            } else {
1080                return value;
1081            }
1082        }
1083    
1084        /**
1085         * Returns a <code>Method<code> allowing access to
1086         * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
1087         * or <code>null</code> if the method
1088         * does not exist.
1089         * 
1090         * @return A <code>Method<code> for <code>Throwable.initCause</code>, or
1091         * <code>null</code> if unavailable.
1092         */ 
1093        private static Method getInitCauseMethod() {
1094            try {
1095                Class[] paramsClasses = new Class[] { Throwable.class };
1096                return Throwable.class.getMethod("initCause", paramsClasses);
1097            } catch (NoSuchMethodException e) {
1098                Log log = LogFactory.getLog(BeanUtils.class);
1099                if (log.isWarnEnabled()) {
1100                    log.warn("Throwable does not have initCause() method in JDK 1.3");
1101                }
1102                return null;
1103            } catch (Throwable e) {
1104                Log log = LogFactory.getLog(BeanUtils.class);
1105                if (log.isWarnEnabled()) {
1106                    log.warn("Error getting the Throwable initCause() method", e);
1107                }
1108                return null;
1109            }
1110        }
1111    }