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 /**
22 * <strong>IBAN</strong> (International Bank Account Number) Check Digit calculation/validation.
23 * <p>
24 * This routine is based on the ISO 7064 Mod 97,10 check digit calculation routine.
25 * <p>
26 * The two check digit characters in a IBAN number are the third and fourth characters
27 * in the code. For <em>check digit</em> calculation/validation the first four characters are moved
28 * to the end of the code.
29 * So {@code CCDDnnnnnnn} becomes {@code nnnnnnnCCDD} (where
30 * {@code CC} is the country code and {@code DD} is the check digit). For
31 * check digit calculation the check digit value should be set to zero (such as
32 * {@code CC00nnnnnnn} in this example).
33 * <p>
34 * Note: the class does not check the format of the IBAN number, only the check digits.
35 * <p>
36 * For further information see
37 * <a href="https://en.wikipedia.org/wiki/International_Bank_Account_Number">Wikipedia -
38 * IBAN number</a>.
39 *
40 * @since 1.4
41 */
42 public final class IBANCheckDigit extends AbstractCheckDigit implements Serializable {
43
44 private static final int MIN_CODE_LEN = 5;
45
46 private static final long serialVersionUID = -3600191725934382801L;
47
48 private static final int MAX_ALPHANUMERIC_VALUE = 35; // Character.getNumericValue('Z')
49
50 /** Singleton IBAN Number Check Digit instance */
51 public static final CheckDigit IBAN_CHECK_DIGIT = new IBANCheckDigit();
52
53 private static final long MAX = 999999999;
54
55 private static final long MODULUS = 97;
56
57 /**
58 * Constructs Check Digit routine for IBAN Numbers.
59 */
60 public IBANCheckDigit() {
61 }
62
63 /**
64 * Calculate the <em>Check Digit</em> for an IBAN code.
65 * <p>
66 * <strong>Note:</strong> The check digit is the third and fourth
67 * characters and is set to the value "{@code 00}".
68 *
69 * @param code The code to calculate the Check Digit for
70 * @return The calculated Check Digit as 2 numeric decimal characters, for example, "42"
71 * @throws CheckDigitException if an error occurs calculating
72 * the check digit for the specified code
73 */
74 @Override
75 public String calculate(String code) throws CheckDigitException {
76 if (code == null || code.length() < MIN_CODE_LEN) {
77 throw new CheckDigitException("Invalid Code length=" + (code == null ? 0 : code.length()));
78 }
79 code = code.substring(0, 2) + "00" + code.substring(4); // CHECKSTYLE IGNORE MagicNumber
80 final int modulusResult = calculateModulus(code);
81 final int charValue = 98 - modulusResult; // CHECKSTYLE IGNORE MagicNumber
82 final String checkDigit = Integer.toString(charValue);
83 return charValue > 9 ? checkDigit : "0" + checkDigit; // CHECKSTYLE IGNORE MagicNumber
84 }
85
86 /**
87 * Calculate the modulus for a code.
88 *
89 * @param code The code to calculate the modulus for.
90 * @return The modulus value
91 * @throws CheckDigitException if an error occurs calculating the modulus
92 * for the specified code
93 */
94 private int calculateModulus(final String code) throws CheckDigitException {
95 final String reformattedCode = code.substring(4) + code.substring(0, 4); // CHECKSTYLE IGNORE MagicNumber
96 long total = 0;
97 for (int i = 0; i < reformattedCode.length(); i++) {
98 final int charValue = Character.getNumericValue(reformattedCode.charAt(i));
99 if (charValue < 0 || charValue > MAX_ALPHANUMERIC_VALUE) {
100 throw new CheckDigitException("Invalid Character[" + i + "] = '" + charValue + "'");
101 }
102 total = (charValue > 9 ? total * 100 : total * 10) + charValue; // CHECKSTYLE IGNORE MagicNumber
103 if (total > MAX) {
104 total %= MODULUS;
105 }
106 }
107 return (int) (total % MODULUS);
108 }
109
110 /**
111 * Validate the check digit of an IBAN code.
112 *
113 * @param code The code to validate
114 * @return {@code true} if the check digit is valid, otherwise
115 * {@code false}
116 */
117 @Override
118 public boolean isValid(final String code) {
119 if (code == null || code.length() < MIN_CODE_LEN) {
120 return false;
121 }
122 final String check = code.substring(2, 4); // CHECKSTYLE IGNORE MagicNumber
123 if ("00".equals(check) || "01".equals(check) || "99".equals(check)) {
124 return false;
125 }
126 try {
127 return calculateModulus(code) == 1;
128 } catch (final CheckDigitException ex) {
129 return false;
130 }
131 }
132
133 }