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