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 *      https://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.locale;
019
020import java.beans.IndexedPropertyDescriptor;
021import java.beans.PropertyDescriptor;
022import java.lang.reflect.InvocationTargetException;
023import java.util.Locale;
024
025import org.apache.commons.beanutils.BeanUtilsBean;
026import org.apache.commons.beanutils.ContextClassLoaderLocal;
027import org.apache.commons.beanutils.ConvertUtils;
028import org.apache.commons.beanutils.ConvertUtilsBean;
029import org.apache.commons.beanutils.DynaBean;
030import org.apache.commons.beanutils.DynaClass;
031import org.apache.commons.beanutils.DynaProperty;
032import org.apache.commons.beanutils.MappedPropertyDescriptor;
033import org.apache.commons.beanutils.PropertyUtilsBean;
034import org.apache.commons.beanutils.expression.Resolver;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037
038/**
039 * <p>Utility methods for populating JavaBeans properties
040 * via reflection in a locale-dependent manner.</p>
041 *
042 * @since 1.7
043 */
044
045public class LocaleBeanUtilsBean extends BeanUtilsBean {
046
047    /**
048     * @deprecated Property name expressions are now processed by
049     * the configured {@link Resolver} implementation and this class
050     * is no longer used by BeanUtils.
051     */
052    @Deprecated
053    protected class Descriptor {
054
055        private int index = -1;    // Indexed subscript value (if any)
056        private String name;
057        private String propName;   // Simple name of target property
058        private String key;        // Mapped key value (if any)
059        private Object target;
060
061        /**
062         * Construct a descriptor instance for the target bean and property.
063         *
064         * @param target The target bean
065         * @param name The property name (includes indexed/mapped expr)
066         * @param propName The property name
067         * @param key The mapped property key (if any)
068         * @param index The indexed property index (if any)
069         */
070        public Descriptor(final Object target, final String name, final String propName, final String key, final int index) {
071
072            setTarget(target);
073            setName(name);
074            setPropName(propName);
075            setKey(key);
076            setIndex(index);
077        }
078
079        /**
080         * Return indexed property index.
081         *
082         * @return indexed property index (if any)
083         */
084        public int getIndex() {
085            return index;
086        }
087
088        /**
089         * Return the mapped property key.
090         *
091         * @return the mapped property key (if any)
092         */
093        public String getKey() {
094            return key;
095        }
096
097        /**
098         * Return property name (includes indexed/mapped expr).
099         *
100         * @return The property name (includes indexed/mapped expr)
101         */
102        public String getName() {
103            return name;
104        }
105
106        /**
107         * Return the property name.
108         *
109         * @return The property name
110         */
111        public String getPropName() {
112            return propName;
113        }
114
115        /**
116         * Return the target bean.
117         *
118         * @return The descriptors target bean
119         */
120        public Object getTarget() {
121            return target;
122        }
123
124        /**
125         * Set the indexed property index.
126         *
127         * @param index The indexed property index (if any)
128         */
129        public void setIndex(final int index) {
130            this.index = index;
131        }
132
133        /**
134         * Set the mapped property key.
135         *
136         * @param key The mapped property key (if any)
137         */
138        public void setKey(final String key) {
139            this.key = key;
140        }
141
142        /**
143         * Set the property name (includes indexed/mapped expr).
144         *
145         * @param name The property name (includes indexed/mapped expr)
146         */
147        public void setName(final String name) {
148            this.name = name;
149        }
150
151        /**
152         * Set the property name.
153         *
154         * @param propName The property name
155         */
156        public void setPropName(final String propName) {
157            this.propName = propName;
158        }
159
160        /**
161         * Set the target bean.
162         *
163         * @param target The target bean
164         */
165        public void setTarget(final Object target) {
166            this.target = target;
167        }
168    }
169
170     /**
171     * Contains <code>LocaleBeanUtilsBean</code> instances indexed by context classloader.
172     */
173    private static final ContextClassLoaderLocal<LocaleBeanUtilsBean>
174            LOCALE_BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<LocaleBeanUtilsBean>() {
175                        // Creates the default instance used when the context classloader is unavailable
176                        @Override
177                        protected LocaleBeanUtilsBean initialValue() {
178                            return new LocaleBeanUtilsBean();
179                        }
180                    };
181
182    /**
183      * Gets singleton instance
184      *
185      * @return the singleton instance
186      */
187     public static LocaleBeanUtilsBean getLocaleBeanUtilsInstance() {
188        return LOCALE_BEANS_BY_CLASSLOADER.get();
189     }
190
191    /**
192     * Sets the instance which provides the functionality for {@link LocaleBeanUtils}.
193     * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
194     * This mechanism provides isolation for web apps deployed in the same container.
195     *
196     * @param newInstance a new singleton instance
197     */
198    public static void setInstance(final LocaleBeanUtilsBean newInstance) {
199        LOCALE_BEANS_BY_CLASSLOADER.set(newInstance);
200    }
201
202    /** All logging goes through this logger */
203    private final Log log = LogFactory.getLog(LocaleBeanUtilsBean.class);
204
205    /** Convertor used by this class */
206    private final LocaleConvertUtilsBean localeConvertUtils;
207
208    /** Construct instance with standard conversion bean */
209    public LocaleBeanUtilsBean() {
210        this.localeConvertUtils = new LocaleConvertUtilsBean();
211    }
212
213    /**
214     * Construct instance that uses given locale conversion
215     *
216     * @param localeConvertUtils use this <code>localeConvertUtils</code> to perform
217     * conversions
218     */
219    public LocaleBeanUtilsBean(final LocaleConvertUtilsBean localeConvertUtils) {
220        this.localeConvertUtils = localeConvertUtils;
221    }
222
223    /**
224     * Construct instance that uses given locale conversion
225     *
226     * @param localeConvertUtils use this <code>localeConvertUtils</code> to perform
227     * conversions
228     * @param convertUtilsBean use this for standard conversions
229     * @param propertyUtilsBean use this for property conversions
230     */
231    public LocaleBeanUtilsBean(
232                            final LocaleConvertUtilsBean localeConvertUtils,
233                            final ConvertUtilsBean convertUtilsBean,
234                            final PropertyUtilsBean propertyUtilsBean) {
235        super(convertUtilsBean, propertyUtilsBean);
236        this.localeConvertUtils = localeConvertUtils;
237    }
238
239    /**
240     * Resolve any nested expression to get the actual target property.
241     *
242     * @param bean The bean
243     * @param name The property name
244     * @return The property's descriptor
245     * @throws IllegalAccessException if the caller does not have
246     *  access to the property accessor method
247     * @throws InvocationTargetException if the property accessor method
248     *  throws an exception
249     * @deprecated Property name expressions are now processed by
250     * the configured {@link Resolver} implementation and this method
251     * is no longer used by BeanUtils.
252     */
253    @Deprecated
254    protected Descriptor calculate(final Object bean, String name)
255            throws IllegalAccessException, InvocationTargetException {
256
257        // Resolve any nested expression to get the actual target bean
258        Object target = bean;
259        final Resolver resolver = getPropertyUtils().getResolver();
260        while (resolver.hasNested(name)) {
261            try {
262                target = getPropertyUtils().getProperty(target, resolver.next(name));
263                name = resolver.remove(name);
264            } catch (final NoSuchMethodException e) {
265                return null; // Skip this property setter
266            }
267        }
268        if (log.isTraceEnabled()) {
269            log.trace("    Target bean = " + target);
270            log.trace("    Target name = " + name);
271        }
272
273        // Declare local variables we will require
274        final String propName = resolver.getProperty(name); // Simple name of target property
275        final int index  = resolver.getIndex(name);         // Indexed subscript value (if any)
276        final String key = resolver.getKey(name);           // Mapped key value (if any)
277
278        return new Descriptor(target, name, propName, key, index);
279    }
280
281    /**
282     *  Convert the specified value to the required type.
283     *
284     * @param type The Java type of target property
285     * @param index The indexed subscript value (if any)
286     * @param value The value to be converted
287     * @return The converted value
288     */
289    protected Object convert(final Class<?> type, final int index, final Object value) {
290
291        Object newValue = null;
292
293        if (type.isArray() && index < 0) { // Scalar value into array
294            if (value instanceof String) {
295                final String[] values = new String[1];
296                values[0] = (String) value;
297                newValue = ConvertUtils.convert(values, type);
298            }
299            else if (value instanceof String[]) {
300                newValue = ConvertUtils.convert((String[]) value, type);
301            }
302            else {
303                newValue = value;
304            }
305        }
306        else if (type.isArray()) {         // Indexed value into array
307            if (value instanceof String) {
308                newValue = ConvertUtils.convert((String) value,
309                        type.getComponentType());
310            }
311            else if (value instanceof String[]) {
312                newValue = ConvertUtils.convert(((String[]) value)[0],
313                        type.getComponentType());
314            }
315            else {
316                newValue = value;
317            }
318        } else if (value instanceof String) {
319            newValue = ConvertUtils.convert((String) value, type);
320        }
321        else if (value instanceof String[]) {
322            newValue = ConvertUtils.convert(((String[]) value)[0],
323                    type);
324        }
325        else {
326            newValue = value;
327        }
328        return newValue;
329    }
330
331    /**
332     * Convert the specified value to the required type using the
333     * specified conversion pattern.
334     *
335     * @param type The Java type of target property
336     * @param index The indexed subscript value (if any)
337     * @param value The value to be converted
338     * @param pattern The conversion pattern
339     * @return The converted value
340     */
341    protected Object convert(final Class<?> type, final int index, final Object value, final String pattern) {
342
343        if (log.isTraceEnabled()) {
344            log.trace("Converting value '" + value + "' to type:" + type);
345        }
346
347        Object newValue = null;
348
349        if (type.isArray() && index < 0) { // Scalar value into array
350            if (value instanceof String) {
351                final String[] values = new String[1];
352                values[0] = (String) value;
353                newValue = getLocaleConvertUtils().convert(values, type, pattern);
354            }
355            else if (value instanceof String[]) {
356                newValue = getLocaleConvertUtils().convert((String[]) value, type, pattern);
357            }
358            else {
359                newValue = value;
360            }
361        }
362        else if (type.isArray()) {         // Indexed value into array
363            if (value instanceof String) {
364                newValue = getLocaleConvertUtils().convert((String) value,
365                        type.getComponentType(), pattern);
366            }
367            else if (value instanceof String[]) {
368                newValue = getLocaleConvertUtils().convert(((String[]) value)[0],
369                        type.getComponentType(), pattern);
370            }
371            else {
372                newValue = value;
373            }
374        } else if (value instanceof String) {
375            newValue = getLocaleConvertUtils().convert((String) value, type, pattern);
376        }
377        else if (value instanceof String[]) {
378            newValue = getLocaleConvertUtils().convert(((String[]) value)[0],
379                    type, pattern);
380        }
381        else {
382            newValue = value;
383        }
384        return newValue;
385    }
386
387    /**
388     * Calculate the property type.
389     *
390     * @param target The bean
391     * @param name The property name
392     * @param propName The Simple name of target property
393     * @return The property's type
394     * @throws IllegalAccessException if the caller does not have
395     *  access to the property accessor method
396     * @throws InvocationTargetException if the property accessor method
397     *  throws an exception
398     */
399    protected Class<?> definePropertyType(final Object target, final String name, final String propName)
400            throws IllegalAccessException, InvocationTargetException {
401
402        Class<?> type = null;               // Java type of target property
403
404        if (target instanceof DynaBean) {
405            final DynaClass dynaClass = ((DynaBean) target).getDynaClass();
406            final DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
407            if (dynaProperty == null) {
408                return null; // Skip this property setter
409            }
410            type = dynaProperty.getType();
411        }
412        else {
413            PropertyDescriptor descriptor = null;
414            try {
415                descriptor =
416                        getPropertyUtils().getPropertyDescriptor(target, name);
417                if (descriptor == null) {
418                    return null; // Skip this property setter
419                }
420            }
421            catch (final NoSuchMethodException e) {
422                return null; // Skip this property setter
423            }
424            if (descriptor instanceof MappedPropertyDescriptor) {
425                type = ((MappedPropertyDescriptor) descriptor).
426                        getMappedPropertyType();
427            }
428            else if (descriptor instanceof IndexedPropertyDescriptor) {
429                type = ((IndexedPropertyDescriptor) descriptor).
430                        getIndexedPropertyType();
431            }
432            else {
433                type = descriptor.getPropertyType();
434            }
435        }
436        return type;
437    }
438
439    /**
440     * Is the pattern to be applied localized
441     * (Indicate whether the pattern is localized or not)
442     *
443     * @return <code>true</code> if pattern is localized,
444     * otherwise <code>false</code>
445     */
446    public boolean getApplyLocalized() {
447
448        return getLocaleConvertUtils().getApplyLocalized();
449    }
450
451    /**
452     * Gets the default Locale
453     * @return the default locale
454     */
455    public Locale getDefaultLocale() {
456
457        return getLocaleConvertUtils().getDefaultLocale();
458    }
459
460    /**
461     * Return the value of the specified locale-sensitive indexed property
462     * of the specified bean, as a String using the default conversion pattern of
463     * the corresponding {@link LocaleConverter}. The zero-relative index
464     * of the required value must be included (in square brackets) as a suffix
465     * to the property name, or <code>IllegalArgumentException</code> will be thrown.
466     *
467     * @param bean Bean whose property is to be extracted
468     * @param name <code>propertyname[index]</code> of the property value
469     *  to be extracted
470     * @return The indexed property's value, converted to a String
471     * @throws IllegalAccessException if the caller does not have
472     *  access to the property accessor method
473     * @throws InvocationTargetException if the property accessor method
474     *  throws an exception
475     * @throws NoSuchMethodException if an accessor method for this
476     *  propety cannot be found
477     */
478    @Override
479    public String getIndexedProperty(
480                                    final Object bean,
481                                    final String name)
482                                        throws
483                                            IllegalAccessException,
484                                            InvocationTargetException,
485                                            NoSuchMethodException {
486
487        return getIndexedProperty(bean, name, null);
488    }
489
490    /**
491     * Return the value of the specified locale-sensetive indexed property
492     * of the specified bean, as a String using the default conversion pattern of
493     * the corresponding {@link LocaleConverter}.
494     * The index is specified as a method parameter and
495     * must *not* be included in the property name expression
496     *
497     * @param bean Bean whose property is to be extracted
498     * @param name Simple property name of the property value to be extracted
499     * @param index Index of the property value to be extracted
500     * @return The indexed property's value, converted to a String
501     * @throws IllegalAccessException if the caller does not have
502     *  access to the property accessor method
503     * @throws InvocationTargetException if the property accessor method
504     *  throws an exception
505     * @throws NoSuchMethodException if an accessor method for this
506     *  propety cannot be found
507     */
508    @Override
509    public String getIndexedProperty(final Object bean,
510                                            final String name, final int index)
511            throws IllegalAccessException, InvocationTargetException,
512            NoSuchMethodException {
513        return getIndexedProperty(bean, name, index, null);
514    }
515
516    /**
517     * Return the value of the specified locale-sensetive indexed property
518     * of the specified bean, as a String using the specified conversion pattern.
519     * The index is specified as a method parameter and
520     * must *not* be included in the property name expression
521     *
522     * @param bean Bean whose property is to be extracted
523     * @param name Simple property name of the property value to be extracted
524     * @param index Index of the property value to be extracted
525     * @param pattern The conversion pattern
526     * @return The indexed property's value, converted to a String
527     * @throws IllegalAccessException if the caller does not have
528     *  access to the property accessor method
529     * @throws InvocationTargetException if the property accessor method
530     *  throws an exception
531     * @throws NoSuchMethodException if an accessor method for this
532     *  propety cannot be found
533     */
534    public String getIndexedProperty(final Object bean,
535                                            final String name, final int index, final String pattern)
536            throws IllegalAccessException, InvocationTargetException,
537            NoSuchMethodException {
538
539        final Object value = getPropertyUtils().getIndexedProperty(bean, name, index);
540        return getLocaleConvertUtils().convert(value, pattern);
541    }
542
543    /**
544     * Return the value of the specified locale-sensitive indexed property
545     * of the specified bean, as a String. The zero-relative index of the
546     * required value must be included (in square brackets) as a suffix to
547     * the property name, or <code>IllegalArgumentException</code> will be
548     * thrown.
549     *
550     * @param bean Bean whose property is to be extracted
551     * @param name <code>propertyname[index]</code> of the property value
552     *  to be extracted
553     * @param pattern The conversion pattern
554     * @return The indexed property's value, converted to a String
555     * @throws IllegalAccessException if the caller does not have
556     *  access to the property accessor method
557     * @throws InvocationTargetException if the property accessor method
558     *  throws an exception
559     * @throws NoSuchMethodException if an accessor method for this
560     *  propety cannot be found
561     */
562    public String getIndexedProperty(
563                                    final Object bean,
564                                    final String name,
565                                    final String pattern)
566                                        throws
567                                            IllegalAccessException,
568                                            InvocationTargetException,
569                                            NoSuchMethodException {
570
571        final Object value = getPropertyUtils().getIndexedProperty(bean, name);
572        return getLocaleConvertUtils().convert(value, pattern);
573    }
574
575    /**
576     * Gets the bean instance used for conversions
577     *
578     * @return the locale converter bean instance
579     */
580    public LocaleConvertUtilsBean getLocaleConvertUtils() {
581        return localeConvertUtils;
582    }
583
584    /**
585     * Return the value of the specified locale-sensitive mapped property
586     * of the specified bean, as a String using the default
587     * conversion pattern of the corresponding {@link LocaleConverter}.
588     * The String-valued key of the required value
589     * must be included (in parentheses) as a suffix to
590     * the property name, or <code>IllegalArgumentException</code> will be
591     * thrown.
592     *
593     * @param bean Bean whose property is to be extracted
594     * @param name <code>propertyname(index)</code> of the property value
595     *  to be extracted
596     * @return The mapped property's value, converted to a String
597     * @throws IllegalAccessException if the caller does not have
598     *  access to the property accessor method
599     * @throws InvocationTargetException if the property accessor method
600     *  throws an exception
601     * @throws NoSuchMethodException if an accessor method for this
602     *  property cannot be found
603     */
604    @Override
605    public String getMappedProperty(final Object bean, final String name)
606                                    throws
607                                        IllegalAccessException,
608                                        InvocationTargetException,
609                                        NoSuchMethodException {
610
611        return getMappedPropertyLocale(bean, name, null);
612    }
613
614    /**
615     * Return the value of the specified mapped locale-sensitive property
616     * of the specified bean, as a String
617     * The key is specified as a method parameter and must *not* be included
618     * in the property name expression
619     *
620     * @param bean Bean whose property is to be extracted
621     * @param name Simple property name of the property value to be extracted
622     * @param key Lookup key of the property value to be extracted
623     * @return The mapped property's value, converted to a String
624     * @throws IllegalAccessException if the caller does not have
625     *  access to the property accessor method
626     * @throws InvocationTargetException if the property accessor method
627     *  throws an exception
628     * @throws NoSuchMethodException if an accessor method for this
629     *  property cannot be found
630     */
631    @Override
632    public String getMappedProperty(final Object bean,
633                                           final String name, final String key)
634            throws IllegalAccessException, InvocationTargetException,
635            NoSuchMethodException {
636
637        return getMappedProperty(bean, name, key, null);
638    }
639
640    /**
641     * Return the value of the specified mapped locale-sensitive property
642     * of the specified bean, as a String using the specified conversion pattern.
643     * The key is specified as a method parameter and must *not* be included in
644     * the property name expression.
645     *
646     * @param bean Bean whose property is to be extracted
647     * @param name Simple property name of the property value to be extracted
648     * @param key Lookup key of the property value to be extracted
649     * @param pattern The conversion pattern
650     * @return The mapped property's value, converted to a String
651     * @throws IllegalAccessException if the caller does not have
652     *  access to the property accessor method
653     * @throws InvocationTargetException if the property accessor method
654     *  throws an exception
655     * @throws NoSuchMethodException if an accessor method for this
656     *  property cannot be found
657     */
658    public String getMappedProperty(
659                                    final Object bean,
660                                    final String name,
661                                    final String key,
662                                    final String pattern)
663                                        throws
664                                            IllegalAccessException,
665                                            InvocationTargetException,
666                                            NoSuchMethodException {
667
668        final Object value = getPropertyUtils().getMappedProperty(bean, name, key);
669        return getLocaleConvertUtils().convert(value, pattern);
670    }
671
672    /**
673     * Return the value of the specified locale-sensitive mapped property
674     * of the specified bean, as a String using the specified pattern.
675     * The String-valued key of the required value
676     * must be included (in parentheses) as a suffix to
677     * the property name, or <code>IllegalArgumentException</code> will be
678     * thrown.
679     *
680     * @param bean Bean whose property is to be extracted
681     * @param name <code>propertyname(index)</code> of the property value
682     *  to be extracted
683     * @param pattern The conversion pattern
684     * @return The mapped property's value, converted to a String
685     * @throws IllegalAccessException if the caller does not have
686     *  access to the property accessor method
687     * @throws InvocationTargetException if the property accessor method
688     *  throws an exception
689     * @throws NoSuchMethodException if an accessor method for this
690     *  property cannot be found
691     */
692    public String getMappedPropertyLocale(
693                                        final Object bean,
694                                        final String name,
695                                        final String pattern)
696                                            throws
697                                                IllegalAccessException,
698                                                InvocationTargetException,
699                                                NoSuchMethodException {
700
701        final Object value = getPropertyUtils().getMappedProperty(bean, name);
702        return getLocaleConvertUtils().convert(value, pattern);
703    }
704
705    /**
706     * Return the value of the (possibly nested) locale-sensitive property
707     * of the specified name, for the specified bean, as a String using the default
708     * conversion pattern of the corresponding {@link LocaleConverter}.
709     *
710     * @param bean Bean whose property is to be extracted
711     * @param name Possibly nested name of the property to be extracted
712     * @return The nested property's value, converted to a String
713     * @throws IllegalAccessException if the caller does not have
714     *  access to the property accessor method
715     * @throws IllegalArgumentException if a nested reference to a
716     *  property returns null
717     * @throws InvocationTargetException if the property accessor method
718     *  throws an exception
719     * @throws NoSuchMethodException if an accessor method for this
720     *  property cannot be found
721     */
722    @Override
723    public String getNestedProperty(final Object bean, final String name)
724                                    throws
725                                        IllegalAccessException,
726                                        InvocationTargetException,
727                                        NoSuchMethodException {
728
729        return getNestedProperty(bean, name, null);
730    }
731
732    /**
733     * Return the value of the (possibly nested) locale-sensitive property
734     * of the specified name, for the specified bean,
735     * as a String using the specified pattern.
736     *
737     * @param bean Bean whose property is to be extracted
738     * @param name Possibly nested name of the property to be extracted
739     * @param pattern The conversion pattern
740     * @return The nested property's value, converted to a String
741     * @throws IllegalAccessException if the caller does not have
742     *  access to the property accessor method
743     * @throws IllegalArgumentException if a nested reference to a
744     *  property returns null
745     * @throws InvocationTargetException if the property accessor method
746     *  throws an exception
747     * @throws NoSuchMethodException if an accessor method for this
748     *  property cannot be found
749     */
750    public String getNestedProperty(
751                                    final Object bean,
752                                    final String name,
753                                    final String pattern)
754                                        throws
755                                            IllegalAccessException,
756                                            InvocationTargetException,
757                                            NoSuchMethodException {
758
759        final Object value = getPropertyUtils().getNestedProperty(bean, name);
760        return getLocaleConvertUtils().convert(value, pattern);
761    }
762
763    /**
764     * Return the value of the specified locale-sensitive property
765     * of the specified bean, no matter which property reference
766     * format is used, as a String using the default
767     * conversion pattern of the corresponding {@link LocaleConverter}.
768     *
769     * @param bean Bean whose property is to be extracted
770     * @param name Possibly indexed and/or nested name of the property
771     *  to be extracted
772     * @return The property's value, converted to a String
773     * @throws IllegalAccessException if the caller does not have
774     *  access to the property accessor method
775     * @throws InvocationTargetException if the property accessor method
776     *  throws an exception
777     * @throws NoSuchMethodException if an accessor method for this
778     *  property cannot be found
779     */
780    @Override
781    public String getProperty(final Object bean, final String name)
782                                throws
783                                    IllegalAccessException,
784                                    InvocationTargetException,
785                                    NoSuchMethodException {
786
787        return getNestedProperty(bean, name);
788    }
789
790    /**
791     * Return the value of the specified locale-sensitive property
792     * of the specified bean, no matter which property reference
793     * format is used, as a String using the specified conversion pattern.
794     *
795     * @param bean Bean whose property is to be extracted
796     * @param name Possibly indexed and/or nested name of the property
797     *  to be extracted
798     * @param pattern The conversion pattern
799     * @return The nested property's value, converted to a String
800     * @throws IllegalAccessException if the caller does not have
801     *  access to the property accessor method
802     * @throws InvocationTargetException if the property accessor method
803     *  throws an exception
804     * @throws NoSuchMethodException if an accessor method for this
805     *  property cannot be found
806     */
807    public String getProperty(final Object bean, final String name, final String pattern)
808                                throws
809                                    IllegalAccessException,
810                                    InvocationTargetException,
811                                    NoSuchMethodException {
812
813        return getNestedProperty(bean, name, pattern);
814    }
815
816    /**
817     * Return the value of the specified simple locale-sensitive property
818     * of the specified bean, converted to a String using the default
819     * conversion pattern of the corresponding {@link LocaleConverter}.
820     *
821     * @param bean Bean whose property is to be extracted
822     * @param name Name of the property to be extracted
823     * @return The property's value, converted to a String
824     * @throws IllegalAccessException if the caller does not have
825     *  access to the property accessor method
826     * @throws InvocationTargetException if the property accessor method
827     *  throws an exception
828     * @throws NoSuchMethodException if an accessor method for this
829     *  property cannot be found
830     */
831    @Override
832    public String getSimpleProperty(final Object bean, final String name)
833            throws IllegalAccessException, InvocationTargetException,
834            NoSuchMethodException {
835
836        return getSimpleProperty(bean, name, null);
837    }
838
839    /**
840     * Return the value of the specified simple locale-sensitive property
841     * of the specified bean, converted to a String using the specified
842     * conversion pattern.
843     *
844     * @param bean Bean whose property is to be extracted
845     * @param name Name of the property to be extracted
846     * @param pattern The conversion pattern
847     * @return The property's value, converted to a String
848     * @throws IllegalAccessException if the caller does not have
849     *  access to the property accessor method
850     * @throws InvocationTargetException if the property accessor method
851     *  throws an exception
852     * @throws NoSuchMethodException if an accessor method for this
853     *  property cannot be found
854     */
855    public String getSimpleProperty(final Object bean, final String name, final String pattern)
856            throws IllegalAccessException, InvocationTargetException,
857            NoSuchMethodException {
858
859        final Object value = getPropertyUtils().getSimpleProperty(bean, name);
860        return getLocaleConvertUtils().convert(value, pattern);
861    }
862
863    /**
864     * Invoke the setter method.
865     *
866     * @param target The bean
867     * @param propName The Simple name of target property
868     * @param key The Mapped key value (if any)
869     * @param index The indexed subscript value (if any)
870     * @param newValue The value to be set
871     * @throws IllegalAccessException if the caller does not have
872     *  access to the property accessor method
873     * @throws InvocationTargetException if the property accessor method
874     *  throws an exception
875     */
876    protected void invokeSetter(final Object target, final String propName, final String key, final int index, final Object newValue)
877            throws IllegalAccessException, InvocationTargetException {
878
879        try {
880            if (index >= 0) {
881                getPropertyUtils().setIndexedProperty(target, propName,
882                        index, newValue);
883            }
884            else if (key != null) {
885                getPropertyUtils().setMappedProperty(target, propName,
886                        key, newValue);
887            }
888            else {
889                getPropertyUtils().setProperty(target, propName, newValue);
890            }
891        }
892        catch (final NoSuchMethodException e) {
893            throw new InvocationTargetException
894                    (e, "Cannot set " + propName);
895        }
896    }
897
898    /**
899     * Sets whether the pattern is applied localized
900     * (Indicate whether the pattern is localized or not)
901     *
902     * @param newApplyLocalized <code>true</code> if pattern is localized,
903     * otherwise <code>false</code>
904     */
905    public void setApplyLocalized(final boolean newApplyLocalized) {
906
907        getLocaleConvertUtils().setApplyLocalized(newApplyLocalized);
908    }
909
910    /**
911     * Sets the default Locale.
912     *
913     * @param locale the default locale
914     */
915    public void setDefaultLocale(final Locale locale) {
916
917        getLocaleConvertUtils().setDefaultLocale(locale);
918    }
919
920    /**
921     * Set the specified locale-sensitive property value, performing type
922     * conversions as required to conform to the type of the destination property
923     * using the default conversion pattern of the corresponding {@link LocaleConverter}.
924     *
925     * @param bean Bean on which setting is to be performed
926     * @param name Property name (can be nested/indexed/mapped/combo)
927     * @param value Value to be set
928     * @throws IllegalAccessException if the caller does not have
929     *  access to the property accessor method
930     * @throws InvocationTargetException if the property accessor method
931     *  throws an exception
932     */
933    @Override
934    public void setProperty(final Object bean, final String name, final Object value)
935                                throws
936                                    IllegalAccessException,
937                                    InvocationTargetException {
938
939        setProperty(bean, name, value, null);
940    }
941
942    /**
943     * Set the specified locale-sensitive property value, performing type
944     * conversions as required to conform to the type of the destination
945     * property using the specified conversion pattern.
946     *
947     * @param bean Bean on which setting is to be performed
948     * @param name Property name (can be nested/indexed/mapped/combo)
949     * @param value Value to be set
950     * @param pattern The conversion pattern
951     * @throws IllegalAccessException if the caller does not have
952     *  access to the property accessor method
953     * @throws InvocationTargetException if the property accessor method
954     *  throws an exception
955     */
956    public void setProperty(
957                            final Object bean,
958                            String name,
959                            final Object value,
960                            final String pattern)
961                                throws
962                                    IllegalAccessException,
963                                    InvocationTargetException {
964
965        // Trace logging (if enabled)
966        if (log.isTraceEnabled()) {
967            final StringBuilder sb = new StringBuilder("  setProperty(");
968            sb.append(bean);
969            sb.append(", ");
970            sb.append(name);
971            sb.append(", ");
972            if (value == null) {
973                sb.append("<NULL>");
974            }
975            else if (value instanceof String) {
976                sb.append((String) value);
977            }
978            else if (value instanceof String[]) {
979                final String[] values = (String[]) value;
980                sb.append('[');
981                for (int i = 0; i < values.length; i++) {
982                    if (i > 0) {
983                        sb.append(',');
984                    }
985                    sb.append(values[i]);
986                }
987                sb.append(']');
988            }
989            else {
990                sb.append(value.toString());
991            }
992            sb.append(')');
993            log.trace(sb.toString());
994        }
995
996        // Resolve any nested expression to get the actual target bean
997        Object target = bean;
998        final Resolver resolver = getPropertyUtils().getResolver();
999        while (resolver.hasNested(name)) {
1000            try {
1001                target = getPropertyUtils().getProperty(target, resolver.next(name));
1002                name = resolver.remove(name);
1003            } catch (final NoSuchMethodException e) {
1004                return; // Skip this property setter
1005            }
1006        }
1007        if (log.isTraceEnabled()) {
1008            log.trace("    Target bean = " + target);
1009            log.trace("    Target name = " + name);
1010        }
1011
1012        // Declare local variables we will require
1013        final String propName = resolver.getProperty(name); // Simple name of target property
1014        final int index  = resolver.getIndex(name);         // Indexed subscript value (if any)
1015        final String key = resolver.getKey(name);           // Mapped key value (if any)
1016
1017        final Class<?> type = definePropertyType(target, name, propName);
1018        if (type != null) {
1019            final Object newValue = convert(type, index, value, pattern);
1020            invokeSetter(target, propName, key, index, newValue);
1021        }
1022    }
1023}
1024