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 * @version $Revision: 1739011 $
068 * @since Validator 1.4
069 */
070public final class CodeValidator implements Serializable {
071
072    private static final long serialVersionUID = 446960910870938233L;
073
074    private final RegexValidator regexValidator;
075    private final int minLength;
076    private final int maxLength;
077    private final CheckDigit checkdigit;
078
079    /**
080     * Construct a code validator with a specified regular
081     * expression and {@link CheckDigit}.
082     * The RegexValidator validator is created to be case-sensitive
083     *
084     * @param regex The format regular expression
085     * @param checkdigit The check digit validation routine
086     */
087    public CodeValidator(String regex, CheckDigit checkdigit) {
088        this(regex, -1, -1, checkdigit);
089    }
090
091    /**
092     * Construct a code validator with a specified regular
093     * expression, length and {@link CheckDigit}.
094     * The RegexValidator validator is created to be case-sensitive
095     *
096     * @param regex The format regular expression.
097     * @param length The length of the code
098     *  (sets the mimimum/maximum to the same)
099     * @param checkdigit The check digit validation routine
100     */
101    public CodeValidator(String regex, int length, CheckDigit checkdigit) {
102        this(regex, length, length, checkdigit);
103    }
104
105    /**
106     * Construct a code validator with a specified regular
107     * expression, minimum/maximum length and {@link CheckDigit} validation.
108     * The RegexValidator validator is created to be case-sensitive
109     *
110     * @param regex The regular expression
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(String regex, int minLength, int maxLength,
116            CheckDigit checkdigit) {
117        if (regex != null && regex.length() > 0) {
118            this.regexValidator = new RegexValidator(regex);
119        } else {
120            this.regexValidator = null;
121        }
122        this.minLength = minLength;
123        this.maxLength = maxLength;
124        this.checkdigit = checkdigit;
125    }
126
127    /**
128     * Construct a code validator with a specified regular expression,
129     * validator and {@link CheckDigit} validation.
130     *
131     * @param regexValidator The format regular expression validator
132     * @param checkdigit The check digit validation routine.
133     */
134    public CodeValidator(RegexValidator regexValidator, CheckDigit checkdigit) {
135        this(regexValidator, -1, -1, checkdigit);
136    }
137
138    /**
139     * Construct a code validator with a specified regular expression,
140     * validator, length and {@link CheckDigit} validation.
141     *
142     * @param regexValidator The format regular expression validator
143     * @param length The length of the code
144     *  (sets the mimimum/maximum to the same value)
145     * @param checkdigit The check digit validation routine
146     */
147    public CodeValidator(RegexValidator regexValidator, int length, CheckDigit checkdigit) {
148        this(regexValidator, length, length, checkdigit);
149    }
150
151    /**
152     * Construct a code validator with a specified regular expression
153     * validator, minimum/maximum length and {@link CheckDigit} validation.
154     *
155     * @param regexValidator The format regular expression validator
156     * @param minLength The minimum length of the code
157     * @param maxLength The maximum length of the code
158     * @param checkdigit The check digit validation routine
159     */
160    public CodeValidator(RegexValidator regexValidator, int minLength, int maxLength,
161            CheckDigit checkdigit) {
162        this.regexValidator = regexValidator;
163        this.minLength = minLength;
164        this.maxLength = maxLength;
165        this.checkdigit = checkdigit;
166    }
167
168    /**
169     * Return the check digit validation routine.
170     * <p>
171     * <b>N.B.</b> Optional, if not set no Check Digit
172     * validation will be performed on the code.
173     *
174     * @return The check digit validation routine
175     */
176    public CheckDigit getCheckDigit() {
177        return checkdigit;
178    }
179
180    /**
181     * Return the minimum length of the code.
182     * <p>
183     * <b>N.B.</b> Optional, if less than zero the
184     * minimum length will not be checked.
185     *
186     * @return The minimum length of the code or
187     * <code>-1</code> if the code has no minimum length
188     */
189    public int getMinLength() {
190        return minLength;
191    }
192
193    /**
194     * Return the maximum length of the code.
195     * <p>
196     * <b>N.B.</b> Optional, if less than zero the
197     * maximum length will not be checked.
198     *
199     * @return The maximum length of the code or
200     * <code>-1</code> if the code has no maximum length
201     */
202    public int getMaxLength() {
203        return maxLength;
204    }
205
206    /**
207     * Return the <i>regular expression</i> validator.
208     * <p>
209     * <b>N.B.</b> Optional, if not set no regular
210     * expression validation will be performed on the code.
211     *
212     * @return The regular expression validator
213     */
214    public RegexValidator getRegexValidator() {
215        return regexValidator;
216    }
217
218    /**
219     * Validate the code returning either <code>true</code>
220     * or <code>false</code>.
221     *
222     * @param input The code to validate
223     * @return <code>true</code> if valid, otherwise
224     * <code>false</code>
225     */
226    public boolean isValid(String input) {
227        return (validate(input) != null);
228    }
229
230    /**
231     * Validate the code returning either the valid code or
232     * <code>null</code> if invalid.
233     *
234     * @param input The code to validate
235     * @return The code if valid, otherwise <code>null</code>
236     * if invalid
237     */
238    public Object validate(String input) {
239
240        if (input == null) {
241            return null;
242        }
243
244        String code = input.trim();
245        if (code.length() == 0) {
246            return null;
247        }
248
249        // validate/reformat using regular expression
250        if (regexValidator != null) {
251            code = regexValidator.validate(code);
252            if (code == null) {
253                return null;
254            }
255        }
256
257        // check the length (must be done after validate as that can change the code)
258        if ((minLength >= 0 && code.length() < minLength) ||
259            (maxLength >= 0 && code.length() > maxLength)) {
260            return null;
261        }
262
263        // validate the check digit
264        if (checkdigit != null && !checkdigit.isValid(code)) {
265            return null;
266        }
267
268        return code;
269
270    }
271
272}