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.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    abstract protected T parse(Object value, String pattern) throws ParseException;
296}