    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 (for example <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 (for example 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) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1201
    1202        if (bean == null) {
    1203            throw new IllegalArgumentException("No bean specified");
    1204        }
    1205        if (name == null) {
    1206            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1207        }
    1208
    1209        // Validate the syntax of the property name
    1210        if (resolver.hasNested(name)) {
    1211            throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1212        }
    1213        if (resolver.isIndexed(name)) {
    1214            throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1215        }
    1216        if (resolver.isMapped(name)) {
    1217            throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1218        }
    1219
    1220        // Handle DynaBean instances specially
    1221        if (bean instanceof DynaBean) {
    1222            final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1223            if (descriptor == null) {
    1224                throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'");
    1225            }
    1226            return ((DynaBean) bean).get(name);
    1227        }
    1228
    1229        // Retrieve the property getter method for the specified property
    1230        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    1231        if (descriptor == null) {
    1232            throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
    1233        }
    1234        final Method readMethod = getReadMethod(bean.getClass(), descriptor);
    1235        if (readMethod == null) {
    1236            throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
    1237        }
    1238
    1239        return invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    1240
    1241    }
    1242
    1243    /**
    1244     * <p>Return an accessible property setter method for this property,
    1245     * if there is one; otherwise return <code>null</code>.</p>
    1246     *
    1247     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1248     *
    1249     * @param clazz The class of the read method will be invoked on
    1250     * @param descriptor Property descriptor to return a setter for
    1251     * @return The write method
    1252     * @since 1.9.1
    1253     */
    1254    public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
    1255        final BeanIntrospectionData data = getIntrospectionData(clazz);
    1256        return MethodUtils.getAccessibleMethod(clazz,
    1257                data.getWriteMethod(clazz, descriptor));
    1258    }
    1259
    1260    /**
    1261     * <p>Return an accessible property setter method for this property,
    1262     * if there is one; otherwise return <code>null</code>.</p>
    1263     *
    1264     * <p><em>Note:</em> This method does not work correctly with custom bean
    1265     * introspection under certain circumstances. It may return {@code null}
    1266     * even if a write method is defined for the property in question. Use
    1267     * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the
    1268     * correct result is returned.</p>
    1269     * <p><strong>FIXME</strong> - Does not work with DynaBeans.</p>
    1270     *
    1271     * @param descriptor Property descriptor to return a setter for
    1272     * @return The write method
    1273     */
    1274    public Method getWriteMethod(final PropertyDescriptor descriptor) {
    1275
    1276        return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
    1277
    1278    }
    1279
    1280    /** This just catches and wraps IllegalArgumentException. */
    1281    private Object invokeMethod(final Method method, final Object bean, final Object[] values) throws IllegalAccessException, InvocationTargetException {
    1282        if (bean == null) {
    1283            throw new IllegalArgumentException("No bean specified " + "- this should have been checked before reaching this method");
    1284        }
    1285        try {
    1286            return method.invoke(bean, values);
    1287        } catch (final NullPointerException | IllegalArgumentException cause) {
    1288            final StringBuilder valueString = new StringBuilder();
    1289            if (values != null) {
    1290                for (int i = 0; i < values.length; i++) {
    1291                    if (i > 0) {
    1292                        valueString.append(", ");
    1293                    }
    1294                    if (values[i] == null) {
    1295                        valueString.append("<null>");
    1296                    } else {
    1297                        valueString.append(values[i].getClass().getName());
    1298                    }
    1299                }
    1300            }
    1301            final StringBuilder expectedString = new StringBuilder();
    1302            final Class<?>[] parTypes = method.getParameterTypes();
    1303            if (parTypes != null) {
    1304                for (int i = 0; i < parTypes.length; i++) {
    1305                    if (i > 0) {
    1306                        expectedString.append(", ");
    1307                    }
    1308                    expectedString.append(parTypes[i].getName());
    1309                }
    1310            }
    1311            throw new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " on bean class '" +
    1312                    bean.getClass() + "' - " + cause.getMessage() + " - had objects of type \"" +
    1313                    valueString.append("\" but expected signature \"").append(expectedString.toString()).append("\"").toString(), cause);
    1314
    1315        }
    1316    }
    1317
    1318    /**
    1319     * <p>
    1320     * Return <code>true</code> if the specified property name identifies a readable property on the specified bean; otherwise, return <code>false</code>.
    1321     *
    1322     * @param bean Bean to be examined (may be a {@link DynaBean}
    1323     * @param name Property name to be evaluated
    1324     * @return <code>true</code> if the property is readable, otherwise <code>false</code>
    1325     * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is <code>null</code>
    1326     * @since BeanUtils 1.6
    1327     */
    1328    public boolean isReadable(Object bean, String name) {
    1329
    1330        // Validate method parameters
    1331        if (bean == null) {
    1332            throw new IllegalArgumentException("No bean specified");
    1333        }
    1334        if (name == null) {
    1335            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1336        }
    1337
    1338        // Resolve nested references
    1339        while (resolver.hasNested(name)) {
    1340            final String next = resolver.next(name);
    1341            Object nestedBean = null;
    1342            try {
    1343                nestedBean = getProperty(bean, next);
    1344            } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    1345                return false;
    1346            }
    1347            if (nestedBean == null) {
    1348                throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
    1349            }
    1350            bean = nestedBean;
    1351            name = resolver.remove(name);
    1352        }
    1353
    1354        // Remove any subscript from the final name value
    1355        name = resolver.getProperty(name);
    1356
    1357        // Treat WrapDynaBean as special case - may be a write-only property
    1358        // (see Jira issue# BEANUTILS-61)
    1359        if (bean instanceof WrapDynaBean) {
    1360            bean = ((WrapDynaBean) bean).getInstance();
    1361        }
    1362
    1363        // Return the requested result
    1364        if (bean instanceof DynaBean) {
    1365            // All DynaBean properties are readable
    1366            return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null;
    1367        }
    1368        try {
    1369            final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
    1370            if (desc == null) {
    1371                return false;
    1372            }
    1373            Method readMethod = getReadMethod(bean.getClass(), desc);
    1374            if (readMethod == null) {
    1375                if (desc instanceof IndexedPropertyDescriptor) {
    1376                    readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
    1377                } else if (desc instanceof MappedPropertyDescriptor) {
    1378                    readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
    1379                }
    1380                readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
    1381            }
    1382            return readMethod != null;
    1383        } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    1384            return false;
    1385        }
    1386
    1387    }
    1388
    1389    /**
    1390     * <p>
    1391     * Return <code>true</code> if the specified property name identifies a writeable property on the specified bean; otherwise, return <code>false</code>.
    1392     *
    1393     * @param bean Bean to be examined (may be a {@link DynaBean}
    1394     * @param name Property name to be evaluated
    1395     * @return <code>true</code> if the property is writeable, otherwise <code>false</code>
    1396     * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is <code>null</code>
    1397     * @since BeanUtils 1.6
    1398     */
    1399    public boolean isWriteable(Object bean, String name) {
    1400
    1401        // Validate method parameters
    1402        if (bean == null) {
    1403            throw new IllegalArgumentException("No bean specified");
    1404        }
    1405        if (name == null) {
    1406            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1407        }
    1408
    1409        // Resolve nested references
    1410        while (resolver.hasNested(name)) {
    1411            final String next = resolver.next(name);
    1412            Object nestedBean = null;
    1413            try {
    1414                nestedBean = getProperty(bean, next);
    1415            } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    1416                return false;
    1417            }
    1418            if (nestedBean == null) {
    1419                throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
    1420            }
    1421            bean = nestedBean;
    1422            name = resolver.remove(name);
    1423        }
    1424
    1425        // Remove any subscript from the final name value
    1426        name = resolver.getProperty(name);
    1427
    1428        // Treat WrapDynaBean as special case - may be a read-only property
    1429        // (see Jira issue# BEANUTILS-61)
    1430        if (bean instanceof WrapDynaBean) {
    1431            bean = ((WrapDynaBean) bean).getInstance();
    1432        }
    1433
    1434        // Return the requested result
    1435        if (bean instanceof DynaBean) {
    1436            // All DynaBean properties are writeable
    1437            return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null;
    1438        }
    1439        try {
    1440            final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
    1441            if (desc == null) {
    1442                return false;
    1443            }
    1444            Method writeMethod = getWriteMethod(bean.getClass(), desc);
    1445            if (writeMethod == null) {
    1446                if (desc instanceof IndexedPropertyDescriptor) {
    1447                    writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
    1448                } else if (desc instanceof MappedPropertyDescriptor) {
    1449                    writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
    1450                }
    1451                writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
    1452            }
    1453            return writeMethod != null;
    1454        } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    1455            return false;
    1456        }
    1457
    1458    }
    1459
    1460    /**
    1461     * Removes the specified <code>BeanIntrospector</code>.
    1462     *
    1463     * @param introspector the <code>BeanIntrospector</code> to be removed
    1464     * @return <strong>true</strong> if the <code>BeanIntrospector</code> existed and could be removed, <strong>false</strong> otherwise
    1465     * @since 1.9
    1466     */
    1467    public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
    1468        return introspectors.remove(introspector);
    1469    }
    1470
    1471    /**
    1472     * Resets the {@link BeanIntrospector} objects registered at this instance. After this method was called, only the default {@code BeanIntrospector} is
    1473     * registered.
    1474     *
    1475     * @since 1.9
    1476     */
    1477    public final void resetBeanIntrospectors() {
    1478        introspectors.clear();
    1479        introspectors.add(DefaultBeanIntrospector.INSTANCE);
    1480        introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
    1481    }
    1482
    1483    /**
    1484     * Set the value of the specified indexed property of the specified bean, with no type conversions. In addition to supporting the JavaBeans specification,
    1485     * this method has been extended to support <code>List</code> objects as well.
    1486     *
    1487     * @param bean  Bean whose property is to be set
    1488     * @param name  Simple property name of the property value to be set
    1489     * @param index Index of the property value to be set
    1490     * @param value Value to which the indexed property element is to be set
    1491     * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property
    1492     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1493     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1494     * @throws InvocationTargetException if the property accessor method throws an exception
    1495     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1496     */
    1497    public void setIndexedProperty(final Object bean, final String name, final int index, final Object value)
    1498            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1499
    1500        if (bean == null) {
    1501            throw new IllegalArgumentException("No bean specified");
    1502        }
    1503        if (name == null || name.length() == 0) {
    1504            if (bean.getClass().isArray()) {
    1505                Array.set(bean, index, value);
    1506                return;
    1507            }
    1508            if (bean instanceof List) {
    1509                final List<Object> list = toObjectList(bean);
    1510                list.set(index, value);
    1511                return;
    1512            }
    1513        }
    1514        if (name == null) {
    1515            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1516        }
    1517
    1518        // Handle DynaBean instances specially
    1519        if (bean instanceof DynaBean) {
    1520            final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1521            if (descriptor == null) {
    1522                throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
    1523            }
    1524            ((DynaBean) bean).set(name, index, value);
    1525            return;
    1526        }
    1527
    1528        // Retrieve the property descriptor for the specified property
    1529        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    1530        if (descriptor == null) {
    1531            throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
    1532        }
    1533
    1534        // Call the indexed setter method if there is one
    1535        if (descriptor instanceof IndexedPropertyDescriptor) {
    1536            Method writeMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod();
    1537            writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
    1538            if (writeMethod != null) {
    1539                final Object[] subscript = new Object[2];
    1540                subscript[0] = Integer.valueOf(index);
    1541                subscript[1] = value;
    1542                try {
    1543                    if (log.isTraceEnabled()) {
    1544                        final String valueClassName = value == null ? "<null>" : value.getClass().getName();
    1545                        log.trace("setSimpleProperty: Invoking method " + writeMethod + " with index=" + index + ", value=" + value + " (class " +
    1546                                valueClassName + ")");
    1547                    }
    1548                    invokeMethod(writeMethod, bean, subscript);
    1549                } catch (final InvocationTargetException e) {
    1550                    if (e.getTargetException() instanceof IndexOutOfBoundsException) {
    1551                        throw (IndexOutOfBoundsException) e.getTargetException();
    1552                    }
    1553                    throw e;
    1554                }
    1555                return;
    1556            }
    1557        }
    1558
    1559        // Otherwise, the underlying property must be an array or a list
    1560        final Method readMethod = getReadMethod(bean.getClass(), descriptor);
    1561        if (readMethod == null) {
    1562            throw new NoSuchMethodException("Property '" + name + "' has no getter method on bean class '" + bean.getClass() + "'");
    1563        }
    1564
    1565        // Call the property getter to get the array or list
    1566        final Object array = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    1567        if (!array.getClass().isArray()) {
    1568            if (!(array instanceof List)) {
    1569                throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
    1570            }
    1571            // Modify the specified value in the List
    1572            final List<Object> list = toObjectList(array);
    1573            list.set(index, value);
    1574        } else {
    1575            // Modify the specified value in the array
    1576            Array.set(array, index, value);
    1577        }
    1578
    1579    }
    1580
    1581    /**
    1582     * 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
    1583     * included (in square brackets) as a suffix to the property name, or <code>IllegalArgumentException</code> will be thrown. In addition to supporting the
    1584     * JavaBeans specification, this method has been extended to support <code>List</code> objects as well.
    1585     *
    1586     * @param bean  Bean whose property is to be modified
    1587     * @param name  <code>propertyname[index]</code> of the property value to be modified
    1588     * @param value Value to which the specified property element should be set
    1589     * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property
    1590     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1591     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1592     * @throws InvocationTargetException if the property accessor method throws an exception
    1593     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1594     */
    1595    public void setIndexedProperty(final Object bean, String name, final Object value)
    1596            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1597
    1598        if (bean == null) {
    1599            throw new IllegalArgumentException("No bean specified");
    1600        }
    1601        if (name == null) {
    1602            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1603        }
    1604
    1605        // Identify the index of the requested individual property
    1606        int index = -1;
    1607        try {
    1608            index = resolver.getIndex(name);
    1609        } catch (final IllegalArgumentException e) {
    1610            throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
    1611        }
    1612        if (index < 0) {
    1613            throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
    1614        }
    1615
    1616        // Isolate the name
    1617        name = resolver.getProperty(name);
    1618
    1619        // Set the specified indexed property value
    1620        setIndexedProperty(bean, name, index, value);
    1621
    1622    }
    1623
    1624    /**
    1625     * 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
    1626     * brackets) as a suffix to the property name, or <code>IllegalArgumentException</code> will be thrown.
    1627     *
    1628     * @param bean  Bean whose property is to be set
    1629     * @param name  <code>propertyname(key)</code> of the property value to be set
    1630     * @param value The property value to be set
    1631     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1632     * @throws InvocationTargetException if the property accessor method throws an exception
    1633     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1634     */
    1635    public void setMappedProperty(final Object bean, String name, final Object value)
    1636            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1637
    1638        if (bean == null) {
    1639            throw new IllegalArgumentException("No bean specified");
    1640        }
    1641        if (name == null) {
    1642            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1643        }
    1644
    1645        // Identify the key of the requested individual property
    1646        String key = null;
    1647        try {
    1648            key = resolver.getKey(name);
    1649        } catch (final IllegalArgumentException e) {
    1650            throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
    1651        }
    1652        if (key == null) {
    1653            throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
    1654        }
    1655
    1656        // Isolate the name
    1657        name = resolver.getProperty(name);
    1658
    1659        // Request the specified indexed property value
    1660        setMappedProperty(bean, name, key, value);
    1661
    1662    }
    1663
    1664    /**
    1665     * Set the value of the specified mapped property of the specified bean, with no type conversions.
    1666     *
    1667     * @param bean  Bean whose property is to be set
    1668     * @param name  Mapped property name of the property value to be set
    1669     * @param key   Key of the property value to be set
    1670     * @param value The property value to be set
    1671     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1672     * @throws InvocationTargetException if the property accessor method throws an exception
    1673     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1674     */
    1675    public void setMappedProperty(final Object bean, final String name, final String key, final Object value)
    1676            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1677
    1678        if (bean == null) {
    1679            throw new IllegalArgumentException("No bean specified");
    1680        }
    1681        if (name == null) {
    1682            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1683        }
    1684        if (key == null) {
    1685            throw new IllegalArgumentException("No key specified for property '" + name + "' on bean class '" + bean.getClass() + "'");
    1686        }
    1687
    1688        // Handle DynaBean instances specially
    1689        if (bean instanceof DynaBean) {
    1690            final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1691            if (descriptor == null) {
    1692                throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
    1693            }
    1694            ((DynaBean) bean).set(name, key, value);
    1695            return;
    1696        }
    1697
    1698        // Retrieve the property descriptor for the specified property
    1699        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    1700        if (descriptor == null) {
    1701            throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
    1702        }
    1703
    1704        if (descriptor instanceof MappedPropertyDescriptor) {
    1705            // Call the keyed setter method if there is one
    1706            Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor).getMappedWriteMethod();
    1707            mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
    1708            if (mappedWriteMethod == null) {
    1709                throw new NoSuchMethodException("Property '" + name + "' has no mapped setter method" + "on bean class '" + bean.getClass() + "'");
    1710            }
    1711            final Object[] params = new Object[2];
    1712            params[0] = key;
    1713            params[1] = value;
    1714            if (log.isTraceEnabled()) {
    1715                final String valueClassName = value == null ? "<null>" : value.getClass().getName();
    1716                log.trace("setSimpleProperty: Invoking method " + mappedWriteMethod + " with key=" + key + ", value=" + value + " (class " + valueClassName +
    1717                        ")");
    1718            }
    1719            invokeMethod(mappedWriteMethod, bean, params);
    1720        } else {
    1721            /* means that the result has to be retrieved from a map */
    1722            final Method readMethod = getReadMethod(bean.getClass(), descriptor);
    1723            if (readMethod == null) {
    1724                throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
    1725            }
    1726            final Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
    1727            /* test and fetch from the map */
    1728            if (invokeResult instanceof java.util.Map) {
    1729                final java.util.Map<String, Object> map = toPropertyMap(invokeResult);
    1730                map.put(key, value);
    1731            }
    1732        }
    1733
    1734    }
    1735
    1736    /**
    1737     * Set the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions.
    1738     * <p>
    1739     * Example values for parameter "name" are:
    1740     * <ul>
    1741     * <li>"a" -- sets the value of property a of the specified bean</li>
    1742     * <li>"a.b" -- gets the value of property a of the specified bean, then on that object sets the value of property b.</li>
    1743     * <li>"a(key)" -- sets a value of mapped-property a on the specified bean. This effectively means bean.setA("key").</li>
    1744     * <li>"a[3]" -- sets a value of indexed-property a on the specified bean. This effectively means bean.setA(3).</li>
    1745     * </ul>
    1746     *
    1747     * @param bean  Bean whose property is to be modified
    1748     * @param name  Possibly nested name of the property to be modified
    1749     * @param value Value to which the property is to be set
    1750     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1751     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1752     * @throws IllegalArgumentException  if a nested reference to a property returns null
    1753     * @throws InvocationTargetException if the property accessor method throws an exception
    1754     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1755     */
    1756    public void setNestedProperty(Object bean, String name, final Object value)
    1757            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1758
    1759        if (bean == null) {
    1760            throw new IllegalArgumentException("No bean specified");
    1761        }
    1762        if (name == null) {
    1763            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1764        }
    1765
    1766        // Resolve nested references
    1767        while (resolver.hasNested(name)) {
    1768            final String next = resolver.next(name);
    1769            Object nestedBean = null;
    1770            if (bean instanceof Map) {
    1771                nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
    1772            } else if (resolver.isMapped(next)) {
    1773                nestedBean = getMappedProperty(bean, next);
    1774            } else if (resolver.isIndexed(next)) {
    1775                nestedBean = getIndexedProperty(bean, next);
    1776            } else {
    1777                nestedBean = getSimpleProperty(bean, next);
    1778            }
    1779            if (nestedBean == null) {
    1780                throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
    1781            }
    1782            bean = nestedBean;
    1783            name = resolver.remove(name);
    1784        }
    1785
    1786        if (bean instanceof Map) {
    1787            setPropertyOfMapBean(toPropertyMap(bean), name, value);
    1788        } else if (resolver.isMapped(name)) {
    1789            setMappedProperty(bean, name, value);
    1790        } else if (resolver.isIndexed(name)) {
    1791            setIndexedProperty(bean, name, value);
    1792        } else {
    1793            setSimpleProperty(bean, name, value);
    1794        }
    1795
    1796    }
    1797
    1798    /**
    1799     * Set the value of the specified property of the specified bean, no matter which property reference format is used, with no type conversions.
    1800     *
    1801     * @param bean  Bean whose property is to be modified
    1802     * @param name  Possibly indexed and/or nested name of the property to be modified
    1803     * @param value Value to which this property is to be set
    1804     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1805     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1806     * @throws InvocationTargetException if the property accessor method throws an exception
    1807     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1808     */
    1809    public void setProperty(final Object bean, final String name, final Object value)
    1810            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1811
    1812        setNestedProperty(bean, name, value);
    1813
    1814    }
    1815
    1816    /**
    1817     * 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
    1818     * Map.
    1819     * <p>
    1820     * The standard implementation here is to:
    1821     * <ul>
    1822     * <li>call bean.set(propertyName) for all propertyName values.</li>
    1823     * <li>throw an IllegalArgumentException if the property specifier contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially simple properties;
    1824     * 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>
    1825     * </ul>
    1826     * <p>
    1827     * The default behavior of beanutils 1.7.1 or later is for assigning to "a.b" to mean a.put(b, obj) always. However the behavior of beanutils version
    1828     * 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
    1829     * (ie the same as the behavior in the current version). In versions prior to 1.5 it meant a.setB(obj) always. [yes, this is all <em>very</em> unfortunate]
    1830     * <p>
    1831     * Users who would like to customize the meaning of "a.b" in method setNestedProperty when a is a Map can create a custom subclass of this class and
    1832     * override this method to implement the behavior of their choice, such as restoring the pre-1.4 behavior of this class if they wish. When overriding this
    1833     * method, do not forget to deal with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
    1834     * <p>
    1835     * 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
    1836     * to override their get/put methods to implement that behavior, and <em>not</em> to solve the problem by modifying the default behavior of the
    1837     * PropertyUtilsBean class by overriding this method.
    1838     *
    1839     * @param bean         Map bean
    1840     * @param propertyName The property name
    1841     * @param value        the property value
    1842     * @throws IllegalArgumentException  when the propertyName is regarded as being invalid.
    1843     * @throws IllegalAccessException    just in case subclasses override this method to try to access real setter methods and find permission is denied.
    1844     * @throws InvocationTargetException just in case subclasses override this method to try to access real setter methods, and find it throws an exception when
    1845     *                                   invoked.
    1846     *
    1847     * @throws NoSuchMethodException     just in case subclasses override this method to try to access real setter methods, and want to fail if no simple method
    1848     *                                   is available.
    1849     * @since 1.8.0
    1850     */
    1851    protected void setPropertyOfMapBean(final Map<String, Object> bean, String propertyName, final Object value)
    1852            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1853
    1854        if (resolver.isMapped(propertyName)) {
    1855            final String name = resolver.getProperty(propertyName);
    1856            if (name == null || name.length() == 0) {
    1857                propertyName = resolver.getKey(propertyName);
    1858            }
    1859        }
    1860
    1861        if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) {
    1862            throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName);
    1863        }
    1864
    1865        bean.put(propertyName, value);
    1866    }
    1867
    1868    /**
    1869     * Configure the {@link Resolver} implementation used by BeanUtils.
    1870     * <p>
    1871     * The {@link Resolver} handles the <em>property name</em> expressions and the implementation in use effectively controls the dialect of the <em>expression
    1872     * language</em> that BeanUtils recongnises.
    1873     * <p>
    1874     * {@link DefaultResolver} is the default implementation used.
    1875     *
    1876     * @param resolver The property expression resolver.
    1877     * @since 1.8.0
    1878     */
    1879    public void setResolver(final Resolver resolver) {
    1880        if (resolver == null) {
    1881            this.resolver = new DefaultResolver();
    1882        } else {
    1883            this.resolver = resolver;
    1884        }
    1885    }
    1886
    1887    /**
    1888     * Set the value of the specified simple property of the specified bean, with no type conversions.
    1889     *
    1890     * @param bean  Bean whose property is to be modified
    1891     * @param name  Name of the property to be modified
    1892     * @param value Value to which the property should be set
    1893     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
    1894     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
    1895     * @throws IllegalArgumentException  if the property name is nested or indexed
    1896     * @throws InvocationTargetException if the property accessor method throws an exception
    1897     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
    1898     */
    1899    public void setSimpleProperty(final Object bean, final String name, final Object value)
    1900            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    1901        if (bean == null) {
    1902            throw new IllegalArgumentException("No bean specified");
    1903        }
    1904        if (name == null) {
    1905            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
    1906        }
    1907        // Validate the syntax of the property name
    1908        if (resolver.hasNested(name)) {
    1909            throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1910        }
    1911        if (resolver.isIndexed(name)) {
    1912            throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1913        }
    1914        if (resolver.isMapped(name)) {
    1915            throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
    1916        }
    1917        // Handle DynaBean instances specially
    1918        if (bean instanceof DynaBean) {
    1919            final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
    1920            if (descriptor == null) {
    1921                throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'");
    1922            }
    1923            ((DynaBean) bean).set(name, value);
    1924            return;
    1925        }
    1926        // Retrieve the property setter method for the specified property
    1927        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
    1928        if (descriptor == null) {
    1929            throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
    1930        }
    1931        final Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
    1932        if (writeMethod == null) {
    1933            throw new NoSuchMethodException("Property '" + name + "' has no setter method in class '" + bean.getClass() + "'");
    1934        }
    1935        // Call the property setter method
    1936        final Object[] values = new Object[1];
    1937        values[0] = value;
    1938        if (log.isTraceEnabled()) {
    1939            final String valueClassName = value == null ? "<null>" : value.getClass().getName();
    1940            log.trace("setSimpleProperty: Invoking method " + writeMethod + " with value " + value + " (class " + valueClassName + ")");
    1941        }
    1942        invokeMethod(writeMethod, bean, values);
    1943    }
    1944}