    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.io.Serializable;
    020
    021import org.apache.commons.validator.GenericValidator;
    022import org.apache.commons.validator.routines.checkdigit.CheckDigit;
    023
    024/**
    025 * Generic <strong>Code Validation</strong> providing format, minimum/maximum
    026 * length and {@link CheckDigit} validations.
    027 * <p>
    028 * Performs the following validations on a code:
    029 * <ul>
    030 *   <li>if the code is null, return null/false as appropriate</li>
    031 *   <li>trim the input. If the resulting code is empty, return null/false as appropriate</li>
    032 *   <li>Check the <em>format</em> of the code using a <em>regular expression.</em> (if specified)</li>
    033 *   <li>Check the <em>minimum</em> and <em>maximum</em> length  (if specified) of the <em>parsed</em> code
    034 *      (that is, parsed by the <em>regular expression</em>).</li>
    035 *   <li>Performs {@link CheckDigit} validation on the parsed code (if specified).</li>
    036 *   <li>The {@link #validate(String)} method returns the trimmed, parsed input (or null if validation failed)</li>
    037 * </ul>
    038 * <p>
    039 * <strong>Note</strong>
    040 * The {@link #isValid(String)} method will return true if the input passes validation.
    041 * Since this includes trimming as well as potentially dropping parts of the input,
    042 * it is possible for a String to pass validation
    043 * but fail the checkdigit test if passed directly to it (the check digit routines generally don't trim input
    044 * nor do they generally check the format/length).
    045 * To be sure that you are passing valid input to a method use {@link #validate(String)} as follows:
    046 * <pre>
    047 * Object valid = validator.validate(input);
    048 * if (valid != null) {
    049 *    some_method(valid.toString());
    050 * }
    051 * </pre>
    052 * <p>
    053 * Configure the validator with the appropriate regular expression, minimum/maximum length
    054 * and {@link CheckDigit} validator and then call one of the two validation
    055 * methods provided:</p>
    056 *    <ul>
    057 *       <li>{@code boolean isValid(code)}</li>
    058 *       <li>{@code String validate(code)}</li>
    059 *    </ul>
    060 * <p>
    061 * Codes often include <em>format</em> characters - such as hyphens - to make them
    062 * more easily human-readable. These can be removed prior to length and check digit
    063 * validation by specifying them as a <em>non-capturing</em> group in the regular
    064 * expression (that is, use the {@code (?:   )} notation).
    065 * <br>
    066 * Or just avoid using parentheses except for the parts you want to capture
    067 *
    068 * @since 1.4
    069 */
    070public final class CodeValidator implements Serializable {
    071
    072    private static final long serialVersionUID = 446960910870938233L;
    073
    074    /** The format regular expression validator. */
    075    private final RegexValidator regexValidator;
    076
    077    /** The minimum length of the code. */
    078    private final int minLength;
    079
    080    /** The maximum length of the code. */
    081    private final int maxLength;
    082
    083    /** The check digit validation routine. */
    084    private final CheckDigit checkdigit;
    085
    086    /**
    087     * Constructs a code validator with a specified regular expression, validator and {@link CheckDigit} validation.
    088     *
    089     * @param regexValidator The format regular expression validator.
    090     * @param checkdigit     The check digit validation routine.
    091     */
    092    public CodeValidator(final RegexValidator regexValidator, final CheckDigit checkdigit) {
    093        this(regexValidator, -1, -1, checkdigit);
    094    }
    095
    096    /**
    097     * Constructs a code validator with a specified regular expression, validator, length and {@link CheckDigit} validation.
    098     *
    099     * @param regexValidator The format regular expression validator.
    100     * @param length         The length of the code. (sets the minimum/maximum to the same value).
    101     * @param checkdigit     The check digit validation routine.
    102     */
    103    public CodeValidator(final RegexValidator regexValidator, final int length, final CheckDigit checkdigit) {
    104        this(regexValidator, length, length, checkdigit);
    105    }
    106
    107    /**
    108     * Constructs a code validator with a specified regular expression validator, minimum/maximum length and {@link CheckDigit} validation.
    109     *
    110     * @param regexValidator The format regular expression validator.
    111     * @param minLength      The minimum length of the code.
    112     * @param maxLength      The maximum length of the code.
    113     * @param checkdigit     The check digit validation routine.
    114     */
    115    public CodeValidator(final RegexValidator regexValidator, final int minLength, final int maxLength, final CheckDigit checkdigit) {
    116        this.regexValidator = regexValidator;
    117        this.minLength = minLength;
    118        this.maxLength = maxLength;
    119        this.checkdigit = checkdigit;
    120    }
    121
    122    /**
    123     * Constructs a code validator with a specified regular expression and {@link CheckDigit}. The RegexValidator validator is created to be case-sensitive
    124     *
    125     * @param regex      The format regular expression.
    126     * @param checkdigit The check digit validation routine.
    127     */
    128    public CodeValidator(final String regex, final CheckDigit checkdigit) {
    129        this(regex, -1, -1, checkdigit);
    130    }
    131
    132    /**
    133     * Constructs a code validator with a specified regular expression, length and {@link CheckDigit}. The RegexValidator validator is created to be
    134     * case-sensitive
    135     *
    136     * @param regex      The format regular expression.
    137     * @param length     The length of the code (sets the minimum/maximum to the same).
    138     * @param checkdigit The check digit validation routine.
    139     */
    140    public CodeValidator(final String regex, final int length, final CheckDigit checkdigit) {
    141        this(regex, length, length, checkdigit);
    142    }
    143
    144    /**
    145     * Constructs a code validator with a specified regular expression, minimum/maximum length and {@link CheckDigit} validation. The RegexValidator validator
    146     * is created to be case-sensitive
    147     *
    148     * @param regex      The regular expression.
    149     * @param minLength  The minimum length of the code.
    150     * @param maxLength  The maximum length of the code.
    151     * @param checkdigit The check digit validation routine.
    152     */
    153    public CodeValidator(final String regex, final int minLength, final int maxLength, final CheckDigit checkdigit) {
    154        this.regexValidator = GenericValidator.isBlankOrNull(regex) ? null : new RegexValidator(regex);
    155        this.minLength = minLength;
    156        this.maxLength = maxLength;
    157        this.checkdigit = checkdigit;
    158    }
    159
    160    /**
    161     * Gets the check digit validation routine.
    162     * <p>
    163     * <strong>N.B.</strong> Optional, if not set no Check Digit validation will be performed on the code.
    164     *
    165     * @return The check digit validation routine
    166     */
    167    public CheckDigit getCheckDigit() {
    168        return checkdigit;
    169    }
    170
    171    /**
    172     * Gets the maximum length of the code.
    173     * <p>
    174     * <strong>N.B.</strong> Optional, if less than zero the maximum length will not be checked.
    175     * </p>
    176     *
    177     * @return The maximum length of the code or {@code -1} if the code has no maximum length
    178     */
    179    public int getMaxLength() {
    180        return maxLength;
    181    }
    182
    183    /**
    184     * Gets the minimum length of the code.
    185     * <p>
    186     * <strong>N.B.</strong> Optional, if less than zero the minimum length will not be checked.
    187     * </p>
    188     *
    189     * @return The minimum length of the code or {@code -1} if the code has no minimum length.
    190     */
    191    public int getMinLength() {
    192        return minLength;
    193    }
    194
    195    /**
    196     * Gets the <em>regular expression</em> validator.
    197     * <p>
    198     * <strong>N.B.</strong> Optional, if not set no regular expression validation will be performed on the code.
    199     * </p>
    200     *
    201     * @return The regular expression validator.
    202     */
    203    public RegexValidator getRegexValidator() {
    204        return regexValidator;
    205    }
    206
    207    /**
    208     * Validates the code returning either {@code true} or {@code false}.
    209     * <p>
    210     * This calls {@link #validate(String)} and returns false if the return value is null, true otherwise.
    211     * </p>
    212     * <p>
    213     * Note that {@link #validate(String)} trims the input and if there is a {@link RegexValidator} it may also change the input as part of the validation.
    214     * </p>
    215     *
    216     * @param input The code to validate.
    217     * @return {@code true} if valid, otherwise {@code false}.
    218     */
    219    public boolean isValid(final String input) {
    220        return validate(input) != null;
    221    }
    222
    223    /**
    224     * Validates the code returning either the valid code or {@code null} if invalid.
    225     * <p>
    226     * Note that this method trims the input and if there is a {@link RegexValidator} it may also change the input as part of the validation.
    227     * </p>
    228     *
    229     * @param input The code to validate.
    230     * @return The code if valid, otherwise {@code null} if invalid.
    231     */
    232    public Object validate(final String input) {
    233        if (input == null) {
    234            return null;
    235        }
    236        String code = input.trim();
    237        if (code.isEmpty()) {
    238            return null;
    239        }
    240        // validate/reformat using regular expression
    241        if (regexValidator != null) {
    242            code = regexValidator.validate(code);
    243            if (code == null) {
    244                return null;
    245            }
    246        }
    247        // check the length (must be done after validate as that can change the code)
    248        if (minLength >= 0 && code.length() < minLength || maxLength >= 0 && code.length() > maxLength) {
    249            return null;
    250        }
    251        // validate the check digit
    252        if (checkdigit != null && !checkdigit.isValid(code)) {
    253            return null;
    254        }
    255        return code;
    256    }
    257
    258}