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.checkdigit; 018 019import java.io.Serializable; 020 021/** 022 * Abstract <b>Modulus</b> Check digit calculation/validation. 023 * <p> 024 * Provides a <i>base</i> class for building <i>modulus</i> Check 025 * Digit routines. 026 * <p> 027 * This implementation only handles <i>numeric</i> codes, such as 028 * <b>EAN-13</b>. For <i>alphanumeric</i> codes such as <b>EAN-128</b> you 029 * will need to implement/override the <code>toInt()</code> and 030 * <code>toChar()</code> methods. 031 * <p> 032 * 033 * @version $Revision: 1227719 $ $Date: 2012-01-05 12:45:51 -0500 (Thu, 05 Jan 2012) $ 034 * @since Validator 1.4 035 */ 036public abstract class ModulusCheckDigit implements CheckDigit, Serializable { 037 038 private static final long serialVersionUID = 2948962251251528941L; 039 040 private final int modulus; 041 042 /** 043 * Construct a {@link CheckDigit} routine for a specified modulus. 044 * 045 * @param modulus The modulus value to use for the check digit calculation 046 */ 047 public ModulusCheckDigit(int modulus) { 048 this.modulus = modulus; 049 } 050 051 /** 052 * Return the modulus value this check digit routine is based on. 053 * 054 * @return The modulus value this check digit routine is based on 055 */ 056 public int getModulus() { 057 return modulus; 058 } 059 060 /** 061 * Validate a modulus check digit for a code. 062 * 063 * @param code The code to validate 064 * @return <code>true</code> if the check digit is valid, otherwise 065 * <code>false</code> 066 */ 067 public boolean isValid(String code) { 068 if (code == null || code.length() == 0) { 069 return false; 070 } 071 try { 072 int modulusResult = calculateModulus(code, true); 073 return (modulusResult == 0); 074 } catch (CheckDigitException ex) { 075 return false; 076 } 077 } 078 079 /** 080 * Calculate a modulus <i>Check Digit</i> for a code. 081 * 082 * @param code The code to calculate the Check Digit for 083 * @return The calculated Check Digit 084 * @throws CheckDigitException if an error occurs calculating 085 * the check digit for the specified code 086 */ 087 public String calculate(String code) throws CheckDigitException { 088 if (code == null || code.length() == 0) { 089 throw new CheckDigitException("Code is missing"); 090 } 091 int modulusResult = calculateModulus(code, false); 092 int charValue = (modulus - modulusResult) % modulus; 093 return toCheckDigit(charValue); 094 } 095 096 /** 097 * Calculate the modulus for a code. 098 * 099 * @param code The code to calculate the modulus for. 100 * @param includesCheckDigit Whether the code includes the Check Digit or not. 101 * @return The modulus value 102 * @throws CheckDigitException if an error occurs calculating the modulus 103 * for the specified code 104 */ 105 protected int calculateModulus(String code, boolean includesCheckDigit) throws CheckDigitException { 106 int total = 0; 107 for (int i = 0; i < code.length(); i++) { 108 int lth = code.length() + (includesCheckDigit ? 0 : 1); 109 int leftPos = i + 1; 110 int rightPos = lth - i; 111 int charValue = toInt(code.charAt(i), leftPos, rightPos); 112 total += weightedValue(charValue, leftPos, rightPos); 113 } 114 if (total == 0) { 115 throw new CheckDigitException("Invalid code, sum is zero"); 116 } 117 return (total % modulus); 118 } 119 120 /** 121 * Calculates the <i>weighted</i> value of a character in the 122 * code at a specified position. 123 * <p> 124 * Some modulus routines weight the value of a character 125 * depending on its position in the code (e.g. ISBN-10), while 126 * others use different weighting factors for odd/even positions 127 * (e.g. EAN or Luhn). Implement the appropriate mechanism 128 * required by overriding this method. 129 * 130 * @param charValue The numeric value of the character 131 * @param leftPos The position of the character in the code, counting from left to right 132 * @param rightPos The positionof the character in the code, counting from right to left 133 * @return The weighted value of the character 134 * @throws CheckDigitException if an error occurs calculating 135 * the weighted value 136 */ 137 protected abstract int weightedValue(int charValue, int leftPos, int rightPos) 138 throws CheckDigitException; 139 140 141 /** 142 * Convert a character at a specified position to an integer value. 143 * <p> 144 * <b>Note:</b> this implementation only handlers numeric values 145 * For non-numeric characters, override this method to provide 146 * character-->integer conversion. 147 * 148 * @param character The character to convert 149 * @param leftPos The position of the character in the code, counting from left to right 150 * @param rightPos The positionof the character in the code, counting from right to left 151 * @return The integer value of the character 152 * @throws CheckDigitException if character is non-numeric 153 */ 154 protected int toInt(char character, int leftPos, int rightPos) 155 throws CheckDigitException { 156 if (Character.isDigit(character)) { 157 return Character.getNumericValue(character); 158 } else { 159 throw new CheckDigitException("Invalid Character[" + 160 leftPos + "] = '" + character + "'"); 161 } 162 } 163 164 /** 165 * Convert an integer value to a check digit. 166 * <p> 167 * <b>Note:</b> this implementation only handles numeric values 168 * For non-numeric characters, override this method to provide 169 * integer-->character conversion. 170 * 171 * @param charValue The integer value of the character 172 * @return The converted character 173 * @throws CheckDigitException if integer character value 174 * doesn't represent a numeric character 175 */ 176 protected String toCheckDigit(int charValue) 177 throws CheckDigitException { 178 if (charValue >= 0 && charValue <= 9) { 179 return Integer.toString(charValue); 180 } else { 181 throw new CheckDigitException("Invalid Check Digit Value =" + 182 + charValue); 183 } 184 } 185 186 /** 187 * Add together the individual digits in a number. 188 * 189 * @param number The number whose digits are to be added 190 * @return The sum of the digits 191 */ 192 public static int sumDigits(int number) { 193 int total = 0; 194 int todo = number; 195 while (todo > 0) { 196 total += todo % 10; 197 todo = todo / 10; 198 } 199 return total; 200 } 201 202}