001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.validator.routines; 018 019import java.util.Arrays; 020import java.util.Map; 021import java.util.concurrent.ConcurrentHashMap; 022 023import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit; 024 025/** 026 * IBAN Validator. 027 * @since 1.5.0 028 */ 029public class IBANValidator { 030 031 private final Map<String, Validator> formatValidators; 032 033 /** 034 * The validation class 035 */ 036 public static class Validator { 037 /* 038 * The minimum length does not appear to be defined by the standard. 039 * Norway is currently the shortest at 15. 040 * 041 * There is no standard for BBANs; they vary between countries. 042 * But a BBAN must consist of a branch id and account number. 043 * Each of these must be at least 2 chars (generally more) so an absolute minimum is 044 * 4 characters for the BBAN and 8 for the IBAN. 045 */ 046 private static final int MIN_LEN = 8; 047 private static final int MAX_LEN = 34; // defined by [3] 048 final String countryCode; 049 final RegexValidator validator; 050 final int lengthOfIBAN; // used to avoid unnecessary regex matching 051 052 /** 053 * Creates the validator 054 * @param cc the country code 055 * @param len the length of the IBAN 056 * @param format the regex to use to check the format 057 */ 058 public Validator(String cc, int len, String format) { 059 if (!(cc.length() == 2 && Character.isUpperCase(cc.charAt(0)) && Character.isUpperCase(cc.charAt(1)))) { 060 throw new IllegalArgumentException("Invalid country Code; must be exactly 2 upper-case characters"); 061 } 062 if (len > MAX_LEN || len < MIN_LEN) { 063 throw new IllegalArgumentException("Invalid length parameter, must be in range "+MIN_LEN+" to "+MAX_LEN+" inclusive: " +len); 064 } 065 if (!format.startsWith(cc)) { 066 throw new IllegalArgumentException("countryCode '"+cc+"' does not agree with format: " + format); 067 } 068 this.countryCode = cc; 069 this.lengthOfIBAN = len; 070 this.validator = new RegexValidator(format); 071 } 072 } 073 074 /* 075 * Wikipedia [1] says that only uppercase is allowed. 076 * The SWIFT PDF file [2] implies that lower case is allowed. 077 * However there are no examples using lower-case. 078 * Unfortunately the relevant ISO documents (ISO 13616-1) are not available for free. 079 * The IBANCheckDigit code treats upper and lower case the same, 080 * so any case validation has to be done in this class. 081 * 082 * Note: the European Payments council has a document [3] which includes a description 083 * of the IBAN. Section 5 clearly states that only upper case is allowed. 084 * Also the maximum length is 34 characters (including the country code), 085 * and the length is fixed for each country. 086 * 087 * It looks like lower-case is permitted in BBANs, but they must be converted to 088 * upper case for IBANs. 089 * 090 * [1] https://en.wikipedia.org/wiki/International_Bank_Account_Number 091 * [2] http://www.swift.com/dsp/resources/documents/IBAN_Registry.pdf (404) 092 * => https://www.swift.com/sites/default/files/resources/iban_registry.pdf 093 * The above is an old version (62, Jan 2016) 094 * As at May 2020, the current IBAN standards are located at: 095 * https://www.swift.com/standards/data-standards/iban 096 * [3] http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf 097 */ 098 099 private static final Validator[] DEFAULT_FORMATS = { 100 new Validator("AD", 24, "AD\\d{10}[A-Z0-9]{12}" ), // Andorra 101 new Validator("AE", 23, "AE\\d{21}" ), // United Arab Emirates (The) 102 new Validator("AL", 28, "AL\\d{10}[A-Z0-9]{16}" ), // Albania 103 new Validator("AT", 20, "AT\\d{18}" ), // Austria 104 new Validator("AZ", 28, "AZ\\d{2}[A-Z]{4}[A-Z0-9]{20}" ), // Azerbaijan 105 new Validator("BA", 20, "BA\\d{18}" ), // Bosnia and Herzegovina 106 new Validator("BE", 16, "BE\\d{14}" ), // Belgium 107 new Validator("BG", 22, "BG\\d{2}[A-Z]{4}\\d{6}[A-Z0-9]{8}" ), // Bulgaria 108 new Validator("BH", 22, "BH\\d{2}[A-Z]{4}[A-Z0-9]{14}" ), // Bahrain 109 new Validator("BR", 29, "BR\\d{25}[A-Z]{1}[A-Z0-9]{1}" ), // Brazil 110 new Validator("BY", 28, "BY\\d{2}[A-Z0-9]{4}\\d{4}[A-Z0-9]{16}" ), // Republic of Belarus 111 new Validator("CH", 21, "CH\\d{7}[A-Z0-9]{12}" ), // Switzerland 112 new Validator("CR", 22, "CR\\d{20}" ), // Costa Rica 113 new Validator("CY", 28, "CY\\d{10}[A-Z0-9]{16}" ), // Cyprus 114 new Validator("CZ", 24, "CZ\\d{22}" ), // Czechia 115 new Validator("DE", 22, "DE\\d{20}" ), // Germany 116 new Validator("DK", 18, "DK\\d{16}" ), // Denmark 117 new Validator("DO", 28, "DO\\d{2}[A-Z0-9]{4}\\d{20}" ), // Dominican Republic 118 new Validator("EE", 20, "EE\\d{18}" ), // Estonia 119 new Validator("EG", 29, "EG\\d{27}" ), // Egypt 120 new Validator("ES", 24, "ES\\d{22}" ), // Spain 121 new Validator("FI", 18, "FI\\d{16}" ), // Finland 122 new Validator("FO", 18, "FO\\d{16}" ), // Faroe Islands 123 new Validator("FR", 27, "FR\\d{12}[A-Z0-9]{11}\\d{2}" ), // France 124 new Validator("GB", 22, "GB\\d{2}[A-Z]{4}\\d{14}" ), // United Kingdom 125 new Validator("GE", 22, "GE\\d{2}[A-Z]{2}\\d{16}" ), // Georgia 126 new Validator("GI", 23, "GI\\d{2}[A-Z]{4}[A-Z0-9]{15}" ), // Gibraltar 127 new Validator("GL", 18, "GL\\d{16}" ), // Greenland 128 new Validator("GR", 27, "GR\\d{9}[A-Z0-9]{16}" ), // Greece 129 new Validator("GT", 28, "GT\\d{2}[A-Z0-9]{24}" ), // Guatemala 130 new Validator("HR", 21, "HR\\d{19}" ), // Croatia 131 new Validator("HU", 28, "HU\\d{26}" ), // Hungary 132 new Validator("IE", 22, "IE\\d{2}[A-Z]{4}\\d{14}" ), // Ireland 133 new Validator("IL", 23, "IL\\d{21}" ), // Israel 134 new Validator("IQ", 23, "IQ\\d{2}[A-Z]{4}\\d{15}" ), // Iraq 135 new Validator("IS", 26, "IS\\d{24}" ), // Iceland 136 new Validator("IT", 27, "IT\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}" ), // Italy 137 new Validator("JO", 30, "JO\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}" ), // Jordan 138 new Validator("KW", 30, "KW\\d{2}[A-Z]{4}[A-Z0-9]{22}" ), // Kuwait 139 new Validator("KZ", 20, "KZ\\d{5}[A-Z0-9]{13}" ), // Kazakhstan 140 new Validator("LB", 28, "LB\\d{6}[A-Z0-9]{20}" ), // Lebanon 141 new Validator("LC", 32, "LC\\d{2}[A-Z]{4}[A-Z0-9]{24}" ), // Saint Lucia 142 new Validator("LI", 21, "LI\\d{7}[A-Z0-9]{12}" ), // Liechtenstein 143 new Validator("LT", 20, "LT\\d{18}" ), // Lithuania 144 new Validator("LU", 20, "LU\\d{5}[A-Z0-9]{13}" ), // Luxembourg 145 new Validator("LV", 21, "LV\\d{2}[A-Z]{4}[A-Z0-9]{13}" ), // Latvia 146 new Validator("MC", 27, "MC\\d{12}[A-Z0-9]{11}\\d{2}" ), // Monaco 147 new Validator("MD", 24, "MD\\d{2}[A-Z0-9]{20}" ), // Moldova 148 new Validator("ME", 22, "ME\\d{20}" ), // Montenegro 149 new Validator("MK", 19, "MK\\d{5}[A-Z0-9]{10}\\d{2}" ), // Macedonia 150 new Validator("MR", 27, "MR\\d{25}" ), // Mauritania 151 new Validator("MT", 31, "MT\\d{2}[A-Z]{4}\\d{5}[A-Z0-9]{18}" ), // Malta 152 new Validator("MU", 30, "MU\\d{2}[A-Z]{4}\\d{19}[A-Z]{3}" ), // Mauritius 153 new Validator("NL", 18, "NL\\d{2}[A-Z]{4}\\d{10}" ), // Netherlands (The) 154 new Validator("NO", 15, "NO\\d{13}" ), // Norway 155 new Validator("PK", 24, "PK\\d{2}[A-Z]{4}[A-Z0-9]{16}" ), // Pakistan 156 new Validator("PL", 28, "PL\\d{26}" ), // Poland 157 new Validator("PS", 29, "PS\\d{2}[A-Z]{4}[A-Z0-9]{21}" ), // Palestine, State of 158 new Validator("PT", 25, "PT\\d{23}" ), // Portugal 159 new Validator("QA", 29, "QA\\d{2}[A-Z]{4}[A-Z0-9]{21}" ), // Qatar 160 new Validator("RO", 24, "RO\\d{2}[A-Z]{4}[A-Z0-9]{16}" ), // Romania 161 new Validator("RS", 22, "RS\\d{20}" ), // Serbia 162 new Validator("SA", 24, "SA\\d{4}[A-Z0-9]{18}" ), // Saudi Arabia 163 new Validator("SC", 31, "SC\\d{2}[A-Z]{4}\\d{20}[A-Z]{3}" ), // Seychelles 164 new Validator("SE", 24, "SE\\d{22}" ), // Sweden 165 new Validator("SI", 19, "SI\\d{17}" ), // Slovenia 166 new Validator("SK", 24, "SK\\d{22}" ), // Slovakia 167 new Validator("SM", 27, "SM\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}" ), // San Marino 168 new Validator("ST", 25, "ST\\d{23}" ), // Sao Tome and Principe 169 new Validator("SV", 28, "SV\\d{2}[A-Z]{4}\\d{20}" ), // El Salvador 170 new Validator("TL", 23, "TL\\d{21}" ), // Timor-Leste 171 new Validator("TN", 24, "TN\\d{22}" ), // Tunisia 172 new Validator("TR", 26, "TR\\d{8}[A-Z0-9]{16}" ), // Turkey 173 new Validator("UA", 29, "UA\\d{8}[A-Z0-9]{19}" ), // Ukraine 174 new Validator("VA", 22, "VA\\d{20}" ), // Vatican City State 175 new Validator("VG", 24, "VG\\d{2}[A-Z]{4}\\d{16}" ), // Virgin Islands 176 new Validator("XK", 20, "XK\\d{18}" ), // Kosovo 177 }; 178 179 /** The singleton instance which uses the default formats */ 180 public static final IBANValidator DEFAULT_IBAN_VALIDATOR = new IBANValidator(); 181 182 /** 183 * Return a singleton instance of the IBAN validator using the default formats 184 * 185 * @return A singleton instance of the ISBN validator 186 */ 187 public static IBANValidator getInstance() { 188 return DEFAULT_IBAN_VALIDATOR; 189 } 190 191 /** 192 * Create a default IBAN validator. 193 */ 194 public IBANValidator() { 195 this(DEFAULT_FORMATS); 196 } 197 198 /** 199 * Create an IBAN validator from the specified map of IBAN formats. 200 * 201 * @param formatMap map of IBAN formats 202 */ 203 public IBANValidator(Validator[] formatMap) { 204 this.formatValidators = createValidators(formatMap); 205 } 206 207 private Map<String, Validator> createValidators(Validator[] formatMap) { 208 Map<String, Validator> m = new ConcurrentHashMap<String, Validator>(); 209 for(Validator v : formatMap) { 210 m.put(v.countryCode, v); 211 } 212 return m; 213 } 214 215 /** 216 * Validate an IBAN Code 217 * 218 * @param code The value validation is being performed on 219 * @return <code>true</code> if the value is valid 220 */ 221 public boolean isValid(String code) { 222 Validator formatValidator = getValidator(code); 223 if (formatValidator == null || code.length() != formatValidator.lengthOfIBAN || !formatValidator.validator.isValid(code)) { 224 return false; 225 } 226 return IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(code); 227 } 228 229 /** 230 * Does the class have the required validator? 231 * 232 * @param code the code to check 233 * @return true if there is a validator 234 */ 235 public boolean hasValidator(String code) { 236 return getValidator(code) != null; 237 } 238 239 /** 240 * Gets a copy of the default Validators. 241 * 242 * @return a copy of the default Validator array 243 */ 244 public Validator[] getDefaultValidators() { 245 return Arrays.copyOf(DEFAULT_FORMATS, DEFAULT_FORMATS.length); 246 } 247 248 /** 249 * Get the Validator for a given IBAN 250 * 251 * @param code a string starting with the ISO country code (e.g. an IBAN) 252 * 253 * @return the validator or {@code null} if there is not one registered. 254 */ 255 public Validator getValidator(String code) { 256 if (code == null || code.length() < 2) { // ensure we can extract the code 257 return null; 258 } 259 String key = code.substring(0, 2); 260 return formatValidators.get(key); 261 } 262 263 /** 264 * Installs a validator. 265 * Will replace any existing entry which has the same countryCode 266 * 267 * @param validator the instance to install. 268 * @return the previous Validator, or {@code null} if there was none 269 * @throws IllegalStateException if an attempt is made to modify the singleton validator 270 */ 271 public Validator setValidator(Validator validator) { 272 if (this == DEFAULT_IBAN_VALIDATOR) { 273 throw new IllegalStateException("The singleton validator cannot be modified"); 274 } 275 return formatValidators.put(validator.countryCode, validator); 276 } 277 278 /** 279 * Installs a validator. 280 * Will replace any existing entry which has the same countryCode. 281 * 282 * @param countryCode the country code 283 * @param length the length of the IBAN. Must be ≥ 8 and ≤ 32. 284 * If the length is < 0, the validator is removed, and the format is not used. 285 * @param format the format of the IBAN (as a regular expression) 286 * @return the previous Validator, or {@code null} if there was none 287 * @throws IllegalArgumentException if there is a problem 288 * @throws IllegalStateException if an attempt is made to modify the singleton validator 289 */ 290 public Validator setValidator(String countryCode, int length, String format) { 291 if (this == DEFAULT_IBAN_VALIDATOR) { 292 throw new IllegalStateException("The singleton validator cannot be modified"); 293 } 294 if (length < 0) { 295 return formatValidators.remove(countryCode); 296 } 297 return setValidator(new Validator(countryCode, length, format)); 298 } 299}