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