Coverage Report - org.apache.commons.beanutils.BeanUtilsBean
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanUtilsBean
62%
205/328
62%
117/188
6.4
BeanUtilsBean$1
100%
2/2
N/A
6.4
 
 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: 834031 $ $Date: 2009-11-09 07:26:52 -0500 (Mon, 09 Nov 2009) $
 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  1
             BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal() {
 66  
                         // Creates the default instance used when the context classloader is unavailable
 67  
                         protected Object initialValue() {
 68  4
                             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  1946
         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  128
         BEANS_BY_CLASSLOADER.set(newInstance);
 92  128
     }
 93  
 
 94  
     // --------------------------------------------------------- Attributes
 95  
 
 96  
     /**
 97  
      * Logging for this instance
 98  
      */
 99  192
     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  1
     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  131
         this(new ConvertUtilsBean(), new PropertyUtilsBean());
 118  131
     }
 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  55
         this(convertUtilsBean, new PropertyUtilsBean());
 131  55
     }
 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  192
                             PropertyUtilsBean propertyUtilsBean) {
 144  
                             
 145  192
         this.convertUtilsBean = convertUtilsBean;
 146  192
         this.propertyUtilsBean = propertyUtilsBean;
 147  192
     }
 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  1
         if (log.isDebugEnabled()) {
 178  0
             log.debug("Cloning bean: " + bean.getClass().getName());
 179  
         }
 180  1
         Object newBean = null;
 181  1
         if (bean instanceof DynaBean) {
 182  1
             newBean = ((DynaBean) bean).getDynaClass().newInstance();
 183  
         } else {
 184  0
             newBean = bean.getClass().newInstance();
 185  
         }
 186  1
         getPropertyUtils().copyProperties(newBean, bean);
 187  1
         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  11
         if (dest == null) {
 239  0
             throw new IllegalArgumentException
 240  
                     ("No destination bean specified");
 241  
         }
 242  11
         if (orig == null) {
 243  0
             throw new IllegalArgumentException("No origin bean specified");
 244  
         }
 245  11
         if (log.isDebugEnabled()) {
 246  0
             log.debug("BeanUtils.copyProperties(" + dest + ", " +
 247  
                       orig + ")");
 248  
         }
 249  
 
 250  
         // Copy the properties, converting as necessary
 251  11
         if (orig instanceof DynaBean) {
 252  4
             DynaProperty[] origDescriptors =
 253  
                 ((DynaBean) orig).getDynaClass().getDynaProperties();
 254  71
             for (int i = 0; i < origDescriptors.length; i++) {
 255  67
                 String name = origDescriptors[i].getName();
 256  
                 // Need to check isReadable() for WrapDynaBean
 257  
                 // (see Jira issue# BEANUTILS-61)
 258  67
                 if (getPropertyUtils().isReadable(orig, name) &&
 259  
                     getPropertyUtils().isWriteable(dest, name)) {
 260  56
                     Object value = ((DynaBean) orig).get(name);
 261  56
                     copyProperty(dest, name, value);
 262  
                 }
 263  
             }
 264  4
         } else if (orig instanceof Map) {
 265  4
             Iterator entries = ((Map) orig).entrySet().iterator();
 266  35
             while (entries.hasNext()) {
 267  31
                 Map.Entry entry = (Map.Entry) entries.next();
 268  31
                 String name = (String)entry.getKey();
 269  31
                 if (getPropertyUtils().isWriteable(dest, name)) {
 270  30
                     copyProperty(dest, name, entry.getValue());
 271  
                 }
 272  31
             }
 273  4
         } else /* if (orig is a standard JavaBean) */ {
 274  3
             PropertyDescriptor[] origDescriptors =
 275  
                 getPropertyUtils().getPropertyDescriptors(orig);
 276  90
             for (int i = 0; i < origDescriptors.length; i++) {
 277  87
                 String name = origDescriptors[i].getName();
 278  87
                 if ("class".equals(name)) {
 279  3
                     continue; // No point in trying to set an object's class
 280  
                 }
 281  84
                 if (getPropertyUtils().isReadable(orig, name) &&
 282  
                     getPropertyUtils().isWriteable(dest, name)) {
 283  
                     try {
 284  58
                         Object value =
 285  
                             getPropertyUtils().getSimpleProperty(orig, name);
 286  52
                         copyProperty(dest, name, value);
 287  6
                     } catch (NoSuchMethodException e) {
 288  
                         // Should not happen
 289  52
                     }
 290  
                 }
 291  
             }
 292  
         }
 293  
 
 294  11
     }
 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  254
         if (log.isTraceEnabled()) {
 333  0
             StringBuffer sb = new StringBuffer("  copyProperty(");
 334  0
             sb.append(bean);
 335  0
             sb.append(", ");
 336  0
             sb.append(name);
 337  0
             sb.append(", ");
 338  0
             if (value == null) {
 339  0
                 sb.append("<NULL>");
 340  0
             } else if (value instanceof String) {
 341  0
                 sb.append((String) value);
 342  0
             } else if (value instanceof String[]) {
 343  0
                 String[] values = (String[]) value;
 344  0
                 sb.append('[');
 345  0
                 for (int i = 0; i < values.length; i++) {
 346  0
                     if (i > 0) {
 347  0
                         sb.append(',');
 348  
                     }
 349  0
                     sb.append(values[i]);
 350  
                 }
 351  0
                 sb.append(']');
 352  0
             } else {
 353  0
                 sb.append(value.toString());
 354  
             }
 355  0
             sb.append(')');
 356  0
             log.trace(sb.toString());
 357  
         }
 358  
 
 359  
         // Resolve any nested expression to get the actual target bean
 360  254
         Object target = bean;
 361  254
         Resolver resolver = getPropertyUtils().getResolver();
 362  281
         while (resolver.hasNested(name)) {
 363  
             try {
 364  27
                 target = getPropertyUtils().getProperty(target, resolver.next(name));
 365  27
                 name = resolver.remove(name);
 366  0
             } catch (NoSuchMethodException e) {
 367  0
                 return; // Skip this property setter
 368  27
             }
 369  
         }
 370  254
         if (log.isTraceEnabled()) {
 371  0
             log.trace("    Target bean = " + target);
 372  0
             log.trace("    Target name = " + name);
 373  
         }
 374  
 
 375  
         // Declare local variables we will require
 376  254
         String propName = resolver.getProperty(name); // Simple name of target property
 377  254
         Class type = null;                            // Java type of target property
 378  254
         int index  = resolver.getIndex(name);         // Indexed subscript value (if any)
 379  254
         String key = resolver.getKey(name);           // Mapped key value (if any)
 380  
 
 381  
         // Calculate the target property type
 382  254
         if (target instanceof DynaBean) {
 383  47
             DynaClass dynaClass = ((DynaBean) target).getDynaClass();
 384  47
             DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
 385  47
             if (dynaProperty == null) {
 386  0
                 return; // Skip this property setter
 387  
             }
 388  47
             type = dynaProperty.getType();
 389  47
         } else {
 390  207
             PropertyDescriptor descriptor = null;
 391  
             try {
 392  207
                 descriptor =
 393  
                     getPropertyUtils().getPropertyDescriptor(target, name);
 394  207
                 if (descriptor == null) {
 395  0
                     return; // Skip this property setter
 396  
                 }
 397  0
             } catch (NoSuchMethodException e) {
 398  0
                 return; // Skip this property setter
 399  207
             }
 400  207
             type = descriptor.getPropertyType();
 401  207
             if (type == null) {
 402  
                 // Most likely an indexed setter on a POJB only
 403  8
                 if (log.isTraceEnabled()) {
 404  0
                     log.trace("    target type for property '" +
 405  
                               propName + "' is null, so skipping ths setter");
 406  
                 }
 407  8
                 return;
 408  
             }
 409  
         }
 410  246
         if (log.isTraceEnabled()) {
 411  0
             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  246
         if (index >= 0) {                    // Destination must be indexed
 417  14
             value = convert(value, type.getComponentType());
 418  
             try {
 419  14
                 getPropertyUtils().setIndexedProperty(target, propName,
 420  
                                                  index, value);
 421  0
             } catch (NoSuchMethodException e) {
 422  0
                 throw new InvocationTargetException
 423  
                     (e, "Cannot set " + propName);
 424  14
             }
 425  232
         } 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  3
                 getPropertyUtils().setMappedProperty(target, propName,
 431  
                                                 key, value);
 432  0
             } catch (NoSuchMethodException e) {
 433  0
                 throw new InvocationTargetException
 434  
                     (e, "Cannot set " + propName);
 435  3
             }
 436  
         } else {                             // Destination must be simple
 437  229
             value = convert(value, type);
 438  
             try {
 439  229
                 getPropertyUtils().setSimpleProperty(target, propName, value);
 440  0
             } catch (NoSuchMethodException e) {
 441  0
                 throw new InvocationTargetException
 442  
                     (e, "Cannot set " + propName);
 443  229
             }
 444  
         }
 445  
 
 446  246
     }
 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  5
         if (bean == null) {
 491  
         //            return (Collections.EMPTY_MAP);
 492  0
             return (new java.util.HashMap());
 493  
         }
 494  
         
 495  5
         if (log.isDebugEnabled()) {
 496  0
             log.debug("Describing bean: " + bean.getClass().getName());
 497  
         }
 498  
             
 499  5
         Map description = new HashMap();
 500  5
         if (bean instanceof DynaBean) {
 501  0
             DynaProperty[] descriptors =
 502  
                 ((DynaBean) bean).getDynaClass().getDynaProperties();
 503  0
             for (int i = 0; i < descriptors.length; i++) {
 504  0
                 String name = descriptors[i].getName();
 505  0
                 description.put(name, getProperty(bean, name));
 506  
             }
 507  0
         } else {
 508  5
             PropertyDescriptor[] descriptors =
 509  
                 getPropertyUtils().getPropertyDescriptors(bean);
 510  5
             Class clazz = bean.getClass();
 511  71
             for (int i = 0; i < descriptors.length; i++) {
 512  66
                 String name = descriptors[i].getName();
 513  66
                 if (getPropertyUtils().getReadMethod(clazz, descriptors[i]) != null) {
 514  56
                     description.put(name, getProperty(bean, name));
 515  
                 }
 516  
             }
 517  
         }
 518  5
         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  14
         Object value = getPropertyUtils().getProperty(bean, name);
 543  14
         if (value == null) {
 544  0
             return (null);
 545  14
         } else if (value instanceof Collection) {
 546  0
             ArrayList values = new ArrayList();
 547  0
             Iterator items = ((Collection) value).iterator();
 548  0
             while (items.hasNext()) {
 549  0
                 Object item = items.next();
 550  0
                 if (item == null) {
 551  0
                     values.add((String) null);
 552  
                 } else {
 553  
                     // convert to string using convert utils
 554  0
                     values.add(getConvertUtils().convert(item));
 555  
                 }
 556  0
             }
 557  0
             return ((String[]) values.toArray(new String[values.size()]));
 558  14
         } else if (value.getClass().isArray()) {
 559  10
             int n = Array.getLength(value);
 560  10
             String[] results = new String[n];
 561  52
             for (int i = 0; i < n; i++) {
 562  42
                 Object item = Array.get(value, i);
 563  42
                 if (item == null) {
 564  0
                     results[i] = null;
 565  
                 } else {
 566  
                     // convert to string using convert utils
 567  42
                     results[i] = getConvertUtils().convert(item);
 568  
                 }
 569  
             }
 570  10
             return (results);
 571  
         } else {
 572  4
             String[] results = new String[1];
 573  4
             results[0] = getConvertUtils().convert(value);
 574  4
             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  8
         Object value = getPropertyUtils().getIndexedProperty(bean, name);
 604  8
         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  8
         Object value = getPropertyUtils().getIndexedProperty(bean, name, index);
 632  8
         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  0
         Object value = getPropertyUtils().getMappedProperty(bean, name);
 661  0
         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  0
         Object value = getPropertyUtils().getMappedProperty(bean, name, key);
 689  0
         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  72
         Object value = getPropertyUtils().getNestedProperty(bean, name);
 716  71
         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  69
         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  5
         Object value = getPropertyUtils().getSimpleProperty(bean, name);
 766  5
         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  19
         if ((bean == null) || (properties == null)) {
 811  0
             return;
 812  
         }
 813  19
         if (log.isDebugEnabled()) {
 814  0
             log.debug("BeanUtils.populate(" + bean + ", " +
 815  
                     properties + ")");
 816  
         }
 817  
 
 818  
         // Loop through the property name/value pairs to be set
 819  19
         Iterator entries = properties.entrySet().iterator();
 820  84
         while (entries.hasNext()) {
 821  
 
 822  
             // Identify the property name and value(s) to be assigned
 823  65
             Map.Entry entry = (Map.Entry)entries.next();
 824  65
             String name = (String) entry.getKey();
 825  65
             if (name == null) {
 826  0
                 continue;
 827  
             }
 828  
 
 829  
             // Perform the assignment for this property
 830  65
             setProperty(bean, name, entry.getValue());
 831  
 
 832  65
         }
 833  
 
 834  19
     }
 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  215
         if (log.isTraceEnabled()) {
 872  0
             StringBuffer sb = new StringBuffer("  setProperty(");
 873  0
             sb.append(bean);
 874  0
             sb.append(", ");
 875  0
             sb.append(name);
 876  0
             sb.append(", ");
 877  0
             if (value == null) {
 878  0
                 sb.append("<NULL>");
 879  0
             } else if (value instanceof String) {
 880  0
                 sb.append((String) value);
 881  0
             } else if (value instanceof String[]) {
 882  0
                 String[] values = (String[]) value;
 883  0
                 sb.append('[');
 884  0
                 for (int i = 0; i < values.length; i++) {
 885  0
                     if (i > 0) {
 886  0
                         sb.append(',');
 887  
                     }
 888  0
                     sb.append(values[i]);
 889  
                 }
 890  0
                 sb.append(']');
 891  0
             } else {
 892  0
                 sb.append(value.toString());
 893  
             }
 894  0
             sb.append(')');
 895  0
             log.trace(sb.toString());
 896  
         }
 897  
 
 898  
         // Resolve any nested expression to get the actual target bean
 899  215
         Object target = bean;
 900  215
         Resolver resolver = getPropertyUtils().getResolver();
 901  234
         while (resolver.hasNested(name)) {
 902  
             try {
 903  19
                 target = getPropertyUtils().getProperty(target, resolver.next(name));
 904  19
                 name = resolver.remove(name);
 905  0
             } catch (NoSuchMethodException e) {
 906  0
                 return; // Skip this property setter
 907  19
             }
 908  
         }
 909  215
         if (log.isTraceEnabled()) {
 910  0
             log.trace("    Target bean = " + target);
 911  0
             log.trace("    Target name = " + name);
 912  
         }
 913  
 
 914  
         // Declare local variables we will require
 915  215
         String propName = resolver.getProperty(name); // Simple name of target property
 916  215
         Class type = null;                            // Java type of target property
 917  215
         int index  = resolver.getIndex(name);         // Indexed subscript value (if any)
 918  215
         String key = resolver.getKey(name);           // Mapped key value (if any)
 919  
 
 920  
         // Calculate the property type
 921  215
         if (target instanceof DynaBean) {
 922  48
             DynaClass dynaClass = ((DynaBean) target).getDynaClass();
 923  48
             DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
 924  48
             if (dynaProperty == null) {
 925  0
                 return; // Skip this property setter
 926  
             }
 927  48
             type = dynaProperty.getType();
 928  48
         } else if (target instanceof Map) {
 929  2
             type = Object.class;
 930  165
         } else if (target != null && target.getClass().isArray() && index >= 0) {
 931  2
             type = Array.get(target, index).getClass();
 932  
         } else {
 933  163
             PropertyDescriptor descriptor = null;
 934  
             try {
 935  163
                 descriptor =
 936  
                     getPropertyUtils().getPropertyDescriptor(target, name);
 937  162
                 if (descriptor == null) {
 938  0
                     return; // Skip this property setter
 939  
                 }
 940  0
             } catch (NoSuchMethodException e) {
 941  0
                 return; // Skip this property setter
 942  162
             }
 943  162
             if (descriptor instanceof MappedPropertyDescriptor) {
 944  6
                 if (((MappedPropertyDescriptor) descriptor).getMappedWriteMethod() == null) {
 945  0
                     if (log.isDebugEnabled()) {
 946  0
                         log.debug("Skipping read-only property");
 947  
                     }
 948  0
                     return; // Read-only, skip this property setter
 949  
                 }
 950  6
                 type = ((MappedPropertyDescriptor) descriptor).
 951  
                     getMappedPropertyType();
 952  156
             } else if (index >= 0 && descriptor instanceof IndexedPropertyDescriptor) {
 953  12
                 if (((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod() == null) {
 954  0
                     if (log.isDebugEnabled()) {
 955  0
                         log.debug("Skipping read-only property");
 956  
                     }
 957  0
                     return; // Read-only, skip this property setter
 958  
                 }
 959  12
                 type = ((IndexedPropertyDescriptor) descriptor).
 960  
                     getIndexedPropertyType();
 961  144
             } else if (key != null) {
 962  1
                 if (descriptor.getReadMethod() == null) {
 963  0
                     if (log.isDebugEnabled()) {
 964  0
                         log.debug("Skipping read-only property");
 965  
                     }
 966  0
                     return; // Read-only, skip this property setter
 967  
                 }
 968  1
                 type = (value == null) ? Object.class : value.getClass();
 969  
             } else {
 970  143
                 if (descriptor.getWriteMethod() == null) {
 971  2
                     if (log.isDebugEnabled()) {
 972  0
                         log.debug("Skipping read-only property");
 973  
                     }
 974  2
                     return; // Read-only, skip this property setter
 975  
                 }
 976  141
                 type = descriptor.getPropertyType();
 977  
             }
 978  
         }
 979  
 
 980  
         // Convert the specified value to the required type
 981  212
         Object newValue = null;
 982  212
         if (type.isArray() && (index < 0)) { // Scalar value into array
 983  20
             if (value == null) {
 984  3
                 String[] values = new String[1];
 985  3
                 values[0] = null;
 986  3
                 newValue = getConvertUtils().convert(values, type);
 987  3
             } else if (value instanceof String) {
 988  8
                 newValue = getConvertUtils().convert(value, type);
 989  9
             } else if (value instanceof String[]) {
 990  5
                 newValue = getConvertUtils().convert((String[]) value, type);
 991  
             } else {
 992  4
                 newValue = convert(value, type);
 993  
             }
 994  192
         } else if (type.isArray()) {         // Indexed value into array
 995  10
             if (value instanceof String || value == null) {
 996  8
                 newValue = getConvertUtils().convert((String) value,
 997  
                                                 type.getComponentType());
 998  2
             } else if (value instanceof String[]) {
 999  0
                 newValue = getConvertUtils().convert(((String[]) value)[0],
 1000  
                                                 type.getComponentType());
 1001  
             } else {
 1002  2
                 newValue = convert(value, type.getComponentType());
 1003  
             }
 1004  
         } else {                             // Value into scalar
 1005  182
             if (value instanceof String) {
 1006  70
                 newValue = getConvertUtils().convert((String) value, type);
 1007  112
             } else if (value instanceof String[]) {
 1008  0
                 newValue = getConvertUtils().convert(((String[]) value)[0],
 1009  
                                                 type);
 1010  
             } else {
 1011  112
                 newValue = convert(value, type);
 1012  
             }
 1013  
         }
 1014  
 
 1015  
         // Invoke the setter method
 1016  
         try {
 1017  210
           getPropertyUtils().setProperty(target, name, newValue);
 1018  0
         } catch (NoSuchMethodException e) {
 1019  0
             throw new InvocationTargetException
 1020  
                 (e, "Cannot set " + propName);
 1021  210
         }
 1022  
 
 1023  210
     }
 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  1094
         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  2989
         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  72
         if (INIT_CAUSE_METHOD != null && cause != null) {
 1053  
             try {
 1054  72
                 INIT_CAUSE_METHOD.invoke(throwable, new Object[] { cause });
 1055  72
                 return true;
 1056  0
             } catch (Throwable e) {
 1057  0
                 return false; // can't initialize cause
 1058  
             }
 1059  
         }
 1060  0
         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  228
         Converter converter = getConvertUtils().lookup(type);
 1076  228
         if (converter != null) {
 1077  212
             log.trace("        USING CONVERTER " + converter);
 1078  212
             return converter.convert(type, value);
 1079  
         } else {
 1080  16
             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  1
             Class[] paramsClasses = new Class[] { Throwable.class };
 1096  1
             return Throwable.class.getMethod("initCause", paramsClasses);
 1097  0
         } catch (NoSuchMethodException e) {
 1098  0
             Log log = LogFactory.getLog(BeanUtils.class);
 1099  0
             if (log.isWarnEnabled()) {
 1100  0
                 log.warn("Throwable does not have initCause() method in JDK 1.3");
 1101  
             }
 1102  0
             return null;
 1103  0
         } catch (Throwable e) {
 1104  0
             Log log = LogFactory.getLog(BeanUtils.class);
 1105  0
             if (log.isWarnEnabled()) {
 1106  0
                 log.warn("Error getting the Throwable initCause() method", e);
 1107  
             }
 1108  0
             return null;
 1109  
         }
 1110  
     }
 1111  
 }