View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils2.locale;
19  
20  import java.beans.IndexedPropertyDescriptor;
21  import java.beans.PropertyDescriptor;
22  import java.lang.reflect.InvocationTargetException;
23  import java.util.Locale;
24  
25  import org.apache.commons.beanutils2.BeanUtilsBean;
26  import org.apache.commons.beanutils2.ContextClassLoaderLocal;
27  import org.apache.commons.beanutils2.ConvertUtils;
28  import org.apache.commons.beanutils2.ConvertUtilsBean;
29  import org.apache.commons.beanutils2.DynaBean;
30  import org.apache.commons.beanutils2.DynaClass;
31  import org.apache.commons.beanutils2.DynaProperty;
32  import org.apache.commons.beanutils2.MappedPropertyDescriptor;
33  import org.apache.commons.beanutils2.PropertyUtilsBean;
34  import org.apache.commons.beanutils2.expression.Resolver;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  /**
39   * <p>
40   * Utility methods for populating JavaBeans properties via reflection in a locale-dependent manner.
41   * </p>
42   *
43   * @since 1.7
44   */
45  public class LocaleBeanUtilsBean extends BeanUtilsBean {
46  
47      /**
48       * Contains {@code LocaleBeanUtilsBean} instances indexed by context classloader.
49       */
50      private static final ContextClassLoaderLocal<LocaleBeanUtilsBean> LOCALE_BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<LocaleBeanUtilsBean>() {
51          // Creates the default instance used when the context classloader is unavailable
52          @Override
53          protected LocaleBeanUtilsBean initialValue() {
54              return new LocaleBeanUtilsBean();
55          }
56      };
57  
58      /** All logging goes through this logger */
59      private static final Log LOG = LogFactory.getLog(LocaleBeanUtilsBean.class);
60  
61      /**
62       * Gets singleton instance
63       *
64       * @return the singleton instance
65       */
66      public static LocaleBeanUtilsBean getLocaleBeanUtilsInstance() {
67          return LOCALE_BEANS_BY_CLASSLOADER.get();
68      }
69  
70      /**
71       * Sets the instance which provides the functionality for {@link LocaleBeanUtils}. This is a pseudo-singleton - an single instance is provided per (thread)
72       * context classloader. This mechanism provides isolation for web apps deployed in the same container.
73       *
74       * @param newInstance a new singleton instance
75       */
76      public static void setInstance(final LocaleBeanUtilsBean newInstance) {
77          LOCALE_BEANS_BY_CLASSLOADER.set(newInstance);
78      }
79  
80      /** Convertor used by this class */
81      private final LocaleConvertUtilsBean localeConvertUtils;
82  
83      /** Constructs instance with standard conversion bean */
84      public LocaleBeanUtilsBean() {
85          this.localeConvertUtils = new LocaleConvertUtilsBean();
86      }
87  
88      /**
89       * Constructs instance that uses given locale conversion
90       *
91       * @param localeConvertUtils use this {@code localeConvertUtils} to perform conversions
92       */
93      public LocaleBeanUtilsBean(final LocaleConvertUtilsBean localeConvertUtils) {
94          this.localeConvertUtils = localeConvertUtils;
95      }
96  
97      /**
98       * Constructs instance that uses given locale conversion
99       *
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 }