View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18
    19  package org.apache.commons.beanutils;
    20
    21
    22  import java.beans.IndexedPropertyDescriptor;
    23  import java.beans.PropertyDescriptor;
    24  import java.lang.reflect.Array;
    25  import java.lang.reflect.InvocationTargetException;
    26  import java.lang.reflect.Method;
    27  import java.util.ArrayList;
    28  import java.util.Collection;
    29  import java.util.HashMap;
    30  import java.util.Iterator;
    31  import java.util.Map;
    32
    33  import org.apache.commons.beanutils.expression.Resolver;
    34  import org.apache.commons.logging.Log;
    35  import org.apache.commons.logging.LogFactory;
    36
    37
    38  /***
    39   * <p>JavaBean property population methods.</p>
    40   *
    41   * <p>This class provides implementations for the utility methods in
    42   * {@link BeanUtils}.
    43   * Different instances can be used to isolate caches between classloaders
    44   * and to vary the value converters registered.</p>
    45   *
    46   * @author Craig R. McClanahan
    47   * @author Ralph Schaer
    48   * @author Chris Audley
    49   * @author Rey Francois
    50   * @author Gregor Rayman
    51   * @version $Revision: 556229 $ $Date: 2007-07-14 07:11:19 +0100 (Sat, 14 Jul 2007) $
    52   * @see BeanUtils
    53   * @since 1.7
    54   */
    55
    56  public class BeanUtilsBean {
    57
    58
    59      // ------------------------------------------------------ Private Class Variables
    60
    61      /***
    62       * Contains <code>BeanUtilsBean</code> instances indexed by context classloader.
    63       */
    64      private static final ContextClassLoaderLocal
    65              BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal() {
    66                          // Creates the default instance used when the context classloader is unavailable
    67                          protected Object initialValue() {
    68                              return new BeanUtilsBean();
    69                          }
    70                      };
    71
    72      /***
    73       * Gets the instance which provides the functionality for {@link BeanUtils}.
    74       * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
    75       * This mechanism provides isolation for web apps deployed in the same container.
    76       *
    77       * @return The (pseudo-singleton) BeanUtils bean instance
    78       */
    79      public static BeanUtilsBean getInstance() {
    80          return (BeanUtilsBean) BEANS_BY_CLASSLOADER.get();
    81      }
    82
    83      /***
    84       * Sets the instance which provides the functionality for {@link BeanUtils}.
    85       * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
    86       * This mechanism provides isolation for web apps deployed in the same container.
    87       *
    88       * @param newInstance The (pseudo-singleton) BeanUtils bean instance
    89       */
    90      public static void setInstance(BeanUtilsBean newInstance) {
    91          BEANS_BY_CLASSLOADER.set(newInstance);
    92      }
    93
    94      // --------------------------------------------------------- Attributes
    95
    96      /***
    97       * Logging for this instance
    98       */
    99      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 names = ((Map) orig).keySet().iterator();
    266             while (names.hasNext()) {
    267                 String name = (String) names.next();
    268                 if (getPropertyUtils().isWriteable(dest, name)) {
    269                     Object value = ((Map) orig).get(name);
    270                     copyProperty(dest, name, value);
    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             for (int i = 0; i < descriptors.length; i++) {
    511                 String name = descriptors[i].getName();
    512                 if (getPropertyUtils().getReadMethod(descriptors[i]) != null) {
    513                     description.put(name, getProperty(bean, name));
    514                 }
    515             }
    516         }
    517         return (description);
    518
    519     }
    520
    521
    522     /***
    523      * Return the value of the specified array property of the specified
    524      * bean, as a String array.
    525      *
    526      * @param bean Bean whose property is to be extracted
    527      * @param name Name of the property to be extracted
    528      * @return The array property value
    529      *
    530      * @exception IllegalAccessException if the caller does not have
    531      *  access to the property accessor method
    532      * @exception InvocationTargetException if the property accessor method
    533      *  throws an exception
    534      * @exception NoSuchMethodException if an accessor method for this
    535      *  property cannot be found
    536      */
    537     public String[] getArrayProperty(Object bean, String name)
    538             throws IllegalAccessException, InvocationTargetException,
    539             NoSuchMethodException {
    540
    541         Object value = getPropertyUtils().getProperty(bean, name);
    542         if (value == null) {
    543             return (null);
    544         } else if (value instanceof Collection) {
    545             ArrayList values = new ArrayList();
    546             Iterator items = ((Collection) value).iterator();
    547             while (items.hasNext()) {
    548                 Object item = items.next();
    549                 if (item == null) {
    550                     values.add((String) null);
    551                 } else {
    552                     // convert to string using convert utils
    553                     values.add(getConvertUtils().convert(item));
    554                 }
    555             }
    556             return ((String[]) values.toArray(new String[values.size()]));
    557         } else if (value.getClass().isArray()) {
    558             int n = Array.getLength(value);
    559             String[] results = new String[n];
    560             for (int i = 0; i < n; i++) {
    561                 Object item = Array.get(value, i);
    562                 if (item == null) {
    563                     results[i] = null;
    564                 } else {
    565                     // convert to string using convert utils
    566                     results[i] = getConvertUtils().convert(item);
    567                 }
    568             }
    569             return (results);
    570         } else {
    571             String[] results = new String[1];
    572             results[0] = getConvertUtils().convert(value);
    573             return (results);
    574         }
    575
    576     }
    577
    578
    579     /***
    580      * Return the value of the specified indexed property of the specified
    581      * bean, as a String.  The zero-relative index of the
    582      * required value must be included (in square brackets) as a suffix to
    583      * the property name, or <code>IllegalArgumentException</code> will be
    584      * thrown.
    585      *
    586      * @param bean Bean whose property is to be extracted
    587      * @param name <code>propertyname[index]</code> of the property value
    588      *  to be extracted
    589      * @return The indexed property's value, converted to a String
    590      *
    591      * @exception IllegalAccessException if the caller does not have
    592      *  access to the property accessor method
    593      * @exception InvocationTargetException if the property accessor method
    594      *  throws an exception
    595      * @exception NoSuchMethodException if an accessor method for this
    596      *  property cannot be found
    597      */
    598     public String getIndexedProperty(Object bean, String name)
    599             throws IllegalAccessException, InvocationTargetException,
    600             NoSuchMethodException {
    601
    602         Object value = getPropertyUtils().getIndexedProperty(bean, name);
    603         return (getConvertUtils().convert(value));
    604
    605     }
    606
    607
    608     /***
    609      * Return the value of the specified indexed property of the specified
    610      * bean, as a String.  The index is specified as a method parameter and
    611      * must *not* be included in the property name expression
    612      *
    613      * @param bean Bean whose property is to be extracted
    614      * @param name Simple property name of the property value to be extracted
    615      * @param index Index of the property value to be extracted
    616      * @return The indexed property's value, converted to a String
    617      *
    618      * @exception IllegalAccessException if the caller does not have
    619      *  access to the property accessor method
    620      * @exception InvocationTargetException if the property accessor method
    621      *  throws an exception
    622      * @exception NoSuchMethodException if an accessor method for this
    623      *  property cannot be found
    624      */
    625     public String getIndexedProperty(Object bean,
    626                                             String name, int index)
    627             throws IllegalAccessException, InvocationTargetException,
    628             NoSuchMethodException {
    629
    630         Object value = getPropertyUtils().getIndexedProperty(bean, name, index);
    631         return (getConvertUtils().convert(value));
    632
    633     }
    634
    635
    636     /***
    637      * Return the value of the specified indexed property of the specified
    638      * bean, as a String.  The String-valued key of the required value
    639      * must be included (in parentheses) as a suffix to
    640      * the property name, or <code>IllegalArgumentException</code> will be
    641      * thrown.
    642      *
    643      * @param bean Bean whose property is to be extracted
    644      * @param name <code>propertyname(index)</code> of the property value
    645      *  to be extracted
    646      * @return The mapped property's value, converted to a String
    647      *
    648      * @exception IllegalAccessException if the caller does not have
    649      *  access to the property accessor method
    650      * @exception InvocationTargetException if the property accessor method
    651      *  throws an exception
    652      * @exception NoSuchMethodException if an accessor method for this
    653      *  property cannot be found
    654      */
    655     public String getMappedProperty(Object bean, String name)
    656             throws IllegalAccessException, InvocationTargetException,
    657             NoSuchMethodException {
    658
    659         Object value = getPropertyUtils().getMappedProperty(bean, name);
    660         return (getConvertUtils().convert(value));
    661
    662     }
    663
    664
    665     /***
    666      * Return the value of the specified mapped property of the specified
    667      * bean, as a String.  The key is specified as a method parameter and
    668      * must *not* be included in the property name expression
    669      *
    670      * @param bean Bean whose property is to be extracted
    671      * @param name Simple property name of the property value to be extracted
    672      * @param key Lookup key of the property value to be extracted
    673      * @return The mapped property's value, converted to a String
    674      *
    675      * @exception IllegalAccessException if the caller does not have
    676      *  access to the property accessor method
    677      * @exception InvocationTargetException if the property accessor method
    678      *  throws an exception
    679      * @exception NoSuchMethodException if an accessor method for this
    680      *  property cannot be found
    681      */
    682     public String getMappedProperty(Object bean,
    683                                            String name, String key)
    684             throws IllegalAccessException, InvocationTargetException,
    685             NoSuchMethodException {
    686
    687         Object value = getPropertyUtils().getMappedProperty(bean, name, key);
    688         return (getConvertUtils().convert(value));
    689
    690     }
    691
    692
    693     /***
    694      * Return the value of the (possibly nested) property of the specified
    695      * name, for the specified bean, as a String.
    696      *
    697      * @param bean Bean whose property is to be extracted
    698      * @param name Possibly nested name of the property to be extracted
    699      * @return The nested property's value, converted to a String
    700      *
    701      * @exception IllegalAccessException if the caller does not have
    702      *  access to the property accessor method
    703      * @exception IllegalArgumentException if a nested reference to a
    704      *  property returns null
    705      * @exception InvocationTargetException if the property accessor method
    706      *  throws an exception
    707      * @exception NoSuchMethodException if an accessor method for this
    708      *  property cannot be found
    709      */
    710     public String getNestedProperty(Object bean, String name)
    711             throws IllegalAccessException, InvocationTargetException,
    712             NoSuchMethodException {
    713
    714         Object value = getPropertyUtils().getNestedProperty(bean, name);
    715         return (getConvertUtils().convert(value));
    716
    717     }
    718
    719
    720     /***
    721      * Return the value of the specified property of the specified bean,
    722      * no matter which property reference format is used, as a String.
    723      *
    724      * @param bean Bean whose property is to be extracted
    725      * @param name Possibly indexed and/or nested name of the property
    726      *  to be extracted
    727      * @return The property's value, converted to a String
    728      *
    729      * @exception IllegalAccessException if the caller does not have
    730      *  access to the property accessor method
    731      * @exception InvocationTargetException if the property accessor method
    732      *  throws an exception
    733      * @exception NoSuchMethodException if an accessor method for this
    734      *  property cannot be found
    735      */
    736     public String getProperty(Object bean, String name)
    737             throws IllegalAccessException, InvocationTargetException,
    738             NoSuchMethodException {
    739
    740         return (getNestedProperty(bean, name));
    741
    742     }
    743
    744
    745     /***
    746      * Return the value of the specified simple property of the specified
    747      * bean, converted to a String.
    748      *
    749      * @param bean Bean whose property is to be extracted
    750      * @param name Name of the property to be extracted
    751      * @return The property's value, converted to a String
    752      *
    753      * @exception IllegalAccessException if the caller does not have
    754      *  access to the property accessor method
    755      * @exception InvocationTargetException if the property accessor method
    756      *  throws an exception
    757      * @exception NoSuchMethodException if an accessor method for this
    758      *  property cannot be found
    759      */
    760     public String getSimpleProperty(Object bean, String name)
    761             throws IllegalAccessException, InvocationTargetException,
    762             NoSuchMethodException {
    763
    764         Object value = getPropertyUtils().getSimpleProperty(bean, name);
    765         return (getConvertUtils().convert(value));
    766
    767     }
    768
    769
    770     /***
    771      * <p>Populate the JavaBeans properties of the specified bean, based on
    772      * the specified name/value pairs.  This method uses Java reflection APIs
    773      * to identify corresponding "property setter" method names, and deals
    774      * with setter arguments of type <code>String</code>, <code>boolean</code>,
    775      * <code>int</code>, <code>long</code>, <code>float</code>, and
    776      * <code>double</code>.  In addition, array setters for these types (or the
    777      * corresponding primitive types) can also be identified.</p>
    778      *
    779      * <p>The particular setter method to be called for each property is
    780      * determined using the usual JavaBeans introspection mechanisms.  Thus,
    781      * you may identify custom setter methods using a BeanInfo class that is
    782      * associated with the class of the bean itself.  If no such BeanInfo
    783      * class is available, the standard method name conversion ("set" plus
    784      * the capitalized name of the property in question) is used.</p>
    785      *
    786      * <p><strong>NOTE</strong>:  It is contrary to the JavaBeans Specification
    787      * to have more than one setter method (with different argument
    788      * signatures) for the same property.</p>
    789      *
    790      * <p><strong>WARNING</strong> - The logic of this method is customized
    791      * for extracting String-based request parameters from an HTTP request.
    792      * It is probably not what you want for general property copying with
    793      * type conversion.  For that purpose, check out the
    794      * <code>copyProperties()</code> method instead.</p>
    795      *
    796      * @param bean JavaBean whose properties are being populated
    797      * @param properties Map keyed by property name, with the
    798      *  corresponding (String or String[]) value(s) to be set
    799      *
    800      * @exception IllegalAccessException if the caller does not have
    801      *  access to the property accessor method
    802      * @exception InvocationTargetException if the property accessor method
    803      *  throws an exception
    804      */
    805     public void populate(Object bean, Map properties)
    806         throws IllegalAccessException, InvocationTargetException {
    807
    808         // Do nothing unless both arguments have been specified
    809         if ((bean == null) || (properties == null)) {
    810             return;
    811         }
    812         if (log.isDebugEnabled()) {
    813             log.debug("BeanUtils.populate(" + bean + ", " +
    814                     properties + ")");
    815         }
    816
    817         // Loop through the property name/value pairs to be set
    818         Iterator names = properties.keySet().iterator();
    819         while (names.hasNext()) {
    820
    821             // Identify the property name and value(s) to be assigned
    822             String name = (String) names.next();
    823             if (name == null) {
    824                 continue;
    825             }
    826             Object value = properties.get(name);
    827
    828             // Perform the assignment for this property
    829             setProperty(bean, name, value);
    830
    831         }
    832
    833     }
    834
    835
    836     /***
    837      * <p>Set the specified property value, performing type conversions as
    838      * required to conform to the type of the destination property.</p>
    839      *
    840      * <p>If the property is read only then the method returns
    841      * without throwing an exception.</p>
    842      *
    843      * <p>If <code>null</code> is passed into a property expecting a primitive value,
    844      * then this will be converted as if it were a <code>null</code> string.</p>
    845      *
    846      * <p><strong>WARNING</strong> - The logic of this method is customized
    847      * to meet the needs of <code>populate()</code>, and is probably not what
    848      * you want for general property copying with type conversion.  For that
    849      * purpose, check out the <code>copyProperty()</code> method instead.</p>
    850      *
    851      * <p><strong>WARNING</strong> - PLEASE do not modify the behavior of this
    852      * method without consulting with the Struts developer community.  There
    853      * are some subtleties to its functionality that are not documented in the
    854      * Javadoc description above, yet are vital to the way that Struts utilizes
    855      * this method.</p>
    856      *
    857      * @param bean Bean on which setting is to be performed
    858      * @param name Property name (can be nested/indexed/mapped/combo)
    859      * @param value Value to be set
    860      *
    861      * @exception IllegalAccessException if the caller does not have
    862      *  access to the property accessor method
    863      * @exception InvocationTargetException if the property accessor method
    864      *  throws an exception
    865      */
    866     public void setProperty(Object bean, String name, Object value)
    867         throws IllegalAccessException, InvocationTargetException {
    868
    869         // Trace logging (if enabled)
    870         if (log.isTraceEnabled()) {
    871             StringBuffer sb = new StringBuffer("  setProperty(");
    872             sb.append(bean);
    873             sb.append(", ");
    874             sb.append(name);
    875             sb.append(", ");
    876             if (value == null) {
    877                 sb.append("<NULL>");
    878             } else if (value instanceof String) {
    879                 sb.append((String) value);
    880             } else if (value instanceof String[]) {
    881                 String[] values = (String[]) value;
    882                 sb.append('[');
    883                 for (int i = 0; i < values.length; i++) {
    884                     if (i > 0) {
    885                         sb.append(',');
    886                     }
    887                     sb.append(values[i]);
    888                 }
    889                 sb.append(']');
    890             } else {
    891                 sb.append(value.toString());
    892             }
    893             sb.append(')');
    894             log.trace(sb.toString());
    895         }
    896
    897         // Resolve any nested expression to get the actual target bean
    898         Object target = bean;
    899         Resolver resolver = getPropertyUtils().getResolver();
    900         while (resolver.hasNested(name)) {
    901             try {
    902                 target = getPropertyUtils().getProperty(target, resolver.next(name));
    903                 name = resolver.remove(name);
    904             } catch (NoSuchMethodException e) {
    905                 return; // Skip this property setter
    906             }
    907         }
    908         if (log.isTraceEnabled()) {
    909             log.trace("    Target bean = " + target);
    910             log.trace("    Target name = " + name);
    911         }
    912
    913         // Declare local variables we will require
    914         String propName = resolver.getProperty(name); // Simple name of target property
    915         Class type = null;                            // Java type of target property
    916         int index  = resolver.getIndex(name);         // Indexed subscript value (if any)
    917         String key = resolver.getKey(name);           // Mapped key value (if any)
    918
    919         // Calculate the property type
    920         if (target instanceof DynaBean) {
    921             DynaClass dynaClass = ((DynaBean) target).getDynaClass();
    922             DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
    923             if (dynaProperty == null) {
    924                 return; // Skip this property setter
    925             }
    926             type = dynaProperty.getType();
    927         } else {
    928             PropertyDescriptor descriptor = null;
    929             try {
    930                 descriptor =
    931                     getPropertyUtils().getPropertyDescriptor(target, name);
    932                 if (descriptor == null) {
    933                     return; // Skip this property setter
    934                 }
    935             } catch (NoSuchMethodException e) {
    936                 return; // Skip this property setter
    937             }
    938             if (descriptor instanceof MappedPropertyDescriptor) {
    939                 if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) {
    940                     if (log.isDebugEnabled()) {
    941                         log.debug("Skipping read-only property");
    942                     }
    943                     return; // Read-only, skip this property setter
    944                 }
    945                 type = ((MappedPropertyDescriptor) descriptor).
    946                     getMappedPropertyType();
    947             } else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) {
    948                 if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) {
    949                     if (log.isDebugEnabled()) {
    950                         log.debug("Skipping read-only property");
    951                     }
    952                     return; // Read-only, skip this property setter
    953                 }
    954                 type = ((IndexedPropertyDescriptor) descriptor).
    955                     getIndexedPropertyType();
    956             } else if (key != null) {
    957                 if (descriptor.getReadMethod() == null) {
    958                     if (log.isDebugEnabled()) {
    959                         log.debug("Skipping read-only property");
    960                     }
    961                     return; // Read-only, skip this property setter
    962                 }
    963                 type = (value == null) ? Object.class : value.getClass();
    964             } else {
    965                 if (descriptor.getWriteMethod() == null) {
    966                     if (log.isDebugEnabled()) {
    967                         log.debug("Skipping read-only property");
    968                     }
    969                     return; // Read-only, skip this property setter
    970                 }
    971                 type = descriptor.getPropertyType();
    972             }
    973         }
    974
    975         // Convert the specified value to the required type
    976         Object newValue = null;
    977         if (type.isArray() && (index < 0)) { // Scalar value into array
    978             if (value == null) {
    979                 String[] values = new String[1];
    980                 values[0] = (String) value;
    981                 newValue = getConvertUtils().convert((String[]) values, type);
    982             } else if (value instanceof String) {
    983                 newValue = getConvertUtils().convert(value, type);
    984             } else if (value instanceof String[]) {
    985                 newValue = getConvertUtils().convert((String[]) value, type);
    986             } else {
    987                 newValue = convert(value, type);
    988             }
    989         } else if (type.isArray()) {         // Indexed value into array
    990             if (value instanceof String || value == null) {
    991                 newValue = getConvertUtils().convert((String) value,
    992                                                 type.getComponentType());
    993             } else if (value instanceof String[]) {
    994                 newValue = getConvertUtils().convert(((String[]) value)[0],
    995                                                 type.getComponentType());
    996             } else {
    997                 newValue = convert(value, type.getComponentType());
    998             }
    999         } else {                             // Value into scalar
    1000             if ((value instanceof String) || (value == null)) {
    1001                 newValue = getConvertUtils().convert((String) value, type);
    1002             } else if (value instanceof String[]) {
    1003                 newValue = getConvertUtils().convert(((String[]) value)[0],
    1004                                                 type);
    1005             } else {
    1006                 newValue = convert(value, type);
    1007             }
    1008         }
    1009
    1010         // Invoke the setter method
    1011         try {
    1012             if (index >= 0) {
    1013                 getPropertyUtils().setIndexedProperty(target, propName,
    1014                                                  index, newValue);
    1015             } else if (key != null) {
    1016                 getPropertyUtils().setMappedProperty(target, propName,
    1017                                                 key, newValue);
    1018             } else {
    1019                 getPropertyUtils().setProperty(target, propName, newValue);
    1020             }
    1021         } catch (NoSuchMethodException e) {
    1022             throw new InvocationTargetException
    1023                 (e, "Cannot set " + propName);
    1024         }
    1025
    1026     }
    1027
    1028     /***
    1029      * Gets the <code>ConvertUtilsBean</code> instance used to perform the conversions.
    1030      *
    1031      * @return The ConvertUtils bean instance
    1032      */
    1033     public ConvertUtilsBean getConvertUtils() {
    1034         return convertUtilsBean;
    1035     }
    1036
    1037     /***
    1038      * Gets the <code>PropertyUtilsBean</code> instance used to access properties.
    1039      *
    1040      * @return The ConvertUtils bean instance
    1041      */
    1042     public PropertyUtilsBean getPropertyUtils() {
    1043         return propertyUtilsBean;
    1044     }
    1045
    1046     /***
    1047      * If we're running on JDK 1.4 or later, initialize the cause for the given throwable.
    1048      *
    1049      * @param  throwable The throwable.
    1050      * @param  cause     The cause of the throwable.
    1051      * @return  true if the cause was initialized, otherwise false.
    1052      */
    1053     public boolean initCause(Throwable throwable, Throwable cause) {
    1054         if (INIT_CAUSE_METHOD != null && cause != null) {
    1055             try {
    1056                 INIT_CAUSE_METHOD.invoke(throwable, new Object[] { cause });
    1057                 return true;
    1058             } catch (Throwable e) {
    1059                 return false; // can't initialize cause
    1060             }
    1061         }
    1062         return false;
    1063     }
    1064
    1065     /***
    1066      * <p>Convert the value to an object of the specified class (if
    1067      * possible).</p>
    1068      *
    1069      * @param value Value to be converted (may be null)
    1070      * @param type Class of the value to be converted to
    1071      * @return The converted value
    1072      *
    1073      * @exception ConversionException if thrown by an underlying Converter
    1074      */
    1075     protected Object convert(Object value, Class type) {
    1076         Converter converter = getConvertUtils().lookup(type);
    1077         if (converter != null) {
    1078             log.trace("        USING CONVERTER " + converter);
    1079             return converter.convert(type, value);
    1080         } else {
    1081             return value;
    1082         }
    1083     }
    1084
    1085     /***
    1086      * Returns a <code>Method<code> allowing access to
    1087      * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
    1088      * or <code>null</code> if the method
    1089      * does not exist.
    1090      *
    1091      * @return A <code>Method<code> for <code>Throwable.initCause</code>, or
    1092      * <code>null</code> if unavailable.
    1093      */
    1094     private static Method getInitCauseMethod() {
    1095         try {
    1096             Class[] paramsClasses = new Class[] { Throwable.class };
    1097             return Throwable.class.getMethod("initCause", paramsClasses);
    1098         } catch (NoSuchMethodException e) {
    1099             Log log = LogFactory.getLog(BeanUtils.class);
    1100             if (log.isWarnEnabled()) {
    1101                 log.warn("Throwable does not have initCause() method in JDK 1.3");
    1102             }
    1103             return null;
    1104         } catch (Throwable e) {
    1105             Log log = LogFactory.getLog(BeanUtils.class);
    1106             if (log.isWarnEnabled()) {
    1107                 log.warn("Error getting the Throwable initCause() method", e);
    1108             }
    1109             return null;
    1110         }
    1111     }
    1112 }