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