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
18 package org.apache.commons.validator.routines.checkdigit;
19
20 import java.io.Serializable;
21
22 import org.apache.commons.validator.GenericValidator;
23
24 /**
25 * Abstracts <strong>Modulus</strong> Check digit calculation/validation.
26 * <p>
27 * Provides a <em>base</em> class for building <em>modulus</em> Check Digit routines.
28 * </p>
29 * <p>
30 * This implementation only handles <em>single-digit numeric</em> codes, such as <strong>EAN-13</strong>. For <em>alphanumeric</em> codes such as
31 * <strong>EAN-128</strong> you will need to implement/override the {@code toInt()} and {@code toChar()} methods.
32 * </p>
33 *
34 * @since 1.4
35 */
36 public abstract class ModulusCheckDigit extends AbstractCheckDigit implements Serializable {
37
38 static final int MODULUS_10 = 10;
39 static final int MODULUS_11 = 11;
40 private static final long serialVersionUID = 2948962251251528941L;
41
42 /**
43 * Adds together the individual digits in a number.
44 *
45 * @param number The number whose digits are to be added.
46 * @return The sum of the digits.
47 */
48 public static int sumDigits(final int number) {
49 int total = 0;
50 int todo = number;
51 while (todo > 0) {
52 total += todo % 10; // CHECKSTYLE IGNORE MagicNumber
53 todo /= 10; // CHECKSTYLE IGNORE MagicNumber
54 }
55 return total;
56 }
57
58 /**
59 * The modulus can be greater than 10 provided that the implementing class overrides toCheckDigit and toInt (for example as in ISBN10CheckDigit).
60 */
61 private final int modulus;
62
63 /**
64 * Constructs a modulus 10 {@link CheckDigit} routine for a specified modulus.
65 */
66 ModulusCheckDigit() {
67 this(MODULUS_10);
68 }
69
70 /**
71 * Constructs a {@link CheckDigit} routine for a specified modulus.
72 *
73 * @param modulus The modulus value to use for the check digit calculation.
74 */
75 public ModulusCheckDigit(final int modulus) {
76 this.modulus = modulus;
77 }
78
79 /**
80 * Calculates a modulus <em>Check Digit</em> for a code which does not yet have one.
81 *
82 * @param code The code for which to calculate the Check Digit; 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 * Calculates 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 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 }