    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}