1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.validator.routines;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ConcurrentMap;
24
25 import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit;
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 public class IBANValidator {
52
53
54
55
56 public static class Validator {
57
58
59
60
61
62
63
64
65
66 private static final int MIN_LEN = 8;
67 private static final int MAX_LEN = 34;
68
69 final String countryCode;
70 final String[] otherCountryCodes;
71 final RegexValidator regexValidator;
72 final int ibanLength;
73
74
75
76
77
78
79
80 public Validator(final String countryCode, final int ibanLength, final String regexWithCC) {
81 this(countryCode, ibanLength, regexWithCC.substring(countryCode.length()), new String[] {});
82 }
83
84
85
86
87
88
89
90 Validator(final String countryCode, final int ibanLength, final String regexWithoutCC, final String... otherCountryCodes) {
91 if (!(countryCode.length() == 2 && Character.isUpperCase(countryCode.charAt(0)) && Character.isUpperCase(countryCode.charAt(1)))) {
92 throw new IllegalArgumentException("Invalid country Code; must be exactly 2 upper-case characters");
93 }
94 if (ibanLength > MAX_LEN || ibanLength < MIN_LEN) {
95 throw new IllegalArgumentException("Invalid length parameter, must be in range " + MIN_LEN + " to " + MAX_LEN + " inclusive: " + ibanLength);
96 }
97 final String regex = countryCode + regexWithoutCC;
98 if (!regex.startsWith(countryCode)) {
99 throw new IllegalArgumentException("countryCode '" + countryCode + "' does not agree with format: " + regex);
100 }
101 this.countryCode = countryCode;
102 this.otherCountryCodes = otherCountryCodes.clone();
103 final List<String> regexList = new ArrayList<>(this.otherCountryCodes.length + 1);
104 regexList.add(countryCode + regexWithoutCC);
105 for (String otherCc : otherCountryCodes) {
106 regexList.add(otherCc + regexWithoutCC);
107 }
108 this.ibanLength = ibanLength;
109 this.regexValidator = new RegexValidator(regexList);
110 }
111
112
113
114
115
116
117
118 public RegexValidator getRegexValidator() {
119 return regexValidator;
120 }
121 }
122
123 private static final int SHORT_CODE_LEN = 2;
124
125 private static final Validator[] DEFAULT_VALIDATORS = {
126 new Validator("AD", 24, "AD\\d{10}[A-Z0-9]{12}"),
127 new Validator("AE", 23, "AE\\d{21}"),
128 new Validator("AL", 28, "AL\\d{10}[A-Z0-9]{16}"),
129 new Validator("AT", 20, "AT\\d{18}"),
130 new Validator("AZ", 28, "AZ\\d{2}[A-Z]{4}[A-Z0-9]{20}"),
131 new Validator("BA", 20, "BA\\d{18}"),
132 new Validator("BE", 16, "BE\\d{14}"),
133 new Validator("BG", 22, "BG\\d{2}[A-Z]{4}\\d{6}[A-Z0-9]{8}"),
134 new Validator("BH", 22, "BH\\d{2}[A-Z]{4}[A-Z0-9]{14}"),
135 new Validator("BI", 27, "BI\\d{25}"),
136 new Validator("BR", 29, "BR\\d{25}[A-Z]{1}[A-Z0-9]{1}"),
137 new Validator("BY", 28, "BY\\d{2}[A-Z0-9]{4}\\d{4}[A-Z0-9]{16}"),
138 new Validator("CH", 21, "CH\\d{7}[A-Z0-9]{12}"),
139 new Validator("CR", 22, "CR\\d{20}"),
140 new Validator("CY", 28, "CY\\d{10}[A-Z0-9]{16}"),
141 new Validator("CZ", 24, "CZ\\d{22}"),
142 new Validator("DE", 22, "DE\\d{20}"),
143 new Validator("DJ", 27, "DJ\\d{25}"),
144 new Validator("DK", 18, "DK\\d{16}"),
145 new Validator("DO", 28, "DO\\d{2}[A-Z0-9]{4}\\d{20}"),
146 new Validator("EE", 20, "EE\\d{18}"),
147 new Validator("EG", 29, "EG\\d{27}"),
148 new Validator("ES", 24, "ES\\d{22}"),
149 new Validator("FI", 18, "\\d{16}", "AX"),
150 new Validator("FO", 18, "FO\\d{16}"),
151 new Validator("FR", 27, "\\d{12}[A-Z0-9]{11}\\d{2}", "GF", "GP", "MQ", "RE", "PF", "TF", "YT", "NC", "BL", "MF", "PM", "WF"),
152 new Validator("GB", 22, "\\d{2}[A-Z]{4}\\d{14}", "IM", "JE", "GG"),
153 new Validator("GE", 22, "GE\\d{2}[A-Z]{2}\\d{16}"),
154 new Validator("GI", 23, "GI\\d{2}[A-Z]{4}[A-Z0-9]{15}"),
155 new Validator("GL", 18, "GL\\d{16}"),
156 new Validator("GR", 27, "GR\\d{9}[A-Z0-9]{16}"),
157 new Validator("GT", 28, "GT\\d{2}[A-Z0-9]{24}"),
158 new Validator("HR", 21, "HR\\d{19}"),
159 new Validator("HU", 28, "HU\\d{26}"),
160 new Validator("IE", 22, "IE\\d{2}[A-Z]{4}\\d{14}"),
161 new Validator("IL", 23, "IL\\d{21}"),
162 new Validator("IQ", 23, "IQ\\d{2}[A-Z]{4}\\d{15}"),
163 new Validator("IS", 26, "IS\\d{24}"),
164 new Validator("IT", 27, "IT\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"),
165 new Validator("JO", 30, "JO\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}"),
166 new Validator("KW", 30, "KW\\d{2}[A-Z]{4}[A-Z0-9]{22}"),
167 new Validator("KZ", 20, "KZ\\d{5}[A-Z0-9]{13}"),
168 new Validator("LB", 28, "LB\\d{6}[A-Z0-9]{20}"),
169 new Validator("LC", 32, "LC\\d{2}[A-Z]{4}[A-Z0-9]{24}"),
170 new Validator("LI", 21, "LI\\d{7}[A-Z0-9]{12}"),
171 new Validator("LT", 20, "LT\\d{18}"),
172 new Validator("LU", 20, "LU\\d{5}[A-Z0-9]{13}"),
173 new Validator("LV", 21, "LV\\d{2}[A-Z]{4}[A-Z0-9]{13}"),
174 new Validator("LY", 25, "LY\\d{23}"),
175 new Validator("MC", 27, "MC\\d{12}[A-Z0-9]{11}\\d{2}"),
176 new Validator("MD", 24, "MD\\d{2}[A-Z0-9]{20}"),
177 new Validator("ME", 22, "ME\\d{20}"),
178 new Validator("MK", 19, "MK\\d{5}[A-Z0-9]{10}\\d{2}"),
179 new Validator("MR", 27, "MR\\d{25}"),
180 new Validator("MT", 31, "MT\\d{2}[A-Z]{4}\\d{5}[A-Z0-9]{18}"),
181 new Validator("MU", 30, "MU\\d{2}[A-Z]{4}\\d{19}[A-Z]{3}"),
182 new Validator("NL", 18, "NL\\d{2}[A-Z]{4}\\d{10}"),
183 new Validator("NO", 15, "NO\\d{13}"),
184 new Validator("PK", 24, "PK\\d{2}[A-Z]{4}[A-Z0-9]{16}"),
185 new Validator("PL", 28, "PL\\d{26}"),
186 new Validator("PS", 29, "PS\\d{2}[A-Z]{4}[A-Z0-9]{21}"),
187 new Validator("PT", 25, "PT\\d{23}"),
188 new Validator("QA", 29, "QA\\d{2}[A-Z]{4}[A-Z0-9]{21}"),
189 new Validator("RO", 24, "RO\\d{2}[A-Z]{4}[A-Z0-9]{16}"),
190 new Validator("RS", 22, "RS\\d{20}"),
191 new Validator("RU", 33, "RU\\d{31}"),
192 new Validator("SA", 24, "SA\\d{4}[A-Z0-9]{18}"),
193 new Validator("SC", 31, "SC\\d{2}[A-Z]{4}\\d{20}[A-Z]{3}"),
194 new Validator("SD", 18, "SD\\d{16}"),
195 new Validator("SE", 24, "SE\\d{22}"),
196 new Validator("SI", 19, "SI\\d{17}"),
197 new Validator("SK", 24, "SK\\d{22}"),
198 new Validator("SM", 27, "SM\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"),
199 new Validator("ST", 25, "ST\\d{23}"),
200 new Validator("SV", 28, "SV\\d{2}[A-Z]{4}\\d{20}"),
201 new Validator("TL", 23, "TL\\d{21}"),
202 new Validator("TN", 24, "TN\\d{22}"),
203 new Validator("TR", 26, "TR\\d{8}[A-Z0-9]{16}"),
204 new Validator("UA", 29, "UA\\d{8}[A-Z0-9]{19}"),
205 new Validator("VA", 22, "VA\\d{20}"),
206 new Validator("VG", 24, "VG\\d{2}[A-Z]{4}\\d{16}"),
207 new Validator("XK", 20, "XK\\d{18}"),
208 };
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 public static final IBANValidator DEFAULT_IBAN_VALIDATOR = new IBANValidator();
240
241
242
243
244
245
246 public static IBANValidator getInstance() {
247 return DEFAULT_IBAN_VALIDATOR;
248 }
249
250 private final ConcurrentMap<String, Validator> validatorMap;
251
252
253
254
255 public IBANValidator() {
256 this(DEFAULT_VALIDATORS);
257 }
258
259
260
261
262
263
264 public IBANValidator(final Validator[] validators) {
265 this.validatorMap = createValidators(validators);
266 }
267
268 private ConcurrentMap<String, Validator> createValidators(final Validator[] validators) {
269 final ConcurrentMap<String, Validator> map = new ConcurrentHashMap<>();
270 for (final Validator validator : validators) {
271 map.put(validator.countryCode, validator);
272 for (String otherCC : validator.otherCountryCodes) {
273 map.put(otherCC, validator);
274 }
275 }
276 return map;
277 }
278
279
280
281
282
283
284 public Validator[] getDefaultValidators() {
285 return Arrays.copyOf(DEFAULT_VALIDATORS, DEFAULT_VALIDATORS.length);
286 }
287
288
289
290
291
292
293
294
295 public Validator getValidator(final String code) {
296 if (code == null || code.length() < SHORT_CODE_LEN) {
297 return null;
298 }
299 final String key = code.substring(0, SHORT_CODE_LEN);
300 return validatorMap.get(key);
301 }
302
303
304
305
306
307
308
309 public boolean hasValidator(final String code) {
310 return getValidator(code) != null;
311 }
312
313
314
315
316
317
318
319 public boolean isValid(final String code) {
320 final Validator formatValidator = getValidator(code);
321 if (formatValidator == null || code.length() != formatValidator.ibanLength || !formatValidator.regexValidator.isValid(code)) {
322 return false;
323 }
324 return IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(code);
325 }
326
327
328
329
330
331
332
333
334
335
336
337
338
339 public Validator setValidator(final String countryCode, final int length, final String format) {
340 if (this == DEFAULT_IBAN_VALIDATOR) {
341 throw new IllegalStateException("The singleton validator cannot be modified");
342 }
343 if (length < 0) {
344 return validatorMap.remove(countryCode);
345 }
346 return setValidator(new Validator(countryCode, length, format));
347 }
348
349
350
351
352
353
354
355
356
357 public Validator setValidator(final Validator validator) {
358 if (this == DEFAULT_IBAN_VALIDATOR) {
359 throw new IllegalStateException("The singleton validator cannot be modified");
360 }
361 return validatorMap.put(validator.countryCode, validator);
362 }
363 }