| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| BeanUtilsBean | 
  | 
  | 6.4;6.4 | ||||
| BeanUtilsBean$1 | 
  | 
  | 6.4;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 | }  |