| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.validator.routines; |
| |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.csv.CSVFormat; |
| import org.apache.commons.csv.CSVParser; |
| import org.apache.commons.csv.CSVRecord; |
| import org.apache.commons.validator.routines.IBANValidator.Validator; |
| import org.apache.commons.validator.routines.checkdigit.IBANCheckDigit; |
| import org.junit.Test; |
| |
| /** |
| * IBANValidator Test Case. |
| * @since 1.5.0 |
| */ |
| public class IBANValidatorTest { |
| |
| // It's not clear whether IBANs can contain lower case characters |
| // so we test for both where possible |
| // Note that the BIC near the start of the code is always upper case or digits |
| private final String[] validIBANFormat = new String[] { |
| "AD1200012030200359100100", |
| "AE070331234567890123456", |
| "AL47212110090000000235698741", |
| "AT611904300234573201", |
| "AZ21NABZ00000000137010001944", |
| "BA391290079401028494", |
| "BE68539007547034", |
| "BG80BNBG96611020345678", |
| "BH67BMAG00001299123456", |
| "BR1800000000141455123924100C2", |
| "BR1800360305000010009795493C1", |
| "BR9700360305000010009795493P1", |
| "BY13NBRB3600900000002Z00AB00", |
| "CH9300762011623852957", |
| "CR05015202001026284066", |
| "CY17002001280000001200527600", |
| "CZ6508000000192000145399", |
| "CZ9455000000001011038930", |
| "DE89370400440532013000", |
| "DK5000400440116243", |
| "DO28BAGR00000001212453611324", |
| "EE382200221020145685", |
| "EG380019000500000000263180002", |
| "ES9121000418450200051332", |
| "FI2112345600000785", |
| "FI5542345670000081", |
| "FO6264600001631634", |
| "FR1420041010050500013M02606", |
| "GB29NWBK60161331926819", |
| "GE29NB0000000101904917", |
| "GI75NWBK000000007099453", |
| "GL8964710001000206", |
| "GR1601101250000000012300695", |
| "GT82TRAJ01020000001210029690", |
| "HR1210010051863000160", |
| "HU42117730161111101800000000", |
| "IE29AIBK93115212345678", |
| "IL620108000000099999999", |
| "IQ98NBIQ850123456789012", |
| "IS140159260076545510730339", |
| "IT60X0542811101000000123456", |
| "JO94CBJO0010000000000131000302", |
| "KW81CBKU0000000000001234560101", |
| "KZ86125KZT5004100100", |
| "LB62099900000001001901229114", |
| "LC55HEMM000100010012001200023015", |
| "LI21088100002324013AA", |
| "LT121000011101001000", |
| "LU280019400644750000", |
| "LY83002048000020100120361", |
| "LV80BANK0000435195001", |
| "MC5811222000010123456789030", |
| "MD24AG000225100013104168", |
| "ME25505000012345678951", |
| "MK07250120000058984", |
| "MR1300020001010000123456753", |
| "MT84MALT011000012345MTLCAST001S", |
| "MU17BOMM0101101030300200000MUR", |
| "NL91ABNA0417164300", |
| "NO9386011117947", |
| "PK36SCBL0000001123456702", |
| "PL61109010140000071219812874", |
| "PS92PALS000000000400123456702", |
| "PT50000201231234567890154", |
| "QA58DOHB00001234567890ABCDEFG", |
| "RO49AAAA1B31007593840000", |
| "RS35260005601001611379", |
| "SA0380000000608010167519", |
| "SC18SSCB11010000000000001497USD", |
| "SE4550000000058398257466", |
| "SI56191000000123438", |
| "SI56263300012039086", |
| "SK3112000000198742637541", |
| "SM86U0322509800000000270100", |
| "ST68000100010051845310112", |
| "SV62CENR00000000000000700025", |
| "SV43ACAT00000000000000123123", |
| "TL380080012345678910157", |
| "TN5910006035183598478831", |
| "TR330006100519786457841326", |
| "UA213223130000026007233566001", |
| "UA213996220000026007233566001", |
| "VA59001123000012345678", |
| "VG96VPVG0000012345678901", |
| "XK051212012345678906", |
| }; |
| |
| private final String[] invalidIBANFormat = new String[] { |
| "", // empty |
| " ", // empty |
| "A", // too short |
| "AB", // too short |
| "FR1420041010050500013m02606", // lowercase version |
| "MT84MALT011000012345mtlcast001s", // lowercase version |
| "LI21088100002324013aa", // lowercase version |
| "QA58DOHB00001234567890abcdefg", // lowercase version |
| "RO49AAAA1b31007593840000", // lowercase version |
| "LC62HEMM000100010012001200023015", // wrong in SWIFT |
| "BY00NBRB3600000000000Z00AB00", // Wrong in SWIFT v73 |
| "ST68000200010192194210112", // ditto |
| "SV62CENR0000000000000700025", // ditto |
| }; |
| |
| private static final IBANValidator VALIDATOR = IBANValidator.getInstance(); |
| |
| @Test |
| public void testValid() { |
| for(String f : validIBANFormat) { |
| assertTrue("Checksum fail: "+f, IBANCheckDigit.IBAN_CHECK_DIGIT.isValid(f)); |
| assertTrue("Missing validator: "+f, VALIDATOR.hasValidator(f)); |
| assertTrue(f, VALIDATOR.isValid(f)); |
| } |
| } |
| |
| @Test |
| public void testInValid() { |
| for(String f : invalidIBANFormat) { |
| assertFalse(f, VALIDATOR.isValid(f)); |
| } |
| } |
| |
| @Test |
| public void testNull() { |
| assertFalse("isValid(null)", VALIDATOR.isValid(null)); |
| } |
| |
| @Test |
| public void testHasValidator() { |
| assertTrue("GB", VALIDATOR.hasValidator("GB")); |
| assertFalse("gb", VALIDATOR.hasValidator("gb")); |
| } |
| |
| @Test |
| public void testGetValidator() { |
| assertNotNull("GB", VALIDATOR.getValidator("GB")); |
| assertNull("gb", VALIDATOR.getValidator("gb")); |
| } |
| |
| @Test(expected=IllegalStateException.class) |
| public void testSetDefaultValidator1() { |
| assertNotNull(VALIDATOR.setValidator("GB", 15, "GB")); |
| } |
| |
| @Test(expected=IllegalStateException.class) |
| public void testSetDefaultValidator2() { |
| assertNotNull(VALIDATOR.setValidator("GB", -1, "GB")); |
| } |
| |
| @Test(expected=IllegalArgumentException.class) |
| public void testSetValidatorLC() { |
| IBANValidator validator = new IBANValidator(); |
| assertNotNull(validator.setValidator("gb", 15, "GB")); |
| } |
| |
| @Test(expected=IllegalArgumentException.class) |
| public void testSetValidatorLen7() { |
| IBANValidator validator = new IBANValidator(); |
| assertNotNull(validator.setValidator("GB", 7, "GB")); |
| } |
| |
| @Test(expected=IllegalArgumentException.class) |
| public void testSetValidatorLen35() { |
| IBANValidator validator = new IBANValidator(); |
| assertNotNull(validator.setValidator("GB", 35, "GB")); // valid params, but immutable validator |
| } |
| |
| @Test |
| public void testSetValidatorLen_1() { |
| IBANValidator validator = new IBANValidator(); |
| assertNotNull("should be present",validator.setValidator("GB", -1, "")); |
| assertNull("no longer present",validator.setValidator("GB", -1, "")); |
| } |
| |
| @Test |
| public void testSorted() { |
| IBANValidator validator = new IBANValidator(); |
| Validator[] vals = validator.getDefaultValidators(); |
| assertNotNull(vals); |
| for(int i=1; i < vals.length; i++) { |
| if (vals[i].countryCode.compareTo(vals[i-1].countryCode) <= 0) { |
| fail("Not sorted: "+vals[i].countryCode+ " <= " + vals[i-1].countryCode); |
| } |
| } |
| } |
| |
| private static int checkIBAN(File file, IBANValidator val) throws Exception { |
| // The IBAN Registry (TXT) file is a TAB-separated file |
| // Rows are the entry types, columns are the countries |
| CSVFormat format = CSVFormat.DEFAULT.withDelimiter('\t'); |
| Reader rdr = new InputStreamReader(new FileInputStream(file), "ISO_8859_1"); |
| CSVParser p = new CSVParser(rdr, format); |
| CSVRecord country = null; |
| CSVRecord cc = null; |
| CSVRecord structure = null; |
| CSVRecord length = null; |
| for (CSVRecord o : p) { |
| String item = o.get(0); |
| if ("Name of country".equals(item)) { |
| country = o; |
| } else if ("IBAN prefix country code (ISO 3166)".equals(item)) { |
| cc = o; |
| } else if ("IBAN structure".equals(item)) { |
| structure = o; |
| } else if ("IBAN length".equals(item)) { |
| length = o; |
| } |
| } |
| for (int i=1; i < country.size(); i++) { |
| try { |
| |
| String newLength = length.get(i).split("!")[0]; // El Salvador currently has "28!n" |
| String newRE = fmtRE(structure.get(i), Integer.parseInt(newLength)); |
| final Validator valre = val.getValidator(cc.get(i)); |
| if (valre == null) { |
| System.out.println("// Missing entry:"); |
| printEntry( |
| cc.get(i), |
| newLength, |
| newRE, |
| country.get(i)); |
| } else { |
| String currentLength = Integer.toString(valre.lengthOfIBAN); |
| String currentRE = valre.validator.toString() |
| .replaceAll("^.+?\\{(.+)}","$1") // Extract RE from RegexValidator{re} string |
| .replaceAll("\\\\d","\\\\\\\\d"); // convert \d to \\d |
| // The above assumes that the RegexValidator contains a single Regex |
| if (currentRE.equals(newRE) && currentLength.equals(newLength)) { |
| |
| } else { |
| System.out.println("// Expected: " + newRE + ", " + newLength + " Actual: " + currentRE + ", " + currentLength); |
| printEntry( |
| cc.get(i), |
| newLength, |
| newRE, |
| country.get(i)); |
| } |
| |
| } |
| |
| } catch (IllegalArgumentException e) { |
| e.printStackTrace(); |
| } |
| } |
| p.close(); |
| return country.size(); |
| } |
| |
| private static void printEntry(String ccode, String length, String ib, String country) { |
| String fmt = String.format("\"%s\"", ib); |
| System.out.printf(" new Validator(\"%s\", %s, %-40s), // %s\n", |
| ccode, |
| length, |
| fmt, |
| country); |
| } |
| |
| // Unfortunately Java only returns the last match of repeated patterns |
| // Use a manual repeat instead |
| private static final String IBAN_PART = "(?:(\\d+)!([acn]))"; // Assume all parts are fixed length |
| private static final Pattern IBAN_PAT = Pattern.compile( |
| "([A-Z]{2})"+IBAN_PART+IBAN_PART+IBAN_PART+IBAN_PART+"?"+IBAN_PART+"?"+IBAN_PART+"?"+IBAN_PART+"?"); |
| |
| // convert IBAN type string and length to regex |
| private static String formatToRE(String type, int len) { |
| char ctype = type.charAt(0); // assume type.length() == 1 |
| switch(ctype) { |
| case 'n': |
| return String.format("\\\\d{%d}",len); |
| case 'a': |
| return String.format("[A-Z]{%d}",len); |
| case 'c': |
| return String.format("[A-Z0-9]{%d}",len); |
| default: |
| throw new IllegalArgumentException("Unexpected type " + type); |
| } |
| } |
| |
| private static String fmtRE(String iban_pat, int iban_len) { |
| Matcher m = IBAN_PAT.matcher(iban_pat); |
| if (m.matches()) { |
| StringBuilder sb = new StringBuilder(); |
| String cc = m.group(1); // country code |
| int totalLen = cc.length(); |
| sb.append(cc); |
| int len = Integer.parseInt(m.group(2)); // length of part |
| String curType = m.group(3); // part type |
| for (int i = 4; i <= m.groupCount(); i += 2) { |
| if (m.group(i) == null) { // reached an optional group |
| break; |
| } |
| int count = Integer.parseInt(m.group(i)); |
| String type = m.group(i+1); |
| if (type.equals(curType)) { // more of the same type |
| len += count; |
| } else { |
| sb.append(formatToRE(curType,len)); |
| totalLen += len; |
| curType = type; |
| len = count; |
| } |
| } |
| sb.append(formatToRE(curType,len)); |
| totalLen += len; |
| if (iban_len != totalLen) { |
| throw new IllegalArgumentException("IBAN pattern " + iban_pat + " does not match length " + iban_len); |
| } |
| return sb.toString(); |
| } else { |
| throw new IllegalArgumentException("Unexpected IBAN pattern " + iban_pat); |
| } |
| } |
| |
| public static void main(String [] a) throws Exception { |
| IBANValidator validator = new IBANValidator(); |
| File iban_tsv = new File("target","iban-registry.tsv"); |
| int countries = 0; |
| if (iban_tsv.canRead()) { |
| countries = checkIBAN(iban_tsv, validator); |
| } else { |
| System.out.println("Please load the file " + iban_tsv.getCanonicalPath() + " from https://www.swift.com/standards/data-standards/iban"); |
| } |
| System.out.println("Processed " + countries + " countries."); |
| } |
| } |