    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.beanutils.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