    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 */
    017
    018package org.apache.commons.validator;
    019
    020import java.io.Serializable;
    021import java.text.DateFormat;
    022import java.text.NumberFormat;
    023import java.text.ParseException;
    024import java.text.ParsePosition;
    025import java.text.SimpleDateFormat;
    026import java.util.Date;
    027import java.util.Locale;
    028
    029import org.apache.commons.logging.Log;
    030import org.apache.commons.logging.LogFactory;
    031
    032/**
    033 * This class contains basic methods for performing validations that return the correctly typed class based on the validation performed.
    034 */
    035public class GenericTypeValidator implements Serializable {
    036
    037    private static final Log LOG = LogFactory.getLog(GenericTypeValidator.class);
    038    private static final long serialVersionUID = 5487162314134261703L;
    039
    040    /**
    041     * Checks if the value can safely be converted to a byte primitive.
    042     *
    043     * @param value The value validation is being performed on.
    044     * @return The converted Byte value.
    045     */
    046    public static Byte formatByte(final String value) {
    047        if (value == null) {
    048            return null;
    049        }
    050        try {
    051            return Byte.valueOf(value);
    052        } catch (final NumberFormatException e) {
    053            return null;
    054        }
    055    }
    056
    057    /**
    058     * Checks if the value can safely be converted to a byte primitive.
    059     *
    060     * @param value  The value validation is being performed on.
    061     * @param locale The locale to use to parse the number (system default if null)
    062     * @return The converted Byte value.
    063     */
    064    public static Byte formatByte(final String value, final Locale locale) {
    065        Byte result = null;
    066        if (value != null) {
    067            final ParsePosition pos = new ParsePosition(0);
    068            final Number num = getIntegerOnlyFormat(locale).parse(value, pos);
    069            // parseIntegerOnly only stops at the decimal separator, so a fractional value written with a negative
    070            // exponent and no decimal point (for example "15E-1" for 1.5) is consumed in full and returned as a
    071            // Double; byteValue() would floor it. NumberFormat returns a Long only for a non-fractional value in
    072            // long range, so guard on that before the byte range check, matching formatLong.
    073            if (pos.getIndex() == value.length() && num instanceof Long) {
    074                final long longValue = (Long) num;
    075                if (longValue >= Byte.MIN_VALUE && longValue <= Byte.MAX_VALUE) {
    076                    result = Byte.valueOf((byte) longValue);
    077                }
    078            }
    079        }
    080        return result;
    081    }
    082
    083    /**
    084     * Checks if the field is a valid credit card number.
    085     *
    086     * <p>
    087     * Reference Sean M. Burke's <a href="https://www.ling.nwu.edu/~sburke/pub/luhn_lib.pl"> script</a>.
    088     * </p>
    089     *
    090     * @param value The value validation is being performed on.
    091     * @return The converted Credit Card number.
    092     */
    093    public static Long formatCreditCard(final String value) {
    094        return GenericValidator.isCreditCard(value) ? Long.valueOf(value) : null;
    095    }
    096
    097    /**
    098     * Checks if the field is a valid date.
    099     *
    100     * <p>
    101     * The {@link Locale} is used with {@link DateFormat}. The {@link java.text.DateFormat#setLenient(boolean)} method is set to {@code false} for
    102     * all.
    103     * </p>
    104     *
    105     * @param value  The value validation is being performed on.
    106     * @param locale The Locale to use to parse the date (system default if null)
    107     * @return The converted Date value.
    108     */
    109    public static Date formatDate(final String value, final Locale locale) {
    110        Date date = null;
    111        if (value == null) {
    112            return null;
    113        }
    114        try {
    115            // Get the formatters to check against
    116            final DateFormat formatterShort = DateFormat.getDateInstance(DateFormat.SHORT, Validator.toLocale(locale));
    117            final DateFormat formatterDefault = DateFormat.getDateInstance(DateFormat.DEFAULT, Validator.toLocale(locale));
    118            // Turn off lenient parsing
    119            formatterShort.setLenient(false);
    120            formatterDefault.setLenient(false);
    121            // Firstly, try with the short form
    122            try {
    123                date = formatterShort.parse(value);
    124            } catch (final ParseException e) {
    125                // Fall back on the default one
    126                date = formatterDefault.parse(value);
    127            }
    128        } catch (final ParseException e) {
    129            // Bad date, so LOG and return null
    130            if (LOG.isDebugEnabled()) {
    131                LOG.debug("Date parse failed value=[" + value + "], " + "locale=[" + locale + "] " + e);
    132            }
    133        }
    134        return date;
    135    }
    136
    137    /**
    138     * Checks if the field is a valid date.
    139     *
    140     * <p>
    141     * The pattern is used with {@link SimpleDateFormat}. If strict is true, then the length will be checked so '2/12/1999' will not pass validation
    142     * with the format 'MM/dd/yyyy' because the month isn't two digits. The {@link java.text.SimpleDateFormat#setLenient(boolean)} method is set to
    143     * {@code false} for all.
    144     * </p>
    145     *
    146     * @param value       The value validation is being performed on.
    147     * @param datePattern The pattern passed to {@link SimpleDateFormat}.
    148     * @param strict      Whether or not to have an exact match of the datePattern.
    149     * @return The converted Date value.
    150     */
    151    public static Date formatDate(final String value, final String datePattern, final boolean strict) {
    152        if (value == null || datePattern == null || datePattern.isEmpty()) {
    153            return null;
    154        }
    155        final SimpleDateFormat formatter = new SimpleDateFormat(datePattern);
    156        formatter.setLenient(false);
    157        final ParsePosition pos = new ParsePosition(0);
    158        Date date = formatter.parse(value, pos);
    159        // parse(String, ParsePosition) stops at the first unparsable character instead of failing, so a strict
    160        // match must also confirm the whole value was consumed; otherwise trailing text such as the 'f' in
    161        // "11/11/199f" is dropped and the truncated date validates.
    162        if (date != null && strict && (pos.getIndex() < value.length() || datePattern.length() != value.length())) {
    163            date = null;
    164        }
    165        return date;
    166    }
    167
    168    /**
    169     * Checks if the value can safely be converted to a double primitive.
    170     *
    171     * @param value The value validation is being performed on.
    172     * @return The converted Double value.
    173     */
    174    public static Double formatDouble(final String value) {
    175        if (value == null) {
    176            return null;
    177        }
    178        try {
    179            return Double.valueOf(value);
    180        } catch (final NumberFormatException e) {
    181            return null;
    182        }
    183    }
    184
    185    /**
    186     * Checks if the value can safely be converted to a double primitive.
    187     *
    188     * @param value  The value validation is being performed on.
    189     * @param locale The locale to use to parse the number (system default if null)
    190     * @return The converted Double value.
    191     */
    192    public static Double formatDouble(final String value, final Locale locale) {
    193        Double result = null;
    194        if (value != null) {
    195            final ParsePosition pos = new ParsePosition(0);
    196            final Number num = getNumberFormat(locale).parse(value, pos);
    197            // If there was no error and we used the whole string
    198            if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && num.doubleValue() >= Double.MAX_VALUE * -1
    199                    && num.doubleValue() <= Double.MAX_VALUE) {
    200                result = Double.valueOf(num.doubleValue());
    201            }
    202        }
    203        return result;
    204    }
    205
    206    /**
    207     * Checks if the value can safely be converted to a float primitive.
    208     *
    209     * @param value The value validation is being performed on.
    210     * @return The converted Float value.
    211     */
    212    public static Float formatFloat(final String value) {
    213        if (value == null) {
    214            return null;
    215        }
    216        try {
    217            return Float.valueOf(value);
    218        } catch (final NumberFormatException e) {
    219            return null;
    220        }
    221    }
    222
    223    /**
    224     * Checks if the value can safely be converted to a float primitive.
    225     *
    226     * @param value  The value validation is being performed on.
    227     * @param locale The locale to use to parse the number (system default if null)
    228     * @return The converted Float value.
    229     */
    230    public static Float formatFloat(final String value, final Locale locale) {
    231        Float result = null;
    232        if (value != null) {
    233            final ParsePosition pos = new ParsePosition(0);
    234            final Number num = getNumberFormat(locale).parse(value, pos);
    235            // If there was no error and we used the whole string
    236            if (pos.getErrorIndex() == -1 && pos.getIndex() == value.length() && num.doubleValue() >= Float.MAX_VALUE * -1
    237                    && num.doubleValue() <= Float.MAX_VALUE) {
    238                result = Float.valueOf(num.floatValue());
    239            }
    240        }
    241        return result;
    242    }
    243
    244    /**
    245     * Checks if the value can safely be converted to an int primitive.
    246     *
    247     * @param value The value validation is being performed on.
    248     * @return The converted Integer value.
    249     */
    250    public static Integer formatInt(final String value) {
    251        if (value == null) {
    252            return null;
    253        }
    254        try {
    255            return Integer.valueOf(value);
    256        } catch (final NumberFormatException e) {
    257            return null;
    258        }
    259    }
    260
    261    /**
    262     * Checks if the value can safely be converted to an int primitive.
    263     *
    264     * @param value  The value validation is being performed on.
    265     * @param locale The locale to use to parse the number (system default if null)
    266     * @return The converted Integer value.
    267     */
    268    public static Integer formatInt(final String value, final Locale locale) {
    269        Integer result = null;
    270        if (value != null) {
    271            final NumberFormat formatter = getIntegerOnlyFormat(locale);
    272            final ParsePosition pos = new ParsePosition(0);
    273            final Number num = formatter.parse(value, pos);
    274            // parseIntegerOnly only stops at the decimal separator, so a fractional value written with a negative
    275            // exponent and no decimal point (for example "15E-1" for 1.5) is consumed in full and returned as a
    276            // Double; intValue() would floor it. NumberFormat returns a Long only for a non-fractional value in
    277            // long range, so guard on that before the int range check, matching formatLong.
    278            if (pos.getIndex() == value.length() && num instanceof Long) {
    279                final long longValue = (Long) num;
    280                if (longValue >= Integer.MIN_VALUE && longValue <= Integer.MAX_VALUE) {
    281                    result = Integer.valueOf((int) longValue);
    282                }
    283            }
    284        }
    285        return result;
    286    }
    287
    288    /**
    289     * Checks if the value can safely be converted to a long primitive.
    290     *
    291     * @param value The value validation is being performed on.
    292     * @return The converted Long value.
    293     */
    294    public static Long formatLong(final String value) {
    295        if (value == null) {
    296            return null;
    297        }
    298        try {
    299            return Long.valueOf(value);
    300        } catch (final NumberFormatException e) {
    301            return null;
    302        }
    303    }
    304
    305    /**
    306     * Checks if the value can safely be converted to a long primitive.
    307     *
    308     * @param value  The value validation is being performed on.
    309     * @param locale The locale to use to parse the number (system default if null)
    310     * @return The converted Long value.
    311     */
    312    public static Long formatLong(final String value, final Locale locale) {
    313        Long result = null;
    314        if (value != null) {
    315            final NumberFormat formatter = getIntegerOnlyFormat(locale);
    316            final ParsePosition pos = new ParsePosition(0);
    317            final Number num = formatter.parse(value, pos);
    318            // If we used the whole string and the value fits in a long.
    319            // NumberFormat returns a Long only when the value fits in a long;
    320            // out-of-range input is returned as a Double, so a doubleValue()
    321            // range check cannot be used here (Long.MAX_VALUE is not exactly
    322            // representable as a double and would let 2^63 through). A failed
    323            // parse returns null, so the instanceof check also covers it and
    324            // the pos.getErrorIndex() test is no longer needed.
    325            if (pos.getIndex() == value.length() && num instanceof Long) {
    326                result = (Long) num;
    327            }
    328        }
    329        return result;
    330    }
    331
    332    /**
    333     * Checks if the value can safely be converted to a short primitive.
    334     *
    335     * @param value The value validation is being performed on.
    336     * @return The converted Short value.
    337     */
    338    public static Short formatShort(final String value) {
    339        if (value == null) {
    340            return null;
    341        }
    342        try {
    343            return Short.valueOf(value);
    344        } catch (final NumberFormatException e) {
    345            return null;
    346        }
    347    }
    348
    349    /**
    350     * Checks if the value can safely be converted to a short primitive.
    351     *
    352     * @param value  The value validation is being performed on.
    353     * @param locale The locale to use to parse the number (system default if null)
    354     * @return The converted Short value.
    355     */
    356    public static Short formatShort(final String value, final Locale locale) {
    357        Short result = null;
    358        if (value != null) {
    359            final NumberFormat formatter = getIntegerOnlyFormat(locale);
    360            final ParsePosition pos = new ParsePosition(0);
    361            final Number num = formatter.parse(value, pos);
    362            // parseIntegerOnly only stops at the decimal separator, so a fractional value written with a negative
    363            // exponent and no decimal point (for example "15E-1" for 1.5) is consumed in full and returned as a
    364            // Double; shortValue() would floor it. NumberFormat returns a Long only for a non-fractional value in
    365            // long range, so guard on that before the short range check, matching formatLong.
    366            if (pos.getIndex() == value.length() && num instanceof Long) {
    367                final long longValue = (Long) num;
    368                if (longValue >= Short.MIN_VALUE && longValue <= Short.MAX_VALUE) {
    369                    result = Short.valueOf((short) longValue);
    370                }
    371            }
    372        }
    373        return result;
    374    }
    375
    376    private static NumberFormat getIntegerOnlyFormat(final Locale locale) {
    377        final NumberFormat formatter = NumberFormat.getNumberInstance(Validator.toLocale(locale));
    378        formatter.setParseIntegerOnly(true);
    379        return formatter;
    380    }
    381
    382    private static NumberFormat getNumberFormat(final Locale locale) {
    383        return NumberFormat.getInstance(Validator.toLocale(locale));
    384    }
    385
    386    /**
    387     * Constructs a new instance.
    388     *
    389     * @deprecated Will be private in the next major version.
    390     */
    391    @Deprecated
    392    public GenericTypeValidator() {
    393        // empty
    394    }
    395}