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