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 */
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 <i>base</i> 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</code> type */
040    public static final int STANDARD_FORMAT = 0;
041
042    /** Currency <code>NumberFormat</code> type */
043    public static final int CURRENCY_FORMAT = 1;
044
045    /** Percent <code>NumberFormat</code> 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</code> type to create for validation, default is STANDARD_FORMAT.
055     */
056    private final int formatType;
057
058    /**
059     * Constructs an instance with specified <i>strict</i>
060     * and <i>decimal</i> parameters.
061     *
062     * @param strict {@code true} if strict
063     *        <code>Format</code> parsing should be used.
064     * @param formatType The <code>NumberFormat</code> 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 <i>multiplier</i> of the <code>NumberFormat</code>.</p>
077     *
078     * @param format The <code>NumberFormat</code> 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</code> for the specified Locale.</p>
110     *
111     * @param locale The locale a <code>NumberFormat</code> is required for,
112     *   system default if null.
113     * @return The <code>NumberFormat</code> to created.
114     */
115    protected Format getFormat(final Locale locale) {
116        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</code> for the specified <i>pattern</i>
148     *    and/or <code>Locale</code>.</p>
149     *
150     * @param pattern The pattern used to validate the value against or
151     *        {@code null} to use the default for the <code>Locale</code>.
152     * @param locale The locale to use for the currency format, system default if null.
153     * @return The <code>NumberFormat</code> to created.
154     */
155    @Override
156    protected Format getFormat(final String pattern, final Locale locale) {
157
158        NumberFormat formatter;
159        final boolean usePattern = !GenericValidator.isBlankOrNull(pattern);
160        if (!usePattern) {
161            formatter = (NumberFormat) getFormat(locale);
162        } else if (locale == null) {
163            formatter = new DecimalFormat(pattern);
164        } else {
165            final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
166            formatter = new DecimalFormat(pattern, symbols);
167        }
168
169        if (!isAllowFractions()) {
170            formatter.setParseIntegerOnly(true);
171        }
172        return formatter;
173    }
174
175    /**
176     * <p>Indicates the type of <code>NumberFormat</code> created
177     *    by this validator instance.</p>
178     *
179     * @return the format type created.
180     */
181    public int getFormatType() {
182        return formatType;
183    }
184
185    /**
186     * <p>Indicates whether the number being validated is
187     *    a decimal or integer.</p>
188     *
189     * @return {@code true} if decimals are allowed
190     *       or {@code false} if the number is an integer.
191     */
192    public boolean isAllowFractions() {
193        return allowFractions;
194    }
195
196    /**
197     * Check if the value is within a specified range.
198     *
199     * @param value The value validation is being performed on.
200     * @param min The minimum value of the range.
201     * @param max The maximum value of the range.
202     * @return {@code true} if the value is within the
203     *         specified range.
204     */
205    public boolean isInRange(final Number value, final Number min, final Number max) {
206        return minValue(value, min) && maxValue(value, max);
207    }
208
209    /**
210     * <p>Validate using the specified <code>Locale</code>.</p>
211     *
212     * @param value The value validation is being performed on.
213     * @param pattern The pattern used to validate the value against, or the
214     *        default for the <code>Locale</code> if {@code null}.
215     * @param locale The locale to use for the date format, system default if null.
216     * @return {@code true} if the value is valid.
217     */
218    @Override
219    public boolean isValid(final String value, final String pattern, final Locale locale) {
220        return parse(value, pattern, locale) != null;
221    }
222
223    /**
224     * Check if the value is less than or equal to a maximum.
225     *
226     * @param value The value validation is being performed on.
227     * @param max The maximum value.
228     * @return {@code true} if the value is less than
229     *         or equal to the maximum.
230     */
231    public boolean maxValue(final Number value, final Number max) {
232        if (isAllowFractions()) {
233            return value.doubleValue() <= max.doubleValue();
234        }
235        return value.longValue() <= max.longValue();
236    }
237
238    /**
239     * Check if the value is greater than or equal to a minimum.
240     *
241     * @param value The value validation is being performed on.
242     * @param min The minimum value.
243     * @return {@code true} if the value is greater than
244     *         or equal to the minimum.
245     */
246    public boolean minValue(final Number value, final Number min) {
247        if (isAllowFractions()) {
248            return value.doubleValue() >= min.doubleValue();
249        }
250        return value.longValue() >= min.longValue();
251    }
252
253    /**
254     * <p>Parse the value using the specified pattern.</p>
255     *
256     * @param value The value validation is being performed on.
257     * @param pattern The pattern used to validate the value against, or the
258     *        default for the <code>Locale</code> if {@code null}.
259     * @param locale The locale to use for the date format, system default if null.
260     * @return The parsed value if valid or {@code null} if invalid.
261     */
262    protected Object parse(String value, final String pattern, final Locale locale) {
263        value = value == null ? null : value.trim();
264        final String value1 = value;
265        if (GenericValidator.isBlankOrNull(value1)) {
266            return null;
267        }
268        final Format formatter = getFormat(pattern, locale);
269        return parse(value, formatter);
270
271    }
272
273    /**
274     * <p>Process the parsed value, performing any further validation
275     *    and type conversion required.</p>
276     *
277     * @param value The parsed object created.
278     * @param formatter The Format used to parse the value with.
279     * @return The parsed value converted to the appropriate type
280     *         if valid or {@code null} if invalid.
281     */
282    @Override
283    protected abstract Object processParsedValue(Object value, Format formatter);
284}