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

Save This Page
Home » commons-beanutils-1.8.3-src » org.apache.commons.beanutils » [javadoc | source]