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 */
017package org.apache.commons.validator.routines;
018
019import java.math.BigDecimal;
020import java.math.BigInteger;
021import java.text.DecimalFormat;
022import java.text.DecimalFormatSymbols;
023import java.text.Format;
024import java.text.NumberFormat;
025import java.util.Locale;
026
027import org.apache.commons.validator.GenericValidator;
028
029/**
030 * Abstract class for Number Validation.
031 * <p>
032 * This is a <em>base</em> class for building Number Validators using format parsing.
033 * </p>
034 *
035 * @since 1.3.0
036 */
037public abstract class AbstractNumberValidator extends AbstractFormatValidator {
038
039    private static final long serialVersionUID = -3088817875906765463L;
040
041    /**
042     * Standard {@code NumberFormat} type.
043     */
044    public static final int STANDARD_FORMAT = 0;
045
046    /**
047     * Currency {@code NumberFormat} type.
048     */
049    public static final int CURRENCY_FORMAT = 1;
050
051    /**
052     * Percent {@code NumberFormat} type.
053     */
054    public static final int PERCENT_FORMAT = 2;
055
056    /**
057     * Compares two values as BigDecimals.
058     *
059     * @param value1 {@code BigDecimal} to compare.
060     * @param value2 {@code BigDecimal} to which {@code value1} is to be compared.
061     * @return -1, 0, or 1 as this {@code BigDecimal} is numerically less than, equal to, or greater than {@code val}.
062     */
063    static int compareTo(final Number value1, final Number value2) {
064        return toBigDecimal(value1).compareTo(toBigDecimal(value2));
065    }
066
067    /**
068     * Tests if the given value is finite.
069     *
070     * @param value The value to test.
071     * @return {@code true} if the value is finite, {@code false} otherwise.
072     */
073    static boolean isFinite(final Number value) {
074        if (value instanceof Double) {
075            return Double.isFinite((Double) value);
076        }
077        if (value instanceof Float) {
078            return Float.isFinite((Float) value);
079        }
080        return true;
081    }
082
083    static Format setParseBigDecimal(final Format format) {
084        if (format instanceof DecimalFormat) {
085            ((DecimalFormat) format).setParseBigDecimal(true);
086        }
087        return format;
088    }
089
090    static BigDecimal toBigDecimal(final Object value) {
091        if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) {
092            return BigDecimal.valueOf(((Number) value).longValue());
093        }
094        if (value instanceof Double || value instanceof Float) {
095            return BigDecimal.valueOf(((Number) value).doubleValue());
096        }
097        if (value instanceof BigDecimal) {
098            return (BigDecimal) value;
099        }
100        if (value instanceof BigInteger) {
101            return new BigDecimal((BigInteger) value);
102        }
103        // No more shortcuts, convert from a string.
104        return new BigDecimal(value.toString());
105    }
106
107    static BigInteger toBigInteger(final Object value) {
108        if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) {
109            return BigInteger.valueOf(((Number) value).longValue());
110        }
111        return toBigDecimal(value).toBigInteger();
112    }
113
114    /**
115     * {@code true} if fractions are allowed or {@code false} if integers only.
116     */
117    private final boolean allowFractions;
118
119    /**
120     * The {@code NumberFormat} type to create for validation, default is STANDARD_FORMAT.
121     */
122    private final int formatType;
123
124    /**
125     * Constructs an instance with specified <em>strict</em> and <em>decimal</em> parameters.
126     *
127     * @param strict         {@code true} if strict {@code Format} parsing should be used.
128     * @param formatType     The {@code NumberFormat} type to create for validation, default is STANDARD_FORMAT.
129     * @param allowFractions {@code true} if fractions are allowed or {@code false} if integers only.
130     */
131    public AbstractNumberValidator(final boolean strict, final int formatType, final boolean allowFractions) {
132        super(strict);
133        this.allowFractions = allowFractions;
134        this.formatType = formatType;
135    }
136
137    /**
138     * Returns the <em>multiplier</em> of the {@code NumberFormat}.
139     *
140     * @param format The {@code NumberFormat} to determine the multiplier of.
141     * @return The multiplying factor for the format.
142     */
143    protected int determineScale(final NumberFormat format) {
144        if (!isStrict()) {
145            return -1;
146        }
147        if (!isAllowFractions() || format.isParseIntegerOnly()) {
148            return 0;
149        }
150        final int minimumFraction = format.getMinimumFractionDigits();
151        final int maximumFraction = format.getMaximumFractionDigits();
152        if (minimumFraction != maximumFraction) {
153            return -1;
154        }
155        int scale = minimumFraction;
156        if (format instanceof DecimalFormat) {
157            final int multiplier = ((DecimalFormat) format).getMultiplier();
158            if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber
159                scale += 2; // CHECKSTYLE IGNORE MagicNumber
160            } else if (multiplier == 1000) { // CHECKSTYLE IGNORE MagicNumber
161                scale += 3; // CHECKSTYLE IGNORE MagicNumber
162            }
163        } else if (formatType == PERCENT_FORMAT) {
164            scale += 2; // CHECKSTYLE IGNORE MagicNumber
165        }
166        return scale;
167    }
168
169    /**
170     * Gets the {@code NumberFormat} for the specified Locale.
171     *
172     * @param locale The locale a {@code NumberFormat} is required for, system default if null.
173     * @return The {@code NumberFormat} to created.
174     */
175    protected Format getFormat(final Locale locale) {
176        final NumberFormat formatter;
177        switch (formatType) {
178        case CURRENCY_FORMAT:
179            if (locale == null) {
180                formatter = NumberFormat.getCurrencyInstance();
181            } else {
182                formatter = NumberFormat.getCurrencyInstance(locale);
183            }
184            break;
185        case PERCENT_FORMAT:
186            if (locale == null) {
187                formatter = NumberFormat.getPercentInstance();
188            } else {
189                formatter = NumberFormat.getPercentInstance(locale);
190            }
191            break;
192        default:
193            if (locale == null) {
194                formatter = NumberFormat.getInstance();
195            } else {
196                formatter = NumberFormat.getInstance(locale);
197            }
198            if (!isAllowFractions()) {
199                formatter.setParseIntegerOnly(true);
200            }
201            break;
202        }
203        return formatter;
204    }
205
206    /**
207     * Gets a {@code NumberFormat} for the specified <em>pattern</em> and/or {@link Locale}.
208     *
209     * @param pattern The pattern used to validate the value against or {@code null} to use the default for the {@link Locale}.
210     * @param locale  The locale to use for the currency format, system default if null.
211     * @return The {@code NumberFormat} to created.
212     */
213    @Override
214    protected Format getFormat(final String pattern, final Locale locale) {
215        final NumberFormat formatter;
216        if (GenericValidator.isBlankOrNull(pattern)) {
217            formatter = (NumberFormat) getFormat(locale);
218        } else if (locale == null) {
219            formatter = new DecimalFormat(pattern);
220        } else {
221            formatter = new DecimalFormat(pattern, new DecimalFormatSymbols(locale));
222        }
223        if (!isAllowFractions()) {
224            formatter.setParseIntegerOnly(true);
225        }
226        return formatter;
227    }
228
229    /**
230     * Gets the type of {@code NumberFormat} created by this validator instance.
231     *
232     * @return The format type created.
233     */
234    public int getFormatType() {
235        return formatType;
236    }
237
238    /**
239     * Tests whether the number being validated is a decimal or integer.
240     *
241     * @return {@code true} if decimals are allowed or {@code false} if the number is an integer.
242     */
243    public boolean isAllowFractions() {
244        return allowFractions;
245    }
246
247    /**
248     * Tests if the value is within a specified range.
249     *
250     * @param value The value validation is being performed on.
251     * @param min   The minimum value of the range.
252     * @param max   The maximum value of the range.
253     * @return {@code true} if the value is within the specified range.
254     */
255    public boolean isInRange(final Number value, final Number min, final Number max) {
256        return minValue(value, min) && maxValue(value, max);
257    }
258
259    /**
260     * Tests validity using the specified {@link Locale}.
261     *
262     * @param value   The value validation is being performed on.
263     * @param pattern The pattern used to validate the value against, or the default for the {@link Locale} if {@code null}.
264     * @param locale  The locale to use for the date format, system default if null.
265     * @return {@code true} if the value is valid.
266     */
267    @Override
268    public boolean isValid(final String value, final String pattern, final Locale locale) {
269        return parse(value, pattern, locale) != null;
270    }
271
272    /**
273     * Tests if the value is less than or equal to a maximum.
274     *
275     * <p>
276     * For an integer validator the bound is compared exactly rather than narrowed to a {@code long}, so a {@code BigInteger} or {@code BigDecimal} bound outside
277     * the long range keeps its magnitude and a fractional bound keeps its fractional part. A non-finite {@link Double} or {@link Float} operand keeps the
278     * {@code doubleValue()} comparison, since {@code BigDecimal} cannot represent {@code NaN} or an infinity.
279     * </p>
280     *
281     * @param value The value validation is being performed on.
282     * @param max   The maximum value.
283     * @return {@code true} if the value is less than or equal to the maximum.
284     */
285    public boolean maxValue(final Number value, final Number max) {
286        if (isAllowFractions()) {
287            return value.doubleValue() <= max.doubleValue();
288        }
289        return isFinite(value) && isFinite(max) ? compareTo(value, max) <= 0 : value.doubleValue() <= max.doubleValue();
290    }
291
292    /**
293     * Tests if the value is greater than or equal to a minimum.
294     *
295     * <p>
296     * For an integer validator the bound is compared exactly rather than narrowed to a {@code long}, so a {@code BigInteger} or {@code BigDecimal} bound outside
297     * the long range keeps its magnitude and a fractional bound keeps its fractional part. A non-finite {@link Double} or {@link Float} operand keeps the
298     * {@code doubleValue()} comparison, since {@code BigDecimal} cannot represent {@code NaN} or an infinity.
299     * </p>
300     *
301     * @param value The value validation is being performed on.
302     * @param min   The minimum value.
303     * @return {@code true} if the value is greater than or equal to the minimum.
304     */
305    public boolean minValue(final Number value, final Number min) {
306        if (isAllowFractions()) {
307            return value.doubleValue() >= min.doubleValue();
308        }
309        return isFinite(value) && isFinite(min) ? compareTo(value, min) >= 0 : value.doubleValue() >= min.doubleValue();
310    }
311
312    /**
313     * Parses the value using the specified pattern.
314     *
315     * @param value   The value validation is being performed on.
316     * @param pattern The pattern used to validate the value against, or the default for the {@link Locale} if {@code null}.
317     * @param locale  The locale to use for the date format, system default if null.
318     * @return The parsed value if valid or {@code null} if invalid.
319     */
320    protected Object parse(String value, final String pattern, final Locale locale) {
321        value = value == null ? null : value.trim();
322        final String value1 = value;
323        if (GenericValidator.isBlankOrNull(value1)) {
324            return null;
325        }
326        return parse(value, getFormat(pattern, locale));
327    }
328
329    /**
330     * Processes the parsed value, performing any further validation and type conversion required.
331     *
332     * @param value     The parsed object created.
333     * @param formatter The Format used to parse the value with.
334     * @return The parsed value converted to the appropriate type if valid or {@code null} if invalid.
335     */
336    @Override
337    protected abstract Object processParsedValue(Object value, Format formatter);
338}