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.beanutils2.locale;
019
020import java.beans.IndexedPropertyDescriptor;
021import java.beans.PropertyDescriptor;
022import java.lang.reflect.InvocationTargetException;
023import java.util.Locale;
024
025import org.apache.commons.beanutils2.BeanUtilsBean;
026import org.apache.commons.beanutils2.ContextClassLoaderLocal;
027import org.apache.commons.beanutils2.ConvertUtils;
028import org.apache.commons.beanutils2.ConvertUtilsBean;
029import org.apache.commons.beanutils2.DynaBean;
030import org.apache.commons.beanutils2.DynaClass;
031import org.apache.commons.beanutils2.DynaProperty;
032import org.apache.commons.beanutils2.MappedPropertyDescriptor;
033import org.apache.commons.beanutils2.PropertyUtilsBean;
034import org.apache.commons.beanutils2.expression.Resolver;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037
038/**
039 * <p>
040 * Utility methods for populating JavaBeans properties via reflection in a locale-dependent manner.
041 * </p>
042 *
043 * @since 1.7
044 */
045public class LocaleBeanUtilsBean extends BeanUtilsBean {
046
047    /**
048     * Contains {@code LocaleBeanUtilsBean} instances indexed by context classloader.
049     */
050    private static final ContextClassLoaderLocal<LocaleBeanUtilsBean> LOCALE_BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<LocaleBeanUtilsBean>() {
051        // Creates the default instance used when the context classloader is unavailable
052        @Override
053        protected LocaleBeanUtilsBean initialValue() {
054            return new LocaleBeanUtilsBean();
055        }
056    };
057
058    /** All logging goes through this logger */
059    private static final Log LOG = LogFactory.getLog(LocaleBeanUtilsBean.class);
060
061    /**
062     * Gets singleton instance
063     *
064     * @return the singleton instance
065     */
066    public static LocaleBeanUtilsBean getLocaleBeanUtilsInstance() {
067        return LOCALE_BEANS_BY_CLASSLOADER.get();
068    }
069
070    /**
071     * Sets the instance which provides the functionality for {@link LocaleBeanUtils}. This is a pseudo-singleton - an single instance is provided per (thread)
072     * context classloader. This mechanism provides isolation for web apps deployed in the same container.
073     *
074     * @param newInstance a new singleton instance
075     */
076    public static void setInstance(final LocaleBeanUtilsBean newInstance) {
077        LOCALE_BEANS_BY_CLASSLOADER.set(newInstance);
078    }
079
080    /** Convertor used by this class */
081    private final LocaleConvertUtilsBean localeConvertUtils;
082
083    /** Constructs instance with standard conversion bean */
084    public LocaleBeanUtilsBean() {
085        this.localeConvertUtils = new LocaleConvertUtilsBean();
086    }
087
088    /**
089     * Constructs instance that uses given locale conversion
090     *
091     * @param localeConvertUtils use this {@code localeConvertUtils} to perform conversions
092     */
093    public LocaleBeanUtilsBean(final LocaleConvertUtilsBean localeConvertUtils) {
094        this.localeConvertUtils = localeConvertUtils;
095    }
096
097    /**
098     * Constructs instance that uses given locale conversion
099     *
100     * @param localeConvertUtils use this {@code localeConvertUtils} to perform conversions
101     * @param convertUtilsBean   use this for standard conversions
102     * @param propertyUtilsBean  use this for property conversions
103     */
104    public LocaleBeanUtilsBean(final LocaleConvertUtilsBean localeConvertUtils, final ConvertUtilsBean convertUtilsBean,
105            final PropertyUtilsBean propertyUtilsBean) {
106        super(convertUtilsBean, propertyUtilsBean);
107        this.localeConvertUtils = localeConvertUtils;
108    }
109
110    /**
111     * Convert the specified value to the required type.
112     *
113     * @param type  The Java type of target property
114     * @param index The indexed subscript value (if any)
115     * @param value The value to be converted
116     * @return The converted value
117     */
118    protected Object convert(final Class<?> type, final int index, final Object value) {
119        Object newValue = null;
120
121        if (type.isArray() && index < 0) { // Scalar value into array
122            if (value instanceof String) {
123                final String[] values = new String[1];
124                values[0] = (String) value;
125                newValue = ConvertUtils.convert(values, type);
126            } else if (value instanceof String[]) {
127                newValue = ConvertUtils.convert((String[]) value, type);
128            } else {
129                newValue = value;
130            }
131        } else if (type.isArray()) { // Indexed value into array
132            if (value instanceof String) {
133                newValue = ConvertUtils.convert((String) value, type.getComponentType());
134            } else if (value instanceof String[]) {
135                newValue = ConvertUtils.convert(((String[]) value)[0], type.getComponentType());
136            } else {
137                newValue = value;
138            }
139        } else if (value instanceof String) {
140            newValue = ConvertUtils.convert((String) value, type);
141        } else if (value instanceof String[]) {
142            newValue = ConvertUtils.convert(((String[]) value)[0], type);
143        } else {
144            newValue = value;
145        }
146        return newValue;
147    }
148
149    /**
150     * Convert the specified value to the required type using the specified conversion pattern.
151     *
152     * @param type    The Java type of target property
153     * @param index   The indexed subscript value (if any)
154     * @param value   The value to be converted
155     * @param pattern The conversion pattern
156     * @return The converted value
157     */
158    protected Object convert(final Class<?> type, final int index, final Object value, final String pattern) {
159        if (LOG.isTraceEnabled()) {
160            LOG.trace("Converting value '" + value + "' to type:" + type);
161        }
162
163        Object newValue = null;
164
165        if (type.isArray() && index < 0) { // Scalar value into array
166            if (value instanceof String) {
167                final String[] values = new String[1];
168                values[0] = (String) value;
169                newValue = getLocaleConvertUtils().convert(values, type, pattern);
170            } else if (value instanceof String[]) {
171                newValue = getLocaleConvertUtils().convert((String[]) value, type, pattern);
172            } else {
173                newValue = value;
174            }
175        } else if (type.isArray()) { // Indexed value into array
176            if (value instanceof String) {
177                newValue = getLocaleConvertUtils().convert((String) value, type.getComponentType(), pattern);
178            } else if (value instanceof String[]) {
179                newValue = getLocaleConvertUtils().convert(((String[]) value)[0], type.getComponentType(), pattern);
180            } else {
181                newValue = value;
182            }
183        } else if (value instanceof String) {
184            newValue = getLocaleConvertUtils().convert((String) value, type, pattern);
185        } else if (value instanceof String[]) {
186            newValue = getLocaleConvertUtils().convert(((String[]) value)[0], type, pattern);
187        } else {
188            newValue = value;
189        }
190        return newValue;
191    }
192
193    /**
194     * Calculate the property type.
195     *
196     * @param target   The bean
197     * @param name     The property name
198     * @param propName The Simple name of target property
199     * @return The property's type
200     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
201     * @throws InvocationTargetException if the property accessor method throws an exception
202     */
203    protected Class<?> definePropertyType(final Object target, final String name, final String propName)
204            throws IllegalAccessException, InvocationTargetException {
205        Class<?> type = null; // Java type of target property
206
207        if (target instanceof DynaBean) {
208            final DynaClass dynaClass = ((DynaBean) target).getDynaClass();
209            final DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
210            if (dynaProperty == null) {
211                return null; // Skip this property setter
212            }
213            type = dynaProperty.getType();
214        } else {
215            PropertyDescriptor descriptor = null;
216            try {
217                descriptor = getPropertyUtils().getPropertyDescriptor(target, name);
218                if (descriptor == null) {
219                    return null; // Skip this property setter
220                }
221            } catch (final NoSuchMethodException e) {
222                return null; // Skip this property setter
223            }
224            if (descriptor instanceof MappedPropertyDescriptor) {
225                type = ((MappedPropertyDescriptor) descriptor).getMappedPropertyType();
226            } else if (descriptor instanceof IndexedPropertyDescriptor) {
227                type = ((IndexedPropertyDescriptor) descriptor).getIndexedPropertyType();
228            } else {
229                type = descriptor.getPropertyType();
230            }
231        }
232        return type;
233    }
234
235    /**
236     * Is the pattern to be applied localized (Indicate whether the pattern is localized or not)
237     *
238     * @return {@code true} if pattern is localized, otherwise {@code false}
239     */
240    public boolean getApplyLocalized() {
241        return getLocaleConvertUtils().getApplyLocalized();
242    }
243
244    /**
245     * Gets the default Locale
246     *
247     * @return the default locale
248     */
249    public Locale getDefaultLocale() {
250        return getLocaleConvertUtils().getDefaultLocale();
251    }
252
253    /**
254     * Gets the value of the specified locale-sensitive indexed property of the specified bean, as a String using the default conversion pattern of the
255     * corresponding {@link LocaleConverter}. The zero-relative index of the required value must be included (in square brackets) as a suffix to the property
256     * name, or {@code IllegalArgumentException} will be thrown.
257     *
258     * @param bean Bean whose property is to be extracted
259     * @param name {@code propertyname[index]} of the property value to be extracted
260     * @return The indexed property's value, converted to a String
261     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
262     * @throws InvocationTargetException if the property accessor method throws an exception
263     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
264     */
265    @Override
266    public String getIndexedProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
267        return getIndexedProperty(bean, name, null);
268    }
269
270    /**
271     * Gets the value of the specified locale-sensetive indexed property of the specified bean, as a String using the default conversion pattern of the
272     * corresponding {@link LocaleConverter}. The index is specified as a method parameter and must *not* be included in the property name expression
273     *
274     * @param bean  Bean whose property is to be extracted
275     * @param name  Simple property name of the property value to be extracted
276     * @param index Index of the property value to be extracted
277     * @return The indexed property's value, converted to a String
278     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
279     * @throws InvocationTargetException if the property accessor method throws an exception
280     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
281     */
282    @Override
283    public String getIndexedProperty(final Object bean, final String name, final int index)
284            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
285        return getIndexedProperty(bean, name, index, null);
286    }
287
288    /**
289     * Gets the value of the specified locale-sensetive indexed property of the specified bean, as a String using the specified conversion pattern. The index is
290     * specified as a method parameter and must *not* be included in the property name expression
291     *
292     * @param bean    Bean whose property is to be extracted
293     * @param name    Simple property name of the property value to be extracted
294     * @param index   Index of the property value to be extracted
295     * @param pattern The conversion pattern
296     * @return The indexed property's value, converted to a String
297     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
298     * @throws InvocationTargetException if the property accessor method throws an exception
299     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
300     */
301    public String getIndexedProperty(final Object bean, final String name, final int index, final String pattern)
302            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
303        final Object value = getPropertyUtils().getIndexedProperty(bean, name, index);
304        return getLocaleConvertUtils().convert(value, pattern);
305    }
306
307    /**
308     * Gets the value of the specified locale-sensitive indexed property of the specified bean, as a String. The zero-relative index of the required value must
309     * be included (in square brackets) as a suffix to the property name, or {@code IllegalArgumentException} will be thrown.
310     *
311     * @param bean    Bean whose property is to be extracted
312     * @param name    {@code propertyname[index]} of the property value to be extracted
313     * @param pattern The conversion pattern
314     * @return The indexed property's value, converted to a String
315     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
316     * @throws InvocationTargetException if the property accessor method throws an exception
317     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
318     */
319    public String getIndexedProperty(final Object bean, final String name, final String pattern)
320            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
321        final Object value = getPropertyUtils().getIndexedProperty(bean, name);
322        return getLocaleConvertUtils().convert(value, pattern);
323    }
324
325    /**
326     * Gets the bean instance used for conversions
327     *
328     * @return the locale converter bean instance
329     */
330    public LocaleConvertUtilsBean getLocaleConvertUtils() {
331        return localeConvertUtils;
332    }
333
334    /**
335     * Gets the value of the specified locale-sensitive mapped property of the specified bean, as a String using the default conversion pattern of the
336     * corresponding {@link LocaleConverter}. The String-valued key of the required value must be included (in parentheses) as a suffix to the property name, or
337     * {@code IllegalArgumentException} will be thrown.
338     *
339     * @param bean Bean whose property is to be extracted
340     * @param name {@code propertyname(index)} of the property value to be extracted
341     * @return The mapped property's value, converted to a String
342     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
343     * @throws InvocationTargetException if the property accessor method throws an exception
344     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
345     */
346    @Override
347    public String getMappedProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
348        return getMappedPropertyLocale(bean, name, null);
349    }
350
351    /**
352     * Gets the value of the specified mapped locale-sensitive property of the specified bean, as a String The key is specified as a method parameter and must
353     * *not* be included in the property name expression
354     *
355     * @param bean Bean whose property is to be extracted
356     * @param name Simple property name of the property value to be extracted
357     * @param key  Lookup key of the property value to be extracted
358     * @return The mapped property's value, converted to a String
359     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
360     * @throws InvocationTargetException if the property accessor method throws an exception
361     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
362     */
363    @Override
364    public String getMappedProperty(final Object bean, final String name, final String key)
365            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
366        return getMappedProperty(bean, name, key, null);
367    }
368
369    /**
370     * Gets the value of the specified mapped locale-sensitive property of the specified bean, as a String using the specified conversion pattern. The key is
371     * specified as a method parameter and must *not* be included in the property name expression.
372     *
373     * @param bean    Bean whose property is to be extracted
374     * @param name    Simple property name of the property value to be extracted
375     * @param key     Lookup key of the property value to be extracted
376     * @param pattern The conversion pattern
377     * @return The mapped property's value, converted to a String
378     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
379     * @throws InvocationTargetException if the property accessor method throws an exception
380     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
381     */
382    public String getMappedProperty(final Object bean, final String name, final String key, final String pattern)
383            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
384        final Object value = getPropertyUtils().getMappedProperty(bean, name, key);
385        return getLocaleConvertUtils().convert(value, pattern);
386    }
387
388    /**
389     * Gets the value of the specified locale-sensitive mapped property of the specified bean, as a String using the specified pattern. The String-valued key of
390     * the required value must be included (in parentheses) as a suffix to the property name, or {@code IllegalArgumentException} will be thrown.
391     *
392     * @param bean    Bean whose property is to be extracted
393     * @param name    {@code propertyname(index)} of the property value to be extracted
394     * @param pattern The conversion pattern
395     * @return The mapped property's value, converted to a String
396     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
397     * @throws InvocationTargetException if the property accessor method throws an exception
398     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
399     */
400    public String getMappedPropertyLocale(final Object bean, final String name, final String pattern)
401            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
402        final Object value = getPropertyUtils().getMappedProperty(bean, name);
403        return getLocaleConvertUtils().convert(value, pattern);
404    }
405
406    /**
407     * Gets the value of the (possibly nested) locale-sensitive property of the specified name, for the specified bean, as a String using the default conversion
408     * pattern of the corresponding {@link LocaleConverter}.
409     *
410     * @param bean Bean whose property is to be extracted
411     * @param name Possibly nested name of the property to be extracted
412     * @return The nested property's value, converted to a String
413     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
414     * @throws IllegalArgumentException  if a nested reference to a property returns null
415     * @throws InvocationTargetException if the property accessor method throws an exception
416     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
417     */
418    @Override
419    public String getNestedProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
420        return getNestedProperty(bean, name, null);
421    }
422
423    /**
424     * Gets the value of the (possibly nested) locale-sensitive property of the specified name, for the specified bean, as a String using the specified pattern.
425     *
426     * @param bean    Bean whose property is to be extracted
427     * @param name    Possibly nested name of the property to be extracted
428     * @param pattern The conversion pattern
429     * @return The nested property's value, converted to a String
430     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
431     * @throws IllegalArgumentException  if a nested reference to a property returns null
432     * @throws InvocationTargetException if the property accessor method throws an exception
433     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
434     */
435    public String getNestedProperty(final Object bean, final String name, final String pattern)
436            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
437        final Object value = getPropertyUtils().getNestedProperty(bean, name);
438        return getLocaleConvertUtils().convert(value, pattern);
439    }
440
441    /**
442     * Gets the value of the specified locale-sensitive property of the specified bean, no matter which property reference format is used, as a String using the
443     * default conversion pattern of the corresponding {@link LocaleConverter}.
444     *
445     * @param bean Bean whose property is to be extracted
446     * @param name Possibly indexed and/or nested name of the property to be extracted
447     * @return The property's value, converted to a String
448     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
449     * @throws InvocationTargetException if the property accessor method throws an exception
450     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
451     */
452    @Override
453    public String getProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
454        return getNestedProperty(bean, name);
455    }
456
457    /**
458     * Gets the value of the specified locale-sensitive property of the specified bean, no matter which property reference format is used, as a String using the
459     * specified conversion pattern.
460     *
461     * @param bean    Bean whose property is to be extracted
462     * @param name    Possibly indexed and/or nested name of the property to be extracted
463     * @param pattern The conversion pattern
464     * @return The nested property's value, converted to a String
465     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
466     * @throws InvocationTargetException if the property accessor method throws an exception
467     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
468     */
469    public String getProperty(final Object bean, final String name, final String pattern)
470            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
471        return getNestedProperty(bean, name, pattern);
472    }
473
474    /**
475     * Gets the value of the specified simple locale-sensitive property of the specified bean, converted to a String using the default conversion pattern of the
476     * corresponding {@link LocaleConverter}.
477     *
478     * @param bean Bean whose property is to be extracted
479     * @param name Name of the property to be extracted
480     * @return The property's value, converted to a String
481     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
482     * @throws InvocationTargetException if the property accessor method throws an exception
483     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
484     */
485    @Override
486    public String getSimpleProperty(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
487        return getSimpleProperty(bean, name, null);
488    }
489
490    /**
491     * Gets the value of the specified simple locale-sensitive property of the specified bean, converted to a String using the specified conversion pattern.
492     *
493     * @param bean    Bean whose property is to be extracted
494     * @param name    Name of the property to be extracted
495     * @param pattern The conversion pattern
496     * @return The property's value, converted to a String
497     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
498     * @throws InvocationTargetException if the property accessor method throws an exception
499     * @throws NoSuchMethodException     if an accessor method for this property cannot be found
500     */
501    public String getSimpleProperty(final Object bean, final String name, final String pattern)
502            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
503        final Object value = getPropertyUtils().getSimpleProperty(bean, name);
504        return getLocaleConvertUtils().convert(value, pattern);
505    }
506
507    /**
508     * Invoke the setter method.
509     *
510     * @param target   The bean
511     * @param propName The Simple name of target property
512     * @param key      The Mapped key value (if any)
513     * @param index    The indexed subscript value (if any)
514     * @param newValue The value to be set
515     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
516     * @throws InvocationTargetException if the property accessor method throws an exception
517     */
518    protected void invokeSetter(final Object target, final String propName, final String key, final int index, final Object newValue)
519            throws IllegalAccessException, InvocationTargetException {
520
521        try {
522            if (index >= 0) {
523                getPropertyUtils().setIndexedProperty(target, propName, index, newValue);
524            } else if (key != null) {
525                getPropertyUtils().setMappedProperty(target, propName, key, newValue);
526            } else {
527                getPropertyUtils().setProperty(target, propName, newValue);
528            }
529        } catch (final NoSuchMethodException e) {
530            throw new InvocationTargetException(e, "Cannot set " + propName);
531        }
532    }
533
534    /**
535     * Sets whether the pattern is applied localized (Indicate whether the pattern is localized or not)
536     *
537     * @param newApplyLocalized {@code true} if pattern is localized, otherwise {@code false}
538     */
539    public void setApplyLocalized(final boolean newApplyLocalized) {
540        getLocaleConvertUtils().setApplyLocalized(newApplyLocalized);
541    }
542
543    /**
544     * Sets the default Locale.
545     *
546     * @param locale the default locale
547     */
548    public void setDefaultLocale(final Locale locale) {
549        getLocaleConvertUtils().setDefaultLocale(locale);
550    }
551
552    /**
553     * Sets the specified locale-sensitive property value, performing type conversions as required to conform to the type of the destination property using the
554     * default conversion pattern of the corresponding {@link LocaleConverter}.
555     *
556     * @param bean  Bean on which setting is to be performed
557     * @param name  Property name (can be nested/indexed/mapped/combo)
558     * @param value Value to be set
559     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
560     * @throws InvocationTargetException if the property accessor method throws an exception
561     */
562    @Override
563    public void setProperty(final Object bean, final String name, final Object value) throws IllegalAccessException, InvocationTargetException {
564        setProperty(bean, name, value, null);
565    }
566
567    /**
568     * Sets the specified locale-sensitive property value, performing type conversions as required to conform to the type of the destination property using the
569     * specified conversion pattern.
570     *
571     * @param bean    Bean on which setting is to be performed
572     * @param name    Property name (can be nested/indexed/mapped/combo)
573     * @param value   Value to be set
574     * @param pattern The conversion pattern
575     * @throws IllegalAccessException    if the caller does not have access to the property accessor method
576     * @throws InvocationTargetException if the property accessor method throws an exception
577     */
578    public void setProperty(final Object bean, String name, final Object value, final String pattern) throws IllegalAccessException, InvocationTargetException {
579        // Trace logging (if enabled)
580        if (LOG.isTraceEnabled()) {
581            final StringBuilder sb = new StringBuilder("  setProperty(");
582            sb.append(bean);
583            sb.append(", ");
584            sb.append(name);
585            sb.append(", ");
586            if (value == null) {
587                sb.append("<NULL>");
588            } else if (value instanceof String) {
589                sb.append((String) value);
590            } else if (value instanceof String[]) {
591                final String[] values = (String[]) value;
592                sb.append('[');
593                for (int i = 0; i < values.length; i++) {
594                    if (i > 0) {
595                        sb.append(',');
596                    }
597                    sb.append(values[i]);
598                }
599                sb.append(']');
600            } else {
601                sb.append(value.toString());
602            }
603            sb.append(')');
604            LOG.trace(sb.toString());
605        }
606
607        // Resolve any nested expression to get the actual target bean
608        Object target = bean;
609        final Resolver resolver = getPropertyUtils().getResolver();
610        while (resolver.hasNested(name)) {
611            try {
612                target = getPropertyUtils().getProperty(target, resolver.next(name));
613                name = resolver.remove(name);
614            } catch (final NoSuchMethodException e) {
615                return; // Skip this property setter
616            }
617        }
618        if (LOG.isTraceEnabled()) {
619            LOG.trace("    Target bean = " + target);
620            LOG.trace("    Target name = " + name);
621        }
622
623        // Declare local variables we will require
624        final String propName = resolver.getProperty(name); // Simple name of target property
625        final int index = resolver.getIndex(name); // Indexed subscript value (if any)
626        final String key = resolver.getKey(name); // Mapped key value (if any)
627
628        final Class<?> type = definePropertyType(target, name, propName);
629        if (type != null) {
630            final Object newValue = convert(type, index, value, pattern);
631            invokeSetter(target, propName, key, index, newValue);
632        }
633    }
634}