    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: 822308 $ $Date: 2009-10-06 16:05:02 +0100 (Tue, 06 Oct 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.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    }