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