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    /**
074     * Return a singleton instance of the ISIN validator
075     * @param checkCountryCode whether to check the country-code prefix or not
076     * @return A singleton instance of the appropriate ISIN validator.
077     */
078    public static ISINValidator getInstance(final boolean checkCountryCode) {
079        return checkCountryCode ? ISIN_VALIDATOR_TRUE : ISIN_VALIDATOR_FALSE;
080    }
081
082    private final boolean checkCountryCode;
083
084    private ISINValidator(final boolean checkCountryCode) {
085        this.checkCountryCode = checkCountryCode;
086    }
087
088    private boolean checkCode(final String code) {
089        return Arrays.binarySearch(CCODES, code) >= 0
090               ||
091               Arrays.binarySearch(SPECIALS, code) >= 0
092        ;
093    }
094
095    /**
096     * Check the code is a valid ISIN code after any transformation
097     * by the validate routine.
098     * @param code The code to validate.
099     * @return {@code true} if a valid ISIN
100     * code, otherwise {@code false}.
101     */
102    public boolean isValid(final String code) {
103        final boolean valid = VALIDATOR.isValid(code);
104        if (valid && checkCountryCode) {
105            return checkCode(code.substring(0,2));
106        }
107        return valid;
108    }
109
110    /**
111     * Check the code is valid ISIN code.
112     *
113     * @param code The code to validate.
114     * @return A valid ISIN code if valid, otherwise <code>null</code>.
115     */
116    public Object validate(final String code) {
117        final Object validate = VALIDATOR.validate(code);
118        if (validate != null && checkCountryCode) {
119            return checkCode(code.substring(0,2)) ? validate : null;
120        }
121        return validate;
122    }
123
124}