Author: sebb Date: Sun Feb 12 18:27:04 2017 New Revision: 1782688 URL: http://svn.apache.org/viewvc?rev=1782688&view=rev Log: VALIDATOR-415 Simplify building new CreditCard validators
Modified: commons/proper/validator/trunk/src/changes/changes.xml commons/proper/validator/trunk/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java commons/proper/validator/trunk/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java Modified: commons/proper/validator/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/validator/trunk/src/changes/changes.xml?rev=1782688&r1=1782687&r2=1782688&view=diff ============================================================================== --- commons/proper/validator/trunk/src/changes/changes.xml (original) +++ commons/proper/validator/trunk/src/changes/changes.xml Sun Feb 12 18:27:04 2017 @@ -65,13 +65,21 @@ The <action> type attribute can be add,u <body> <release version="1.5.2" date="TBA" description=" -This is a maintenance release. +This is primarily a maintenance release. All projects are encouraged to update to this release of Apache Commons Validator. Commons Validator requires Java 1.6 or later. +Main enhancements +================= + +* Modulus Ten Check Digit Implementation + +* Generic CreditCard validation (syntax and checkdigit only; does not check IIN) + +* CreditCard validation specification by numeric range IMPORTANT NOTES =============== @@ -90,6 +98,9 @@ The dependencies for Validator have not For the current list of dependencies, please see http://commons.apache.org/validator/dependencies.html "> + <action issue="VALIDATOR-415" type="add" dev="sebb"> + Simplify building new CreditCard validators + </action> <action issue="VALIDATOR-413" type="add" dev="sebb"> Generic CreditCard validation </action> Modified: commons/proper/validator/trunk/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java URL: http://svn.apache.org/viewvc/commons/proper/validator/trunk/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java?rev=1782688&r1=1782687&r2=1782688&view=diff ============================================================================== --- commons/proper/validator/trunk/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java (original) +++ commons/proper/validator/trunk/src/main/java/org/apache/commons/validator/routines/CreditCardValidator.java Sun Feb 12 18:27:04 2017 @@ -38,14 +38,40 @@ import java.util.ArrayList; * * <p> * configures the validator to only pass American Express and Visa cards. - * If a card type is not directly supported by this class, you can implement - * the CreditCardType interface and pass an instance into the - * <code>addAllowedCardType</code> method. + * If a card type is not directly supported by this class, you can create an + * instance of the {@link CodeValidator} class and pass it to a {@link CreditCardValidator} + * constructor along with any existing validators. For example: * </p> * + * <pre> + * <code>CreditCardValidator ccv = new CreditCardValidator( + * new CodeValidator[] { + * CreditCardValidator.AMEX_VALIDATOR, + * CreditCardValidator.VISA_VALIDATOR, + * new CodeValidator("^(4)(\\d{12,18})$", LUHN_VALIDATOR) // add VPAY + * };</code> + * </pre> + * + * <p> + * Alternatively you can define a validator using the {@link CreditCardRange} class. + * For example: + * </p> + * + * <pre> + * <code>CreditCardValidator ccv = new CreditCardValidator( + * new CreditCardRange[]{ + * new CreditCardRange("300", "305", 14, 14), // Diners + * new CreditCardRange("3095", null, 14, 14), // Diners + * new CreditCardRange("36", null, 14, 14), // Diners + * new CreditCardRange("38", "39", 14, 14), // Diners + * } + * ); + * </code> + * </pre> + * <p> + * This can be combined with a list of {@code CodeValidator}s + * </p> * <p> - * For a similar implementation in Perl, reference Sean M. Burke's - * <a href="http://www.speech.cs.cmu.edu/~sburke/pub/luhn_lib.html">script</a>. * More information can be found in Michael Gilleland's essay * <a href="http://web.archive.org/web/20120614072656/http://www.merriampark.com/anatomycc.htm">Anatomy of Credit Card Numbers</a>. * </p> @@ -62,6 +88,38 @@ public class CreditCardValidator impleme private static final int MAX_CC_LENGTH = 19; // maximum allowed length /** + * Class that represents a credit card range. + */ + public static class CreditCardRange { + final String low; // e.g. 34 or 644 + final String high; // e.g. 34 or 65 + final int minLen; // e.g. 16 + final int maxLen; // e.g. 19 + + /** + * Create a credit card range specifier for use in validation + * of the number syntax including the IIN range. + * <p> + * The low and high parameters may be shorter than the length + * of an IIN (currently 6 digits) in which case subsequent digits + * are ignored and may range from 0-9. + * <b> + * The low and high parameters may be different lengths. + * e.g. Discover "644" and "65". + * @param low the low digits of the IIN range + * @param high the high digits of the IIN range + * @param minLen the minimum length of the entire number + * @param maxLen the maximum length of the entire number + */ + public CreditCardRange(String low, String high, int minLen, int maxLen) { + this.low = low; + this.high = high; + this.minLen = minLen; + this.maxLen = maxLen; + } + } + + /** * Option specifying that no cards are allowed. This is useful if * you want only custom card types to validate so you turn off the * default cards with this option. @@ -256,6 +314,39 @@ public class CreditCardValidator impleme } /** + * Create a new CreditCardValidator with the specified {@link CreditCardRange}s. + * @param creditCardRanges Set of valid code validators + * @since 1.5.2 + */ + public CreditCardValidator(CreditCardRange[] creditCardRanges) { + if (creditCardRanges == null) { + throw new IllegalArgumentException("Card ranges are missing"); + } + Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR)); + } + + /** + * Create a new CreditCardValidator with the specified {@link CodeValidator}s + * and {@link CreditCardRange}s. + * <p> + * This can be used to combine predefined validators such as {@link #MASTERCARD_VALIDATOR} + * with additional validators using the simpler {@link CreditCardRange}s. + * @param creditCardValidators Set of valid code validators + * @param creditCardRanges Set of valid code validators + * @since 1.5.2 + */ + public CreditCardValidator(CodeValidator[] creditCardValidators, CreditCardRange[] creditCardRanges) { + if (creditCardValidators == null) { + throw new IllegalArgumentException("Card validators are missing"); + } + if (creditCardRanges == null) { + throw new IllegalArgumentException("Card ranges are missing"); + } + Collections.addAll(cardTypes, creditCardValidators); + Collections.addAll(cardTypes, createRangeValidator(creditCardRanges, LUHN_VALIDATOR)); + } + + /** * Create a new generic CreditCardValidator which validates the syntax and check digit only. * Does not check the Issuer Identification Number (IIN) * @@ -328,6 +419,47 @@ public class CreditCardValidator impleme return null; } + + // package protected for unit test access + static CodeValidator createRangeValidator(final CreditCardRange[] creditCardRanges, final CheckDigit digitCheck ) { + return new CodeValidator( + // must be numeric (rest of validation is done later) + new RegexValidator("(\\d+)") { + private static final long serialVersionUID = 1L; + private CreditCardRange[] ccr = creditCardRanges.clone(); + @Override + // must return full string + public String validate(String value) { + if (super.match(value) != null) { + int length = value.length(); + for(CreditCardRange range : ccr) { + if (length >= range.minLen && length <= range.maxLen) { + if (range.high == null) { // single prefix only + if (value.startsWith(range.low)) { + return value; + } + } else if (range.low.compareTo(value) <= 0 // no need to trim value here + && + // here we have to ignore digits beyond the prefix + range.high.compareTo(value.substring(0, range.high.length())) >= 0) { + return value; + } + } + } + } + return null; + } + @Override + public boolean isValid(String value) { + return validate(value) != null; + } + @Override + public String[] match(String value) { + return new String[]{validate(value)}; + } + }, digitCheck); + } + /** * Tests whether the given flag is on. If the flag is not a power of 2 * (ie. 3) this tests whether the combination of flags is on. Modified: commons/proper/validator/trunk/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java URL: http://svn.apache.org/viewvc/commons/proper/validator/trunk/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java?rev=1782688&r1=1782687&r2=1782688&view=diff ============================================================================== --- commons/proper/validator/trunk/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java (original) +++ commons/proper/validator/trunk/src/test/java/org/apache/commons/validator/routines/CreditCardValidatorTest.java Sun Feb 12 18:27:04 2017 @@ -18,6 +18,7 @@ package org.apache.commons.validator.rou import junit.framework.TestCase; import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit; +import org.apache.commons.validator.routines.CreditCardValidator.CreditCardRange; /** * Test the CreditCardValidator class. @@ -555,6 +556,52 @@ public class CreditCardValidatorTest ext for(String s : VALID_CARDS) { assertTrue(s, ccv.isValid(s)); } + for(String s : ERROR_CARDS) { + assertFalse(s, ccv.isValid(s)); + } + } + + public void testRangeGeneratorNoLuhn() { + CodeValidator cv = CreditCardValidator.createRangeValidator( + new CreditCardRange[]{ + new CreditCardRange("1",null,6,7), + new CreditCardRange("644","65", 8, 8) + }, + null); + assertTrue(cv.isValid("1990000")); + assertTrue(cv.isValid("199000")); + assertFalse(cv.isValid("000000")); + assertFalse(cv.isValid("099999")); + assertFalse(cv.isValid("200000")); + + assertFalse(cv.isValid("64399999")); + assertTrue(cv.isValid("64400000")); + assertTrue(cv.isValid("64900000")); + assertTrue(cv.isValid("65000000")); + assertTrue(cv.isValid("65999999")); + assertFalse(cv.isValid("66000000")); + } + + public void testRangeGenerator() { + CreditCardValidator ccv = new CreditCardValidator( + new CodeValidator[] { + CreditCardValidator.AMEX_VALIDATOR, + CreditCardValidator.VISA_VALIDATOR, + CreditCardValidator.MASTERCARD_VALIDATOR, + CreditCardValidator.DISCOVER_VALIDATOR, + }, + // Add missing validator + new CreditCardRange[]{ + new CreditCardRange("300", "305", 14, 14), // Diners + new CreditCardRange("3095", null, 14, 14), // Diners + new CreditCardRange("36", null, 14, 14), // Diners + new CreditCardRange("38", "39", 14, 14), // Diners + } + // we don't have any VPAY examples yet that aren't handled by VISA + ); + for(String s : VALID_CARDS) { + assertTrue(s, ccv.isValid(s)); + } for(String s : ERROR_CARDS) { assertFalse(s, ccv.isValid(s)); }