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.io.Serializable; 020import java.util.Arrays; 021import java.util.Locale; 022 023import org.apache.commons.validator.routines.checkdigit.ISINCheckDigit; 024 025/** 026 * <b>ISIN</b> (International Securities Identifying Number) validation. 027 * 028 * <p> 029 * ISIN Numbers are 12 character alphanumeric codes used to identify Securities. 030 * </p> 031 * 032 * <p> 033 * ISINs consist of two alphabetic characters, 034 * which are the ISO 3166-1 alpha-2 code for the issuing country, 035 * nine alpha-numeric characters (the National Securities Identifying Number, or NSIN, which identifies the security), 036 * and one numerical check digit. 037 * They are 12 characters in length. 038 * </p> 039 * 040 * <p> 041 * See <a href="http://en.wikipedia.org/wiki/ISIN">Wikipedia - ISIN</a> 042 * for more details. 043 * </p> 044 * 045 * @since 1.7 046 */ 047public class ISINValidator implements Serializable { 048 049 private static final long serialVersionUID = -5964391439144260936L; 050 051 private static final String ISIN_REGEX = "([A-Z]{2}[A-Z0-9]{9}[0-9])"; 052 053 private static final CodeValidator VALIDATOR = new CodeValidator(ISIN_REGEX, 12, ISINCheckDigit.ISIN_CHECK_DIGIT); 054 055 /** ISIN Code Validator (no countryCode check) */ 056 private static final ISINValidator ISIN_VALIDATOR_FALSE = new ISINValidator(false); 057 058 /** ISIN Code Validator (with countryCode check) */ 059 private static final ISINValidator ISIN_VALIDATOR_TRUE = new ISINValidator(true); 060 061 private static final String [] CCODES = Locale.getISOCountries(); 062 063 private static final String [] SPECIALS = { 064 "EZ", // http://www.anna-web.org/standards/isin-iso-6166/ 065 "XS", // https://www.isin.org/isin/ 066 }; 067 068 static { 069 Arrays.sort(CCODES); // we cannot assume the codes are sorted 070 Arrays.sort(SPECIALS); // Just in case ... 071 } 072 073 private final boolean checkCountryCode; 074 075 /** 076 * Return a singleton instance of the ISIN validator 077 * @param checkCountryCode whether to check the country-code prefix or not 078 * @return A singleton instance of the appropriate ISIN validator. 079 */ 080 public static ISINValidator getInstance(boolean checkCountryCode) { 081 return checkCountryCode ? ISIN_VALIDATOR_TRUE : ISIN_VALIDATOR_FALSE; 082 } 083 084 private ISINValidator(boolean checkCountryCode) { 085 this.checkCountryCode = checkCountryCode; 086 } 087 088 /** 089 * Check the code is a valid ISIN code after any transformation 090 * by the validate routine. 091 * @param code The code to validate. 092 * @return <code>true</code> if a valid ISIN 093 * code, otherwise <code>false</code>. 094 */ 095 public boolean isValid(String code) { 096 final boolean valid = VALIDATOR.isValid(code); 097 if (valid && checkCountryCode) { 098 return checkCode(code.substring(0,2)); 099 } 100 return valid; 101 } 102 103 /** 104 * Check the code is valid ISIN code. 105 * 106 * @param code The code to validate. 107 * @return A valid ISIN code if valid, otherwise <code>null</code>. 108 */ 109 public Object validate(String code) { 110 final Object validate = VALIDATOR.validate(code); 111 if (validate != null && checkCountryCode) { 112 return checkCode(code.substring(0,2)) ? validate : null; 113 } 114 return validate; 115 } 116 117 private boolean checkCode(String code) { 118 return Arrays.binarySearch(CCODES, code) >= 0 119 || 120 Arrays.binarySearch(SPECIALS, code) >= 0 121 ; 122 } 123 124}