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 (final 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("FK", 18, "FK\\d{2}[A-Z]{2}\\d{12}"),
151 new Validator("FO", 18, "FO\\d{16}"),
152 new Validator("FR", 27, "\\d{12}[A-Z0-9]{11}\\d{2}", "GF", "GP", "MQ", "RE", "PF", "TF", "YT", "NC", "BL", "MF", "PM", "WF"),
153 new Validator("GB", 22, "\\d{2}[A-Z]{4}\\d{14}", "IM", "JE", "GG"),
154 new Validator("GE", 22, "GE\\d{2}[A-Z]{2}\\d{16}"),
155 new Validator("GI", 23, "GI\\d{2}[A-Z]{4}[A-Z0-9]{15}"),
156 new Validator("GL", 18, "GL\\d{16}"),
157 new Validator("GR", 27, "GR\\d{9}[A-Z0-9]{16}"),
158 new Validator("GT", 28, "GT\\d{2}[A-Z0-9]{24}"),
159 new Validator("HR", 21, "HR\\d{19}"),
160 new Validator("HU", 28, "HU\\d{26}"),
161 new Validator("IE", 22, "IE\\d{2}[A-Z]{4}\\d{14}"),
162 new Validator("IL", 23, "IL\\d{21}"),
163 new Validator("IQ", 23, "IQ\\d{2}[A-Z]{4}\\d{15}"),
164 new Validator("IS", 26, "IS\\d{24}"),
165 new Validator("IT", 27, "IT\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"),
166 new Validator("JO", 30, "JO\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}"),
167 new Validator("KW", 30, "KW\\d{2}[A-Z]{4}[A-Z0-9]{22}"),
168 new Validator("KZ", 20, "KZ\\d{5}[A-Z0-9]{13}"),
169 new Validator("LB", 28, "LB\\d{6}[A-Z0-9]{20}"),
170 new Validator("LC", 32, "LC\\d{2}[A-Z]{4}[A-Z0-9]{24}"),
171 new Validator("LI", 21, "LI\\d{7}[A-Z0-9]{12}"),
172 new Validator("LT", 20, "LT\\d{18}"),
173 new Validator("LU", 20, "LU\\d{5}[A-Z0-9]{13}"),
174 new Validator("LV", 21, "LV\\d{2}[A-Z]{4}[A-Z0-9]{13}"),
175 new Validator("LY", 25, "LY\\d{23}"),
176 new Validator("MC", 27, "MC\\d{12}[A-Z0-9]{11}\\d{2}"),
177 new Validator("MD", 24, "MD\\d{2}[A-Z0-9]{20}"),
178 new Validator("ME", 22, "ME\\d{20}"),
179 new Validator("MK", 19, "MK\\d{5}[A-Z0-9]{10}\\d{2}"),
180 new Validator("MN", 20, "MN\\d{18}"),
181 new Validator("MR", 27, "MR\\d{25}"),
182 new Validator("MT", 31, "MT\\d{2}[A-Z]{4}\\d{5}[A-Z0-9]{18}"),
183 new Validator("MU", 30, "MU\\d{2}[A-Z]{4}\\d{19}[A-Z]{3}"),
184 new Validator("NI", 28, "NI\\d{2}[A-Z]{4}\\d{20}"),
185 new Validator("NL", 18, "NL\\d{2}[A-Z]{4}\\d{10}"),
186 new Validator("NO", 15, "NO\\d{13}"),
187 new Validator("OM", 23, "OM\\d{5}[A-Z0-9]{16}"),
188 new Validator("PK", 24, "PK\\d{2}[A-Z]{4}[A-Z0-9]{16}"),
189 new Validator("PL", 28, "PL\\d{26}"),
190 new Validator("PS", 29, "PS\\d{2}[A-Z]{4}[A-Z0-9]{21}"),
191 new Validator("PT", 25, "PT\\d{23}"),
192 new Validator("QA", 29, "QA\\d{2}[A-Z]{4}[A-Z0-9]{21}"),
193 new Validator("RO", 24, "RO\\d{2}[A-Z]{4}[A-Z0-9]{16}"),
194 new Validator("RS", 22, "RS\\d{20}"),
195 new Validator("RU", 33, "RU\\d{16}[A-Z0-9]{15}"),
196 new Validator("SA", 24, "SA\\d{4}[A-Z0-9]{18}"),
197 new Validator("SC", 31, "SC\\d{2}[A-Z]{4}\\d{20}[A-Z]{3}"),
198 new Validator("SD", 18, "SD\\d{16}"),
199 new Validator("SE", 24, "SE\\d{22}"),
200 new Validator("SI", 19, "SI\\d{17}"),
201 new Validator("SK", 24, "SK\\d{22}"),
202 new Validator("SM", 27, "SM\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"),
203 new Validator("SO", 23, "SO\\d{21}"),
204 new Validator("ST", 25, "ST\\d{23}"),
205 new Validator("SV", 28, "SV\\d{2}[A-Z]{4}\\d{20}"),
206 new Validator("TL", 23, "TL\\d{21}"),
207 new Validator("TN", 24, "TN\\d{22}"),
208 new Validator("TR", 26, "TR\\d{8}[A-Z0-9]{16}"),
209 new Validator("UA", 29, "UA\\d{8}[A-Z0-9]{19}"),
210 new Validator("VA", 22, "VA\\d{20}"),
211 new Validator("VG", 24, "VG\\d{2}[A-Z]{4}\\d{16}"),
212 new Validator("XK", 20, "XK\\d{18}"),
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
240
241
242
243
244 public static final IBANValidator DEFAULT_IBAN_VALIDATOR = new IBANValidator();
245
246
247
248
249
250
251 public static IBANValidator getInstance() {
252 return DEFAULT_IBAN_VALIDATOR;
253 }
254
255 private final ConcurrentMap<String, Validator> validatorMap;
256
257
258
259
260 public IBANValidator() {
261 this(DEFAULT_VALIDATORS);
262 }
263
264
265
266
267
268
269 public IBANValidator(final Validator[] validators) {
270 this.validatorMap = createValidators(validators);
271 }
272
273 private ConcurrentMap<String, Validator> createValidators(final Validator[] validators) {
274 final ConcurrentMap<String, Validator> map = new ConcurrentHashMap<>();
275 for (final Validator validator : validators) {
276 map.put(validator.countryCode, validator);
277 for (final String otherCC : validator.otherCountryCodes) {
278 map.put(otherCC, validator);
279 }
280 }
281 return map;
282 }
283
284
285
286
287
288
289 public Validator[] getDefaultValidators() {
290 return Arrays.copyOf(DEFAULT_VALIDATORS, DEFAULT_VALIDATORS.length);
291 }
292
293
294
295
296
297
298
299
300 public Validator getValidator(final String code) {
301 if (code == null || code.length() < SHORT_CODE_LEN) {
302 return null;
303 }
304 final String key = code.substring(0, SHORT_CODE_LEN);
305 return validatorMap.get(key);
306 }
307
308
309
310
311
312
313
314 public boolean hasValidator(final String code) {
315 return getValidator(code) != null;
316 }
317
318
319
320
321
322
323
324 public boolean isValid(final String code) {
325 final Validator formatValidator = getValidator(code);
326 if (formatValidator == null || code.length() != formatValidator.ibanLength || !formatValidator.regexValidator.isValid(code)) {
327 return false;
328 }
329 return IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(code);
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343
344 public Validator setValidator(final String countryCode, final int length, final String format) {
345 if (this == DEFAULT_IBAN_VALIDATOR) {
346 throw new IllegalStateException("The singleton validator cannot be modified");
347 }
348 if (length < 0) {
349 return validatorMap.remove(countryCode);
350 }
351 return setValidator(new Validator(countryCode, length, format));
352 }
353
354
355
356
357
358
359
360
361
362 public Validator setValidator(final Validator validator) {
363 if (this == DEFAULT_IBAN_VALIDATOR) {
364 throw new IllegalStateException("The singleton validator cannot be modified");
365 }
366 return validatorMap.put(validator.countryCode, validator);
367 }
368 }