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