ISINValidator.java

  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. import java.io.Serializable;
  19. import java.util.Arrays;
  20. import java.util.Locale;

  21. import org.apache.commons.validator.routines.checkdigit.ISINCheckDigit;

  22. /**
  23.  * <b>ISIN</b> (International Securities Identifying Number) validation.
  24.  *
  25.  * <p>
  26.  * ISIN Numbers are 12 character alphanumeric codes used to identify Securities.
  27.  * </p>
  28.  *
  29.  * <p>
  30.  * ISINs consist of two alphabetic characters,
  31.  * which are the ISO 3166-1 alpha-2 code for the issuing country,
  32.  * nine alpha-numeric characters (the National Securities Identifying Number, or NSIN, which identifies the security),
  33.  * and one numerical check digit.
  34.  * They are 12 characters in length.
  35.  * </p>
  36.  *
  37.  * <p>
  38.  * See <a href="https://en.wikipedia.org/wiki/ISIN">Wikipedia - ISIN</a>
  39.  * for more details.
  40.  * </p>
  41.  *
  42.  * @since 1.7
  43.  */
  44. public class ISINValidator implements Serializable {

  45.     private static final long serialVersionUID = -5964391439144260936L;

  46.     private static final String ISIN_REGEX = "([A-Z]{2}[A-Z0-9]{9}[0-9])";

  47.     private static final CodeValidator VALIDATOR = new CodeValidator(ISIN_REGEX, 12, ISINCheckDigit.ISIN_CHECK_DIGIT);

  48.     /** ISIN Code Validator (no countryCode check) */
  49.     private static final ISINValidator ISIN_VALIDATOR_FALSE = new ISINValidator(false);

  50.     /** ISIN Code Validator (with countryCode check) */
  51.     private static final ISINValidator ISIN_VALIDATOR_TRUE = new ISINValidator(true);

  52.     private static final String [] CCODES = Locale.getISOCountries();

  53.     /**
  54.      * All codes from ISO 3166-1 alpha-2 except unassigned code elements.
  55.      *
  56.      * From https://www.iso.org/obp/ui/#iso:pub:PUB500001:en as of 2024-03-23.
  57.      */
  58.     private static final String[] SPECIALS = {
  59.             "AA",
  60.             "AC",
  61.             "AD",
  62.             "AE",
  63.             "AF",
  64.             "AG",
  65.             "AI",
  66.             "AL",
  67.             "AM",
  68.             "AN",
  69.             "AO",
  70.             "AP",
  71.             "AQ",
  72.             "AR",
  73.             "AS",
  74.             "AT",
  75.             "AU",
  76.             "AW",
  77.             "AX",
  78.             "AZ",
  79.             "BA",
  80.             "BB",
  81.             "BD",
  82.             "BE",
  83.             "BF",
  84.             "BG",
  85.             "BH",
  86.             "BI",
  87.             "BJ",
  88.             "BL",
  89.             "BM",
  90.             "BN",
  91.             "BO",
  92.             "BQ",
  93.             "BR",
  94.             "BS",
  95.             "BT",
  96.             "BU",
  97.             "BV",
  98.             "BW",
  99.             "BX",
  100.             "BY",
  101.             "BZ",
  102.             "CA",
  103.             "CC",
  104.             "CD",
  105.             "CF",
  106.             "CG",
  107.             "CH",
  108.             "CI",
  109.             "CK",
  110.             "CL",
  111.             "CM",
  112.             "CN",
  113.             "CO",
  114.             "CP",
  115.             "CQ",
  116.             "CR",
  117.             "CS",
  118.             "CT",
  119.             "CU",
  120.             "CV",
  121.             "CW",
  122.             "CX",
  123.             "CY",
  124.             "CZ",
  125.             "DD",
  126.             "DE",
  127.             "DG",
  128.             "DJ",
  129.             "DK",
  130.             "DM",
  131.             "DO",
  132.             "DY",
  133.             "DZ",
  134.             "EA",
  135.             "EC",
  136.             "EE",
  137.             "EF",
  138.             "EG",
  139.             "EH",
  140.             "EM",
  141.             "EP",
  142.             "ER",
  143.             "ES",
  144.             "ET",
  145.             "EU",
  146.             "EV",
  147.             "EW",
  148.             "EZ",
  149.             "FI",
  150.             "FJ",
  151.             "FK",
  152.             "FL",
  153.             "FM",
  154.             "FO",
  155.             "FQ",
  156.             "FR",
  157.             "FX",
  158.             "GA",
  159.             "GB",
  160.             "GC",
  161.             "GD",
  162.             "GE",
  163.             "GF",
  164.             "GG",
  165.             "GH",
  166.             "GI",
  167.             "GL",
  168.             "GM",
  169.             "GN",
  170.             "GP",
  171.             "GQ",
  172.             "GR",
  173.             "GS",
  174.             "GT",
  175.             "GU",
  176.             "GW",
  177.             "GY",
  178.             "HK",
  179.             "HM",
  180.             "HN",
  181.             "HR",
  182.             "HT",
  183.             "HU",
  184.             "HV",
  185.             "IB",
  186.             "IC",
  187.             "ID",
  188.             "IE",
  189.             "IL",
  190.             "IM",
  191.             "IN",
  192.             "IO",
  193.             "IQ",
  194.             "IR",
  195.             "IS",
  196.             "IT",
  197.             "JA",
  198.             "JE",
  199.             "JM",
  200.             "JO",
  201.             "JP",
  202.             "JT",
  203.             "KE",
  204.             "KG",
  205.             "KH",
  206.             "KI",
  207.             "KM",
  208.             "KN",
  209.             "KP",
  210.             "KR",
  211.             "KW",
  212.             "KY",
  213.             "KZ",
  214.             "LA",
  215.             "LB",
  216.             "LC",
  217.             "LF",
  218.             "LI",
  219.             "LK",
  220.             "LR",
  221.             "LS",
  222.             "LT",
  223.             "LU",
  224.             "LV",
  225.             "LY",
  226.             "MA",
  227.             "MC",
  228.             "MD",
  229.             "ME",
  230.             "MF",
  231.             "MG",
  232.             "MH",
  233.             "MI",
  234.             "MK",
  235.             "ML",
  236.             "MM",
  237.             "MN",
  238.             "MO",
  239.             "MP",
  240.             "MQ",
  241.             "MR",
  242.             "MS",
  243.             "MT",
  244.             "MU",
  245.             "MV",
  246.             "MW",
  247.             "MX",
  248.             "MY",
  249.             "MZ",
  250.             "NA",
  251.             "NC",
  252.             "NE",
  253.             "NF",
  254.             "NG",
  255.             "NH",
  256.             "NI",
  257.             "NL",
  258.             "NO",
  259.             "NP",
  260.             "NQ",
  261.             "NR",
  262.             "NT",
  263.             "NU",
  264.             "NZ",
  265.             "OA",
  266.             "OM",
  267.             "PA",
  268.             "PC",
  269.             "PE",
  270.             "PF",
  271.             "PG",
  272.             "PH",
  273.             "PI",
  274.             "PK",
  275.             "PL",
  276.             "PM",
  277.             "PN",
  278.             "PR",
  279.             "PS",
  280.             "PT",
  281.             "PU",
  282.             "PW",
  283.             "PY",
  284.             "PZ",
  285.             "QA",
  286.             "QM",
  287.             "QN",
  288.             "QO",
  289.             "QP",
  290.             "QQ",
  291.             "QR",
  292.             "QS",
  293.             "QT",
  294.             "QU",
  295.             "QV",
  296.             "QW",
  297.             "QX",
  298.             "QY",
  299.             "QZ",
  300.             "RA",
  301.             "RB",
  302.             "RC",
  303.             "RE",
  304.             "RH",
  305.             "RI",
  306.             "RL",
  307.             "RM",
  308.             "RN",
  309.             "RO",
  310.             "RP",
  311.             "RS",
  312.             "RU",
  313.             "RW",
  314.             "SA",
  315.             "SB",
  316.             "SC",
  317.             "SD",
  318.             "SE",
  319.             "SF",
  320.             "SG",
  321.             "SH",
  322.             "SI",
  323.             "SJ",
  324.             "SK",
  325.             "SL",
  326.             "SM",
  327.             "SN",
  328.             "SO",
  329.             "SR",
  330.             "SS",
  331.             "ST",
  332.             "SU",
  333.             "SV",
  334.             "SX",
  335.             "SY",
  336.             "SZ",
  337.             "TA",
  338.             "TC",
  339.             "TD",
  340.             "TF",
  341.             "TG",
  342.             "TH",
  343.             "TJ",
  344.             "TK",
  345.             "TL",
  346.             "TM",
  347.             "TN",
  348.             "TO",
  349.             "TP",
  350.             "TR",
  351.             "TT",
  352.             "TV",
  353.             "TW",
  354.             "TZ",
  355.             "UA",
  356.             "UG",
  357.             "UK",
  358.             "UM",
  359.             "UN",
  360.             "US",
  361.             "UY",
  362.             "UZ",
  363.             "VA",
  364.             "VC",
  365.             "VD",
  366.             "VE",
  367.             "VG",
  368.             "VI",
  369.             "VN",
  370.             "VU",
  371.             "WF",
  372.             "WG",
  373.             "WK",
  374.             "WL",
  375.             "WO",
  376.             "WS",
  377.             "WV",
  378.             "XA",
  379.             "XB",
  380.             "XC",
  381.             "XD",
  382.             "XE",
  383.             "XF",
  384.             "XG",
  385.             "XH",
  386.             "XI",
  387.             "XJ",
  388.             "XK",
  389.             "XL",
  390.             "XM",
  391.             "XN",
  392.             "XO",
  393.             "XP",
  394.             "XQ",
  395.             "XR",
  396.             "XS",
  397.             "XT",
  398.             "XU",
  399.             "XV",
  400.             "XW",
  401.             "XX",
  402.             "XY",
  403.             "XZ",
  404.             "YD",
  405.             "YE",
  406.             "YT",
  407.             "YU",
  408.             "YV",
  409.             "ZA",
  410.             "ZM",
  411.             "ZR",
  412.             "ZW",
  413.             "ZZ",
  414.     };

  415.     static {
  416.         Arrays.sort(CCODES); // we cannot assume the codes are sorted
  417.         Arrays.sort(SPECIALS); // Just in case ...
  418.     }

  419.     /**
  420.      * Gets the singleton instance of the ISIN validator.
  421.      *
  422.      * @param checkCountryCode whether to check the country-code prefix or not
  423.      * @return A singleton instance of the appropriate ISIN validator.
  424.      */
  425.     public static ISINValidator getInstance(final boolean checkCountryCode) {
  426.         return checkCountryCode ? ISIN_VALIDATOR_TRUE : ISIN_VALIDATOR_FALSE;
  427.     }

  428.     private final boolean checkCountryCode;

  429.     private ISINValidator(final boolean checkCountryCode) {
  430.         this.checkCountryCode = checkCountryCode;
  431.     }

  432.     private boolean checkCode(final String code) {
  433.         return Arrays.binarySearch(CCODES, code) >= 0
  434.                ||
  435.                Arrays.binarySearch(SPECIALS, code) >= 0
  436.         ;
  437.     }

  438.     /**
  439.      * Tests whether the code is a valid ISIN code after any transformation
  440.      * by the validate routine.
  441.      *
  442.      * @param code The code to validate.
  443.      * @return {@code true} if a valid ISIN
  444.      * code, otherwise {@code false}.
  445.      */
  446.     public boolean isValid(final String code) {
  447.         final boolean valid = VALIDATOR.isValid(code);
  448.         if (valid && checkCountryCode) {
  449.             return checkCode(code.substring(0, 2));
  450.         }
  451.         return valid;
  452.     }

  453.     /**
  454.      * Checks the code is valid ISIN code.
  455.      *
  456.      * @param code The code to validate.
  457.      * @return A valid ISIN code if valid, otherwise {@code null}.
  458.      */
  459.     public Object validate(final String code) {
  460.         final Object validate = VALIDATOR.validate(code);
  461.         if (validate != null && checkCountryCode) {
  462.             return checkCode(code.substring(0, 2)) ? validate : null;
  463.         }
  464.         return validate;
  465.     }

  466. }