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 * https://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 <strong>Modulus</strong> Check digit calculation/validation.
25 * <p>
26 * Provides a <em>base</em> class for building <em>modulus</em> Check Digit routines.
27 * </p>
28 * <p>
29 * This implementation only handles <em>single-digit numeric</em> codes, such as <strong>EAN-13</strong>. For <em>alphanumeric</em> codes such as <strong>EAN-128</strong> you will need
30 * to implement/override the {@code toInt()} and {@code toChar()} 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 <em>Check Digit</em> 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 * <strong>Note:</strong> 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 * <strong>Note:</strong> 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 <em>weighted</em> 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 (for example, ISBN-10), while
194 * others use different weighting factors for odd/even positions
195 * (for example, 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 position of 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 }