blob: 3ff30c3c164f9d3f3415fa426744cbbbde1a0669 [file] [log] [blame]
/*
* 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 java.io.Serializable;
import java.util.Arrays;
import java.util.Locale;
import org.apache.commons.validator.routines.checkdigit.ISINCheckDigit;
/**
* <b>ISIN</b> (International Securities Identifying Number) validation.
*
* <p>
* ISIN Numbers are 12 character alphanumeric codes used to identify Securities.
* </p>
*
* <p>
* ISINs consist of two alphabetic characters,
* which are the ISO 3166-1 alpha-2 code for the issuing country,
* nine alpha-numeric characters (the National Securities Identifying Number, or NSIN, which identifies the security),
* and one numerical check digit.
* They are 12 characters in length.
* </p>
*
* <p>
* See <a href="https://en.wikipedia.org/wiki/ISIN">Wikipedia - ISIN</a>
* for more details.
* </p>
*
* @since 1.7
*/
public class ISINValidator implements Serializable {
private static final long serialVersionUID = -5964391439144260936L;
private static final String ISIN_REGEX = "([A-Z]{2}[A-Z0-9]{9}[0-9])";
private static final CodeValidator VALIDATOR = new CodeValidator(ISIN_REGEX, 12, ISINCheckDigit.ISIN_CHECK_DIGIT);
/** ISIN Code Validator (no countryCode check) */
private static final ISINValidator ISIN_VALIDATOR_FALSE = new ISINValidator(false);
/** ISIN Code Validator (with countryCode check) */
private static final ISINValidator ISIN_VALIDATOR_TRUE = new ISINValidator(true);
private static final String [] CCODES = Locale.getISOCountries();
/**
* All codes from ISO 3166-1 alpha-2 except unassigned code elements.
*
* From https://www.iso.org/obp/ui/#iso:pub:PUB500001:en as of 2024-03-23.
*/
private static final String[] SPECIALS = {
"AA",
"AC",
"AD",
"AE",
"AF",
"AG",
"AI",
"AL",
"AM",
"AN",
"AO",
"AP",
"AQ",
"AR",
"AS",
"AT",
"AU",
"AW",
"AX",
"AZ",
"BA",
"BB",
"BD",
"BE",
"BF",
"BG",
"BH",
"BI",
"BJ",
"BL",
"BM",
"BN",
"BO",
"BQ",
"BR",
"BS",
"BT",
"BU",
"BV",
"BW",
"BX",
"BY",
"BZ",
"CA",
"CC",
"CD",
"CF",
"CG",
"CH",
"CI",
"CK",
"CL",
"CM",
"CN",
"CO",
"CP",
"CQ",
"CR",
"CS",
"CT",
"CU",
"CV",
"CW",
"CX",
"CY",
"CZ",
"DD",
"DE",
"DG",
"DJ",
"DK",
"DM",
"DO",
"DY",
"DZ",
"EA",
"EC",
"EE",
"EF",
"EG",
"EH",
"EM",
"EP",
"ER",
"ES",
"ET",
"EU",
"EV",
"EW",
"EZ",
"FI",
"FJ",
"FK",
"FL",
"FM",
"FO",
"FQ",
"FR",
"FX",
"GA",
"GB",
"GC",
"GD",
"GE",
"GF",
"GG",
"GH",
"GI",
"GL",
"GM",
"GN",
"GP",
"GQ",
"GR",
"GS",
"GT",
"GU",
"GW",
"GY",
"HK",
"HM",
"HN",
"HR",
"HT",
"HU",
"HV",
"IB",
"IC",
"ID",
"IE",
"IL",
"IM",
"IN",
"IO",
"IQ",
"IR",
"IS",
"IT",
"JA",
"JE",
"JM",
"JO",
"JP",
"JT",
"KE",
"KG",
"KH",
"KI",
"KM",
"KN",
"KP",
"KR",
"KW",
"KY",
"KZ",
"LA",
"LB",
"LC",
"LF",
"LI",
"LK",
"LR",
"LS",
"LT",
"LU",
"LV",
"LY",
"MA",
"MC",
"MD",
"ME",
"MF",
"MG",
"MH",
"MI",
"MK",
"ML",
"MM",
"MN",
"MO",
"MP",
"MQ",
"MR",
"MS",
"MT",
"MU",
"MV",
"MW",
"MX",
"MY",
"MZ",
"NA",
"NC",
"NE",
"NF",
"NG",
"NH",
"NI",
"NL",
"NO",
"NP",
"NQ",
"NR",
"NT",
"NU",
"NZ",
"OA",
"OM",
"PA",
"PC",
"PE",
"PF",
"PG",
"PH",
"PI",
"PK",
"PL",
"PM",
"PN",
"PR",
"PS",
"PT",
"PU",
"PW",
"PY",
"PZ",
"QA",
"QM",
"QN",
"QO",
"QP",
"QQ",
"QR",
"QS",
"QT",
"QU",
"QV",
"QW",
"QX",
"QY",
"QZ",
"RA",
"RB",
"RC",
"RE",
"RH",
"RI",
"RL",
"RM",
"RN",
"RO",
"RP",
"RS",
"RU",
"RW",
"SA",
"SB",
"SC",
"SD",
"SE",
"SF",
"SG",
"SH",
"SI",
"SJ",
"SK",
"SL",
"SM",
"SN",
"SO",
"SR",
"SS",
"ST",
"SU",
"SV",
"SX",
"SY",
"SZ",
"TA",
"TC",
"TD",
"TF",
"TG",
"TH",
"TJ",
"TK",
"TL",
"TM",
"TN",
"TO",
"TP",
"TR",
"TT",
"TV",
"TW",
"TZ",
"UA",
"UG",
"UK",
"UM",
"UN",
"US",
"UY",
"UZ",
"VA",
"VC",
"VD",
"VE",
"VG",
"VI",
"VN",
"VU",
"WF",
"WG",
"WK",
"WL",
"WO",
"WS",
"WV",
"XA",
"XB",
"XC",
"XD",
"XE",
"XF",
"XG",
"XH",
"XI",
"XJ",
"XK",
"XL",
"XM",
"XN",
"XO",
"XP",
"XQ",
"XR",
"XS",
"XT",
"XU",
"XV",
"XW",
"XX",
"XY",
"XZ",
"YD",
"YE",
"YT",
"YU",
"YV",
"ZA",
"ZM",
"ZR",
"ZW",
"ZZ",
};
static {
Arrays.sort(CCODES); // we cannot assume the codes are sorted
Arrays.sort(SPECIALS); // Just in case ...
}
/**
* Gets the singleton instance of the ISIN validator.
*
* @param checkCountryCode whether to check the country-code prefix or not
* @return A singleton instance of the appropriate ISIN validator.
*/
public static ISINValidator getInstance(final boolean checkCountryCode) {
return checkCountryCode ? ISIN_VALIDATOR_TRUE : ISIN_VALIDATOR_FALSE;
}
private final boolean checkCountryCode;
private ISINValidator(final boolean checkCountryCode) {
this.checkCountryCode = checkCountryCode;
}
private boolean checkCode(final String code) {
return Arrays.binarySearch(CCODES, code) >= 0
||
Arrays.binarySearch(SPECIALS, code) >= 0
;
}
/**
* Tests whether the code is a valid ISIN code after any transformation
* by the validate routine.
*
* @param code The code to validate.
* @return {@code true} if a valid ISIN
* code, otherwise {@code false}.
*/
public boolean isValid(final String code) {
final boolean valid = VALIDATOR.isValid(code);
if (valid && checkCountryCode) {
return checkCode(code.substring(0,2));
}
return valid;
}
/**
* Checks the code is valid ISIN code.
*
* @param code The code to validate.
* @return A valid ISIN code if valid, otherwise {@code null}.
*/
public Object validate(final String code) {
final Object validate = VALIDATOR.validate(code);
if (validate != null && checkCountryCode) {
return checkCode(code.substring(0,2)) ? validate : null;
}
return validate;
}
}