    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
    025/**
    026 * <p>Abstract class for Number Validation.</p>
    027 *
    028 * <p>This is a <i>base</i> class for building Number
    029 *    Validators using format parsing.</p>
    030 *
    031 * @since 1.3.0
    032 */
    033public abstract class AbstractNumberValidator extends AbstractFormatValidator {
    034
    035    private static final long serialVersionUID = -3088817875906765463L;
    036
    037    /** Standard <code>NumberFormat</code> type */
    038    public static final int STANDARD_FORMAT = 0;
    039
    040    /** Currency <code>NumberFormat</code> type */
    041    public static final int CURRENCY_FORMAT = 1;
    042
    043    /** Percent <code>NumberFormat</code> type */
    044    public static final int PERCENT_FORMAT = 2;
    045
    046    /**
    047     * {@code true} if fractions are allowed or {@code false} if integers only.
    048     */
    049    private final boolean allowFractions;
    050
    051    /**
    052     * The <code>NumberFormat</code> type to create for validation, default is STANDARD_FORMAT.
    053     */
    054    private final int formatType;
    055
    056    /**
    057     * Constructs an instance with specified <i>strict</i>
    058     * and <i>decimal</i> parameters.
    059     *
    060     * @param strict {@code true} if strict
    061     *        <code>Format</code> parsing should be used.
    062     * @param formatType The <code>NumberFormat</code> type to
    063     *        create for validation, default is STANDARD_FORMAT.
    064     * @param allowFractions {@code true} if fractions are
    065     *        allowed or {@code false} if integers only.
    066     */
    067    public AbstractNumberValidator(final boolean strict, final int formatType, final boolean allowFractions) {
    068        super(strict);
    069        this.allowFractions = allowFractions;
    070        this.formatType = formatType;
    071    }
    072
    073    /**
    074     * <p>Returns the <i>multiplier</i> of the <code>NumberFormat</code>.</p>
    075     *
    076     * @param format The <code>NumberFormat</code> to determine the
    077     *        multiplier of.
    078     * @return The multiplying factor for the format..
    079     */
    080    protected int determineScale(final NumberFormat format) {
    081        if (!isStrict()) {
    082            return -1;
    083        }
    084        if (!isAllowFractions() || format.isParseIntegerOnly()) {
    085            return 0;
    086        }
    087        final int minimumFraction = format.getMinimumFractionDigits();
    088        final int maximumFraction = format.getMaximumFractionDigits();
    089        if (minimumFraction != maximumFraction) {
    090            return -1;
    091        }
    092        int scale = minimumFraction;
    093        if (format instanceof DecimalFormat) {
    094            final int multiplier = ((DecimalFormat) format).getMultiplier();
    095            if (multiplier == 100) { // CHECKSTYLE IGNORE MagicNumber
    096                scale += 2; // CHECKSTYLE IGNORE MagicNumber
    097            } else if (multiplier == 1000) { // CHECKSTYLE IGNORE MagicNumber
    098                scale += 3; // CHECKSTYLE IGNORE MagicNumber
    099            }
    100        } else if (formatType == PERCENT_FORMAT) {
    101            scale += 2; // CHECKSTYLE IGNORE MagicNumber
    102        }
    103        return scale;
    104    }
    105
    106    /**
    107     * <p>Returns a <code>NumberFormat</code> for the specified Locale.</p>
    108     *
    109     * @param locale The locale a <code>NumberFormat</code> is required for,
    110     *   system default if null.
    111     * @return The <code>NumberFormat</code> to created.
    112     */
    113    protected Format getFormat(final Locale locale) {
    114        NumberFormat formatter;
    115        switch (formatType) {
    116        case CURRENCY_FORMAT:
    117            if (locale == null) {
    118                formatter = NumberFormat.getCurrencyInstance();
    119            } else {
    120                formatter = NumberFormat.getCurrencyInstance(locale);
    121            }
    122            break;
    123        case PERCENT_FORMAT:
    124            if (locale == null) {
    125                formatter = NumberFormat.getPercentInstance();
    126            } else {
    127                formatter = NumberFormat.getPercentInstance(locale);
    128            }
    129            break;
    130        default:
    131            if (locale == null) {
    132                formatter = NumberFormat.getInstance();
    133            } else {
    134                formatter = NumberFormat.getInstance(locale);
    135            }
    136            if (!isAllowFractions()) {
    137                formatter.setParseIntegerOnly(true);
    138            }
    139            break;
    140        }
    141        return formatter;
    142    }
    143
    144    /**
    145     * <p>Returns a <code>NumberFormat</code> for the specified <i>pattern</i>
    146     *    and/or <code>Locale</code>.</p>
    147     *
    148     * @param pattern The pattern used to validate the value against or
    149     *        <code>null</code> to use the default for the <code>Locale</code>.
    150     * @param locale The locale to use for the currency format, system default if null.
    151     * @return The <code>NumberFormat</code> to created.
    152     */
    153    @Override
    154    protected Format getFormat(final String pattern, final Locale locale) {
    155
    156        NumberFormat formatter;
    157        final boolean usePattern = pattern != null && !pattern.isEmpty();
    158        if (!usePattern) {
    159            formatter = (NumberFormat) getFormat(locale);
    160        } else if (locale == null) {
    161            formatter = new DecimalFormat(pattern);
    162        } else {
    163            final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
    164            formatter = new DecimalFormat(pattern, symbols);
    165        }
    166
    167        if (!isAllowFractions()) {
    168            formatter.setParseIntegerOnly(true);
    169        }
    170        return formatter;
    171    }
    172
    173    /**
    174     * <p>Indicates the type of <code>NumberFormat</code> 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 <code>Locale</code>.</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 <code>Locale</code> if <code>null</code>.
    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        final Object parsedValue = parse(value, pattern, locale);
    219        return parsedValue == null ? false : true;
    220    }
    221
    222    /**
    223     * Check if the value is less than or equal to a maximum.
    224     *
    225     * @param value The value validation is being performed on.
    226     * @param max The maximum value.
    227     * @return {@code true} if the value is less than
    228     *         or equal to the maximum.
    229     */
    230    public boolean maxValue(final Number value, final Number max) {
    231        if (isAllowFractions()) {
    232            return value.doubleValue() <= max.doubleValue();
    233        }
    234        return value.longValue() <= max.longValue();
    235    }
    236
    237    /**
    238     * Check if the value is greater than or equal to a minimum.
    239     *
    240     * @param value The value validation is being performed on.
    241     * @param min The minimum value.
    242     * @return {@code true} if the value is greater than
    243     *         or equal to the minimum.
    244     */
    245    public boolean minValue(final Number value, final Number min) {
    246        if (isAllowFractions()) {
    247            return value.doubleValue() >= min.doubleValue();
    248        }
    249        return value.longValue() >= min.longValue();
    250    }
    251
    252    /**
    253     * <p>Parse the value using the specified pattern.</p>
    254     *
    255     * @param value The value validation is being performed on.
    256     * @param pattern The pattern used to validate the value against, or the
    257     *        default for the <code>Locale</code> if <code>null</code>.
    258     * @param locale The locale to use for the date format, system default if null.
    259     * @return The parsed value if valid or <code>null</code> if invalid.
    260     */
    261    protected Object parse(String value, final String pattern, final Locale locale) {
    262        value = value == null ? null : value.trim();
    263        if (value == null || value.isEmpty()) {
    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</code> if invalid.
    279     */
    280    @Override
    281    protected abstract Object processParsedValue(Object value, Format formatter);
    282}