LuhnIdentifierValidator.java

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.patient.impl;

import org.openmrs.patient.UnallowedIdentifierException;

/**
 * A IdentifierValidator based on the Regenstrief Institute's version of the Luhn Algorithm.
 */
public class LuhnIdentifierValidator extends BaseHyphenatedIdentifierValidator {
	
	private static final String ALLOWED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
	
	private static final String LUHN_NAME = "Luhn CheckDigit Validator";
	
	@Override
	protected int getCheckDigit(String undecoratedIdentifier) {
		//		 remove leading or trailing whitespace, convert to uppercase
		String trimmedUppercaseUndecoratedIdentifier = undecoratedIdentifier.trim().toUpperCase();
		
		// this will privatebe a running total
		int sum = 0;
		
		// loop through digits from right to left
		for (int i = 0; i < trimmedUppercaseUndecoratedIdentifier.length(); i++) {
			
			// set ch to "current" character to be processed
			char ch = trimmedUppercaseUndecoratedIdentifier.charAt(trimmedUppercaseUndecoratedIdentifier.length() - i - 1);
			
			// our "digit" is calculated using ASCII value - 48
			int digit = (int) ch - 48;
			
			// weight will be the current digit's contribution to
			// the running total
			int weight;
			if (i % 2 == 0) {
				
				// for alternating digits starting with the rightmost, we
				// use our formula this is the same as multiplying x 2 and
				// adding digits together for values 0 to 9. Using the
				// following formula allows us to gracefully calculate a
				// weight for non-numeric "digits" as well (from their
				// ASCII value - 48).
				weight = (2 * digit) - (int) (digit / 5) * 9;
				
			} else {
				
				// even-positioned digits just contribute their ascii
				// value minus 48
				weight = digit;
				
			}
			
			// keep a running total of weights
			sum += weight;
			
		}
		
		// avoid sum less than 10 (if characters below "0" allowed,
		// this could happen)
		sum = Math.abs(sum) + 10;
		
		// check digit is amount needed to reach next number
		// divisible by ten
		return (10 - (sum % 10)) % 10;
	}
	
	/**
	 * @see org.openmrs.patient.IdentifierValidator#getName()
	 */
	@Override
	public String getName() {
		return LUHN_NAME;
	}
	
	/**
	 * @see org.openmrs.patient.IdentifierValidator#getAllowedCharacters()
	 */
	@Override
	public String getAllowedCharacters() {
		return ALLOWED_CHARS;
	}
	
	/**
	 * @see org.openmrs.patient.IdentifierValidator#getValidIdentifier(java.lang.String)
	 * @should get valid identifier
	 * @should fail with invalid identifiers
	 */
	@Override
	public String getValidIdentifier(String undecoratedIdentifier) throws UnallowedIdentifierException {
		
		checkAllowedIdentifier(undecoratedIdentifier);
		
		int checkDigit = getCheckDigit(undecoratedIdentifier);
		
		String result = undecoratedIdentifier + "-" + checkDigit;
		return result;
	}
}