1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.validator.routines.checkdigit; 18 19 import java.io.Serializable; 20 21 import org.apache.commons.validator.GenericValidator; 22 23 /** 24 * Abstract <b>Modulus</b> Check digit calculation/validation. 25 * <p> 26 * Provides a <i>base</i> class for building <i>modulus</i> Check Digit routines. 27 * </p> 28 * <p> 29 * This implementation only handles <i>single-digit numeric</i> codes, such as <b>EAN-13</b>. For <i>alphanumeric</i> codes such as <b>EAN-128</b> you will need 30 * to implement/override the <code>toInt()</code> and <code>toChar()</code> methods. 31 * </p> 32 * 33 * @since 1.4 34 */ 35 public abstract class ModulusCheckDigit extends AbstractCheckDigit implements Serializable { 36 37 static final int MODULUS_10 = 10; 38 static final int MODULUS_11 = 11; 39 private static final long serialVersionUID = 2948962251251528941L; 40 41 /** 42 * Add together the individual digits in a number. 43 * 44 * @param number The number whose digits are to be added 45 * @return The sum of the digits 46 */ 47 public static int sumDigits(final int number) { 48 int total = 0; 49 int todo = number; 50 while (todo > 0) { 51 total += todo % 10; // CHECKSTYLE IGNORE MagicNumber 52 todo /= 10; // CHECKSTYLE IGNORE MagicNumber 53 } 54 return total; 55 } 56 57 /** 58 * The modulus can be greater than 10 provided that the implementing class overrides toCheckDigit and toInt (for example as in ISBN10CheckDigit). 59 */ 60 private final int modulus; 61 62 /** 63 * Constructs a modulus 10 {@link CheckDigit} routine for a specified modulus. 64 */ 65 ModulusCheckDigit() { 66 this(MODULUS_10); 67 } 68 69 /** 70 * Constructs a {@link CheckDigit} routine for a specified modulus. 71 * 72 * @param modulus The modulus value to use for the check digit calculation 73 */ 74 public ModulusCheckDigit(final int modulus) { 75 this.modulus = modulus; 76 } 77 78 /** 79 * Calculate a modulus <i>Check Digit</i> for a code which does not yet have one. 80 * 81 * @param code The code for which to calculate the Check Digit; 82 * the check digit should not be included 83 * @return The calculated Check Digit 84 * @throws CheckDigitException if an error occurs calculating the check digit 85 */ 86 @Override 87 public String calculate(final String code) throws CheckDigitException { 88 if (GenericValidator.isBlankOrNull(code)) { 89 throw new CheckDigitException("Code is missing"); 90 } 91 final int modulusResult = calculateModulus(code, false); 92 final int charValue = (modulus - modulusResult) % modulus; 93 return toCheckDigit(charValue); 94 } 95 96 /** 97 * Calculate the modulus for a code. 98 * 99 * @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(final String code, final boolean includesCheckDigit) throws CheckDigitException { 106 int total = 0; 107 for (int i = 0; i < code.length(); i++) { 108 final int lth = code.length() + (includesCheckDigit ? 0 : 1); 109 final int leftPos = i + 1; 110 final int rightPos = lth - i; 111 final 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 * Gets the modulus value this check digit routine is based on. 122 * 123 * @return The modulus value this check digit routine is based on 124 */ 125 public int getModulus() { 126 return modulus; 127 } 128 129 /** 130 * Validate a modulus check digit for a code. 131 * 132 * @param code The code to validate 133 * @return {@code true} if the check digit is valid, otherwise 134 * {@code false} 135 */ 136 @Override 137 public boolean isValid(final String code) { 138 if (GenericValidator.isBlankOrNull(code)) { 139 return false; 140 } 141 try { 142 final int modulusResult = calculateModulus(code, true); 143 return modulusResult == 0; 144 } catch (final CheckDigitException ex) { 145 return false; 146 } 147 } 148 149 /** 150 * Convert an integer value to a check digit. 151 * <p> 152 * <b>Note:</b> this implementation only handles single-digit numeric values 153 * For non-numeric characters, override this method to provide 154 * integer-->character conversion. 155 * 156 * @param charValue The integer value of the character 157 * @return The converted character 158 * @throws CheckDigitException if integer character value 159 * doesn't represent a numeric character 160 */ 161 protected String toCheckDigit(final int charValue) throws CheckDigitException { 162 if (charValue >= 0 && charValue <= 9) { // CHECKSTYLE IGNORE MagicNumber 163 return Integer.toString(charValue); 164 } 165 throw new CheckDigitException("Invalid Check Digit Value =" + +charValue); 166 } 167 168 /** 169 * Convert a character at a specified position to an integer value. 170 * <p> 171 * <b>Note:</b> this implementation only handlers numeric values 172 * For non-numeric characters, override this method to provide 173 * character-->integer conversion. 174 * 175 * @param character The character to convert 176 * @param leftPos The position of the character in the code, counting from left to right (for identifiying the position in the string) 177 * @param rightPos The position of the character in the code, counting from right to left (not used here) 178 * @return The integer value of the character 179 * @throws CheckDigitException if character is non-numeric 180 */ 181 protected int toInt(final char character, final int leftPos, final int rightPos) throws CheckDigitException { 182 if (Character.isDigit(character)) { 183 return Character.getNumericValue(character); 184 } 185 throw new CheckDigitException("Invalid Character[" + leftPos + "] = '" + character + "'"); 186 } 187 188 /** 189 * Calculates the <i>weighted</i> value of a character in the 190 * code at a specified position. 191 * <p> 192 * Some modulus routines weight the value of a character 193 * depending on its position in the code (e.g. ISBN-10), while 194 * others use different weighting factors for odd/even positions 195 * (e.g. EAN or Luhn). Implement the appropriate mechanism 196 * required by overriding this method. 197 * 198 * @param charValue The numeric value of the character 199 * @param leftPos The position of the character in the code, counting from left to right 200 * @param rightPos The positionof the character in the code, counting from right to left 201 * @return The weighted value of the character 202 * @throws CheckDigitException if an error occurs calculating 203 * the weighted value 204 */ 205 protected abstract int weightedValue(int charValue, int leftPos, int rightPos) throws CheckDigitException; 206 207 }