Please see the
Maven Dependency
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
import java.math.BigInteger;
public class Coupon {
private byte numberOfChar;
private BigInteger code;
private BigInteger crc;
public byte getNumberOfChar() {
return numberOfChar;
}
public void setNumberOfChar(byte numberOfChar) {
this.numberOfChar = numberOfChar;
}
public BigInteger getCode() {
return code;
}
public void setCode(BigInteger code) {
this.code = code;
}
public BigInteger getCrc() {
return crc;
}
public void setCrc(BigInteger crc) {
this.crc = crc;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Coupon)) {
return false;
}
Coupon c = (Coupon) o;
if (code.equals(c.code)) {
return true;
}
return false;
}
@Override
public int hashCode() {
int hash = 5;
hash = 89 * hash + (code != null ? code.hashCode() : 0);
hash = 89 * hash + (crc != null ? crc.hashCode() : 0);
return hash;
}
}
public class CouponGenerateError extends Exception {
public CouponGenerateError(String message) {
super(message);
}
private static final long serialVersionUID = -8232285755941178115L;
}
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.LinkedHashSet;
import java.util.Set;
public class CouponGenerator {
private SecureRandom random = new SecureRandom();
/**
* Represent of number of CRC chracter. If it is 1. One Character of CRC code will be generated and attached next to
* coupon code
*/
static int CRC_SIZE = 1;
/**
* CRC Generator should never be changed unless you like to invalidate all previously published coupons. It is a key
* value to prove if the CRC value is right or not
*/
static int CRC_GENERATOR = 31;
/**
* @param numberOfChar
* is define how long is the Voucher code. Voucher code consists of coupon code and CRC code
* @param numberOfCoupon
* is define how many Vouchers must be generated
* @return
* @throws CouponGenerateError
*/
public Set getCouponSet(byte numberOfChar, long numberOfCoupon) throws CouponGenerateError {
isValidNumberOfChar(numberOfChar);
Set couponSet = new LinkedHashSet();
while (numberOfCoupon > couponSet.size()) {
couponSet.add(generateCoupon(numberOfChar));
}
return couponSet;
}
/**
* @param numberOfChar
* It must be bigger than 2 and smaller than 12. If the parameter is 2, it will have 32 cases of coupon
* code. It is meaning less to generate.
* If the parameter is bigger than 12. Internal long type variable can not calculate it. It will make a
* byte overflow and return garbage value. Because, Long is 8 byte which is 64 bits. Each coupon chracter
* takes 5 bit. 13 Chracters will take 65 bits
* @throws CouponGenerateError
*/
private void isValidNumberOfChar(byte numberOfChar) throws CouponGenerateError {
if (numberOfChar < 3 || numberOfChar > 12) {
throw new CouponGenerateError(
"Invalid numberOfChar for Coupon chracters. It must be bigger than 2 and smaller than 12");
}
}
/**
* @param numberOfChar
* length of Alphanumeric code value is defined by numberOfChar
* @param numberOfChar
* must be bigger than 3 or equal and smaller than 12 or equal chracters
* @return
* @throws CouponGenerateError
*/
public Coupon getCoupon(byte numberOfChar) throws CouponGenerateError {
isValidNumberOfChar(numberOfChar);
return generateCoupon(numberOfChar);
}
private Coupon generateCoupon(byte numberOfChar) throws CouponGenerateError {
Coupon coupon = new Coupon();
coupon.setNumberOfChar(numberOfChar);
// Create 5 random bits per character. 5 bits represent Base32 Encoding
coupon.setCode(new BigInteger((numberOfChar - CRC_SIZE) * 5, random));
coupon.setCrc(CouponUtil.calculateCrc(coupon));
return coupon;
}
}
import java.math.BigInteger;
import org.apache.commons.lang.StringUtils;
public class CouponUtil {
/**
* @param coupon
*
* 7. Base 32 Encoding with Extended Hex Alphabet
*
* The following description of base 32 is derived from [7]. This
* encoding may be referred to as "base32hex". This encoding should not
* be regarded as the same as the "base32" encoding and should not be
* referred to as only "base32". This encoding is used by, e.g.,
* NextSECure3 (NSEC3) [10].
*
* One property with this alphabet, which the base64 and base32
* alphabets lack, is that encoded data maintains its sort order when
* the encoded data is compared bit-wise.
*
* This encoding is identical to the previous one, except for the
* alphabet. The new alphabet is found in Table 4.
*
* Table 4: The "Extended Hex" Base 32 Alphabet
*
* Value Encoding Value Encoding Value Encoding Value Encoding
* 0 0 9 9 18 I 27 R
* 1 1 10 A 19 J 28 S
* 2 2 11 B 20 K 29 T
* 3 3 12 C 21 L 30 U
* 4 4 13 D 22 M 31 V
* 5 5 14 E 23 N
* 6 6 15 F 24 O (pad) =
* 7 7 16 G 25 P
* 8 8 17 H 26 Q
*
* @return
*/
public static String getVoucherString(Coupon coupon) {
byte numberOfChar = coupon.getNumberOfChar();
String code = coupon.getCode().toString(32);
if (!isCodeSizeRight(numberOfChar, code)) {
code = leftPadding0(numberOfChar, code);
}
// Debug purpose
// System.out.println("code : " + code);
// System.out.println("bit code : " + coupon.getCode().toString(2));
// System.out.println("CRC : " + coupon.getCrc().toString(32));
return code + coupon.getCrc().toString(32);
}
private static String leftPadding0(byte numberOfChar, String code) {
return StringUtils.repeat("0", numberOfChar - CouponGenerator.CRC_SIZE).substring(
code.length() % numberOfChar)
+ code;
}
private static boolean isCodeSizeRight(byte numberOfChar, String code) {
return code.length() % (numberOfChar - CouponGenerator.CRC_SIZE) == 0;
}
/**
* It validate the CRC value from the coupon.
*
* @param coupon
* @return
*/
static boolean isValidCrc(Coupon coupon) {
if (coupon.getCrc().equals(calculateCrc(coupon))) {
return true;
} else {
return false;
}
}
/**
* It cancluate CRC value and return it. coupon parameter must be set code value to calculate CRC value.
*
* @param coupon
* @return
*/
static BigInteger calculateCrc(Coupon coupon) {
BigInteger code = coupon.getCode();
String crcValue = String.valueOf(code.longValue() % CouponGenerator.CRC_GENERATOR);
// Debug purpose
// System.out.println("code.toString(2) : " + code.toString(2));
// System.out.println("code.longValue() : " + code.longValue());
// System.out.println("crcValue : " + crcValue);
return new BigInteger(crcValue, 10);
}
}
import java.math.BigInteger;
import java.util.Set;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class CouponGeneratorTest {
static CouponGenerator couponGenerator = new CouponGenerator();
static Set couponSet;
@BeforeClass
public static void init() throws CouponGenerateError {
couponSet = couponGenerator.getCouponSet((byte) 10, 1000);
}
/**
* Test if the CouponGenerator generate requested number of coupons
*
* @throws Exception
*/
@Test
public void testCouponCount() throws Exception {
Assert.assertEquals("Coupon creation didn't match the requested number of coupon", 1000, couponSet.size());
}
/**
* Test if the CodeGenerator generate exact size of vaucher code chracter size.
*
* @throws Exception
*/
@Test
public void testCouponCharacterSize() throws Exception {
for (Coupon coupon : couponSet) {
// System.out.println(CouponUtil.getVoucherString(coupon));
Assert.assertEquals("Coupon Characters didn't match the requested number of character", 10, CouponUtil
.getVoucherString(coupon).length());
}
}
/**
* Test if the generated CRC value passes validation check.
* If it fails, one of generation or validation is wrong
*
* @throws Exception
*/
@Test
public void testCrcCreation() throws Exception {
for (Coupon coupon : couponSet) {
Assert.assertTrue("Checksum value is not valid", CouponUtil.isValidCrc(coupon));
}
}
/**
* Test if the given coupon passes CRC validation check.
*
* @throws Exception
*/
@Test
public void testCrcValidation() throws Exception {
Coupon coupon = new Coupon();
coupon.setCode(new BigInteger("54321", 32));
// CRC value is 1. ex ) 54321 (32 radix) % 31 = 5377089 (10 radix) % 31 = 15 (F)
coupon.setCrc(new BigInteger("f", 32));
Assert.assertTrue("CRC value value is not valid", CouponUtil.isValidCrc(coupon));
}
/**
* Test if the given coupon fails CRC validation check.
*
* @throws Exception
*/
@Test
public void testCrcValidation2() throws Exception {
Coupon coupon = new Coupon();
coupon.setCode(new BigInteger("54321", 32));
// CRC value is 1. ex ) 54321 (32 radix) % 31 = 5377089 (10 radix) % 31 = 15 (F)
coupon.setCrc(new BigInteger("2", 32));
Assert.assertFalse("CRC value must not be valid", CouponUtil.isValidCrc(coupon));
}
/**
* Test if the CouponGenerator accept requesting less than 3 chracters or bigger than 12 chracters. In this case, it
* must throw Exception
*/
@Test
public void testCouponMinusCharacterSize() {
try {
couponGenerator.getCoupon((byte) 2);
Assert.fail("small charater size is not allowed");
} catch (CouponGenerateError e) {
Assert.assertTrue(true);
}
try {
couponGenerator.getCoupon((byte) -6);
Assert.fail("Minus charater size is not allowed");
} catch (CouponGenerateError e) {
Assert.assertTrue(true);
}
try {
couponGenerator.getCoupon((byte) 13);
Assert.fail("Bigger than 12 charater size is not allowed");
} catch (CouponGenerateError e) {
Assert.assertTrue(true);
}
}
}