    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.beanutils;
    019
    020import java.beans.IndexedPropertyDescriptor;
    021import java.beans.IntrospectionException;
    022import java.beans.Introspector;
    023import java.beans.PropertyDescriptor;
    024import java.lang.reflect.Array;
    025import java.lang.reflect.InvocationTargetException;
    026import java.lang.reflect.Method;
    027import java.util.HashMap;
    028import java.util.Iterator;
    029import java.util.List;
    030import java.util.Map;
    031import java.util.Map.Entry;
    032import java.util.concurrent.CopyOnWriteArrayList;
    033
    034import org.apache.commons.beanutils.expression.DefaultResolver;
    035import org.apache.commons.beanutils.expression.Resolver;
    036import org.apache.commons.collections.FastHashMap;
    037import org.apache.commons.logging.Log;
    038import org.apache.commons.logging.LogFactory;
    039
    040/**
    041 * Utility methods for using Java Reflection APIs to facilitate generic
    042 * property getter and setter operations on Java objects.  Much of this
    043 * code was originally included in <code>BeanUtils</code>, but has been
    044 * separated because of the volume of code involved.
    045 * <p>
    046 * In general, the objects that are examined and modified using these
    047 * methods are expected to conform to the property getter and setter method
    048 * naming conventions described in the JavaBeans Specification (Version 1.0.1).
    049 * No data type conversions are performed, and there are no usage of any
    050 * <code>PropertyEditor</code> classes that have been registered, although
    051 * a convenient way to access the registered classes themselves is included.
    052 * <p>
    053 * For the purposes of this class, five formats for referencing a particular
    054 * property value of a bean are defined, with the <em>default</em> layout of an
    055 * identifying String in parentheses. However the notation for these formats
    056 * and how they are resolved is now (since BeanUtils 1.8.0) controlled by
    057 * the configured {@link Resolver} implementation:
    058 * <ul>
    059 * <li><strong>Simple (<code>name</code>)</strong> - The specified
    060 *     <code>name</code> identifies an individual property of a particular
    061 *     JavaBean.  The name of the actual getter or setter method to be used
    062 *     is determined using standard JavaBeans instrospection, so that (unless
    063 *     overridden by a <code>BeanInfo</code> class, a property named "xyz"
    064 *     will have a getter method named <code>getXyz()</code> or (for boolean
    065 *     properties only) <code>isXyz()</code>, and a setter method named
    066 *     <code>setXyz()</code>.</li>
    067 * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first
    068 *     name element is used to select a property getter, as for simple
    069 *     references above.  The object returned for this property is then
    070 *     consulted, using the same approach, for a property getter for a
    071 *     property named <code>name2</code>, and so on.  The property value that
    072 *     is ultimately retrieved or modified is the one identified by the
    073 *     last name element.</li>
    074 * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying
    075 *     property value is assumed to be an array, or this JavaBean is assumed
    076 *     to have indexed property getter and setter methods.  The appropriate
    077 *     (zero-relative) entry in the array is selected.  <code>List</code>
    078 *     objects are now also supported for read/write.  You simply need to define
    079 *     a getter that returns the <code>List</code></li>
    080 * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean
    081 *     is assumed to have an property getter and setter methods with an
    082 *     additional attribute of type <code>java.lang.String</code>.</li>
    083 * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> -
    084 *     Combining mapped, nested, and indexed references is also
    085 *     supported.</li>
    086 * </ul>
    087 *
    088 * @see Resolver
    089 * @see PropertyUtils
    090 * @since 1.7
    091 */
    092
    093public class PropertyUtilsBean {
    094
    095    /** An empty object array */
    096    private static final Object[] EMPTY_OBJECT_ARRAY = {};
    097
    098    /**
    099     * Return the PropertyUtils bean instance.
    100     * @return The PropertyUtils bean instance
    101     */
    102    protected static PropertyUtilsBean getInstance() {
    103        return BeanUtilsBean.getInstance().getPropertyUtils();
    104    }
    105
    106    /**
    107     * Converts an object to a list of objects. This method is used when dealing
    108     * with indexed properties. It assumes that indexed properties are stored as
    109     * lists of objects.
    110     *
    111     * @param obj the object to be converted
    112     * @return the resulting list of objects
    113     */
    114    private static List<Object> toObjectList(final Object obj) {
    115        return (List<Object>) obj;
    116    }
    117    /**
    118     * Converts an object to a map with property values. This method is used
    119     * when dealing with mapped properties. It assumes that mapped properties
    120     * are stored in a Map&lt;String, Object&gt;.
    121     *
    122     * @param obj the object to be converted
    123     * @return the resulting properties map
    124     */
    125    private static Map<String, Object> toPropertyMap(final Object obj) {
    126        return (Map<String, Object>) obj;
    127    }
    128
    129    private Resolver resolver = new DefaultResolver();
    130
    131    /**
    132     * The cache of PropertyDescriptor arrays for beans we have already
    133     * introspected, keyed by the java.lang.Class of this object.
    134     */
    135    private final WeakFastHashMap<Class<?>, BeanIntrospectionData> descriptorsCache;
    136
    137    private final WeakFastHashMap<Class<?>, FastHashMap> mappedDescriptorsCache;
    138
    139    /** Log instance */
    140    private final Log log = LogFactory.getLog(PropertyUtils.class);
    141
    142    /** The list with BeanIntrospector objects. */
    143    private final List<BeanIntrospector> introspectors;
    144
    145    /** Base constructor */
    146    public PropertyUtilsBean() {
    147        descriptorsCache = new WeakFastHashMap<>();
    148        descriptorsCache.setFast(true);
    149        mappedDescriptorsCache = new WeakFastHashMap<>();
    150        mappedDescriptorsCache.setFast(true);
    151        introspectors = new CopyOnWriteArrayList<>();
    152        resetBeanIntrospectors();
    153    }
    154
    155    /**
    156     * Adds a <code>BeanIntrospector</code>. This object is invoked when the
    157     * property descriptors of a class need to be obtained.
    158     *
    159     * @param introspector the <code>BeanIntrospector</code> to be added (must
    160     *        not be <strong>null</strong>
    161     * @throws IllegalArgumentException if the argument is <strong>null</strong>
    162     * @since 1.9
    163     */
    164    public void addBeanIntrospector(final BeanIntrospector introspector) {
    165        if (introspector == null) {
    166            throw new IllegalArgumentException(
    167                    "BeanIntrospector must not be null!");
    168        }
    169        introspectors.add(introspector);
    170    }
    171
    172    /**
    173     * Clear any cached property descriptors information for all classes
    174     * loaded by any class loaders.  This is useful in cases where class
    175     * loaders are thrown away to implement class reloading.
    176     */
    177    public void clearDescriptors() {
    178
    179        descriptorsCache.clear();
    180        mappedDescriptorsCache.clear();
    181        Introspector.flushCaches();
    182
    183    }
    184
    185    /**
    186     * <p>Copy property values from the "origin" bean to the "destination" bean
    187     * for all cases where the property names are the same (even though the
    188     * actual getter and setter methods might have been customized via
    189     * <code>BeanInfo</code> classes).  No conversions are performed on the
    190     * actual property values -- it is assumed that the values retrieved from
    191     * the origin bean are assignment-compatible with the types expected by
    192     * the destination bean.</p>
    193     *
    194     * <p>If the origin "bean" is actually a <code>Map</code>, it is assumed
    195     * to contain String-valued <strong>simple</strong> property names as the keys, pointing
    196     * at the corresponding property values that will be set in the destination
    197     * bean.<strong>Note</strong> that this method is intended to perform
    198     * a "shallow copy" of the properties and so complex properties
    199     * (for example, nested ones) will not be copied.</p>
    200     *
    201     * <p>Note, that this method will not copy a List to a List, or an Object[]
    202     * to an Object[]. It's specifically for copying JavaBean properties. </p>
    203     *
    204     * @param dest Destination bean whose properties are modified
    205     * @param orig Origin bean whose properties are retrieved
    206     * @throws IllegalAccessException if the caller does not have
    207     *  access to the property accessor method
    208     * @throws IllegalArgumentException if the <code>dest</code> or
    209     *  <code>orig</code> argument is null
    210     * @throws InvocationTargetException if the property accessor method
    211     *  throws an exception
    212     * @throws NoSuchMethodException if an accessor method for this
    213     *  propety cannot be found
    214     */
    215    public void copyProperties(final Object dest, final Object orig)
    216            throws IllegalAccessException, InvocationTargetException,
    217            NoSuchMethodException {
    218
    219        if (dest == null) {
    220            throw new IllegalArgumentException
    221                    ("No destination bean specified");
    222        }
    223        if (orig == null) {
    224            throw new IllegalArgumentException("No origin bean specified");
    225        }
    226
    227        if (orig instanceof DynaBean) {
    228            final DynaProperty[] origDescriptors =
    229                ((DynaBean) orig).getDynaClass().getDynaProperties();
    230            for (final DynaProperty origDescriptor : origDescriptors) {
    231                final String name = origDescriptor.getName();
    232                if (isReadable(orig, name) && isWriteable(dest, name)) {
    233                    try {
    234                        final Object value = ((DynaBean) orig).get(name);
    235                        if (dest instanceof DynaBean) {
    236                            ((DynaBean) dest).set(name, value);
    237                        } else {
    238                                setSimpleProperty(dest, name, value);
    239                        }
    240                    } catch (final NoSuchMethodException e) {
    241                        if (log.isDebugEnabled()) {
    242                            log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
    243                        }
    244                    }
    245                }
    246            }
    247        } else if (orig instanceof Map) {
    248            final Iterator<?> entries = ((Map<?, ?>) orig).entrySet().iterator();
    249            while (entries.hasNext()) {
    250                final Map.Entry<?, ?> entry = (Entry<?, ?>) entries.next();
    251                final String name = (String)entry.getKey();
    252                if (isWriteable(dest, name)) {
    253                    try {
    254                        if (dest instanceof DynaBean) {
    255                            ((DynaBean) dest).set(name, entry.getValue());
    256                        } else {
    257                            setSimpleProperty(dest, name, entry.getValue());
    258                        }
    259                    } catch (final NoSuchMethodException e) {
    260                        if (log.isDebugEnabled()) {
    261                            log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
    262                        }
    263                    }
    264                }
    265            }
    266        } else /* if (orig is a standard JavaBean) */ {
    267            final PropertyDescriptor[] origDescriptors =
    268                getPropertyDescriptors(orig);
    269            for (final PropertyDescriptor origDescriptor : origDescriptors) {
    270                final String name = origDescriptor.getName();
    271                if (isReadable(orig, name) && isWriteable(dest, name)) {
    272                    try {
    273                        final Object value = getSimpleProperty(orig, name);
    274                        if (dest instanceof DynaBean) {
    275                            ((DynaBean) dest).set(name, value);
    276                        } else {
    277                                setSimpleProperty(dest, name, value);
    278                        }
    279                    } catch (final NoSuchMethodException e) {
    280                        if (log.isDebugEnabled()) {
    281                            log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
    282                        }
    283                    }
    284                }
    285            }
    286        }
    287
    288    }
    289
    290    /**
    291     * <p>Return the entire set of properties for which the specified bean
    292     * provides a read method.  This map contains the unconverted property
    293     * values for all properties for which a read method is provided
    294     * (i.e. where the <code>getReadMethod()</code> returns non-null).</p>
    295     *
    296     * <p><strong>FIXME</strong> - Does not account for mapped properties.</p>
    297     *
    298     * @param bean Bean whose properties are to be extracted
    299     * @return The set of properties for the bean
    300     * @throws IllegalAccessException if the caller does not have
    301     *  access to the property accessor method
    302     * @throws IllegalArgumentException if <code>bean</code> is null
    303     * @throws InvocationTargetException if the property accessor method
    304     *  throws an exception
    305     * @throws NoSuchMethodException if an accessor method for this
    306     *  propety cannot be found
    307     */
    308    public Map<String, Object> describe(final Object bean)
    309            throws IllegalAccessException, InvocationTargetException,
    310            NoSuchMethodException {
    311
    312        if (bean == null) {
    313            throw new IllegalArgumentException("No bean specified");
    314        }
    315        final Map<String, Object> description = new HashMap<>();
    316        if (bean instanceof DynaBean) {
    317            final DynaProperty[] descriptors =
    318                ((DynaBean) bean).getDynaClass().getDynaProperties();
    319            for (final DynaProperty descriptor : descriptors) {
    320                final String name = descriptor.getName();
    321                description.put(name, getProperty(bean, name));
    322            }
    323        } else {
    324            final PropertyDescriptor[] descriptors =
    325                getPropertyDescriptors(bean);
    326            for (final PropertyDescriptor descriptor : descriptors) {
    327                final String name = descriptor.getName();
    328                if (descriptor.getReadMethod() != null) {
    329                    description.put(name, getProperty(bean, name));
    330                }
    331            }
    332        }
    333        return description;
    334
    335    }
    336
    337    /**
    338     * Performs introspection on the specified class. This method invokes all {@code BeanIntrospector} objects that were
    339     * added to this instance.
    340     *
    341     * @param beanClass the class to be inspected
    342     * @return a data object with the results of introspection
    343     */
    344    private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
    345        final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
    346
    347        for (final BeanIntrospector bi : introspectors) {
    348            try {
    349                bi.introspect(ictx);
    350            } catch (final IntrospectionException iex) {
    351                log.error("Exception during introspection", iex);
    352            }
    353        }
    354
    355        return new BeanIntrospectionData(ictx.getPropertyDescriptors());
    356    }
    357
    358    /**
    359     * Return the value of the specified indexed property of the specified
    360     * bean, with no type conversions.  The zero-relative index of the
    361     * required value must be included (in square brackets) as a suffix to
    362     * the property name, or <code>IllegalArgumentException</code> will be
    363     * thrown.  In addition to supporting the JavaBeans specification, this
    364     * method has been extended to support <code>List</code> objects as well.
    365     *
    366     * @param bean Bean whose property is to be extracted
    367     * @param name <code>propertyname[index]</code> of the property value
    368     *  to be extracted
    369     * @return the indexed property value
    370     * @throws IndexOutOfBoundsException if the specified index
    371     *  is outside the valid range for the underlying array or List
    372     * @throws IllegalAccessException if the caller does not have
    373     *  access to the property accessor method
    374     * @throws IllegalArgumentException if <code>bean</code> or
    375     *  <code>name</code> is null
    376     * @throws InvocationTargetException if the property accessor method
    377     *  throws an exception
    378     * @throws NoSuchMethodException if an accessor method for this
    379     *  propety cannot be found
    380     */
    381    public Object getIndexedProperty(final Object bean, String name)
    382            throws IllegalAccessException, InvocationTargetException,
    383            NoSuchMethodException {
    384
    385        if (bean == null) {
    386            throw new IllegalArgumentException("No bean specified");
    387        }
    388        if (name == null) {
    389            throw new IllegalArgumentException("No name specified for bean class '" +
    390                    bean.getClass() + "'");
    391        }
    392
    393        // Identify the index of the requested individual property
    394        int index = -1;
    395        try {
    396            index = resolver.getIndex(name);
    397        } catch (final IllegalArgumentException e) {
    398            throw new IllegalArgumentException("Invalid indexed property '" +
    399                    name + "' on bean class '" + bean.getClass() + "' " +
    400                    e.getMessage());
    401        }
    402        if (index < 0) {
    403            throw new IllegalArgumentException("Invalid indexed property '" +
    404                    name + "' on bean class '" + bean.getClass() + "'");
    405        }
    406
    407        // Isolate the name
    408        name = resolver.getProperty(name);
    409
    410        // Request the specified indexed property value
    411        return getIndexedProperty(bean, name, index);
    412
    413    }
    414
    415    /**
    416     * Return the value of the specified indexed property of the specified
    417     * bean, with no type conversions.  In addition to supporting the JavaBeans
    418     * specification, this method has been extended to support
    419     * <code>List</code> objects as well.
    420     *
    421     * @param bean Bean whose property is to be extracted
    422     * @param name Simple property name of the property value to be extracted
    423     * @param index Index of the property value to be extracted
    424     * @return the indexed property value
    425     * @throws IndexOutOfBoundsException if the specified index
    426     *  is outside the valid range for the underlying property
    427     * @throws IllegalAccessException if the caller does not have
    428     *  access to the property accessor method
    429     * @throws IllegalArgumentException if <code>bean</code> or
    430     *  <code>name</code> is null
    431     * @throws InvocationTargetException if the property accessor method
    432     *  throws an exception
    433     * @throws NoSuchMethodException if an accessor method for this
    434     *  propety cannot be found
    435     */
    436    public Object getIndexedProperty(final Object bean,
    437                                            final String name, final int index)
    438            throws IllegalAccessException, InvocationTargetException,
    439            NoSuchMethodException {
    440
    441        if (bean == null) {
    442            throw new IllegalArgumentException("No bean specified");
    443        }
    444        if (name == null || name.length() == 0) {
    445            if (bean.getClass().isArray()) {
    446                return Array.get(bean, index);
    447            }
    448            if (bean instanceof List) {
    449                return ((List<?>)bean).get(index);
    450            }
    451        }
    452        if (name == null) {
    453            throw new IllegalArgumentException("No name specified for bean class '" +
    454                    bean.getClass() + "'");
    455        }
    456
    457        // Handle DynaBean instances specially
    458        if (bean instanceof DynaBean) {
    459            final DynaProperty descriptor =
    460                    ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    461            if (descriptor == null) {
    462                throw new NoSuchMethodException("Unknown property '" +
    463                    name + "' on bean class '" + bean.getClass() + "'");
    464            }
    465            return ((DynaBean) bean).get(name, index);
    466        }
    467
    468        // Retrieve the property descriptor for the specified property
    469        final PropertyDescriptor descriptor =
    470                getPropertyDescriptor(bean, name);
    471        if (descriptor == null) {
    472            throw new NoSuchMethodException("Unknown property '" +
    473                    name + "' on bean class '" + bean.getClass() + "'");
    474        }
    475
    476        // Call the indexed getter method if there is one
    477        if (descriptor instanceof IndexedPropertyDescriptor) {
    478            Method readMethod = ((IndexedPropertyDescriptor) descriptor).
    479                    getIndexedReadMethod();
    480            readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
    481            if (readMethod != null) {
    482                final Object[] subscript = new Object[1];
    483                subscript[0] = Integer.valueOf(index);
    484                try {
    485                    return invokeMethod(readMethod,bean, subscript);
    486                } catch (final InvocationTargetException e) {
    487                    if (e.getTargetException() instanceof
    488                            IndexOutOfBoundsException) {
    489                        throw (IndexOutOfBoundsException)
    490                                e.getTargetException();
    491                    }
    492                    throw e;
    493                }
    494            }
    495        }
    496
    497        // Otherwise, the underlying property must be an array
    498        final Method readMethod = getReadMethod(bean.getClass(), descriptor);
    499        if (readMethod == null) {
    500            throw new NoSuchMethodException("Property '" + name + "' has no " +
    501                    "getter method on bean class '" + bean.getClass() + "'");
    502        }
    503
    504        // Call the property getter and return the value
    505        final Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    506        if (!value.getClass().isArray()) {
    507            if (!(value instanceof List)) {
    508                throw new IllegalArgumentException("Property '" + name +
    509                        "' is not indexed on bean class '" + bean.getClass() + "'");
    510            }
    511            //get the List's value
    512            return ((List<?>) value).get(index);
    513        }
    514        //get the array's value
    515        try {
    516            return Array.get(value, index);
    517        } catch (final ArrayIndexOutOfBoundsException e) {
    518            throw new ArrayIndexOutOfBoundsException("Index: " +
    519                    index + ", Size: " + Array.getLength(value) +
    520                    " for property '" + name + "'");
    521        }
    522
    523    }
    524
    525    /**
    526     * Obtains the {@code BeanIntrospectionData} object describing the specified bean
    527     * class. This object is looked up in the internal cache. If necessary, introspection
    528     * is performed now on the affected bean class, and the results object is created.
    529     *
    530     * @param beanClass the bean class in question
    531     * @return the {@code BeanIntrospectionData} object for this class
    532     * @throws IllegalArgumentException if the bean class is <strong>null</strong>
    533     */
    534    private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
    535        if (beanClass == null) {
    536            throw new IllegalArgumentException("No bean class specified");
    537        }
    538
    539        // Look up any cached information for this bean class
    540        BeanIntrospectionData data = descriptorsCache.get(beanClass);
    541        if (data == null) {
    542            data = fetchIntrospectionData(beanClass);
    543            descriptorsCache.put(beanClass, data);
    544        }
    545
    546        return data;
    547    }
    548
    549    /**
    550     * Return the value of the specified mapped property of the
    551     * specified bean, with no type conversions.  The key of the
    552     * required value must be included (in brackets) as a suffix to
    553     * the property name, or <code>IllegalArgumentException</code> will be
    554     * thrown.
    555     *
    556     * @param bean Bean whose property is to be extracted
    557     * @param name <code>propertyname(key)</code> of the property value
    558     *  to be extracted
    559     * @return the mapped property value
    560     * @throws IllegalAccessException if the caller does not have
    561     *  access to the property accessor method
    562     * @throws InvocationTargetException if the property accessor method
    563     *  throws an exception
    564     * @throws NoSuchMethodException if an accessor method for this
    565     *  propety cannot be found
    566     */
    567    public Object getMappedProperty(final Object bean, String name)
    568            throws IllegalAccessException, InvocationTargetException,
    569            NoSuchMethodException {
    570
    571        if (bean == null) {
    572            throw new IllegalArgumentException("No bean specified");
    573        }
    574        if (name == null) {
    575            throw new IllegalArgumentException("No name specified for bean class '" +
    576                    bean.getClass() + "'");
    577        }
    578
    579        // Identify the key of the requested individual property
    580        String key  = null;
    581        try {
    582            key = resolver.getKey(name);
    583        } catch (final IllegalArgumentException e) {
    584            throw new IllegalArgumentException
    585                    ("Invalid mapped property '" + name +
    586                    "' on bean class '" + bean.getClass() + "' " + e.getMessage());
    587        }
    588        if (key == null) {
    589            throw new IllegalArgumentException("Invalid mapped property '" +
    590                    name + "' on bean class '" + bean.getClass() + "'");
    591        }
    592
    593        // Isolate the name
    594        name = resolver.getProperty(name);
    595
    596        // Request the specified indexed property value
    597        return getMappedProperty(bean, name, key);
    598
    599    }
    600
    601    /**
    602     * Return the value of the specified mapped property of the specified
    603     * bean, with no type conversions.
    604     *
    605     * @param bean Bean whose property is to be extracted
    606     * @param name Mapped property name of the property value to be extracted
    607     * @param key Key of the property value to be extracted
    608     * @return the mapped property value
    609     * @throws IllegalAccessException if the caller does not have
    610     *  access to the property accessor method
    611     * @throws InvocationTargetException if the property accessor method
    612     *  throws an exception
    613     * @throws NoSuchMethodException if an accessor method for this
    614     *  propety cannot be found
    615     */
    616    public Object getMappedProperty(final Object bean,
    617                                           final String name, final String key)
    618            throws IllegalAccessException, InvocationTargetException,
    619            NoSuchMethodException {
    620
    621        if (bean == null) {
    622            throw new IllegalArgumentException("No bean specified");
    623        }
    624        if (name == null) {
    625            throw new IllegalArgumentException("No name specified for bean class '" +
    626                    bean.getClass() + "'");
    627        }
    628        if (key == null) {
    629            throw new IllegalArgumentException("No key specified for property '" +
    630                    name + "' on bean class " + bean.getClass() + "'");
    631        }
    632
    633        // Handle DynaBean instances specially
    634        if (bean instanceof DynaBean) {
    635            final DynaProperty descriptor =
    636                    ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    637            if (descriptor == null) {
    638                throw new NoSuchMethodException("Unknown property '" +
    639                        name + "'+ on bean class '" + bean.getClass() + "'");
    640            }
    641            return ((DynaBean) bean).get(name, key);
    642        }
    643
    644        Object result = null;
    645
    646        // Retrieve the property descriptor for the specified property
    647        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    648        if (descriptor == null) {
    649            throw new NoSuchMethodException("Unknown property '" +
    650                    name + "'+ on bean class '" + bean.getClass() + "'");
    651        }
    652
    653        if (descriptor instanceof MappedPropertyDescriptor) {
    654            // Call the keyed getter method if there is one
    655            Method readMethod = ((MappedPropertyDescriptor) descriptor).
    656                    getMappedReadMethod();
    657            readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
    658            if (readMethod == null) {
    659                throw new NoSuchMethodException("Property '" + name +
    660                        "' has no mapped getter method on bean class '" +
    661                        bean.getClass() + "'");
    662            }
    663            final Object[] keyArray = new Object[1];
    664            keyArray[0] = key;
    665            result = invokeMethod(readMethod, bean, keyArray);
    666        } else {
    667          /* means that the result has to be retrieved from a map */
    668          final Method readMethod = getReadMethod(bean.getClass(), descriptor);
    669          if (readMethod == null) {
    670            throw new NoSuchMethodException("Property '" + name +
    671                    "' has no mapped getter method on bean class '" +
    672                    bean.getClass() + "'");
    673          }
    674        final Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    675        /* test and fetch from the map */
    676        if (invokeResult instanceof Map) {
    677          result = ((Map<?, ?>)invokeResult).get(key);
    678        }
    679        }
    680        return result;
    681
    682    }
    683
    684    /**
    685     * <p>Return the mapped property descriptors for this bean class.</p>
    686     *
    687     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    688     *
    689     * @param beanClass Bean class to be introspected
    690     * @return the mapped property descriptors
    691     * @deprecated This method should not be exposed
    692     */
    693    @Deprecated
    694    public FastHashMap getMappedPropertyDescriptors(final Class<?> beanClass) {
    695
    696        if (beanClass == null) {
    697            return null;
    698        }
    699
    700        // Look up any cached descriptors for this bean class
    701        return mappedDescriptorsCache.get(beanClass);
    702
    703    }
    704
    705    /**
    706     * <p>Return the mapped property descriptors for this bean.</p>
    707     *
    708     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    709     *
    710     * @param bean Bean to be introspected
    711     * @return the mapped property descriptors
    712     * @deprecated This method should not be exposed
    713     */
    714    @Deprecated
    715    public FastHashMap getMappedPropertyDescriptors(final Object bean) {
    716
    717        if (bean == null) {
    718            return null;
    719        }
    720        return getMappedPropertyDescriptors(bean.getClass());
    721
    722    }
    723
    724    /**
    725     * Return the value of the (possibly nested) property of the specified
    726     * name, for the specified bean, with no type conversions.
    727     *
    728     * @param bean Bean whose property is to be extracted
    729     * @param name Possibly nested name of the property to be extracted
    730     * @return the nested property value
    731     * @throws IllegalAccessException if the caller does not have
    732     *  access to the property accessor method
    733     * @throws IllegalArgumentException if <code>bean</code> or
    734     *  <code>name</code> is null
    735     * @throws NestedNullException if a nested reference to a
    736     *  property returns null
    737     * @throws InvocationTargetException
    738     * if the property accessor method throws an exception
    739     * @throws NoSuchMethodException if an accessor method for this
    740     *  propety cannot be found
    741     */
    742    public Object getNestedProperty(Object bean, String name)
    743            throws IllegalAccessException, InvocationTargetException,
    744            NoSuchMethodException {
    745
    746        if (bean == null) {
    747            throw new IllegalArgumentException("No bean specified");
    748        }
    749        if (name == null) {
    750            throw new IllegalArgumentException("No name specified for bean class '" +
    751                    bean.getClass() + "'");
    752        }
    753
    754        // Resolve nested references
    755        while (resolver.hasNested(name)) {
    756            final String next = resolver.next(name);
    757            Object nestedBean = null;
    758            if (bean instanceof Map) {
    759                nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
    760            } else if (resolver.isMapped(next)) {
    761                nestedBean = getMappedProperty(bean, next);
    762            } else if (resolver.isIndexed(next)) {
    763                nestedBean = getIndexedProperty(bean, next);
    764            } else {
    765                nestedBean = getSimpleProperty(bean, next);
    766            }
    767            if (nestedBean == null) {
    768                throw new NestedNullException
    769                        ("Null property value for '" + name +
    770                        "' on bean class '" + bean.getClass() + "'");
    771            }
    772            bean = nestedBean;
    773            name = resolver.remove(name);
    774        }
    775
    776        if (bean instanceof Map) {
    777            bean = getPropertyOfMapBean((Map<?, ?>) bean, name);
    778        } else if (resolver.isMapped(name)) {
    779            bean = getMappedProperty(bean, name);
    780        } else if (resolver.isIndexed(name)) {
    781            bean = getIndexedProperty(bean, name);
    782        } else {
    783            bean = getSimpleProperty(bean, name);
    784        }
    785        return bean;
    786
    787    }
    788
    789    /**
    790     * Return the value of the specified property of the specified bean,
    791     * no matter which property reference format is used, with no
    792     * type conversions.
    793     *
    794     * @param bean Bean whose property is to be extracted
    795     * @param name Possibly indexed and/or nested name of the property
    796     *  to be extracted
    797     * @return the property value
    798     * @throws IllegalAccessException if the caller does not have
    799     *  access to the property accessor method
    800     * @throws IllegalArgumentException if <code>bean</code> or
    801     *  <code>name</code> is null
    802     * @throws InvocationTargetException if the property accessor method
    803     *  throws an exception
    804     * @throws NoSuchMethodException if an accessor method for this
    805     *  propety cannot be found
    806     */
    807    public Object getProperty(final Object bean, final String name)
    808            throws IllegalAccessException, InvocationTargetException,
    809            NoSuchMethodException {
    810
    811        return getNestedProperty(bean, name);
    812
    813    }
    814
    815    /**
    816     * <p>Retrieve the property descriptor for the specified property of the
    817     * specified bean, or return <code>null</code> if there is no such
    818     * descriptor.  This method resolves indexed and nested property
    819     * references in the same manner as other methods in this class, except
    820     * that if the last (or only) name element is indexed, the descriptor
    821     * for the last resolved property itself is returned.</p>
    822     *
    823     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    824     *
    825     * <p>Note that for Java 8 and above, this method no longer return
    826     * IndexedPropertyDescriptor for {@link List}-typed properties, only for
    827     * properties typed as native array. (BEANUTILS-492).
    828     *
    829     * @param bean Bean for which a property descriptor is requested
    830     * @param name Possibly indexed and/or nested name of the property for
    831     *  which a property descriptor is requested
    832     * @return the property descriptor
    833     * @throws IllegalAccessException if the caller does not have
    834     *  access to the property accessor method
    835     * @throws IllegalArgumentException if <code>bean</code> or
    836     *  <code>name</code> is null
    837     * @throws IllegalArgumentException if a nested reference to a
    838     *  property returns null
    839     * @throws InvocationTargetException if the property accessor method
    840     *  throws an exception
    841     * @throws NoSuchMethodException if an accessor method for this
    842     *  propety cannot be found
    843     */
    844    public PropertyDescriptor getPropertyDescriptor(Object bean,
    845                                                           String name)
    846            throws IllegalAccessException, InvocationTargetException,
    847            NoSuchMethodException {
    848
    849        if (bean == null) {
    850            throw new IllegalArgumentException("No bean specified");
    851        }
    852        if (name == null) {
    853            throw new IllegalArgumentException("No name specified for bean class '" +
    854                    bean.getClass() + "'");
    855        }
    856
    857        // Resolve nested references
    858        while (resolver.hasNested(name)) {
    859            final String next = resolver.next(name);
    860            final Object nestedBean = getProperty(bean, next);
    861            if (nestedBean == null) {
    862                throw new NestedNullException
    863                        ("Null property value for '" + next +
    864                        "' on bean class '" + bean.getClass() + "'");
    865            }
    866            bean = nestedBean;
    867            name = resolver.remove(name);
    868        }
    869
    870        // Remove any subscript from the final name value
    871        name = resolver.getProperty(name);
    872
    873        // Look up and return this property from our cache
    874        // creating and adding it to the cache if not found.
    875        if (name == null) {
    876            return null;
    877        }
    878
    879        final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
    880        PropertyDescriptor result = data.getDescriptor(name);
    881        if (result != null) {
    882            return result;
    883        }
    884
    885        FastHashMap mappedDescriptors =
    886                getMappedPropertyDescriptors(bean);
    887        if (mappedDescriptors == null) {
    888            mappedDescriptors = new FastHashMap();
    889            mappedDescriptors.setFast(true);
    890            mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors);
    891        }
    892        result = (PropertyDescriptor) mappedDescriptors.get(name);
    893        if (result == null) {
    894            // not found, try to create it
    895            try {
    896                result = new MappedPropertyDescriptor(name, bean.getClass());
    897            } catch (final IntrospectionException ie) {
    898                /* Swallow IntrospectionException
    899                 * TODO: Why?
    900                 */
    901            }
    902            if (result != null) {
    903                mappedDescriptors.put(name, result);
    904            }
    905        }
    906
    907        return result;
    908
    909    }
    910
    911    /**
    912     * <p>Retrieve the property descriptors for the specified class,
    913     * introspecting and caching them the first time a particular bean class
    914     * is encountered.</p>
    915     *
    916     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    917     *
    918     * @param beanClass Bean class for which property descriptors are requested
    919     * @return the property descriptors
    920     * @throws IllegalArgumentException if <code>beanClass</code> is null
    921     */
    922    public PropertyDescriptor[]
    923            getPropertyDescriptors(final Class<?> beanClass) {
    924
    925        return getIntrospectionData(beanClass).getDescriptors();
    926
    927    }
    928
    929    /**
    930     * <p>Retrieve the property descriptors for the specified bean,
    931     * introspecting and caching them the first time a particular bean class
    932     * is encountered.</p>
    933     *
    934     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    935     *
    936     * @param bean Bean for which property descriptors are requested
    937     * @return the property descriptors
    938     * @throws IllegalArgumentException if <code>bean</code> is null
    939     */
    940    public PropertyDescriptor[] getPropertyDescriptors(final Object bean) {
    941
    942        if (bean == null) {
    943            throw new IllegalArgumentException("No bean specified");
    944        }
    945        return getPropertyDescriptors(bean.getClass());
    946
    947    }
    948
    949    /**
    950     * <p>Return the Java Class repesenting the property editor class that has
    951     * been registered for this property (if any).  This method follows the
    952     * same name resolution rules used by <code>getPropertyDescriptor()</code>,
    953     * so if the last element of a name reference is indexed, the property
    954     * editor for the underlying property's class is returned.</p>
    955     *
    956     * <p>Note that <code>null</code> will be returned if there is no property,
    957     * or if there is no registered property editor class.  Because this
    958     * return value is ambiguous, you should determine the existence of the
    959     * property itself by other means.</p>
    960     *
    961     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    962     *
    963     * @param bean Bean for which a property descriptor is requested
    964     * @param name Possibly indexed and/or nested name of the property for
    965     *  which a property descriptor is requested
    966     * @return the property editor class
    967     * @throws IllegalAccessException if the caller does not have
    968     *  access to the property accessor method
    969     * @throws IllegalArgumentException if <code>bean</code> or
    970     *  <code>name</code> is null
    971     * @throws IllegalArgumentException if a nested reference to a
    972     *  property returns null
    973     * @throws InvocationTargetException if the property accessor method
    974     *  throws an exception
    975     * @throws NoSuchMethodException if an accessor method for this
    976     *  propety cannot be found
    977     */
    978    public Class<?> getPropertyEditorClass(final Object bean, final String name)
    979            throws IllegalAccessException, InvocationTargetException,
    980            NoSuchMethodException {
    981
    982        if (bean == null) {
    983            throw new IllegalArgumentException("No bean specified");
    984        }
    985        if (name == null) {
    986            throw new IllegalArgumentException("No name specified for bean class '" +
    987                    bean.getClass() + "'");
    988        }
    989
    990        final PropertyDescriptor descriptor =
    991                getPropertyDescriptor(bean, name);
    992        if (descriptor != null) {
    993            return descriptor.getPropertyEditorClass();
    994        }
    995        return null;
    996
    997    }
    998
    999    /**
    1000     * This method is called by getNestedProperty and setNestedProperty to
    1001     * define what it means to get a property from an object which implements
    1002     * Map. See setPropertyOfMapBean for more information.
    1003     *
    1004     * @param bean Map bean
    1005     * @param propertyName The property name
    1006     * @return the property value
    1007     * @throws IllegalArgumentException when the propertyName is regarded as
    1008     * being invalid.
    1009     *
    1010     * @throws IllegalAccessException just in case subclasses override this
    1011     * method to try to access real getter methods and find permission is denied.
    1012     *
    1013     * @throws InvocationTargetException just in case subclasses override this
    1014     * method to try to access real getter methods, and find it throws an
    1015     * exception when invoked.
    1016     *
    1017     * @throws NoSuchMethodException just in case subclasses override this
    1018     * method to try to access real getter methods, and want to fail if
    1019     * no simple method is available.
    1020     * @since 1.8.0
    1021     */
    1022    protected Object getPropertyOfMapBean(final Map<?, ?> bean, String propertyName)
    1023        throws IllegalArgumentException, IllegalAccessException,
    1024        InvocationTargetException, NoSuchMethodException {
    1025
    1026        if (resolver.isMapped(propertyName)) {
    1027            final String name = resolver.getProperty(propertyName);
    1028            if (name == null || name.length() == 0) {
    1029                propertyName = resolver.getKey(propertyName);
    1030            }
    1031        }
    1032
    1033        if (resolver.isIndexed(propertyName) ||
    1034            resolver.isMapped(propertyName)) {
    1035            throw new IllegalArgumentException(
    1036                    "Indexed or mapped properties are not supported on"
    1037                    + " objects of type Map: " + propertyName);
    1038        }
    1039
    1040        return bean.get(propertyName);
    1041    }
    1042
    1043    /**
    1044     * Return the Java Class representing the property type of the specified
    1045     * property, or <code>null</code> if there is no such property for the
    1046     * specified bean.  This method follows the same name resolution rules
    1047     * used by <code>getPropertyDescriptor()</code>, so if the last element
    1048     * of a name reference is indexed, the type of the property itself will
    1049     * be returned.  If the last (or only) element has no property with the
    1050     * specified name, <code>null</code> is returned.
    1051     * <p>
    1052     * If the property is an indexed property (e.g. <code>String[]</code>),
    1053     * this method will return the type of the items within that array.
    1054     * Note that from Java 8 and newer, this method do not support
    1055     * such index types from items within an Collection, and will
    1056     * instead return the collection type (e.g. java.util.List) from the
    1057     * getter mtethod.
    1058     *
    1059     * @param bean Bean for which a property descriptor is requested
    1060     * @param name Possibly indexed and/or nested name of the property for
    1061     *  which a property descriptor is requested
    1062     * @return The property type
    1063     * @throws IllegalAccessException if the caller does not have
    1064     *  access to the property accessor method
    1065     * @throws IllegalArgumentException if <code>bean</code> or
    1066     *  <code>name</code> is null
    1067     * @throws IllegalArgumentException if a nested reference to a
    1068     *  property returns null
    1069     * @throws InvocationTargetException if the property accessor method
    1070     *  throws an exception
    1071     * @throws NoSuchMethodException if an accessor method for this
    1072     *  propety cannot be found
    1073     */
    1074    public Class<?> getPropertyType(Object bean, String name)
    1075            throws IllegalAccessException, InvocationTargetException,
    1076            NoSuchMethodException {
    1077
    1078        if (bean == null) {
    1079            throw new IllegalArgumentException("No bean specified");
    1080        }
    1081        if (name == null) {
    1082            throw new IllegalArgumentException("No name specified for bean class '" +
    1083                    bean.getClass() + "'");
    1084        }
    1085
    1086        // Resolve nested references
    1087        while (resolver.hasNested(name)) {
    1088            final String next = resolver.next(name);
    1089            final Object nestedBean = getProperty(bean, next);
    1090            if (nestedBean == null) {
    1091                throw new NestedNullException
    1092                        ("Null property value for '" + next +
    1093                        "' on bean class '" + bean.getClass() + "'");
    1094            }
    1095            bean = nestedBean;
    1096            name = resolver.remove(name);
    1097        }
    1098
    1099        // Remove any subscript from the final name value
    1100        name = resolver.getProperty(name);
    1101
    1102        // Special handling for DynaBeans
    1103        if (bean instanceof DynaBean) {
    1104            final DynaProperty descriptor =
    1105                    ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1106            if (descriptor == null) {
    1107                return null;
    1108            }
    1109            final Class<?> type = descriptor.getType();
    1110            if (type == null) {
    1111                return null;
    1112            }
    1113            if (type.isArray()) {
    1114                return type.getComponentType();
    1115            }
    1116            return type;
    1117        }
    1118
    1119        final PropertyDescriptor descriptor =
    1120                getPropertyDescriptor(bean, name);
    1121        if (descriptor == null) {
    1122            return null;
    1123        }
    1124        if (descriptor instanceof IndexedPropertyDescriptor) {
    1125            return ((IndexedPropertyDescriptor) descriptor).
    1126                    getIndexedPropertyType();
    1127        }
    1128        if (descriptor instanceof MappedPropertyDescriptor) {
    1129            return ((MappedPropertyDescriptor) descriptor).
    1130                    getMappedPropertyType();
    1131        }
    1132        return descriptor.getPropertyType();
    1133
    1134    }
    1135
    1136    /**
    1137     * <p>Return an accessible property getter method for this property,
    1138     * if there is one; otherwise return <code>null</code>.</p>
    1139     *
    1140     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1141     *
    1142     * @param clazz The class of the read method will be invoked on
    1143     * @param descriptor Property descriptor to return a getter for
    1144     * @return The read method
    1145     */
    1146    Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
    1147        return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod());
    1148    }
    1149
    1150    /**
    1151     * <p>Return an accessible property getter method for this property,
    1152     * if there is one; otherwise return <code>null</code>.</p>
    1153     *
    1154     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1155     *
    1156     * @param descriptor Property descriptor to return a getter for
    1157     * @return The read method
    1158     */
    1159    public Method getReadMethod(final PropertyDescriptor descriptor) {
    1160
    1161        return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
    1162
    1163    }
    1164
    1165    /**
    1166     * Return the configured {@link Resolver} implementation used by BeanUtils.
    1167     * <p>
    1168     * The {@link Resolver} handles the <em>property name</em>
    1169     * expressions and the implementation in use effectively
    1170     * controls the dialect of the <em>expression language</em>
    1171     * that BeanUtils recongnises.
    1172     * <p>
    1173     * {@link DefaultResolver} is the default implementation used.
    1174     *
    1175     * @return resolver The property expression resolver.
    1176     * @since 1.8.0
    1177     */
    1178    public Resolver getResolver() {
    1179        return resolver;
    1180    }
    1181
    1182    /**
    1183     * Return the value of the specified simple property of the specified
    1184     * bean, with no type conversions.
    1185     *
    1186     * @param bean Bean whose property is to be extracted
    1187     * @param name Name of the property to be extracted
    1188     * @return The property value
    1189     * @throws IllegalAccessException if the caller does not have
    1190     *  access to the property accessor method
    1191     * @throws IllegalArgumentException if <code>bean</code> or
    1192     *  <code>name</code> is null
    1193     * @throws IllegalArgumentException if the property name
    1194     *  is nested or indexed
    1195     * @throws InvocationTargetException if the property accessor method
    1196     *  throws an exception
    1197     * @throws NoSuchMethodException if an accessor method for this
    1198     *  propety cannot be found
    1199     */
    1200    public Object getSimpleProperty(final Object bean, final String name)
    1201            throws IllegalAccessException, InvocationTargetException,
    1202            NoSuchMethodException {
    1203
    1204        if (bean == null) {
    1205            throw new IllegalArgumentException("No bean specified");
    1206        }
    1207        if (name == null) {
    1208            throw new IllegalArgumentException("No name specified for bean class '" +
    1209                    bean.getClass() + "'");
    1210        }
    1211
    1212        // Validate the syntax of the property name
    1213        if (resolver.hasNested(name)) {
    1214            throw new IllegalArgumentException
    1215                    ("Nested property names are not allowed: Property '" +
    1216                    name + "' on bean class '" + bean.getClass() + "'");
    1217        }
    1218        if (resolver.isIndexed(name)) {
    1219            throw new IllegalArgumentException
    1220                    ("Indexed property names are not allowed: Property '" +
    1221                    name + "' on bean class '" + bean.getClass() + "'");
    1222        }
    1223        if (resolver.isMapped(name)) {
    1224            throw new IllegalArgumentException
    1225                    ("Mapped property names are not allowed: Property '" +
    1226                    name + "' on bean class '" + bean.getClass() + "'");
    1227        }
    1228
    1229        // Handle DynaBean instances specially
    1230        if (bean instanceof DynaBean) {
    1231            final DynaProperty descriptor =
    1232                    ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1233            if (descriptor == null) {
    1234                throw new NoSuchMethodException("Unknown property '" +
    1235                        name + "' on dynaclass '" +
    1236                        ((DynaBean) bean).getDynaClass() + "'" );
    1237            }
    1238            return ((DynaBean) bean).get(name);
    1239        }
    1240
    1241        // Retrieve the property getter method for the specified property
    1242        final PropertyDescriptor descriptor =
    1243                getPropertyDescriptor(bean, name);
    1244        if (descriptor == null) {
    1245            throw new NoSuchMethodException("Unknown property '" +
    1246                    name + "' on class '" + bean.getClass() + "'" );
    1247        }
    1248        final Method readMethod = getReadMethod(bean.getClass(), descriptor);
    1249        if (readMethod == null) {
    1250            throw new NoSuchMethodException("Property '" + name +
    1251                    "' has no getter method in class '" + bean.getClass() + "'");
    1252        }
    1253
    1254        return invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    1255
    1256    }
    1257
    1258    /**
    1259     * <p>Return an accessible property setter method for this property,
    1260     * if there is one; otherwise return <code>null</code>.</p>
    1261     *
    1262     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1263     *
    1264     * @param clazz The class of the read method will be invoked on
    1265     * @param descriptor Property descriptor to return a setter for
    1266     * @return The write method
    1267     * @since 1.9.1
    1268     */
    1269    public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
    1270        final BeanIntrospectionData data = getIntrospectionData(clazz);
    1271        return MethodUtils.getAccessibleMethod(clazz,
    1272                data.getWriteMethod(clazz, descriptor));
    1273    }
    1274
    1275    /**
    1276     * <p>Return an accessible property setter method for this property,
    1277     * if there is one; otherwise return <code>null</code>.</p>
    1278     *
    1279     * <p><em>Note:</em> This method does not work correctly with custom bean
    1280     * introspection under certain circumstances. It may return {@code null}
    1281     * even if a write method is defined for the property in question. Use
    1282     * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the
    1283     * correct result is returned.</p>
    1284     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1285     *
    1286     * @param descriptor Property descriptor to return a setter for
    1287     * @return The write method
    1288     */
    1289    public Method getWriteMethod(final PropertyDescriptor descriptor) {
    1290
    1291        return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
    1292
    1293    }
    1294
    1295    /** This just catches and wraps IllegalArgumentException. */
    1296    private Object invokeMethod(final Method method, final Object bean, final Object[] values) throws IllegalAccessException, InvocationTargetException {
    1297        if (bean == null) {
    1298            throw new IllegalArgumentException("No bean specified " + "- this should have been checked before reaching this method");
    1299        }
    1300        try {
    1301            return method.invoke(bean, values);
    1302        } catch (final NullPointerException | IllegalArgumentException cause) {
    1303            final StringBuilder valueString = new StringBuilder();
    1304            if (values != null) {
    1305                for (int i = 0; i < values.length; i++) {
    1306                    if (i > 0) {
    1307                        valueString.append(", ");
    1308                    }
    1309                    if (values[i] == null) {
    1310                        valueString.append("<null>");
    1311                    } else {
    1312                        valueString.append(values[i].getClass().getName());
    1313                    }
    1314                }
    1315            }
    1316            final StringBuilder expectedString = new StringBuilder();
    1317            final Class<?>[] parTypes = method.getParameterTypes();
    1318            if (parTypes != null) {
    1319                for (int i = 0; i < parTypes.length; i++) {
    1320                    if (i > 0) {
    1321                        expectedString.append(", ");
    1322                    }
    1323                    expectedString.append(parTypes[i].getName());
    1324                }
    1325            }
    1326            throw new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " on bean class '" +
    1327                    bean.getClass() + "' - " + cause.getMessage() + " - had objects of type \"" +
    1328                    valueString.append("\" but expected signature \"").append(expectedString.toString()).append("\"").toString(), cause);
    1329
    1330        }
    1331    }
    1332
    1333    /**
    1334     * <p>
    1335     * Return <code>true</code> if the specified property name identifies a readable property on the specified bean; otherwise, return <code>false</code>.
    1336     *
    1337     * @param bean Bean to be examined (may be a {@link DynaBean}
    1338     * @param name Property name to be evaluated
    1339     * @return <code>true</code> if the property is readable, otherwise <code>false</code>
    1340     * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is <code>null</code>
    1341     * @since BeanUtils 1.6
    1342     */
    1343    public boolean isReadable(Object bean, String name) {
    1344
    1345        // Validate method parameters
    1346        if (bean == null) {
    1347            throw new IllegalArgumentException("No bean specified");
    1348        }
    1349        if (name == null) {
    1350            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1351        }
    1352
    1353        // Resolve nested references
    1354        while (resolver.hasNested(name)) {
    1355            final String next = resolver.next(name);
    1356            Object nestedBean = null;
    1357            try {
    1358                nestedBean = getProperty(bean, next);
    1359            } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    1360                return false;
    1361            }
    1362            if (nestedBean == null) {
    1363                throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
    1364            }
    1365            bean = nestedBean;
    1366            name = resolver.remove(name);
    1367        }
    1368
    1369        // Remove any subscript from the final name value
    1370        name = resolver.getProperty(name);
    1371
    1372        // Treat WrapDynaBean as special case - may be a write-only property
    1373        // (see Jira issue# BEANUTILS-61)
    1374        if (bean instanceof WrapDynaBean) {
    1375            bean = ((WrapDynaBean) bean).getInstance();
    1376        }
    1377
    1378        // Return the requested result
    1379        if (bean instanceof DynaBean) {
    1380            // All DynaBean properties are readable
    1381            return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null;
    1382        }
    1383        try {
    1384            final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
    1385            if (desc == null) {
    1386                return false;
    1387            }
    1388            Method readMethod = getReadMethod(bean.getClass(), desc);
    1389            if (readMethod == null) {
    1390                if (desc instanceof IndexedPropertyDescriptor) {
    1391                    readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
    1392                } else if (desc instanceof MappedPropertyDescriptor) {
    1393                    readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
    1394                }
    1395                readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
    1396            }
    1397            return readMethod != null;
    1398        } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    1399            return false;
    1400        }
    1401
    1402    }
    1403
    1404    /**
    1405     * <p>
    1406     * Return <code>true</code> if the specified property name identifies a writeable property on the specified bean; otherwise, return <code>false</code>.
    1407     *
    1408     * @param bean Bean to be examined (may be a {@link DynaBean}
    1409     * @param name Property name to be evaluated
    1410     * @return <code>true</code> if the property is writeable, otherwise <code>false</code>
    1411     * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is <code>null</code>
    1412     * @since BeanUtils 1.6
    1413     */
    1414    public boolean isWriteable(Object bean, String name) {
    1415
    1416        // Validate method parameters
    1417        if (bean == null) {
    1418            throw new IllegalArgumentException("No bean specified");
    1419        }
    1420        if (name == null) {
    1421            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1422        }
    1423
    1424        // Resolve nested references
    1425        while (resolver.hasNested(name)) {
    1426            final String next = resolver.next(name);
    1427            Object nestedBean = null;
    1428            try {
    1429                nestedBean = getProperty(bean, next);
    1430            } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    1431                return false;
    1432            }
    1433            if (nestedBean == null) {
    1434                throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
    1435            }
    1436            bean = nestedBean;
    1437            name = resolver.remove(name);
    1438        }
    1439
    1440        // Remove any subscript from the final name value
    1441        name = resolver.getProperty(name);
    1442
    1443        // Treat WrapDynaBean as special case - may be a read-only property
    1444        // (see Jira issue# BEANUTILS-61)
    1445        if (bean instanceof WrapDynaBean) {
    1446            bean = ((WrapDynaBean) bean).getInstance();
    1447        }
    1448
    1449        // Return the requested result
    1450        if (bean instanceof DynaBean) {
    1451            // All DynaBean properties are writeable
    1452            return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null;
    1453        }
    1454        try {
    1455            final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
    1456            if (desc == null) {
    1457                return false;
    1458            }
    1459            Method writeMethod = getWriteMethod(bean.getClass(), desc);
    1460            if (writeMethod == null) {
    1461                if (desc instanceof IndexedPropertyDescriptor) {
    1462                    writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
    1463                } else if (desc instanceof MappedPropertyDescriptor) {
    1464                    writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
    1465                }
    1466                writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
    1467            }
    1468            return writeMethod != null;
    1469        } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    1470            return false;
    1471        }
    1472
    1473    }
    1474
    1475    /**
    1476     * Removes the specified <code>BeanIntrospector</code>.
    1477     *
    1478     * @param introspector the <code>BeanIntrospector</code> to be removed
    1479     * @return <strong>true</strong> if the <code>BeanIntrospector</code> existed and could be removed, <strong>false</strong> otherwise
    1480     * @since 1.9
    1481     */
    1482    public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
    1483        return introspectors.remove(introspector);
    1484    }
    1485
    1486    /**
    1487     * Resets the {@link BeanIntrospector} objects registered at this instance. After this method was called, only the default {@code BeanIntrospector} is
    1488     * registered.
    1489     *
    1490     * @since 1.9
    1491     */
    1492    public final void resetBeanIntrospectors() {
    1493        introspectors.clear();
    1494        introspectors.add(DefaultBeanIntrospector.INSTANCE);
    1495        introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
    1496    }
    1497
    1498    /**
    1499     * Set the value of the specified indexed property of the specified bean, with no type conversions. In addition to supporting the JavaBeans specification,
    1500     * this method has been extended to support <code>List</code> objects as well.
    1501     *
    1502     * @param bean  Bean whose property is to be set
    1503     * @param name  Simple property name of the property value to be set
    1504     * @param index Index of the property value to be set
    1505     * @param value Value to which the indexed property element is to be set
    1506     * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property
    1507     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1508     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1509     * @throws InvocationTargetException if the property accessor method throws an exception
    1510     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1511     */
    1512    public void setIndexedProperty(final Object bean, final String name, final int index, final Object value)
    1513            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1514
    1515        if (bean == null) {
    1516            throw new IllegalArgumentException("No bean specified");
    1517        }
    1518        if (name == null || name.length() == 0) {
    1519            if (bean.getClass().isArray()) {
    1520                Array.set(bean, index, value);
    1521                return;
    1522            }
    1523            if (bean instanceof List) {
    1524                final List<Object> list = toObjectList(bean);
    1525                list.set(index, value);
    1526                return;
    1527            }
    1528        }
    1529        if (name == null) {
    1530            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1531        }
    1532
    1533        // Handle DynaBean instances specially
    1534        if (bean instanceof DynaBean) {
    1535            final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1536            if (descriptor == null) {
    1537                throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
    1538            }
    1539            ((DynaBean) bean).set(name, index, value);
    1540            return;
    1541        }
    1542
    1543        // Retrieve the property descriptor for the specified property
    1544        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    1545        if (descriptor == null) {
    1546            throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
    1547        }
    1548
    1549        // Call the indexed setter method if there is one
    1550        if (descriptor instanceof IndexedPropertyDescriptor) {
    1551            Method writeMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod();
    1552            writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
    1553            if (writeMethod != null) {
    1554                final Object[] subscript = new Object[2];
    1555                subscript[0] = Integer.valueOf(index);
    1556                subscript[1] = value;
    1557                try {
    1558                    if (log.isTraceEnabled()) {
    1559                        final String valueClassName = value == null ? "<null>" : value.getClass().getName();
    1560                        log.trace("setSimpleProperty: Invoking method " + writeMethod + " with index=" + index + ", value=" + value + " (class " +
    1561                                valueClassName + ")");
    1562                    }
    1563                    invokeMethod(writeMethod, bean, subscript);
    1564                } catch (final InvocationTargetException e) {
    1565                    if (e.getTargetException() instanceof IndexOutOfBoundsException) {
    1566                        throw (IndexOutOfBoundsException) e.getTargetException();
    1567                    }
    1568                    throw e;
    1569                }
    1570                return;
    1571            }
    1572        }
    1573
    1574        // Otherwise, the underlying property must be an array or a list
    1575        final Method readMethod = getReadMethod(bean.getClass(), descriptor);
    1576        if (readMethod == null) {
    1577            throw new NoSuchMethodException("Property '" + name + "' has no getter method on bean class '" + bean.getClass() + "'");
    1578        }
    1579
    1580        // Call the property getter to get the array or list
    1581        final Object array = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    1582        if (!array.getClass().isArray()) {
    1583            if (!(array instanceof List)) {
    1584                throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
    1585            }
    1586            // Modify the specified value in the List
    1587            final List<Object> list = toObjectList(array);
    1588            list.set(index, value);
    1589        } else {
    1590            // Modify the specified value in the array
    1591            Array.set(array, index, value);
    1592        }
    1593
    1594    }
    1595
    1596    /**
    1597     * Set the value of the specified indexed property of the specified bean, with no type conversions. The zero-relative index of the required value must be
    1598     * included (in square brackets) as a suffix to the property name, or <code>IllegalArgumentException</code> will be thrown. In addition to supporting the
    1599     * JavaBeans specification, this method has been extended to support <code>List</code> objects as well.
    1600     *
    1601     * @param bean  Bean whose property is to be modified
    1602     * @param name  <code>propertyname[index]</code> of the property value to be modified
    1603     * @param value Value to which the specified property element should be set
    1604     * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property
    1605     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1606     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1607     * @throws InvocationTargetException if the property accessor method throws an exception
    1608     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1609     */
    1610    public void setIndexedProperty(final Object bean, String name, final Object value)
    1611            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1612
    1613        if (bean == null) {
    1614            throw new IllegalArgumentException("No bean specified");
    1615        }
    1616        if (name == null) {
    1617            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1618        }
    1619
    1620        // Identify the index of the requested individual property
    1621        int index = -1;
    1622        try {
    1623            index = resolver.getIndex(name);
    1624        } catch (final IllegalArgumentException e) {
    1625            throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
    1626        }
    1627        if (index < 0) {
    1628            throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
    1629        }
    1630
    1631        // Isolate the name
    1632        name = resolver.getProperty(name);
    1633
    1634        // Set the specified indexed property value
    1635        setIndexedProperty(bean, name, index, value);
    1636
    1637    }
    1638
    1639    /**
    1640     * Set the value of the specified mapped property of the specified bean, with no type conversions. The key of the value to set must be included (in
    1641     * brackets) as a suffix to the property name, or <code>IllegalArgumentException</code> will be thrown.
    1642     *
    1643     * @param bean  Bean whose property is to be set
    1644     * @param name  <code>propertyname(key)</code> of the property value to be set
    1645     * @param value The property value to be set
    1646     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1647     * @throws InvocationTargetException if the property accessor method throws an exception
    1648     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1649     */
    1650    public void setMappedProperty(final Object bean, String name, final Object value)
    1651            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1652
    1653        if (bean == null) {
    1654            throw new IllegalArgumentException("No bean specified");
    1655        }
    1656        if (name == null) {
    1657            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1658        }
    1659
    1660        // Identify the key of the requested individual property
    1661        String key = null;
    1662        try {
    1663            key = resolver.getKey(name);
    1664        } catch (final IllegalArgumentException e) {
    1665            throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
    1666        }
    1667        if (key == null) {
    1668            throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
    1669        }
    1670
    1671        // Isolate the name
    1672        name = resolver.getProperty(name);
    1673
    1674        // Request the specified indexed property value
    1675        setMappedProperty(bean, name, key, value);
    1676
    1677    }
    1678
    1679    /**
    1680     * Set the value of the specified mapped property of the specified bean, with no type conversions.
    1681     *
    1682     * @param bean  Bean whose property is to be set
    1683     * @param name  Mapped property name of the property value to be set
    1684     * @param key   Key of the property value to be set
    1685     * @param value The property value to be set
    1686     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1687     * @throws InvocationTargetException if the property accessor method throws an exception
    1688     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1689     */
    1690    public void setMappedProperty(final Object bean, final String name, final String key, final Object value)
    1691            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1692
    1693        if (bean == null) {
    1694            throw new IllegalArgumentException("No bean specified");
    1695        }
    1696        if (name == null) {
    1697            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1698        }
    1699        if (key == null) {
    1700            throw new IllegalArgumentException("No key specified for property '" + name + "' on bean class '" + bean.getClass() + "'");
    1701        }
    1702
    1703        // Handle DynaBean instances specially
    1704        if (bean instanceof DynaBean) {
    1705            final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1706            if (descriptor == null) {
    1707                throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
    1708            }
    1709            ((DynaBean) bean).set(name, key, value);
    1710            return;
    1711        }
    1712
    1713        // Retrieve the property descriptor for the specified property
    1714        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    1715        if (descriptor == null) {
    1716            throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
    1717        }
    1718
    1719        if (descriptor instanceof MappedPropertyDescriptor) {
    1720            // Call the keyed setter method if there is one
    1721            Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor).getMappedWriteMethod();
    1722            mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
    1723            if (mappedWriteMethod == null) {
    1724                throw new NoSuchMethodException("Property '" + name + "' has no mapped setter method" + "on bean class '" + bean.getClass() + "'");
    1725            }
    1726            final Object[] params = new Object[2];
    1727            params[0] = key;
    1728            params[1] = value;
    1729            if (log.isTraceEnabled()) {
    1730                final String valueClassName = value == null ? "<null>" : value.getClass().getName();
    1731                log.trace("setSimpleProperty: Invoking method " + mappedWriteMethod + " with key=" + key + ", value=" + value + " (class " + valueClassName +
    1732                        ")");
    1733            }
    1734            invokeMethod(mappedWriteMethod, bean, params);
    1735        } else {
    1736            /* means that the result has to be retrieved from a map */
    1737            final Method readMethod = getReadMethod(bean.getClass(), descriptor);
    1738            if (readMethod == null) {
    1739                throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
    1740            }
    1741            final Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    1742            /* test and fetch from the map */
    1743            if (invokeResult instanceof java.util.Map) {
    1744                final java.util.Map<String, Object> map = toPropertyMap(invokeResult);
    1745                map.put(key, value);
    1746            }
    1747        }
    1748
    1749    }
    1750
    1751    /**
    1752     * Set the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions.
    1753     * <p>
    1754     * Example values for parameter "name" are:
    1755     * <ul>
    1756     * <li>"a" -- sets the value of property a of the specified bean</li>
    1757     * <li>"a.b" -- gets the value of property a of the specified bean, then on that object sets the value of property b.</li>
    1758     * <li>"a(key)" -- sets a value of mapped-property a on the specified bean. This effectively means bean.setA("key").</li>
    1759     * <li>"a[3]" -- sets a value of indexed-property a on the specified bean. This effectively means bean.setA(3).</li>
    1760     * </ul>
    1761     *
    1762     * @param bean  Bean whose property is to be modified
    1763     * @param name  Possibly nested name of the property to be modified
    1764     * @param value Value to which the property is to be set
    1765     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1766     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1767     * @throws IllegalArgumentException  if a nested reference to a property returns null
    1768     * @throws InvocationTargetException if the property accessor method throws an exception
    1769     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1770     */
    1771    public void setNestedProperty(Object bean, String name, final Object value)
    1772            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1773
    1774        if (bean == null) {
    1775            throw new IllegalArgumentException("No bean specified");
    1776        }
    1777        if (name == null) {
    1778            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1779        }
    1780
    1781        // Resolve nested references
    1782        while (resolver.hasNested(name)) {
    1783            final String next = resolver.next(name);
    1784            Object nestedBean = null;
    1785            if (bean instanceof Map) {
    1786                nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
    1787            } else if (resolver.isMapped(next)) {
    1788                nestedBean = getMappedProperty(bean, next);
    1789            } else if (resolver.isIndexed(next)) {
    1790                nestedBean = getIndexedProperty(bean, next);
    1791            } else {
    1792                nestedBean = getSimpleProperty(bean, next);
    1793            }
    1794            if (nestedBean == null) {
    1795                throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
    1796            }
    1797            bean = nestedBean;
    1798            name = resolver.remove(name);
    1799        }
    1800
    1801        if (bean instanceof Map) {
    1802            setPropertyOfMapBean(toPropertyMap(bean), name, value);
    1803        } else if (resolver.isMapped(name)) {
    1804            setMappedProperty(bean, name, value);
    1805        } else if (resolver.isIndexed(name)) {
    1806            setIndexedProperty(bean, name, value);
    1807        } else {
    1808            setSimpleProperty(bean, name, value);
    1809        }
    1810
    1811    }
    1812
    1813    /**
    1814     * Set the value of the specified property of the specified bean, no matter which property reference format is used, with no type conversions.
    1815     *
    1816     * @param bean  Bean whose property is to be modified
    1817     * @param name  Possibly indexed and/or nested name of the property to be modified
    1818     * @param value Value to which this property is to be set
    1819     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1820     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1821     * @throws InvocationTargetException if the property accessor method throws an exception
    1822     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1823     */
    1824    public void setProperty(final Object bean, final String name, final Object value)
    1825            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1826
    1827        setNestedProperty(bean, name, value);
    1828
    1829    }
    1830
    1831    /**
    1832     * This method is called by method setNestedProperty when the current bean is found to be a Map object, and defines how to deal with setting a property on a
    1833     * Map.
    1834     * <p>
    1835     * The standard implementation here is to:
    1836     * <ul>
    1837     * <li>call bean.set(propertyName) for all propertyName values.</li>
    1838     * <li>throw an IllegalArgumentException if the property specifier contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially simple properties;
    1839     * mapping and indexing operations do not make sense when accessing a map (even thought the returned object may be a Map or an Array).</li>
    1840     * </ul>
    1841     * <p>
    1842     * The default behaviour of beanutils 1.7.1 or later is for assigning to "a.b" to mean a.put(b, obj) always. However the behaviour of beanutils version
    1843     * 1.6.0, 1.6.1, 1.7.0 was for "a.b" to mean a.setB(obj) if such a method existed, and a.put(b, obj) otherwise. In version 1.5 it meant a.put(b, obj) always
    1844     * (ie the same as the behaviour in the current version). In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is all <em>very</em> unfortunate]
    1845     * <p>
    1846     * Users who would like to customise the meaning of "a.b" in method setNestedProperty when a is a Map can create a custom subclass of this class and
    1847     * override this method to implement the behaviour of their choice, such as restoring the pre-1.4 behaviour of this class if they wish. When overriding this
    1848     * method, do not forget to deal with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
    1849     * <p>
    1850     * Note, however, that the recommended solution for objects that implement Map but want their simple properties to come first is for <em>those</em> objects
    1851     * to override their get/put methods to implement that behaviour, and <em>not</em> to solve the problem by modifying the default behaviour of the
    1852     * PropertyUtilsBean class by overriding this method.
    1853     *
    1854     * @param bean         Map bean
    1855     * @param propertyName The property name
    1856     * @param value        the property value
    1857     * @throws IllegalArgumentException  when the propertyName is regarded as being invalid.
    1858     * @throws IllegalAccessException    just in case subclasses override this method to try to access real setter methods and find permission is denied.
    1859     * @throws InvocationTargetException just in case subclasses override this method to try to access real setter methods, and find it throws an exception when
    1860     *                                   invoked.
    1861     *
    1862     * @throws NoSuchMethodException     just in case subclasses override this method to try to access real setter methods, and want to fail if no simple method
    1863     *                                   is available.
    1864     * @since 1.8.0
    1865     */
    1866    protected void setPropertyOfMapBean(final Map<String, Object> bean, String propertyName, final Object value)
    1867            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1868
    1869        if (resolver.isMapped(propertyName)) {
    1870            final String name = resolver.getProperty(propertyName);
    1871            if (name == null || name.length() == 0) {
    1872                propertyName = resolver.getKey(propertyName);
    1873            }
    1874        }
    1875
    1876        if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) {
    1877            throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName);
    1878        }
    1879
    1880        bean.put(propertyName, value);
    1881    }
    1882
    1883    /**
    1884     * Configure the {@link Resolver} implementation used by BeanUtils.
    1885     * <p>
    1886     * The {@link Resolver} handles the <em>property name</em> expressions and the implementation in use effectively controls the dialect of the <em>expression
    1887     * language</em> that BeanUtils recongnises.
    1888     * <p>
    1889     * {@link DefaultResolver} is the default implementation used.
    1890     *
    1891     * @param resolver The property expression resolver.
    1892     * @since 1.8.0
    1893     */
    1894    public void setResolver(final Resolver resolver) {
    1895        if (resolver == null) {
    1896            this.resolver = new DefaultResolver();
    1897        } else {
    1898            this.resolver = resolver;
    1899        }
    1900    }
    1901
    1902    /**
    1903     * Set the value of the specified simple property of the specified bean, with no type conversions.
    1904     *
    1905     * @param bean  Bean whose property is to be modified
    1906     * @param name  Name of the property to be modified
    1907     * @param value Value to which the property should be set
    1908     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1909     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1910     * @throws IllegalArgumentException  if the property name is nested or indexed
    1911     * @throws InvocationTargetException if the property accessor method throws an exception
    1912     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1913     */
    1914    public void setSimpleProperty(final Object bean, final String name, final Object value)
    1915            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1916        if (bean == null) {
    1917            throw new IllegalArgumentException("No bean specified");
    1918        }
    1919        if (name == null) {
    1920            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1921        }
    1922        // Validate the syntax of the property name
    1923        if (resolver.hasNested(name)) {
    1924            throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1925        }
    1926        if (resolver.isIndexed(name)) {
    1927            throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1928        }
    1929        if (resolver.isMapped(name)) {
    1930            throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1931        }
    1932        // Handle DynaBean instances specially
    1933        if (bean instanceof DynaBean) {
    1934            final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1935            if (descriptor == null) {
    1936                throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'");
    1937            }
    1938            ((DynaBean) bean).set(name, value);
    1939            return;
    1940        }
    1941        // Retrieve the property setter method for the specified property
    1942        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    1943        if (descriptor == null) {
    1944            throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
    1945        }
    1946        final Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
    1947        if (writeMethod == null) {
    1948            throw new NoSuchMethodException("Property '" + name + "' has no setter method in class '" + bean.getClass() + "'");
    1949        }
    1950        // Call the property setter method
    1951        final Object[] values = new Object[1];
    1952        values[0] = value;
    1953        if (log.isTraceEnabled()) {
    1954            final String valueClassName = value == null ? "<null>" : value.getClass().getName();
    1955            log.trace("setSimpleProperty: Invoking method " + writeMethod + " with value " + value + " (class " + valueClassName + ")");
    1956        }
    1957        invokeMethod(writeMethod, bean, values);
    1958    }
    1959}