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.text.DecimalFormat;
020import java.text.DecimalFormatSymbols;
021import java.text.Format;
022import java.text.NumberFormat;
023import java.util.Locale;
024
025import org.apache.commons.validator.GenericValidator;
026
027/**
028 * <p>Abstract class for Number Validation.</p>
029 *
030 * <p>This is a <em>base</em> class for building Number
031 *    Validators using format parsing.</p>
032 *
033 * @since 1.3.0
034 */
035public abstract class AbstractNumberValidator extends AbstractFormatValidator {
036
037    private static final long serialVersionUID = -3088817875906765463L;
038
039    /** Standard {@code NumberFormat} type */
040    public static final int STANDARD_FORMAT = 0;
041
042    /** Currency {@code NumberFormat} type */
043    public static final int CURRENCY_FORMAT = 1;
044
045    /** Percent {@code NumberFormat} type */
046    public static final int PERCENT_FORMAT = 2;
047
048    /**
049     * {@code true} if fractions are allowed or {@code false} if integers only.
050     */
051    private final boolean allowFractions;
052
053    /**
054     * The {@code NumberFormat} type to create for validation, default is STANDARD_FORMAT.
055     */
056    private final int formatType;
057
058    /**
059     * Constructs an instance with specified <em>strict</em>
060     * and <em>decimal</em> parameters.
061     *
062     * @param strict {@code true} if strict
063     *        {@code Format} parsing should be used.
064     * @param formatType The {@code NumberFormat} type to
065     *        create for validation, default is STANDARD_FORMAT.
066     * @param allowFractions {@code true} if fractions are
067     *        allowed or {@code false} if integers only.
068     */
069    public AbstractNumberValidator(final boolean strict, final int formatType, final boolean allowFractions) {
070        super(strict);
071        this.allowFractions = allowFractions;
072        this.formatType = formatType;
073    }
074
075    /**
076     * <p>Returns the <em>multiplier</em> of the {@code NumberFormat}.</p>
077     *
078     * @param format The {@code NumberFormat} to determine the
079     *        multiplier of.
080     * @return The multiplying factor for the format.
081     */
082    protected int determineScale(final NumberFormat format) {
083        if (!isStrict()) {
084            return -1;
085        }
086        if (!isAllowFractions() || format.isParseIntegerOnly()) {
087            return 0;
088        }
089        final int minimumFraction = format.getMinimumFractionDigits();
090        final int maximumFraction = format.getMaximumFractionDigits();
091        if (minimumFraction != maximumFraction) {
092            return -1;
093        }
094        int scale = minimumFraction;
095        if (format instanceof DecimalFormat) {
096            final int multiplier = ((DecimalFormat) format).getMultiplier();
097            if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber
098                scale += 2; // CHECKSTYLE IGNORE MagicNumber
099            } else if (multiplier == 1000) { // CHECKSTYLE IGNORE MagicNumber
100                scale += 3; // CHECKSTYLE IGNORE MagicNumber
101            }
102        } else if (formatType == PERCENT_FORMAT) {
103            scale += 2; // CHECKSTYLE IGNORE MagicNumber
104        }
105        return scale;
106    }
107
108    /**
109     * <p>Returns a {@code NumberFormat} for the specified Locale.</p>
110     *
111     * @param locale The locale a {@code NumberFormat} is required for,
112     *   system default if null.
113     * @return The {@code NumberFormat} to created.
114     */
115    protected Format getFormat(final Locale locale) {
116        final NumberFormat formatter;
117        switch (formatType) {
118        case CURRENCY_FORMAT:
119            if (locale == null) {
120                formatter = NumberFormat.getCurrencyInstance();
121            } else {
122                formatter = NumberFormat.getCurrencyInstance(locale);
123            }
124            break;
125        case PERCENT_FORMAT:
126            if (locale == null) {
127                formatter = NumberFormat.getPercentInstance();
128            } else {
129                formatter = NumberFormat.getPercentInstance(locale);
130            }
131            break;
132        default:
133            if (locale == null) {
134                formatter = NumberFormat.getInstance();
135            } else {
136                formatter = NumberFormat.getInstance(locale);
137            }
138            if (!isAllowFractions()) {
139                formatter.setParseIntegerOnly(true);
140            }
141            break;
142        }
143        return formatter;
144    }
145
146    /**
147     * <p>Returns a {@code NumberFormat} for the specified <em>pattern</em>
148     *    and/or {@link Locale}.</p>
149     *
150     * @param pattern The pattern used to validate the value against or
151     *        {@code null} to use the default for the {@link Locale}.
152     * @param locale The locale to use for the currency format, system default if null.
153     * @return The {@code NumberFormat} to created.
154     */
155    @Override
156    protected Format getFormat(final String pattern, final Locale locale) {
157        final NumberFormat formatter;
158        final boolean usePattern = !GenericValidator.isBlankOrNull(pattern);
159        if (!usePattern) {
160            formatter = (NumberFormat) getFormat(locale);
161        } else if (locale == null) {
162            formatter = new DecimalFormat(pattern);
163        } else {
164            final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
165            formatter = new DecimalFormat(pattern, symbols);
166        }
167        if (!isAllowFractions()) {
168            formatter.setParseIntegerOnly(true);
169        }
170        return formatter;
171    }
172
173    /**
174     * <p>Indicates the type of {@code NumberFormat} created
175     *    by this validator instance.</p>
176     *
177     * @return the format type created.
178     */
179    public int getFormatType() {
180        return formatType;
181    }
182
183    /**
184     * <p>Indicates whether the number being validated is
185     *    a decimal or integer.</p>
186     *
187     * @return {@code true} if decimals are allowed
188     *       or {@code false} if the number is an integer.
189     */
190    public boolean isAllowFractions() {
191        return allowFractions;
192    }
193
194    /**
195     * Check if the value is within a specified range.
196     *
197     * @param value The value validation is being performed on.
198     * @param min The minimum value of the range.
199     * @param max The maximum value of the range.
200     * @return {@code true} if the value is within the
201     *         specified range.
202     */
203    public boolean isInRange(final Number value, final Number min, final Number max) {
204        return minValue(value, min) && maxValue(value, max);
205    }
206
207    /**
208     * <p>Validate using the specified {@link Locale}.</p>
209     *
210     * @param value The value validation is being performed on.
211     * @param pattern The pattern used to validate the value against, or the
212     *        default for the {@link Locale} if {@code null}.
213     * @param locale The locale to use for the date format, system default if null.
214     * @return {@code true} if the value is valid.
215     */
216    @Override
217    public boolean isValid(final String value, final String pattern, final Locale locale) {
218        return parse(value, pattern, locale) != null;
219    }
220
221    /**
222     * Check if the value is less than or equal to a maximum.
223     *
224     * @param value The value validation is being performed on.
225     * @param max The maximum value.
226     * @return {@code true} if the value is less than
227     *         or equal to the maximum.
228     */
229    public boolean maxValue(final Number value, final Number max) {
230        if (isAllowFractions()) {
231            return value.doubleValue() <= max.doubleValue();
232        }
233        return value.longValue() <= max.longValue();
234    }
235
236    /**
237     * Check if the value is greater than or equal to a minimum.
238     *
239     * @param value The value validation is being performed on.
240     * @param min The minimum value.
241     * @return {@code true} if the value is greater than
242     *         or equal to the minimum.
243     */
244    public boolean minValue(final Number value, final Number min) {
245        if (isAllowFractions()) {
246            return value.doubleValue() >= min.doubleValue();
247        }
248        return value.longValue() >= min.longValue();
249    }
250
251    /**
252     * <p>Parse the value using the specified pattern.</p>
253     *
254     * @param value The value validation is being performed on.
255     * @param pattern The pattern used to validate the value against, or the
256     *        default for the {@link Locale} if {@code null}.
257     * @param locale The locale to use for the date format, system default if null.
258     * @return The parsed value if valid or {@code null} if invalid.
259     */
260    protected Object parse(String value, final String pattern, final Locale locale) {
261        value = value == null ? null : value.trim();
262        final String value1 = value;
263        if (GenericValidator.isBlankOrNull(value1)) {
264            return null;
265        }
266        final Format formatter = getFormat(pattern, locale);
267        return parse(value, formatter);
268
269    }
270
271    /**
272     * <p>Process the parsed value, performing any further validation
273     *    and type conversion required.</p>
274     *
275     * @param value The parsed object created.
276     * @param formatter The Format used to parse the value with.
277     * @return The parsed value converted to the appropriate type
278     *         if valid or {@code null} if invalid.
279     */
280    @Override
281    protected abstract Object processParsedValue(Object value, Format formatter);
282}