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