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