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}