PropertyUtilsBean.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.beanutils2;

  18. import java.beans.IndexedPropertyDescriptor;
  19. import java.beans.IntrospectionException;
  20. import java.beans.Introspector;
  21. import java.beans.PropertyDescriptor;
  22. import java.lang.reflect.Array;
  23. import java.lang.reflect.InvocationTargetException;
  24. import java.lang.reflect.Method;
  25. import java.util.HashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Objects;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import java.util.concurrent.CopyOnWriteArrayList;

  31. import org.apache.commons.beanutils2.expression.DefaultResolver;
  32. import org.apache.commons.beanutils2.expression.Resolver;
  33. import org.apache.commons.logging.Log;
  34. import org.apache.commons.logging.LogFactory;

  35. /**
  36.  * Utility methods for using Java Reflection APIs to facilitate generic property getter and setter operations on Java objects. Much of this code was originally
  37.  * included in {@code BeanUtils}, but has been separated because of the volume of code involved.
  38.  * <p>
  39.  * In general, the objects that are examined and modified using these methods are expected to conform to the property getter and setter method naming
  40.  * conventions described in the JavaBeans Specification (Version 1.0.1). No data type conversions are performed, and there are no usage of any
  41.  * {@code PropertyEditor} classes that have been registered, although a convenient way to access the registered classes themselves is included.
  42.  * <p>
  43.  * 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
  44.  * 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
  45.  * configured {@link Resolver} implementation:
  46.  * <ul>
  47.  * <li><strong>Simple ({@code name})</strong> - The specified {@code name} identifies an individual property of a particular JavaBean. The name of the actual
  48.  * getter or setter method to be used is determined using standard JavaBeans introspection, so that (unless overridden by a {@code BeanInfo} class, a property
  49.  * named "xyz" will have a getter method named {@code getXyz()} or (for boolean properties only) {@code isXyz()}, and a setter method named
  50.  * {@code setXyz()}.</li>
  51.  * <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
  52.  * 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
  53.  * property value that is ultimately retrieved or modified is the one identified by the last name element.</li>
  54.  * <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
  55.  * property getter and setter methods. The appropriate (zero-relative) entry in the array is selected. {@code List} objects are now also supported for
  56.  * read/write. You simply need to define a getter that returns the {@code List}</li>
  57.  * <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
  58.  * {@link String}.</li>
  59.  * <li><strong>Combined ({@code name1.name2[index].name3(key)})</strong> - Combining mapped, nested, and indexed references is also supported.</li>
  60.  * </ul>
  61.  *
  62.  * @see Resolver
  63.  * @see PropertyUtils
  64.  * @since 1.7
  65.  */
  66. public class PropertyUtilsBean {

  67.     /** Log instance */
  68.     private static final Log LOG = LogFactory.getLog(PropertyUtilsBean.class);

  69.     /**
  70.      * Gets the PropertyUtils bean instance.
  71.      *
  72.      * @return The PropertyUtils bean instance
  73.      */
  74.     protected static PropertyUtilsBean getInstance() {
  75.         return BeanUtilsBean.getInstance().getPropertyUtils();
  76.     }

  77.     /**
  78.      * 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
  79.      * of objects.
  80.      *
  81.      * @param obj the object to be converted
  82.      * @return the resulting list of objects
  83.      */
  84.     @SuppressWarnings("unchecked")
  85.     private static List<Object> toObjectList(final Object obj) {
  86.         // indexed properties are stored in lists of objects
  87.         return (List<Object>) obj;
  88.     }

  89.     /**
  90.      * 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
  91.      * in a Map&lt;String, Object&gt;.
  92.      *
  93.      * @param obj the object to be converted
  94.      * @return the resulting properties map
  95.      */
  96.     @SuppressWarnings("unchecked")
  97.     private static Map<String, Object> toPropertyMap(final Object obj) {
  98.         // mapped properties are stores in maps of type <String, Object>
  99.         return (Map<String, Object>) obj;
  100.     }

  101.     private Resolver resolver = new DefaultResolver();

  102.     /**
  103.      * The cache of PropertyDescriptor arrays for beans we have already introspected, keyed by the java.lang.Class of this object.
  104.      */
  105.     private final Map<Class<?>, BeanIntrospectionData> descriptorsCache;

  106.     private final Map<Class<?>, Map> mappedDescriptorsCache;

  107.     /** The list with BeanIntrospector objects. */
  108.     private final List<BeanIntrospector> introspectors;

  109.     /** Base constructor */
  110.     public PropertyUtilsBean() {
  111.         descriptorsCache = BeanUtils.createCache();
  112.         mappedDescriptorsCache = BeanUtils.createCache();
  113.         introspectors = new CopyOnWriteArrayList<>();
  114.         resetBeanIntrospectors();
  115.     }

  116.     /**
  117.      * Adds a {@code BeanIntrospector}. This object is invoked when the property descriptors of a class need to be obtained.
  118.      *
  119.      * @param introspector the {@code BeanIntrospector} to be added (must not be <strong>null</strong>
  120.      * @throws IllegalArgumentException if the argument is <strong>null</strong>
  121.      * @since 1.9
  122.      */
  123.     public void addBeanIntrospector(final BeanIntrospector introspector) {
  124.         introspectors.add(Objects.requireNonNull(introspector, "introspector"));
  125.     }

  126.     /**
  127.      * Clear any cached property descriptors information for all classes loaded by any class loaders. This is useful in cases where class loaders are thrown
  128.      * away to implement class reloading.
  129.      */
  130.     public void clearDescriptors() {
  131.         descriptorsCache.clear();
  132.         mappedDescriptorsCache.clear();
  133.         Introspector.flushCaches();
  134.     }

  135.     /**
  136.      * <p>
  137.      * 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
  138.      * and setter methods might have been customized via {@code BeanInfo} classes). No conversions are performed on the actual property values -- it is assumed
  139.      * that the values retrieved from the origin bean are assignment-compatible with the types expected by the destination bean.
  140.      * </p>
  141.      *
  142.      * <p>
  143.      * 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
  144.      * 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"
  145.      * of the properties and so complex properties (for example, nested ones) will not be copied.
  146.      * </p>
  147.      *
  148.      * <p>
  149.      * 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.
  150.      * </p>
  151.      *
  152.      * @param dest Destination bean whose properties are modified
  153.      * @param orig Origin bean whose properties are retrieved
  154.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  155.      * @throws IllegalArgumentException  if the {@code dest} or {@code orig} argument is null
  156.      * @throws InvocationTargetException if the property accessor method throws an exception
  157.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  158.      */
  159.     public void copyProperties(final Object dest, final Object orig) throws IllegalAccessException, InvocationTargetException,
  160.             // TODO BEFORE 2.0
  161.             // MISMATCH between implementation and Javadoc.
  162.             NoSuchMethodException {
  163.         Objects.requireNonNull(dest, "dest");
  164.         Objects.requireNonNull(orig, "orig");
  165.         if (orig instanceof DynaBean) {
  166.             final DynaProperty[] origDescriptors = ((DynaBean) orig).getDynaClass().getDynaProperties();
  167.             for (final DynaProperty origDescriptor : origDescriptors) {
  168.                 final String name = origDescriptor.getName();
  169.                 if (isReadable(orig, name) && isWriteable(dest, name)) {
  170.                     try {
  171.                         final Object value = ((DynaBean) orig).get(name);
  172.                         if (dest instanceof DynaBean) {
  173.                             ((DynaBean) dest).set(name, value);
  174.                         } else {
  175.                             setSimpleProperty(dest, name, value);
  176.                         }
  177.                     } catch (final NoSuchMethodException e) {
  178.                         if (LOG.isDebugEnabled()) {
  179.                             LOG.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
  180.                         }
  181.                     }
  182.                 }
  183.             }
  184.         } else if (orig instanceof Map) {
  185.             for (final Map.Entry<?, ?> entry : ((Map<?, ?>) orig).entrySet()) {
  186.                 final String name = (String) entry.getKey();
  187.                 if (isWriteable(dest, name)) {
  188.                     try {
  189.                         if (dest instanceof DynaBean) {
  190.                             ((DynaBean) dest).set(name, entry.getValue());
  191.                         } else {
  192.                             setSimpleProperty(dest, name, entry.getValue());
  193.                         }
  194.                     } catch (final NoSuchMethodException e) {
  195.                         if (LOG.isDebugEnabled()) {
  196.                             LOG.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
  197.                         }
  198.                     }
  199.                 }
  200.             }
  201.         } else /* if (orig is a standard JavaBean) */ {
  202.             final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
  203.             for (final PropertyDescriptor origDescriptor : origDescriptors) {
  204.                 final String name = origDescriptor.getName();
  205.                 if (isReadable(orig, name) && isWriteable(dest, name)) {
  206.                     try {
  207.                         final Object value = getSimpleProperty(orig, name);
  208.                         if (dest instanceof DynaBean) {
  209.                             ((DynaBean) dest).set(name, value);
  210.                         } else {
  211.                             setSimpleProperty(dest, name, value);
  212.                         }
  213.                     } catch (final NoSuchMethodException e) {
  214.                         if (LOG.isDebugEnabled()) {
  215.                             LOG.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
  216.                         }
  217.                     }
  218.                 }
  219.             }
  220.         }

  221.     }

  222.     /**
  223.      * <p>
  224.      * Return the entire set of properties for which the specified bean provides a read method. This map contains the unconverted property values for all
  225.      * properties for which a read method is provided (i.e. where the {@code getReadMethod()} returns non-null).
  226.      * </p>
  227.      *
  228.      * <p>
  229.      * <strong>FIXME</strong> - Does not account for mapped properties.
  230.      * </p>
  231.      *
  232.      * @param bean Bean whose properties are to be extracted
  233.      * @return The set of properties for the bean
  234.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  235.      * @throws IllegalArgumentException  if {@code bean} is null
  236.      * @throws InvocationTargetException if the property accessor method throws an exception
  237.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  238.      */
  239.     public Map<String, Object> describe(final Object bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  240.         Objects.requireNonNull(bean, "bean");
  241.         final Map<String, Object> description = new HashMap<>();
  242.         if (bean instanceof DynaBean) {
  243.             final DynaProperty[] descriptors = ((DynaBean) bean).getDynaClass().getDynaProperties();
  244.             for (final DynaProperty descriptor : descriptors) {
  245.                 final String name = descriptor.getName();
  246.                 description.put(name, getProperty(bean, name));
  247.             }
  248.         } else {
  249.             final PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
  250.             for (final PropertyDescriptor descriptor : descriptors) {
  251.                 final String name = descriptor.getName();
  252.                 if (descriptor.getReadMethod() != null) {
  253.                     description.put(name, getProperty(bean, name));
  254.                 }
  255.             }
  256.         }
  257.         return description;
  258.     }

  259.     /**
  260.      * Performs introspection on the specified class. This method invokes all {@code BeanIntrospector} objects that were added to this instance.
  261.      *
  262.      * @param beanClass the class to be inspected
  263.      * @return a data object with the results of introspection
  264.      */
  265.     private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
  266.         final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);

  267.         for (final BeanIntrospector bi : introspectors) {
  268.             try {
  269.                 bi.introspect(ictx);
  270.             } catch (final IntrospectionException iex) {
  271.                 LOG.error("Exception during introspection", iex);
  272.             }
  273.         }

  274.         return new BeanIntrospectionData(ictx.getPropertyDescriptors());
  275.     }

  276.     /**
  277.      * 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
  278.      * included (in square brackets) as a suffix to the property name, or {@code IllegalArgumentException} will be thrown. In addition to supporting the
  279.      * JavaBeans specification, this method has been extended to support {@code List} objects as well.
  280.      *
  281.      * @param bean Bean whose property is to be extracted
  282.      * @param name {@code propertyname[index]} of the property value to be extracted
  283.      * @return the indexed property value
  284.      * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying array or List
  285.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  286.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  287.      * @throws InvocationTargetException if the property accessor method throws an exception
  288.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  289.      */
  290.     public Object getIndexedProperty(final Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  291.         Objects.requireNonNull(bean, "bean");
  292.         Objects.requireNonNull(name, "name");
  293.         // Identify the index of the requested individual property
  294.         int index = -1;
  295.         try {
  296.             index = resolver.getIndex(name);
  297.         } catch (final IllegalArgumentException e) {
  298.             throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "' " + e.getMessage());
  299.         }
  300.         if (index < 0) {
  301.             throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
  302.         }

  303.         // Isolate the name
  304.         name = resolver.getProperty(name);

  305.         // Request the specified indexed property value
  306.         return getIndexedProperty(bean, name, index);
  307.     }

  308.     /**
  309.      * Gets the value of the specified indexed property of the specified bean, with no type conversions. In addition to supporting the JavaBeans specification,
  310.      * this method has been extended to support {@code List} objects as well.
  311.      *
  312.      * @param bean  Bean whose property is to be extracted
  313.      * @param name  Simple property name of the property value to be extracted
  314.      * @param index Index of the property value to be extracted
  315.      * @return the indexed property value
  316.      * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property
  317.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  318.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  319.      * @throws InvocationTargetException if the property accessor method throws an exception
  320.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  321.      */
  322.     public Object getIndexedProperty(final Object bean, final String name, final int index)
  323.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  324.         Objects.requireNonNull(bean, "bean");
  325.         if (name == null || name.isEmpty()) {
  326.             if (bean.getClass().isArray()) {
  327.                 return Array.get(bean, index);
  328.             }
  329.             if (bean instanceof List) {
  330.                 return ((List<?>) bean).get(index);
  331.             }
  332.         }
  333.         Objects.requireNonNull(name, "name");
  334.         // Handle DynaBean instances specially
  335.         if (bean instanceof DynaBean) {
  336.             final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  337.             if (descriptor == null) {
  338.                 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
  339.             }
  340.             return ((DynaBean) bean).get(name, index);
  341.         }

  342.         // Retrieve the property descriptor for the specified property
  343.         final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  344.         if (descriptor == null) {
  345.             throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
  346.         }

  347.         // Call the indexed getter method if there is one
  348.         if (descriptor instanceof IndexedPropertyDescriptor) {
  349.             Method readMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedReadMethod();
  350.             readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
  351.             if (readMethod != null) {
  352.                 try {
  353.                     return invokeMethod(readMethod, bean, Integer.valueOf(index));
  354.                 } catch (final InvocationTargetException e) {
  355.                     if (e.getTargetException() instanceof IndexOutOfBoundsException) {
  356.                         throw (IndexOutOfBoundsException) e.getTargetException();
  357.                     }
  358.                     throw e;
  359.                 }
  360.             }
  361.         }

  362.         // Otherwise, the underlying property must be an array
  363.         final Method readMethod = getReadMethod(bean.getClass(), descriptor);
  364.         if (readMethod == null) {
  365.             throw new NoSuchMethodException("Property '" + name + "' has no " + "getter method on bean class '" + bean.getClass() + "'");
  366.         }

  367.         // Call the property getter and return the value
  368.         final Object value = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
  369.         if (!value.getClass().isArray()) {
  370.             if (!(value instanceof List)) {
  371.                 throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
  372.             }
  373.             // get the List's value
  374.             return ((List<?>) value).get(index);
  375.         }
  376.         // get the array's value
  377.         try {
  378.             return Array.get(value, index);
  379.         } catch (final ArrayIndexOutOfBoundsException e) {
  380.             throw new ArrayIndexOutOfBoundsException("Index: " + index + ", Size: " + Array.getLength(value) + " for property '" + name + "'");
  381.         }
  382.     }

  383.     /**
  384.      * Obtains the {@code BeanIntrospectionData} object describing the specified bean class. This object is looked up in the internal cache. If necessary,
  385.      * introspection is performed now on the affected bean class, and the results object is created.
  386.      *
  387.      * @param beanClass the bean class in question
  388.      * @return the {@code BeanIntrospectionData} object for this class
  389.      * @throws IllegalArgumentException if the bean class is <strong>null</strong>
  390.      */
  391.     private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
  392.         Objects.requireNonNull(beanClass, "beanClass");
  393.         // Look up any cached information for this bean class
  394.         BeanIntrospectionData data = descriptorsCache.get(beanClass);
  395.         if (data == null) {
  396.             data = fetchIntrospectionData(beanClass);
  397.             descriptorsCache.put(beanClass, data);
  398.         }
  399.         return data;
  400.     }

  401.     /**
  402.      * 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
  403.      * brackets) as a suffix to the property name, or {@code IllegalArgumentException} will be thrown.
  404.      *
  405.      * @param bean Bean whose property is to be extracted
  406.      * @param name {@code propertyname(key)} of the property value to be extracted
  407.      * @return the mapped property value
  408.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  409.      * @throws InvocationTargetException if the property accessor method throws an exception
  410.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  411.      */
  412.     public Object getMappedProperty(final Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  413.         Objects.requireNonNull(bean, "bean");
  414.         Objects.requireNonNull(name, "name");
  415.         // Identify the key of the requested individual property
  416.         String key = null;
  417.         try {
  418.             key = resolver.getKey(name);
  419.         } catch (final IllegalArgumentException e) {
  420.             throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "' " + e.getMessage());
  421.         }
  422.         if (key == null) {
  423.             throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
  424.         }

  425.         // Isolate the name
  426.         name = resolver.getProperty(name);

  427.         // Request the specified indexed property value
  428.         return getMappedProperty(bean, name, key);
  429.     }

  430.     /**
  431.      * Gets the value of the specified mapped property of the specified bean, with no type conversions.
  432.      *
  433.      * @param bean Bean whose property is to be extracted
  434.      * @param name Mapped property name of the property value to be extracted
  435.      * @param key  Key of the property value to be extracted
  436.      * @return the mapped property value
  437.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  438.      * @throws InvocationTargetException if the property accessor method throws an exception
  439.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  440.      */
  441.     public Object getMappedProperty(final Object bean, final String name, final String key)
  442.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  443.         Objects.requireNonNull(bean, "bean");
  444.         Objects.requireNonNull(name, "name");
  445.         Objects.requireNonNull(key, "key");
  446.         // Handle DynaBean instances specially
  447.         if (bean instanceof DynaBean) {
  448.             final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  449.             if (descriptor == null) {
  450.                 throw new NoSuchMethodException("Unknown property '" + name + "'+ on bean class '" + bean.getClass() + "'");
  451.             }
  452.             return ((DynaBean) bean).get(name, key);
  453.         }

  454.         Object result = null;

  455.         // Retrieve the property descriptor for the specified property
  456.         final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  457.         if (descriptor == null) {
  458.             throw new NoSuchMethodException("Unknown property '" + name + "'+ on bean class '" + bean.getClass() + "'");
  459.         }

  460.         if (descriptor instanceof MappedPropertyDescriptor) {
  461.             // Call the keyed getter method if there is one
  462.             Method readMethod = ((MappedPropertyDescriptor) descriptor).getMappedReadMethod();
  463.             readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
  464.             if (readMethod == null) {
  465.                 throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
  466.             }
  467.             result = invokeMethod(readMethod, bean, key);
  468.         } else {
  469.             /* means that the result has to be retrieved from a map */
  470.             final Method readMethod = getReadMethod(bean.getClass(), descriptor);
  471.             if (readMethod == null) {
  472.                 throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
  473.             }
  474.             final Object invokeResult = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
  475.             /* test and fetch from the map */
  476.             if (invokeResult instanceof Map) {
  477.                 result = ((Map<?, ?>) invokeResult).get(key);
  478.             }
  479.         }
  480.         return result;
  481.     }

  482.     /**
  483.      * <p>
  484.      * Return the mapped property descriptors for this bean class.
  485.      * </p>
  486.      *
  487.      * <p>
  488.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  489.      * </p>
  490.      *
  491.      * @param beanClass Bean class to be introspected
  492.      * @return the mapped property descriptors
  493.      */
  494.     Map<Class<?>, Map> getMappedPropertyDescriptors(final Class<?> beanClass) {
  495.         if (beanClass == null) {
  496.             return null;
  497.         }
  498.         // Look up any cached descriptors for this bean class
  499.         return mappedDescriptorsCache.get(beanClass);
  500.     }

  501.     /**
  502.      * <p>
  503.      * Return the mapped property descriptors for this bean.
  504.      * </p>
  505.      *
  506.      * <p>
  507.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  508.      * </p>
  509.      *
  510.      * @param bean Bean to be introspected
  511.      * @return the mapped property descriptors
  512.      */
  513.     Map getMappedPropertyDescriptors(final Object bean) {
  514.         if (bean == null) {
  515.             return null;
  516.         }
  517.         return getMappedPropertyDescriptors(bean.getClass());
  518.     }

  519.     /**
  520.      * Gets the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions.
  521.      *
  522.      * @param bean Bean whose property is to be extracted
  523.      * @param name Possibly nested name of the property to be extracted
  524.      * @return the nested property value
  525.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  526.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  527.      * @throws NestedNullException       if a nested reference to a property returns null
  528.      * @throws InvocationTargetException if the property accessor method throws an exception
  529.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  530.      */
  531.     public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  532.         Objects.requireNonNull(bean, "bean");
  533.         Objects.requireNonNull(name, "name");
  534.         // Resolve nested references
  535.         while (resolver.hasNested(name)) {
  536.             final String next = resolver.next(name);
  537.             Object nestedBean = null;
  538.             if (bean instanceof Map) {
  539.                 nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
  540.             } else if (resolver.isMapped(next)) {
  541.                 nestedBean = getMappedProperty(bean, next);
  542.             } else if (resolver.isIndexed(next)) {
  543.                 nestedBean = getIndexedProperty(bean, next);
  544.             } else {
  545.                 nestedBean = getSimpleProperty(bean, next);
  546.             }
  547.             if (nestedBean == null) {
  548.                 throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
  549.             }
  550.             bean = nestedBean;
  551.             name = resolver.remove(name);
  552.         }

  553.         if (bean instanceof Map) {
  554.             bean = getPropertyOfMapBean((Map<?, ?>) bean, name);
  555.         } else if (resolver.isMapped(name)) {
  556.             bean = getMappedProperty(bean, name);
  557.         } else if (resolver.isIndexed(name)) {
  558.             bean = getIndexedProperty(bean, name);
  559.         } else {
  560.             bean = getSimpleProperty(bean, name);
  561.         }
  562.         return bean;
  563.     }

  564.     /**
  565.      * Gets the value of the specified property of the specified bean, no matter which property reference format is used, with no type conversions.
  566.      *
  567.      * @param bean Bean whose property is to be extracted
  568.      * @param name Possibly indexed and/or nested name of the property to be extracted
  569.      * @return the property value
  570.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  571.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  572.      * @throws InvocationTargetException if the property accessor method throws an exception
  573.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  574.      */
  575.     public Object getProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  576.         return getNestedProperty(bean, name);
  577.     }

  578.     /**
  579.      * <p>
  580.      * Retrieve the property descriptor for the specified property of the specified bean, or return {@code null} if there is no such descriptor. This method
  581.      * 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
  582.      * indexed, the descriptor for the last resolved property itself is returned.
  583.      * </p>
  584.      *
  585.      * <p>
  586.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  587.      * </p>
  588.      *
  589.      * <p>
  590.      * Note that for Java 8 and above, this method no longer return IndexedPropertyDescriptor for {@link List}-typed properties, only for properties typed as
  591.      * native array. (BEANUTILS-492).
  592.      *
  593.      * @param bean Bean for which a property descriptor is requested
  594.      * @param name Possibly indexed and/or nested name of the property for which a property descriptor is requested
  595.      * @return the property descriptor
  596.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  597.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  598.      * @throws IllegalArgumentException  if a nested reference to a property returns null
  599.      * @throws InvocationTargetException if the property accessor method throws an exception
  600.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  601.      */
  602.     public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  603.         Objects.requireNonNull(bean, "bean");
  604.         Objects.requireNonNull(name, "name");
  605.         // Resolve nested references
  606.         while (resolver.hasNested(name)) {
  607.             final String next = resolver.next(name);
  608.             final Object nestedBean = getProperty(bean, next);
  609.             if (nestedBean == null) {
  610.                 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
  611.             }
  612.             bean = nestedBean;
  613.             name = resolver.remove(name);
  614.         }

  615.         // Remove any subscript from the final name value
  616.         name = resolver.getProperty(name);

  617.         // Look up and return this property from our cache
  618.         // creating and adding it to the cache if not found.
  619.         if (name == null) {
  620.             return null;
  621.         }

  622.         final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
  623.         PropertyDescriptor result = data.getDescriptor(name);
  624.         if (result != null) {
  625.             return result;
  626.         }

  627.         Map mappedDescriptors = getMappedPropertyDescriptors(bean);
  628.         if (mappedDescriptors == null) {
  629.             mappedDescriptors = new ConcurrentHashMap<Class<?>, Map>();
  630.             mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors);
  631.         }
  632.         result = (PropertyDescriptor) mappedDescriptors.get(name);
  633.         if (result == null) {
  634.             // not found, try to create it
  635.             try {
  636.                 result = new MappedPropertyDescriptor(name, bean.getClass());
  637.             } catch (final IntrospectionException ie) {
  638.                 /*
  639.                  * Swallow IntrospectionException TODO: Why?
  640.                  */
  641.             }
  642.             if (result != null) {
  643.                 mappedDescriptors.put(name, result);
  644.             }
  645.         }

  646.         return result;
  647.     }

  648.     /**
  649.      * <p>
  650.      * Retrieve the property descriptors for the specified class, introspecting and caching them the first time a particular bean class is encountered.
  651.      * </p>
  652.      *
  653.      * <p>
  654.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  655.      * </p>
  656.      *
  657.      * @param beanClass Bean class for which property descriptors are requested
  658.      * @return the property descriptors
  659.      * @throws IllegalArgumentException if {@code beanClass} is null
  660.      */
  661.     public PropertyDescriptor[] getPropertyDescriptors(final Class<?> beanClass) {
  662.         return getIntrospectionData(beanClass).getDescriptors();
  663.     }

  664.     /**
  665.      * <p>
  666.      * Retrieve the property descriptors for the specified bean, introspecting and caching them the first time a particular bean class is encountered.
  667.      * </p>
  668.      *
  669.      * <p>
  670.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  671.      * </p>
  672.      *
  673.      * @param bean Bean for which property descriptors are requested
  674.      * @return the property descriptors
  675.      * @throws IllegalArgumentException if {@code bean} is null
  676.      */
  677.     public PropertyDescriptor[] getPropertyDescriptors(final Object bean) {
  678.         Objects.requireNonNull(bean, "bean");
  679.         return getPropertyDescriptors(bean.getClass());
  680.     }

  681.     /**
  682.      * <p>
  683.      * Return the Java Class repesenting the property editor class that has been registered for this property (if any). This method follows the same name
  684.      * resolution rules used by {@code getPropertyDescriptor()}, so if the last element of a name reference is indexed, the property editor for the underlying
  685.      * property's class is returned.
  686.      * </p>
  687.      *
  688.      * <p>
  689.      * 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
  690.      * ambiguous, you should determine the existence of the property itself by other means.
  691.      * </p>
  692.      *
  693.      * <p>
  694.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  695.      * </p>
  696.      *
  697.      * @param bean Bean for which a property descriptor is requested
  698.      * @param name Possibly indexed and/or nested name of the property for which a property descriptor is requested
  699.      * @return the property editor class
  700.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  701.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  702.      * @throws IllegalArgumentException  if a nested reference to a property returns null
  703.      * @throws InvocationTargetException if the property accessor method throws an exception
  704.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  705.      */
  706.     public Class<?> getPropertyEditorClass(final Object bean, final String name)
  707.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  708.         Objects.requireNonNull(bean, "bean");
  709.         Objects.requireNonNull(name, "name");
  710.         final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  711.         if (descriptor != null) {
  712.             return descriptor.getPropertyEditorClass();
  713.         }
  714.         return null;
  715.     }

  716.     /**
  717.      * This method is called by getNestedProperty and setNestedProperty to define what it means to get a property from an object which implements Map. See
  718.      * setPropertyOfMapBean for more information.
  719.      *
  720.      * @param bean         Map bean
  721.      * @param propertyName The property name
  722.      * @return the property value
  723.      * @throws IllegalArgumentException  when the propertyName is regarded as being invalid.
  724.      * @throws IllegalAccessException    just in case subclasses override this method to try to access real getter methods and find permission is denied.
  725.      * @throws InvocationTargetException just in case subclasses override this method to try to access real getter methods, and find it throws an exception when
  726.      *                                   invoked.
  727.      *
  728.      * @throws NoSuchMethodException     just in case subclasses override this method to try to access real getter methods, and want to fail if no simple method
  729.      *                                   is available.
  730.      * @since 1.8.0
  731.      */
  732.     protected Object getPropertyOfMapBean(final Map<?, ?> bean, String propertyName)
  733.             throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {

  734.         if (resolver.isMapped(propertyName)) {
  735.             final String name = resolver.getProperty(propertyName);
  736.             if (name == null || name.isEmpty()) {
  737.                 propertyName = resolver.getKey(propertyName);
  738.             }
  739.         }

  740.         if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) {
  741.             throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName);
  742.         }

  743.         return bean.get(propertyName);
  744.     }

  745.     /**
  746.      * 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
  747.      * 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
  748.      * the property itself will be returned. If the last (or only) element has no property with the specified name, {@code null} is returned.
  749.      * <p>
  750.      * 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
  751.      * 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
  752.      * java.util.List) from the getter method.
  753.      * </p>
  754.      *
  755.      * @param bean Bean for which a property descriptor is requested
  756.      * @param name Possibly indexed and/or nested name of the property for which a property descriptor is requested
  757.      * @return The property type
  758.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  759.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  760.      * @throws IllegalArgumentException  if a nested reference to a property returns null
  761.      * @throws InvocationTargetException if the property accessor method throws an exception
  762.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  763.      */
  764.     public Class<?> getPropertyType(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  765.         Objects.requireNonNull(bean, "bean");
  766.         Objects.requireNonNull(name, "name");
  767.         // Resolve nested references
  768.         while (resolver.hasNested(name)) {
  769.             final String next = resolver.next(name);
  770.             final Object nestedBean = getProperty(bean, next);
  771.             if (nestedBean == null) {
  772.                 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
  773.             }
  774.             bean = nestedBean;
  775.             name = resolver.remove(name);
  776.         }

  777.         // Remove any subscript from the final name value
  778.         name = resolver.getProperty(name);

  779.         // Special handling for DynaBeans
  780.         if (bean instanceof DynaBean) {
  781.             final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  782.             if (descriptor == null) {
  783.                 return null;
  784.             }
  785.             final Class<?> type = descriptor.getType();
  786.             if (type == null) {
  787.                 return null;
  788.             }
  789.             if (type.isArray()) {
  790.                 return type.getComponentType();
  791.             }
  792.             return type;
  793.         }

  794.         final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  795.         if (descriptor == null) {
  796.             return null;
  797.         }
  798.         if (descriptor instanceof IndexedPropertyDescriptor) {
  799.             return ((IndexedPropertyDescriptor) descriptor).getIndexedPropertyType();
  800.         }
  801.         if (descriptor instanceof MappedPropertyDescriptor) {
  802.             return ((MappedPropertyDescriptor) descriptor).getMappedPropertyType();
  803.         }
  804.         return descriptor.getPropertyType();
  805.     }

  806.     /**
  807.      * <p>
  808.      * 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}.
  809.      * </p>
  810.      *
  811.      * <p>
  812.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  813.      * </p>
  814.      *
  815.      * <p>
  816.      * 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
  817.      * with the standard code (for example that of {@link #getProperty getProperty()}) by calling this method instead of using
  818.      * {@code descriptor.getReadMethod()} directly.
  819.      * </p>
  820.      *
  821.      * @param clazz      The class of the read method will be invoked on
  822.      * @param descriptor Property descriptor to return a getter for
  823.      * @return The read method
  824.      * @since 2.0.0
  825.      */
  826.     public Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
  827.         return MethodUtils.getAccessibleMethod(clazz, descriptor.getReadMethod());
  828.     }

  829.     /**
  830.      * <p>
  831.      * Return an accessible property getter method for this property, if there is one; otherwise return {@code null}.
  832.      * </p>
  833.      *
  834.      * <p>
  835.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  836.      * </p>
  837.      *
  838.      * @param descriptor Property descriptor to return a getter for
  839.      * @return The read method
  840.      */
  841.     public Method getReadMethod(final PropertyDescriptor descriptor) {
  842.         return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
  843.     }

  844.     /**
  845.      * Gets the configured {@link Resolver} implementation used by BeanUtils.
  846.      * <p>
  847.      * The {@link Resolver} handles the <em>property name</em> expressions and the implementation in use effectively controls the dialect of the <em>expression
  848.      * language</em> that BeanUtils recognizes.
  849.      * <p>
  850.      * {@link DefaultResolver} is the default implementation used.
  851.      *
  852.      * @return resolver The property expression resolver.
  853.      * @since 1.8.0
  854.      */
  855.     public Resolver getResolver() {
  856.         return resolver;
  857.     }

  858.     /**
  859.      * Gets the value of the specified simple property of the specified bean, with no type conversions.
  860.      *
  861.      * @param bean Bean whose property is to be extracted
  862.      * @param name Name of the property to be extracted
  863.      * @return The property value
  864.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  865.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  866.      * @throws IllegalArgumentException  if the property name is nested or indexed
  867.      * @throws InvocationTargetException if the property accessor method throws an exception
  868.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  869.      */
  870.     public Object getSimpleProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  871.         Objects.requireNonNull(bean, "bean");
  872.         Objects.requireNonNull(name, "name");
  873.         // Validate the syntax of the property name
  874.         if (resolver.hasNested(name)) {
  875.             throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
  876.         }
  877.         if (resolver.isIndexed(name)) {
  878.             throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
  879.         }
  880.         if (resolver.isMapped(name)) {
  881.             throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
  882.         }

  883.         // Handle DynaBean instances specially
  884.         if (bean instanceof DynaBean) {
  885.             final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  886.             if (descriptor == null) {
  887.                 throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'");
  888.             }
  889.             return ((DynaBean) bean).get(name);
  890.         }

  891.         // Retrieve the property getter method for the specified property
  892.         final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  893.         if (descriptor == null) {
  894.             throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
  895.         }
  896.         final Method readMethod = getReadMethod(bean.getClass(), descriptor);
  897.         if (readMethod == null) {
  898.             throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
  899.         }

  900.         // Call the property getter and return the value
  901.         return invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
  902.     }

  903.     /**
  904.      * <p>
  905.      * 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}.
  906.      * </p>
  907.      *
  908.      * <p>
  909.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  910.      * </p>
  911.      *
  912.      * <p>
  913.      * 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
  914.      * with the standard code (for example that of {@link #setProperty setProperty()}) by calling this method instead of using
  915.      * {@code descriptor.getWriteMethod()} directly.
  916.      * </p>
  917.      *
  918.      * @param clazz      The class of the read method will be invoked on
  919.      * @param descriptor Property descriptor to return a setter for
  920.      * @return The write method
  921.      * @since 1.9.1
  922.      */
  923.     public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {
  924.         final BeanIntrospectionData data = getIntrospectionData(clazz);
  925.         return MethodUtils.getAccessibleMethod(clazz, data.getWriteMethod(clazz, descriptor));
  926.     }

  927.     /**
  928.      * <p>
  929.      * Return an accessible property setter method for this property, if there is one; otherwise return {@code null}.
  930.      * </p>
  931.      *
  932.      * <p>
  933.      * <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
  934.      * method is defined for the property in question. Use {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the correct result is returned.
  935.      * </p>
  936.      * <p>
  937.      * <strong>FIXME</strong> - Does not work with DynaBeans.
  938.      * </p>
  939.      *
  940.      * @param descriptor Property descriptor to return a setter for
  941.      * @return The write method
  942.      */
  943.     public Method getWriteMethod(final PropertyDescriptor descriptor) {
  944.         return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());
  945.     }

  946.     /**
  947.      * Delegates to {@link Method#invoke(Object, Object...)} and handles some unchecked exceptions.
  948.      *
  949.      * @see Method#invoke(Object, Object...)
  950.      */
  951.     private Object invokeMethod(final Method method, final Object bean, final Object... values) throws IllegalAccessException, InvocationTargetException {
  952.         Objects.requireNonNull(bean, "bean");
  953.         try {
  954.             return method.invoke(bean, values);
  955.         } catch (final NullPointerException | IllegalArgumentException cause) {
  956.             // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
  957.             // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
  958.             final StringBuilder valueString = new StringBuilder();
  959.             if (values != null) {
  960.                 for (int i = 0; i < values.length; i++) {
  961.                     if (i > 0) {
  962.                         valueString.append(", ");
  963.                     }
  964.                     if (values[i] == null) {
  965.                         valueString.append("<null>");
  966.                     } else {
  967.                         valueString.append(values[i].getClass().getName());
  968.                     }
  969.                 }
  970.             }
  971.             final StringBuilder expectedString = new StringBuilder();
  972.             final Class<?>[] parTypes = method.getParameterTypes();
  973.             if (parTypes != null) {
  974.                 for (int i = 0; i < parTypes.length; i++) {
  975.                     if (i > 0) {
  976.                         expectedString.append(", ");
  977.                     }
  978.                     expectedString.append(parTypes[i].getName());
  979.                 }
  980.             }
  981.             throw new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " on bean class '"
  982.                     + bean.getClass() + "' - " + cause.getMessage()
  983.                     // as per https://issues.apache.org/jira/browse/BEANUTILS-224
  984.                     + " - had objects of type \"" + valueString + "\" but expected signature \"" + expectedString + "\"", cause);
  985.         }
  986.     }

  987.     /**
  988.      * Return {@code true} if the specified property name identifies a readable property on the specified bean; otherwise, return {@code false}.
  989.      *
  990.      * @param bean Bean to be examined (may be a {@link DynaBean}
  991.      * @param name Property name to be evaluated
  992.      * @return {@code true} if the property is readable, otherwise {@code false}
  993.      * @throws IllegalArgumentException if {@code bean} or {@code name</code> is <code>null}
  994.      * @since 1.6
  995.      */
  996.     public boolean isReadable(Object bean, String name) {
  997.         // Validate method parameters
  998.         Objects.requireNonNull(bean, "bean");
  999.         Objects.requireNonNull(name, "name");
  1000.         // Resolve nested references
  1001.         while (resolver.hasNested(name)) {
  1002.             final String next = resolver.next(name);
  1003.             Object nestedBean = null;
  1004.             try {
  1005.                 nestedBean = getProperty(bean, next);
  1006.             } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
  1007.                 return false;
  1008.             }
  1009.             if (nestedBean == null) {
  1010.                 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
  1011.             }
  1012.             bean = nestedBean;
  1013.             name = resolver.remove(name);
  1014.         }

  1015.         // Remove any subscript from the final name value
  1016.         name = resolver.getProperty(name);

  1017.         // Treat WrapDynaBean as special case - may be a write-only property
  1018.         // (see Jira issue# BEANUTILS-61)
  1019.         if (bean instanceof WrapDynaBean) {
  1020.             bean = ((WrapDynaBean) bean).getInstance();
  1021.         }

  1022.         // Return the requested result
  1023.         if (bean instanceof DynaBean) {
  1024.             // All DynaBean properties are readable
  1025.             return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null;
  1026.         }
  1027.         try {
  1028.             final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
  1029.             if (desc != null) {
  1030.                 Method readMethod = getReadMethod(bean.getClass(), desc);
  1031.                 if (readMethod == null) {
  1032.                     if (desc instanceof IndexedPropertyDescriptor) {
  1033.                         readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
  1034.                     } else if (desc instanceof MappedPropertyDescriptor) {
  1035.                         readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
  1036.                     }
  1037.                     readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
  1038.                 }
  1039.                 return readMethod != null;
  1040.             }
  1041.             return false;
  1042.         } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
  1043.             return false;
  1044.         }
  1045.     }

  1046.     /**
  1047.      * Return {@code true} if the specified property name identifies a writable property on the specified bean; otherwise, return {@code false}.
  1048.      *
  1049.      * @param bean Bean to be examined (may be a {@link DynaBean}
  1050.      * @param name Property name to be evaluated
  1051.      * @return {@code true} if the property is writable, otherwise {@code false}
  1052.      * @throws IllegalArgumentException if {@code bean} or {@code name</code> is <code>null}
  1053.      * @since 1.6
  1054.      */
  1055.     public boolean isWriteable(Object bean, String name) {
  1056.         // Validate method parameters
  1057.         Objects.requireNonNull(bean, "bean");
  1058.         Objects.requireNonNull(name, "name");
  1059.         // Resolve nested references
  1060.         while (resolver.hasNested(name)) {
  1061.             final String next = resolver.next(name);
  1062.             Object nestedBean = null;
  1063.             try {
  1064.                 nestedBean = getProperty(bean, next);
  1065.             } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
  1066.                 return false;
  1067.             }
  1068.             if (nestedBean == null) {
  1069.                 throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
  1070.             }
  1071.             bean = nestedBean;
  1072.             name = resolver.remove(name);
  1073.         }

  1074.         // Remove any subscript from the final name value
  1075.         name = resolver.getProperty(name);

  1076.         // Treat WrapDynaBean as special case - may be a read-only property
  1077.         // (see Jira issue# BEANUTILS-61)
  1078.         if (bean instanceof WrapDynaBean) {
  1079.             bean = ((WrapDynaBean) bean).getInstance();
  1080.         }

  1081.         // Return the requested result
  1082.         if (bean instanceof DynaBean) {
  1083.             // All DynaBean properties are writable
  1084.             return ((DynaBean) bean).getDynaClass().getDynaProperty(name) != null;
  1085.         }
  1086.         try {
  1087.             final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
  1088.             if (desc != null) {
  1089.                 Method writeMethod = getWriteMethod(bean.getClass(), desc);
  1090.                 if (writeMethod == null) {
  1091.                     if (desc instanceof IndexedPropertyDescriptor) {
  1092.                         writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
  1093.                     } else if (desc instanceof MappedPropertyDescriptor) {
  1094.                         writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
  1095.                     }
  1096.                     writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
  1097.                 }
  1098.                 return writeMethod != null;
  1099.             }
  1100.             return false;
  1101.         } catch (final IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
  1102.             return false;
  1103.         }
  1104.     }

  1105.     /**
  1106.      * Removes the specified {@code BeanIntrospector}.
  1107.      *
  1108.      * @param introspector the {@code BeanIntrospector} to be removed
  1109.      * @return <strong>true</strong> if the {@code BeanIntrospector} existed and could be removed, <strong>false</strong> otherwise
  1110.      * @since 1.9
  1111.      */
  1112.     public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
  1113.         return introspectors.remove(introspector);
  1114.     }

  1115.     /**
  1116.      * Resets the {@link BeanIntrospector} objects registered at this instance. After this method was called, only the default {@code BeanIntrospector} is
  1117.      * registered.
  1118.      *
  1119.      * @since 1.9
  1120.      */
  1121.     public final void resetBeanIntrospectors() {
  1122.         introspectors.clear();
  1123.         introspectors.add(DefaultBeanIntrospector.INSTANCE);
  1124.         introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
  1125.     }

  1126.     /**
  1127.      * Sets the value of the specified indexed property of the specified bean, with no type conversions. In addition to supporting the JavaBeans specification,
  1128.      * this method has been extended to support {@code List} objects as well.
  1129.      *
  1130.      * @param bean  Bean whose property is to be set
  1131.      * @param name  Simple property name of the property value to be set
  1132.      * @param index Index of the property value to be set
  1133.      * @param value Value to which the indexed property element is to be set
  1134.      * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property
  1135.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  1136.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  1137.      * @throws InvocationTargetException if the property accessor method throws an exception
  1138.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  1139.      */
  1140.     public void setIndexedProperty(final Object bean, final String name, final int index, final Object value)
  1141.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  1142.         Objects.requireNonNull(bean, "bean");
  1143.         if (name == null || name.isEmpty()) {
  1144.             if (bean.getClass().isArray()) {
  1145.                 Array.set(bean, index, value);
  1146.                 return;
  1147.             }
  1148.             if (bean instanceof List) {
  1149.                 final List<Object> list = toObjectList(bean);
  1150.                 list.set(index, value);
  1151.                 return;
  1152.             }
  1153.         }
  1154.         Objects.requireNonNull(name, "name");
  1155.         // Handle DynaBean instances specially
  1156.         if (bean instanceof DynaBean) {
  1157.             final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  1158.             if (descriptor == null) {
  1159.                 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
  1160.             }
  1161.             ((DynaBean) bean).set(name, index, value);
  1162.             return;
  1163.         }

  1164.         // Retrieve the property descriptor for the specified property
  1165.         final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  1166.         if (descriptor == null) {
  1167.             throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
  1168.         }

  1169.         // Call the indexed setter method if there is one
  1170.         if (descriptor instanceof IndexedPropertyDescriptor) {
  1171.             Method writeMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod();
  1172.             writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
  1173.             if (writeMethod != null) {
  1174.                 try {
  1175.                     if (LOG.isTraceEnabled()) {
  1176.                         final String valueClassName = value == null ? "<null>" : value.getClass().getName();
  1177.                         LOG.trace("setSimpleProperty: Invoking method " + writeMethod + " with index=" + index + ", value=" + value + " (class "
  1178.                                 + valueClassName + ")");
  1179.                     }
  1180.                     invokeMethod(writeMethod, bean, Integer.valueOf(index), value);
  1181.                 } catch (final InvocationTargetException e) {
  1182.                     if (e.getTargetException() instanceof IndexOutOfBoundsException) {
  1183.                         throw (IndexOutOfBoundsException) e.getTargetException();
  1184.                     }
  1185.                     throw e;
  1186.                 }
  1187.                 return;
  1188.             }
  1189.         }

  1190.         // Otherwise, the underlying property must be an array or a list
  1191.         final Method readMethod = getReadMethod(bean.getClass(), descriptor);
  1192.         if (readMethod == null) {
  1193.             throw new NoSuchMethodException("Property '" + name + "' has no getter method on bean class '" + bean.getClass() + "'");
  1194.         }

  1195.         // Call the property getter to get the array or list
  1196.         final Object array = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
  1197.         if (!array.getClass().isArray()) {
  1198.             if (!(array instanceof List)) {
  1199.                 throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
  1200.             }
  1201.             // Modify the specified value in the List
  1202.             final List<Object> list = toObjectList(array);
  1203.             list.set(index, value);
  1204.         } else {
  1205.             // Modify the specified value in the array
  1206.             Array.set(array, index, value);
  1207.         }
  1208.     }

  1209.     /**
  1210.      * 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
  1211.      * included (in square brackets) as a suffix to the property name, or {@code IllegalArgumentException} will be thrown. In addition to supporting the
  1212.      * JavaBeans specification, this method has been extended to support {@code List} objects as well.
  1213.      *
  1214.      * @param bean  Bean whose property is to be modified
  1215.      * @param name  {@code propertyname[index]} of the property value to be modified
  1216.      * @param value Value to which the specified property element should be set
  1217.      * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the underlying property
  1218.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  1219.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  1220.      * @throws InvocationTargetException if the property accessor method throws an exception
  1221.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  1222.      */
  1223.     public void setIndexedProperty(final Object bean, String name, final Object value)
  1224.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  1225.         Objects.requireNonNull(bean, "bean");
  1226.         Objects.requireNonNull(name, "name");
  1227.         // Identify the index of the requested individual property
  1228.         int index = -1;
  1229.         try {
  1230.             index = resolver.getIndex(name);
  1231.         } catch (final IllegalArgumentException e) {
  1232.             throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
  1233.         }
  1234.         if (index < 0) {
  1235.             throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
  1236.         }

  1237.         // Isolate the name
  1238.         name = resolver.getProperty(name);

  1239.         // Set the specified indexed property value
  1240.         setIndexedProperty(bean, name, index, value);
  1241.     }

  1242.     /**
  1243.      * 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
  1244.      * brackets) as a suffix to the property name, or {@code IllegalArgumentException} will be thrown.
  1245.      *
  1246.      * @param bean  Bean whose property is to be set
  1247.      * @param name  {@code propertyname(key)} of the property value to be set
  1248.      * @param value The property value to be set
  1249.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  1250.      * @throws InvocationTargetException if the property accessor method throws an exception
  1251.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  1252.      */
  1253.     public void setMappedProperty(final Object bean, String name, final Object value)
  1254.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  1255.         Objects.requireNonNull(bean, "bean");
  1256.         Objects.requireNonNull(name, "name");

  1257.         // Identify the key of the requested individual property
  1258.         String key = null;
  1259.         try {
  1260.             key = resolver.getKey(name);
  1261.         } catch (final IllegalArgumentException e) {
  1262.             throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
  1263.         }
  1264.         if (key == null) {
  1265.             throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
  1266.         }

  1267.         // Isolate the name
  1268.         name = resolver.getProperty(name);

  1269.         // Request the specified indexed property value
  1270.         setMappedProperty(bean, name, key, value);
  1271.     }

  1272.     /**
  1273.      * Sets the value of the specified mapped property of the specified bean, with no type conversions.
  1274.      *
  1275.      * @param bean  Bean whose property is to be set
  1276.      * @param name  Mapped property name of the property value to be set
  1277.      * @param key   Key of the property value to be set
  1278.      * @param value The property value to be set
  1279.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  1280.      * @throws InvocationTargetException if the property accessor method throws an exception
  1281.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  1282.      */
  1283.     public void setMappedProperty(final Object bean, final String name, final String key, final Object value)
  1284.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  1285.         Objects.requireNonNull(bean, "bean");
  1286.         Objects.requireNonNull(name, "name");
  1287.         Objects.requireNonNull(key, "key");
  1288.         // Handle DynaBean instances specially
  1289.         if (bean instanceof DynaBean) {
  1290.             final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  1291.             if (descriptor == null) {
  1292.                 throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
  1293.             }
  1294.             ((DynaBean) bean).set(name, key, value);
  1295.             return;
  1296.         }

  1297.         // Retrieve the property descriptor for the specified property
  1298.         final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  1299.         if (descriptor == null) {
  1300.             throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
  1301.         }

  1302.         if (descriptor instanceof MappedPropertyDescriptor) {
  1303.             // Call the keyed setter method if there is one
  1304.             Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor).getMappedWriteMethod();
  1305.             mappedWriteMethod = MethodUtils.getAccessibleMethod(bean.getClass(), mappedWriteMethod);
  1306.             if (mappedWriteMethod == null) {
  1307.                 throw new NoSuchMethodException("Property '" + name + "' has no mapped setter method" + "on bean class '" + bean.getClass() + "'");
  1308.             }
  1309.             if (LOG.isTraceEnabled()) {
  1310.                 final String valueClassName = value == null ? "<null>" : value.getClass().getName();
  1311.                 LOG.trace("setSimpleProperty: Invoking method " + mappedWriteMethod + " with key=" + key + ", value=" + value + " (class " + valueClassName
  1312.                         + ")");
  1313.             }
  1314.             invokeMethod(mappedWriteMethod, bean, key, value);
  1315.         } else {
  1316.             /* means that the result has to be retrieved from a map */
  1317.             final Method readMethod = getReadMethod(bean.getClass(), descriptor);
  1318.             if (readMethod == null) {
  1319.                 throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
  1320.             }
  1321.             final Object invokeResult = invokeMethod(readMethod, bean, BeanUtils.EMPTY_OBJECT_ARRAY);
  1322.             /* test and fetch from the map */
  1323.             if (invokeResult instanceof Map) {
  1324.                 toPropertyMap(invokeResult).put(key, value);
  1325.             }
  1326.         }
  1327.     }

  1328.     /**
  1329.      * Sets the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions.
  1330.      * <p>
  1331.      * Example values for parameter "name" are:
  1332.      * <ul>
  1333.      * <li>"a" -- sets the value of property a of the specified bean</li>
  1334.      * <li>"a.b" -- gets the value of property a of the specified bean, then on that object sets the value of property b.</li>
  1335.      * <li>"a(key)" -- sets a value of mapped-property a on the specified bean. This effectively means bean.setA("key").</li>
  1336.      * <li>"a[3]" -- sets a value of indexed-property a on the specified bean. This effectively means bean.setA(3).</li>
  1337.      * </ul>
  1338.      *
  1339.      * @param bean  Bean whose property is to be modified
  1340.      * @param name  Possibly nested name of the property to be modified
  1341.      * @param value Value to which the property is to be set
  1342.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  1343.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  1344.      * @throws IllegalArgumentException  if a nested reference to a property returns null
  1345.      * @throws InvocationTargetException if the property accessor method throws an exception
  1346.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  1347.      */
  1348.     public void setNestedProperty(Object bean, String name, final Object value)
  1349.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  1350.         Objects.requireNonNull(bean, "bean");
  1351.         Objects.requireNonNull(name, "name");
  1352.         // Resolve nested references
  1353.         while (resolver.hasNested(name)) {
  1354.             final String next = resolver.next(name);
  1355.             Object nestedBean = null;
  1356.             if (bean instanceof Map) {
  1357.                 nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
  1358.             } else if (resolver.isMapped(next)) {
  1359.                 nestedBean = getMappedProperty(bean, next);
  1360.             } else if (resolver.isIndexed(next)) {
  1361.                 nestedBean = getIndexedProperty(bean, next);
  1362.             } else {
  1363.                 nestedBean = getSimpleProperty(bean, next);
  1364.             }
  1365.             if (nestedBean == null) {
  1366.                 throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
  1367.             }
  1368.             bean = nestedBean;
  1369.             name = resolver.remove(name);
  1370.         }

  1371.         if (bean instanceof Map) {
  1372.             setPropertyOfMapBean(toPropertyMap(bean), name, value);
  1373.         } else if (resolver.isMapped(name)) {
  1374.             setMappedProperty(bean, name, value);
  1375.         } else if (resolver.isIndexed(name)) {
  1376.             setIndexedProperty(bean, name, value);
  1377.         } else {
  1378.             setSimpleProperty(bean, name, value);
  1379.         }
  1380.     }

  1381.     /**
  1382.      * Sets the value of the specified property of the specified bean, no matter which property reference format is used, with no type conversions.
  1383.      *
  1384.      * @param bean  Bean whose property is to be modified
  1385.      * @param name  Possibly indexed and/or nested name of the property to be modified
  1386.      * @param value Value to which this property is to be set
  1387.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  1388.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  1389.      * @throws InvocationTargetException if the property accessor method throws an exception
  1390.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  1391.      */
  1392.     public void setProperty(final Object bean, final String name, final Object value)
  1393.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  1394.         setNestedProperty(bean, name, value);
  1395.     }

  1396.     /**
  1397.      * 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
  1398.      * Map.
  1399.      * <p>
  1400.      * The standard implementation here is to:
  1401.      * <ul>
  1402.      * <li>call bean.set(propertyName) for all propertyName values.</li>
  1403.      * <li>throw an IllegalArgumentException if the property specifier contains MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially simple properties;
  1404.      * 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>
  1405.      * </ul>
  1406.      * <p>
  1407.      * 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,
  1408.      * 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
  1409.      * 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]
  1410.      * <p>
  1411.      * 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
  1412.      * 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
  1413.      * method, do not forget to deal with MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
  1414.      * <p>
  1415.      * 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
  1416.      * 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
  1417.      * PropertyUtilsBean class by overriding this method.
  1418.      *
  1419.      * @param bean         Map bean
  1420.      * @param propertyName The property name
  1421.      * @param value        the property value
  1422.      * @throws IllegalArgumentException  when the propertyName is regarded as being invalid.
  1423.      * @throws IllegalAccessException    just in case subclasses override this method to try to access real setter methods and find permission is denied.
  1424.      * @throws InvocationTargetException just in case subclasses override this method to try to access real setter methods, and find it throws an exception when
  1425.      *                                   invoked.
  1426.      *
  1427.      * @throws NoSuchMethodException     just in case subclasses override this method to try to access real setter methods, and want to fail if no simple method
  1428.      *                                   is available.
  1429.      * @since 1.8.0
  1430.      */
  1431.     protected void setPropertyOfMapBean(final Map<String, Object> bean, String propertyName, final Object value)
  1432.             throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  1433.         if (resolver.isMapped(propertyName)) {
  1434.             final String name = resolver.getProperty(propertyName);
  1435.             if (name == null || name.isEmpty()) {
  1436.                 propertyName = resolver.getKey(propertyName);
  1437.             }
  1438.         }

  1439.         if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) {
  1440.             throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName);
  1441.         }

  1442.         bean.put(propertyName, value);
  1443.     }

  1444.     /**
  1445.      * Configure the {@link Resolver} implementation used by BeanUtils.
  1446.      * <p>
  1447.      * The {@link Resolver} handles the <em>property name</em> expressions and the implementation in use effectively controls the dialect of the <em>expression
  1448.      * language</em> that BeanUtils recognizes.
  1449.      * <p>
  1450.      * {@link DefaultResolver} is the default implementation used.
  1451.      *
  1452.      * @param resolver The property expression resolver.
  1453.      * @since 1.8.0
  1454.      */
  1455.     public void setResolver(final Resolver resolver) {
  1456.         if (resolver == null) {
  1457.             this.resolver = new DefaultResolver();
  1458.         } else {
  1459.             this.resolver = resolver;
  1460.         }
  1461.     }

  1462.     /**
  1463.      * Sets the value of the specified simple property of the specified bean, with no type conversions.
  1464.      *
  1465.      * @param bean  Bean whose property is to be modified
  1466.      * @param name  Name of the property to be modified
  1467.      * @param value Value to which the property should be set
  1468.      * @throws IllegalAccessException    if the caller does not have access to the property accessor method
  1469.      * @throws IllegalArgumentException  if {@code bean} or {@code name} is null
  1470.      * @throws IllegalArgumentException  if the property name is nested or indexed
  1471.      * @throws InvocationTargetException if the property accessor method throws an exception
  1472.      * @throws NoSuchMethodException     if an accessor method for this property cannot be found
  1473.      */
  1474.     public void setSimpleProperty(final Object bean, final String name, final Object value)
  1475.             throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  1476.         Objects.requireNonNull(bean, "bean");
  1477.         Objects.requireNonNull(name, "name");
  1478.         final Class<?> beanClass = bean.getClass();
  1479.         // Validate the syntax of the property name
  1480.         if (resolver.hasNested(name)) {
  1481.             throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + beanClass + "'");
  1482.         }
  1483.         if (resolver.isIndexed(name)) {
  1484.             throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + beanClass + "'");
  1485.         }
  1486.         if (resolver.isMapped(name)) {
  1487.             throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + beanClass + "'");
  1488.         }

  1489.         // Handle DynaBean instances specially
  1490.         if (bean instanceof DynaBean) {
  1491.             final DynaProperty descriptor = ((DynaBean) bean).getDynaClass().getDynaProperty(name);
  1492.             if (descriptor == null) {
  1493.                 throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean) bean).getDynaClass() + "'");
  1494.             }
  1495.             ((DynaBean) bean).set(name, value);
  1496.             return;
  1497.         }

  1498.         // Retrieve the property setter method for the specified property
  1499.         final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
  1500.         if (descriptor == null) {
  1501.             throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + beanClass + "'");
  1502.         }
  1503.         final Method writeMethod = getWriteMethod(beanClass, descriptor);
  1504.         if (writeMethod == null) {
  1505.             throw new NoSuchMethodException("Property '" + name + "' has no setter method in class '" + beanClass + "'");
  1506.         }

  1507.         // Call the property setter method
  1508.         if (LOG.isTraceEnabled()) {
  1509.             final String valueClassName = value == null ? "<null>" : value.getClass().getName();
  1510.             LOG.trace("setSimpleProperty: Invoking method " + writeMethod + " with value " + value + " (class " + valueClassName + ")");
  1511.         }
  1512.         invokeMethod(writeMethod, bean, value);
  1513.     }
  1514. }