    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}