Coverage Report - org.apache.commons.validator.routines.IBANValidator
 
Classes in this File Line Coverage Branch Coverage Complexity
IBANValidator
86%
26/30
85%
17/20
3.091
IBANValidator$Validator
90%
10/11
75%
9/12
3.091
 
 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  
  *      http://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;
 18  
 
 19  
 import java.util.Arrays;
 20  
 import java.util.Map;
 21  
 import java.util.concurrent.ConcurrentHashMap;
 22  
 
 23  
 import org.apache.commons.validator.routines.RegexValidator;
 24  
 import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit;
 25  
 
 26  
 /**
 27  
  * IBAN Validator.
 28  
  * @since 1.5.0
 29  
  */
 30  
 public class IBANValidator {
 31  
 
 32  
     private final Map<String, Validator> formatValidators;
 33  
 
 34  
     /**
 35  
      * The validation class
 36  
      */
 37  
     public static class Validator {
 38  
         /*
 39  
          * The minimum length does not appear to be defined by the standard.
 40  
          * Norway is currently the shortest at 15.
 41  
          *
 42  
          * There is no standard for BBANs; they vary between countries.
 43  
          * But a BBAN must consist of a branch id and account number.
 44  
          * Each of these must be at least 2 chars (generally more) so an absolute minimum is
 45  
          * 4 characters for the BBAN and 8 for the IBAN.
 46  
          */
 47  
         private static final int MIN_LEN = 8;
 48  
         private static final int MAX_LEN = 34; // defined by [3]
 49  
         final String countryCode;
 50  
         final RegexValidator validator;
 51  
         final int lengthOfIBAN; // used to avoid unnecessary regex matching
 52  
 
 53  
         /**
 54  
          * Creates the validator
 55  
          * @param cc the country code
 56  
          * @param len the length of the IBAN
 57  
          * @param format the regex to use to check the format
 58  
          */
 59  77
         public Validator(String cc, int len, String format) {
 60  77
             if (!(cc.length() == 2 && Character.isUpperCase(cc.charAt(0)) && Character.isUpperCase(cc.charAt(1)))) {
 61  1
                 throw new IllegalArgumentException("Invalid country Code; must be exactly 2 upper-case characters");
 62  
             }
 63  76
             if (len > MAX_LEN || len < MIN_LEN) {
 64  2
                 throw new IllegalArgumentException("Invalid length parameter, must be in range "+MIN_LEN+" to "+MAX_LEN+" inclusive: " +len);
 65  
             }
 66  74
             if (!format.startsWith(cc)) {
 67  0
                 throw new IllegalArgumentException("countryCode '"+cc+"' does not agree with format: " + format);
 68  
             }
 69  74
             this.countryCode = cc;
 70  74
             this.lengthOfIBAN = len;
 71  74
             this.validator = new RegexValidator(format);
 72  74
         }
 73  
     }
 74  
 
 75  
     /*
 76  
      * Wikipedia [1] says that only uppercase is allowed.
 77  
      * The SWIFT PDF file [2] implies that lower case is allowed.
 78  
      * However there are no examples using lower-case.
 79  
      * Unfortunately the relevant ISO documents (ISO 13616-1) are not available for free.
 80  
      * The IBANCheckDigit code treats upper and lower case the same,
 81  
      * so any case validation has to be done in this class.
 82  
      *
 83  
      * Note: the European Payments council has a document [3] which includes a description
 84  
      * of the IBAN. Section 5 clearly states that only upper case is allowed.
 85  
      * Also the maximum length is 34 characters (including the country code),
 86  
      * and the length is fixed for each country.
 87  
      *
 88  
      * It looks like lower-case is permitted in BBANs, but they must be converted to
 89  
      * upper case for IBANs.
 90  
      *
 91  
      * [1] https://en.wikipedia.org/wiki/International_Bank_Account_Number
 92  
      * [2] http://www.swift.com/dsp/resources/documents/IBAN_Registry.pdf
 93  
      * [3] http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
 94  
      */
 95  
     
 96  1
     private static final Validator[] DEFAULT_FORMATS = {
 97  
             new Validator("AD", 24, "AD\\d{10}[A-Z0-9]{12}"                 ), // Andorra
 98  
             new Validator("AE", 23, "AE\\d{21}"                             ), // United Arab Emirates
 99  
             new Validator("AL", 28, "AL\\d{10}[A-Z0-9]{16}"                 ), // Albania
 100  
             new Validator("AT", 20, "AT\\d{18}"                             ), // Austria
 101  
             new Validator("AZ", 28, "AZ\\d{2}[A-Z]{4}[A-Z0-9]{20}"          ), // Republic of Azerbaijan
 102  
             new Validator("BA", 20, "BA\\d{18}"                             ), // Bosnia and Herzegovina
 103  
             new Validator("BE", 16, "BE\\d{14}"                             ), // Belgium
 104  
             new Validator("BG", 22, "BG\\d{2}[A-Z]{4}\\d{6}[A-Z0-9]{8}"     ), // Bulgaria
 105  
             new Validator("BH", 22, "BH\\d{2}[A-Z]{4}[A-Z0-9]{14}"          ), // Bahrain (Kingdom of)
 106  
             new Validator("BR", 29, "BR\\d{25}[A-Z]{1}[A-Z0-9]{1}"          ), // Brazil
 107  
             new Validator("BY", 28, "BY\\d{2}[A-Z0-9]{4}\\d{4}[A-Z0-9]{16}" ), // Republic of Belarus
 108  
             new Validator("CH", 21, "CH\\d{7}[A-Z0-9]{12}"                  ), // Switzerland
 109  
             new Validator("CR", 22, "CR\\d{20}"                             ), // Costa Rica
 110  
             new Validator("CY", 28, "CY\\d{10}[A-Z0-9]{16}"                 ), // Cyprus
 111  
             new Validator("CZ", 24, "CZ\\d{22}"                             ), // Czech Republic
 112  
             new Validator("DE", 22, "DE\\d{20}"                             ), // Germany
 113  
             new Validator("DK", 18, "DK\\d{16}"                             ), // Denmark
 114  
             new Validator("DO", 28, "DO\\d{2}[A-Z0-9]{4}\\d{20}"            ), // Dominican Republic
 115  
             new Validator("EE", 20, "EE\\d{18}"                             ), // Estonia
 116  
             new Validator("ES", 24, "ES\\d{22}"                             ), // Spain
 117  
             new Validator("FI", 18, "FI\\d{16}"                             ), // Finland
 118  
             new Validator("FO", 18, "FO\\d{16}"                             ), // Denmark (Faroes)
 119  
             new Validator("FR", 27, "FR\\d{12}[A-Z0-9]{11}\\d{2}"           ), // France
 120  
             new Validator("GB", 22, "GB\\d{2}[A-Z]{4}\\d{14}"               ), // United Kingdom
 121  
             new Validator("GE", 22, "GE\\d{2}[A-Z]{2}\\d{16}"               ), // Georgia
 122  
             new Validator("GI", 23, "GI\\d{2}[A-Z]{4}[A-Z0-9]{15}"          ), // Gibraltar
 123  
             new Validator("GL", 18, "GL\\d{16}"                             ), // Denmark (Greenland)
 124  
             new Validator("GR", 27, "GR\\d{9}[A-Z0-9]{16}"                  ), // Greece
 125  
             new Validator("GT", 28, "GT\\d{2}[A-Z0-9]{24}"                  ), // Guatemala
 126  
             new Validator("HR", 21, "HR\\d{19}"                             ), // Croatia
 127  
             new Validator("HU", 28, "HU\\d{26}"                             ), // Hungary
 128  
             new Validator("IE", 22, "IE\\d{2}[A-Z]{4}\\d{14}"               ), // Ireland
 129  
             new Validator("IL", 23, "IL\\d{21}"                             ), // Israel
 130  
             new Validator("IS", 26, "IS\\d{24}"                             ), // Iceland
 131  
             new Validator("IT", 27, "IT\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"   ), // Italy
 132  
             new Validator("IQ", 23, "IQ\\d{2}[A-Z]{4}\\d{15}"               ), // Iraq
 133  
             new Validator("JO", 30, "JO\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}"    ), // Jordan
 134  
             new Validator("KW", 30, "KW\\d{2}[A-Z]{4}[A-Z0-9]{22}"          ), // Kuwait
 135  
             new Validator("KZ", 20, "KZ\\d{5}[A-Z0-9]{13}"                  ), // Kazakhstan
 136  
             new Validator("LB", 28, "LB\\d{6}[A-Z0-9]{20}"                  ), // Lebanon
 137  
             new Validator("LC", 32, "LC\\d{2}[A-Z]{4}[A-Z0-9]{24}"          ), // Saint Lucia
 138  
             new Validator("LI", 21, "LI\\d{7}[A-Z0-9]{12}"                  ), // Liechtenstein (Principality of)
 139  
             new Validator("LT", 20, "LT\\d{18}"                             ), // Lithuania
 140  
             new Validator("LU", 20, "LU\\d{5}[A-Z0-9]{13}"                  ), // Luxembourg
 141  
             new Validator("LV", 21, "LV\\d{2}[A-Z]{4}[A-Z0-9]{13}"          ), // Latvia
 142  
             new Validator("MC", 27, "MC\\d{12}[A-Z0-9]{11}\\d{2}"           ), // Monaco
 143  
             new Validator("MD", 24, "MD\\d{2}[A-Z0-9]{20}"                  ), // Moldova
 144  
             new Validator("ME", 22, "ME\\d{20}"                             ), // Montenegro
 145  
             new Validator("MK", 19, "MK\\d{5}[A-Z0-9]{10}\\d{2}"            ), // Macedonia, Former Yugoslav Republic of
 146  
             new Validator("MR", 27, "MR\\d{25}"                             ), // Mauritania
 147  
             new Validator("MT", 31, "MT\\d{2}[A-Z]{4}\\d{5}[A-Z0-9]{18}"    ), // Malta
 148  
             new Validator("MU", 30, "MU\\d{2}[A-Z]{4}\\d{19}[A-Z]{3}"       ), // Mauritius
 149  
             new Validator("NL", 18, "NL\\d{2}[A-Z]{4}\\d{10}"               ), // The Netherlands
 150  
             new Validator("NO", 15, "NO\\d{13}"                             ), // Norway
 151  
             new Validator("PK", 24, "PK\\d{2}[A-Z]{4}[A-Z0-9]{16}"          ), // Pakistan
 152  
             new Validator("PL", 28, "PL\\d{26}"                             ), // Poland
 153  
             new Validator("PS", 29, "PS\\d{2}[A-Z]{4}[A-Z0-9]{21}"          ), // Palestine, State of
 154  
             new Validator("PT", 25, "PT\\d{23}"                             ), // Portugal
 155  
             new Validator("QA", 29, "QA\\d{2}[A-Z]{4}[A-Z0-9]{21}"          ), // Qatar
 156  
             new Validator("RO", 24, "RO\\d{2}[A-Z]{4}[A-Z0-9]{16}"          ), // Romania
 157  
             new Validator("RS", 22, "RS\\d{20}"                             ), // Serbia
 158  
             new Validator("SA", 24, "SA\\d{4}[A-Z0-9]{18}"                  ), // Saudi Arabia
 159  
             new Validator("SC", 31, "SC\\d{2}[A-Z]{4}\\d{20}[A-Z]{3}"       ), // Seychelles
 160  
             new Validator("SE", 24, "SE\\d{22}"                             ), // Sweden
 161  
             new Validator("SI", 19, "SI\\d{17}"                             ), // Slovenia
 162  
             new Validator("SK", 24, "SK\\d{22}"                             ), // Slovak Republic
 163  
             new Validator("SM", 27, "SM\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}"   ), // San Marino
 164  
             new Validator("ST", 25, "ST\\d{23}"                             ), // Sao Tome and Principe
 165  
             new Validator("TL", 23, "TL\\d{21}"                             ), // Timor-Leste
 166  
             new Validator("TN", 24, "TN\\d{22}"                             ), // Tunisia
 167  
             new Validator("TR", 26, "TR\\d{8}[A-Z0-9]{16}"                  ), // Turkey
 168  
             new Validator("UA", 29, "UA\\d{8}[A-Z0-9]{19}"                  ), // Ukraine
 169  
             new Validator("VG", 24, "VG\\d{2}[A-Z]{4}\\d{16}"               ), // Virgin Islands, British
 170  
             new Validator("XK", 20, "XK\\d{18}"                             ), // Republic of Kosovo
 171  
     };
 172  
 
 173  
     /** The singleton instance which uses the default formats */
 174  1
     public static final IBANValidator DEFAULT_IBAN_VALIDATOR = new IBANValidator();
 175  
 
 176  
     /**
 177  
      * Return a singleton instance of the IBAN validator using the default formats
 178  
      *
 179  
      * @return A singleton instance of the ISBN validator
 180  
      */
 181  
     public static IBANValidator getInstance() {
 182  1
         return DEFAULT_IBAN_VALIDATOR;
 183  
     }
 184  
 
 185  
     /**
 186  
      * Create a default IBAN validator.
 187  
      */
 188  
     public IBANValidator() {
 189  5
         this(DEFAULT_FORMATS);
 190  5
     }
 191  
 
 192  
     /**
 193  
      * Create an IBAN validator from the specified map of IBAN formats.
 194  
      *
 195  
      * @param formatMap map of IBAN formats
 196  
      */
 197  5
     public IBANValidator(Validator[] formatMap) {
 198  5
         this.formatValidators = createValidators(formatMap);
 199  5
     }
 200  
 
 201  
     private Map<String, Validator> createValidators(Validator[] formatMap) {
 202  5
         Map<String, Validator> m = new ConcurrentHashMap<String, Validator>();
 203  375
         for(Validator v : formatMap) {
 204  370
             m.put(v.countryCode, v);
 205  
         }
 206  5
         return m;
 207  
     }
 208  
 
 209  
     /**
 210  
      * Validate an IBAN Code
 211  
      *
 212  
      * @param code The value validation is being performed on
 213  
      * @return <code>true</code> if the value is valid
 214  
      */
 215  
     public boolean isValid(String code) {
 216  93
         Validator formatValidator = getValidator(code);
 217  93
         if (formatValidator == null || code.length() != formatValidator.lengthOfIBAN || !formatValidator.validator.isValid(code)) {
 218  11
             return false;
 219  
         }
 220  82
         return IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(code);
 221  
     }
 222  
 
 223  
     /**
 224  
      * Does the class have the required validator?
 225  
      *
 226  
      * @param code the code to check
 227  
      * @return true if there is a validator
 228  
      */
 229  
     public boolean hasValidator(String code) {
 230  81
         return getValidator(code) != null;
 231  
     }
 232  
 
 233  
     /**
 234  
      * Gets a copy of the default Validators.
 235  
      * 
 236  
      * @return a copy of the default Validator array
 237  
      */
 238  
     public Validator[] getDefaultValidators() {
 239  0
         return Arrays.copyOf(DEFAULT_FORMATS, DEFAULT_FORMATS.length);
 240  
     }
 241  
 
 242  
     /**
 243  
      * Get the Validator for a given IBAN
 244  
      * 
 245  
      * @param code a string starting with the ISO country code (e.g. an IBAN)
 246  
      * 
 247  
      * @return the validator or {@code null} if there is not one registered.
 248  
      */
 249  
     public Validator getValidator(String code) {
 250  176
         if (code == null || code.length() < 2) { // ensure we can extract the code
 251  3
             return null;
 252  
         }
 253  173
         String key = code.substring(0, 2);
 254  173
         return formatValidators.get(key);
 255  
     }
 256  
 
 257  
     /**
 258  
      * Installs a validator.
 259  
      * Will replace any existing entry which has the same countryCode
 260  
      * 
 261  
      * @param validator the instance to install.
 262  
      * @return the previous Validator, or {@code null} if there was none
 263  
      * @throws IllegalStateException if an attempt is made to modify the singleton validator
 264  
      */
 265  
     public Validator setValidator(Validator validator) {
 266  0
         if (this == DEFAULT_IBAN_VALIDATOR) {
 267  0
             throw new IllegalStateException("The singleton validator cannot be modified");
 268  
         }
 269  0
         return formatValidators.put(validator.countryCode, validator);
 270  
     }
 271  
 
 272  
     /**
 273  
      * Installs a validator.
 274  
      * Will replace any existing entry which has the same countryCode.
 275  
      * 
 276  
      * @param countryCode the country code
 277  
      * @param length the length of the IBAN. Must be &ge; 8 and &le; 32.
 278  
      * If the length is &lt; 0, the validator is removed, and the format is not used.
 279  
      * @param format the format of the IBAN (as a regular expression)
 280  
      * @return the previous Validator, or {@code null} if there was none
 281  
      * @throws IllegalArgumentException if there is a problem
 282  
      * @throws IllegalStateException if an attempt is made to modify the singleton validator
 283  
      */
 284  
     public Validator setValidator(String countryCode, int length, String format) {
 285  7
         if (this == DEFAULT_IBAN_VALIDATOR) {
 286  2
             throw new IllegalStateException("The singleton validator cannot be modified");
 287  
         }
 288  5
         if (length < 0) {
 289  2
             return formatValidators.remove(countryCode);
 290  
         }
 291  3
         return setValidator(new Validator(countryCode, length, format));
 292  
     }
 293  
 }