    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.beanutils2.locale;
    019
    020import java.text.ParseException;
    021import java.util.Locale;
    022
    023import org.apache.commons.beanutils2.ConversionException;
    024import org.apache.commons.beanutils2.ConvertUtils;
    025import org.apache.commons.logging.Log;
    026import org.apache.commons.logging.LogFactory;
    027
    028/**
    029 * The base class for all standard type locale-sensitive converters. It has {@link LocaleConverter} and {@link org.apache.commons.beanutils2.Converter}
    030 * implementations, that convert an incoming locale-sensitive Object into an object of correspond type, optionally using a default value or throwing a
    031 * {@link ConversionException} if a conversion error occurs.
    032 *
    033 * @param <T> The converter type.
    034 */
    035public abstract class BaseLocaleConverter<T> implements LocaleConverter<T> {
    036
    037    /**
    038     * Builds instances of {@link BaseLocaleConverter} subclasses.
    039     *
    040     * @param <B> The builder type.
    041     * @param <T> The converter type.
    042     */
    043    public abstract static class Builder<B extends Builder<B, T>, T> {
    044
    045        /** The default value specified to our Constructor, if any. */
    046        protected T defaultValue;
    047
    048        /** The locale specified to our Constructor, by default - system locale. */
    049        protected Locale locale = Locale.getDefault();
    050
    051        /** The flag indicating whether the given pattern string is localized or not. */
    052        protected boolean localizedPattern;
    053
    054        /** The default pattern specified to our Constructor, if any. */
    055        protected String pattern;
    056
    057        /** Should we return the default value on conversion errors? */
    058        protected boolean useDefault;
    059
    060        /**
    061         * Constructs a new instance.
    062         */
    063        public Builder() {
    064            // empty
    065        }
    066
    067        /**
    068         * Returns this instance cast as the exact subclass type.
    069         *
    070         * @return this instance cast as the exact subclass type.
    071         */
    072        @SuppressWarnings("unchecked")
    073        protected B asThis() {
    074            return (B) this;
    075        }
    076
    077        /**
    078         * Gets a newly built instance.
    079         *
    080         * @return a newly built instance.
    081         */
    082        public abstract BaseLocaleConverter<?> get();
    083
    084        /**
    085         * Sets the default value.
    086         *
    087         * @param defaultValue the default value.
    088         * @return {@code this} instance.
    089         */
    090        public B setDefault(final T defaultValue) {
    091            this.defaultValue = defaultValue;
    092            return asThis();
    093        }
    094
    095        /**
    096         * Sets the locale.
    097         *
    098         * @param locale the locale.
    099         * @return {@code this} instance.
    100         */
    101        public B setLocale(final Locale locale) {
    102            this.locale = locale;
    103            return asThis();
    104        }
    105
    106        /**
    107         * Sets the localized pattern.
    108         *
    109         * @param localizedPattern the localized pattern.
    110         * @return {@code this} instance.
    111         */
    112        public B setLocalizedPattern(final boolean localizedPattern) {
    113            this.localizedPattern = localizedPattern;
    114            return asThis();
    115        }
    116
    117        /**
    118         * Sets the pattern.
    119         *
    120         * @param pattern the pattern.
    121         * @return {@code this} instance.
    122         */
    123        public B setPattern(final String pattern) {
    124            this.pattern = pattern;
    125            return asThis();
    126        }
    127
    128        /**
    129         * Sets the use of default.
    130         *
    131         * @param useDefault the use of default.
    132         * @return {@code this} instance.
    133         */
    134        public B setUseDefault(final boolean useDefault) {
    135            this.useDefault = useDefault;
    136            return asThis();
    137        }
    138
    139    }
    140
    141    /** All logging goes through this logger */
    142    private static final Log LOG = LogFactory.getLog(BaseLocaleConverter.class);
    143
    144    /**
    145     * Checks whether the result of a conversion is conform to the specified target type. If this is the case, the passed in result object is cast to the
    146     * correct target type. Otherwise, an exception is thrown.
    147     *
    148     * @param <T>    the desired result type
    149     * @param type   the target class of the conversion
    150     * @param result the conversion result object
    151     * @return the result cast to the target class
    152     * @throws ConversionException if the result object is not compatible with the target type
    153     */
    154    private static <R> R checkConversionResult(final Class<R> type, final Object result) {
    155        if (type == null) {
    156            // In this case we cannot do much: The result object is returned.
    157            return (R) result;
    158        }
    159
    160        if (result == null) {
    161            return null;
    162        }
    163        if (type.isInstance(result)) {
    164            return type.cast(result);
    165        }
    166        throw new ConversionException("Unsupported target type: " + type);
    167    }
    168
    169    /** The default value specified to our Constructor, if any. */
    170    protected final T defaultValue;
    171
    172    /** The locale specified to our Constructor, by default - system locale. */
    173    protected final Locale locale;
    174
    175    /** The flag indicating whether the given pattern string is localized or not. */
    176    protected final boolean localizedPattern;
    177
    178    /** The default pattern specified to our Constructor, if any. */
    179    protected final String pattern;
    180
    181    /** Should we return the default value on conversion errors? */
    182    protected final boolean useDefault;
    183
    184    /**
    185     * Constructs a {@link LocaleConverter} that will return the specified default value or throw a {@link ConversionException} if a conversion error occurs.
    186     *
    187     * @param defaultValue The default value to be returned
    188     * @param locale       The locale
    189     * @param pattern      The conversion pattern
    190     * @param useDefault   Indicate whether the default value is used or not
    191     * @param locPattern   Indicate whether the pattern is localized or not
    192     */
    193    protected BaseLocaleConverter(final T defaultValue, final Locale locale, final String pattern, final boolean useDefault, final boolean locPattern) {
    194        this.defaultValue = useDefault ? defaultValue : null;
    195        this.useDefault = useDefault;
    196        this.locale = locale != null ? locale : Locale.getDefault();
    197        this.pattern = pattern;
    198        this.localizedPattern = locPattern;
    199    }
    200
    201    /**
    202     * Converts the specified locale-sensitive input object into an output object of the specified type. The default pattern is used for the conversion.
    203     *
    204     * @param type  Data type to which this value should be converted
    205     * @param value The input object to be converted
    206     * @return The converted value
    207     * @throws ConversionException if conversion cannot be performed successfully
    208     */
    209    @Override
    210    public <R> R convert(final Class<R> type, final Object value) {
    211        return convert(type, value, null);
    212    }
    213
    214    /**
    215     * Converts the specified locale-sensitive input object into an output object of the specified type.
    216     *
    217     * @param type    Data is type to which this value should be converted
    218     * @param value   is the input object to be converted
    219     * @param pattern is the pattern is used for the conversion; if null is passed then the default pattern associated with the converter object will be used.
    220     * @return The converted value
    221     * @throws ConversionException if conversion cannot be performed successfully
    222     */
    223    @Override
    224    public <R> R convert(final Class<R> type, final Object value, final String pattern) {
    225        final Class<R> targetType = ConvertUtils.primitiveToWrapper(type);
    226        if (value == null) {
    227            if (useDefault) {
    228                return getDefaultAs(targetType);
    229            }
    230            // symmetric BeanUtils function allows null
    231            // so do not: throw new ConversionException("No value specified");
    232            LOG.debug("Null value specified for conversion, returning null");
    233            return null;
    234        }
    235
    236        try {
    237            if (pattern != null) {
    238                return checkConversionResult(targetType, parse(value, pattern));
    239            }
    240            return checkConversionResult(targetType, parse(value, this.pattern));
    241        } catch (final Exception e) {
    242            if (useDefault) {
    243                return getDefaultAs(targetType);
    244            }
    245            if (e instanceof ConversionException) {
    246                throw (ConversionException) e;
    247            }
    248            throw new ConversionException(e);
    249        }
    250    }
    251
    252    /**
    253     * Converts the specified locale-sensitive input object into an output object. The default pattern is used for the conversion.
    254     *
    255     * @param value The input object to be converted
    256     * @return The converted value
    257     * @throws ConversionException if conversion cannot be performed successfully
    258     */
    259    public Object convert(final Object value) {
    260        return convert(value, null);
    261    }
    262
    263    /**
    264     * Converts the specified locale-sensitive input object into an output object.
    265     *
    266     * @param value   The input object to be converted
    267     * @param pattern The pattern is used for the conversion
    268     * @return The converted value
    269     * @throws ConversionException if conversion cannot be performed successfully
    270     */
    271    public T convert(final Object value, final String pattern) {
    272        return convert(null, value, pattern);
    273    }
    274
    275    /**
    276     * Gets the default object specified for this converter cast for the given target type. If the default value is not conform to the given type, an exception
    277     * is thrown.
    278     *
    279     * @param type the target class of the conversion
    280     * @return the default value in the given target type
    281     * @throws ConversionException if the default object is not compatible with the target type
    282     */
    283    private <R> R getDefaultAs(final Class<R> type) {
    284        return checkConversionResult(type, defaultValue);
    285    }
    286
    287    /**
    288     * Converts the specified locale-sensitive input object into an output object of the specified type.
    289     *
    290     * @param value   The input object to be converted
    291     * @param pattern The pattern is used for the conversion
    292     * @return The converted value
    293     * @throws ParseException if conversion cannot be performed successfully
    294     */
    295    protected abstract T parse(Object value, String pattern) throws ParseException;
    296}