    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 org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit;
    021import org.apache.commons.validator.routines.checkdigit.ISBN10CheckDigit;
    022import org.apache.commons.validator.routines.checkdigit.CheckDigitException;
    023
    024/**
    025 * <b>ISBN-10</b> and <b>ISBN-13</b> Code Validation.
    026 * <p>
    027 * This validator validates the code is either a valid ISBN-10
    028 * (using a {@link CodeValidator} with the {@link ISBN10CheckDigit})
    029 * or a valid ISBN-13 code (using a {@link CodeValidator} with the
    030 * the {@link EAN13CheckDigit} routine).
    031 * <p>
    032 * The <code>validate()</code> methods return the ISBN code with formatting
    033 * characters removed if valid or <code>null</code> if invalid.
    034 * <p>
    035 * This validator also provides the facility to convert ISBN-10 codes to
    036 * ISBN-13 if the <code>convert</code> property is <code>true</code>.
    037 * <p>
    038 * From 1st January 2007 the book industry will start to use a new 13 digit
    039 * ISBN number (rather than this 10 digit ISBN number). ISBN-13 codes are
    040 * <a href="http://en.wikipedia.org/wiki/European_Article_Number">EAN</a>
    041 * codes, for more information see:</p>
    042 *
    043 * <ul>
    044 *   <li><a href="http://en.wikipedia.org/wiki/ISBN">Wikipedia - International
    045 *       Standard Book Number (ISBN)</a>.</li>
    046 *   <li>EAN - see
    047 *       <a href="http://en.wikipedia.org/wiki/European_Article_Number">Wikipedia -
    048 *       European Article Number</a>.</li>
    049 *   <li><a href="http://www.isbn.org/standards/home/isbn/transition.asp">ISBN-13
    050 *       Transition details</a>.</li>
    051 * </ul>
    052 *
    053 * <p>ISBN-13s are either prefixed with 978 or 979. 978 prefixes are only assigned
    054 * to the ISBN agency. 979 prefixes may be assigned to ISBNs or ISMNs
    055 * (<a href="https://www.ismn-international.org/">International
    056 * Standard Music Numbers</a>).
    057 * <ul>
    058 *     <li>979-0 are assigned to the ISMN agency</li>
    059 *     <li>979-10, 979-11, 979-12 are assigned to the ISBN agency</li>
    060 * </ul>
    061 * All other 979 prefixed EAN-13 numbers have not yet been assigned to an agency. The
    062 * validator validates all 13 digit codes with 978 or 979 prefixes.
    063 *
    064 * @version $Revision$
    065 * @since Validator 1.4
    066 */
    067public class ISBNValidator implements Serializable {
    068
    069    private static final int ISBN_10_LEN = 10;
    070
    071    private static final long serialVersionUID = 4319515687976420405L;
    072
    073    private static final String SEP = "(?:\\-|\\s)";
    074    private static final String GROUP = "(\\d{1,5})";
    075    private static final String PUBLISHER = "(\\d{1,7})";
    076    private static final String TITLE = "(\\d{1,6})";
    077
    078    /**
    079     * ISBN-10 consists of 4 groups of numbers separated by either dashes (-)
    080     * or spaces.  The first group is 1-5 characters, second 1-7, third 1-6,
    081     * and fourth is 1 digit or an X.
    082     */
    083    static final String ISBN10_REGEX     =
    084                  "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9X])))$";
    085
    086    /**
    087     * ISBN-13 consists of 5 groups of numbers separated by either dashes (-)
    088     * or spaces.  The first group is 978 or 979, the second group is
    089     * 1-5 characters, third 1-7, fourth 1-6, and fifth is 1 digit.
    090     */
    091    static final String ISBN13_REGEX     =
    092        "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9])))$";
    093
    094    /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
    095    private static final ISBNValidator ISBN_VALIDATOR = new ISBNValidator();
    096
    097    /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
    098    private static final ISBNValidator ISBN_VALIDATOR_NO_CONVERT = new ISBNValidator(false);
    099
    100
    101    /** ISBN-10 Code Validator */
    102    private final CodeValidator isbn10Validator = new CodeValidator(ISBN10_REGEX, 10, ISBN10CheckDigit.ISBN10_CHECK_DIGIT);
    103
    104    /** ISBN-13 Code Validator */
    105    private final CodeValidator isbn13Validator = new CodeValidator(ISBN13_REGEX, 13, EAN13CheckDigit.EAN13_CHECK_DIGIT);
    106
    107    private final boolean convert;
    108
    109    /**
    110     * Return a singleton instance of the ISBN validator which
    111     * converts ISBN-10 codes to ISBN-13.
    112     *
    113     * @return A singleton instance of the ISBN validator.
    114     */
    115    public static ISBNValidator getInstance() {
    116        return ISBN_VALIDATOR;
    117    }
    118
    119    /**
    120     * Return a singleton instance of the ISBN validator specifying
    121     * whether ISBN-10 codes should be converted to ISBN-13.
    122     *
    123     * @param convert <code>true</code> if valid ISBN-10 codes
    124     * should be converted to ISBN-13 codes or <code>false</code>
    125     * if valid ISBN-10 codes should be returned unchanged.
    126     * @return A singleton instance of the ISBN validator.
    127     */
    128    public static ISBNValidator getInstance(boolean convert) {
    129        return (convert ? ISBN_VALIDATOR : ISBN_VALIDATOR_NO_CONVERT);
    130    }
    131
    132    /**
    133     * Construct an ISBN validator which converts ISBN-10 codes
    134     * to ISBN-13.
    135     */
    136    public ISBNValidator() {
    137        this(true);
    138    }
    139
    140    /**
    141     * Construct an ISBN validator indicating whether
    142     * ISBN-10 codes should be converted to ISBN-13.
    143     *
    144     * @param convert <code>true</code> if valid ISBN-10 codes
    145     * should be converted to ISBN-13 codes or <code>false</code>
    146     * if valid ISBN-10 codes should be returned unchanged.
    147     */
    148    public ISBNValidator(boolean convert) {
    149        this.convert = convert;
    150    }
    151
    152    /**
    153     * Check the code is either a valid ISBN-10 or ISBN-13 code.
    154     *
    155     * @param code The code to validate.
    156     * @return <code>true</code> if a valid ISBN-10 or
    157     * ISBN-13 code, otherwise <code>false</code>.
    158     */
    159    public boolean isValid(String code) {
    160        return (isValidISBN13(code) || isValidISBN10(code));
    161    }
    162
    163    /**
    164     * Check the code is a valid ISBN-10 code.
    165     *
    166     * @param code The code to validate.
    167     * @return <code>true</code> if a valid ISBN-10
    168     * code, otherwise <code>false</code>.
    169     */
    170    public boolean isValidISBN10(String code) {
    171        return isbn10Validator.isValid(code);
    172    }
    173
    174    /**
    175     * Check the code is a valid ISBN-13 code.
    176     *
    177     * @param code The code to validate.
    178     * @return <code>true</code> if a valid ISBN-13
    179     * code, otherwise <code>false</code>.
    180     */
    181    public boolean isValidISBN13(String code) {
    182        return isbn13Validator.isValid(code);
    183    }
    184
    185    /**
    186     * Check the code is either a valid ISBN-10 or ISBN-13 code.
    187     * <p>
    188     * If valid, this method returns the ISBN code with
    189     * formatting characters removed (i.e. space or hyphen).
    190     * <p>
    191     * Converts an ISBN-10 codes to ISBN-13 if
    192     * <code>convertToISBN13</code> is <code>true</code>.
    193     *
    194     * @param code The code to validate.
    195     * @return A valid ISBN code if valid, otherwise <code>null</code>.
    196     */
    197    public String validate(String code) {
    198        String result = validateISBN13(code);
    199        if (result == null) {
    200            result = validateISBN10(code);
    201            if (result != null && convert) {
    202                result = convertToISBN13(result);
    203            }
    204        }
    205        return result;
    206    }
    207
    208    /**
    209     * Check the code is a valid ISBN-10 code.
    210     * <p>
    211     * If valid, this method returns the ISBN-10 code with
    212     * formatting characters removed (i.e. space or hyphen).
    213     *
    214     * @param code The code to validate.
    215     * @return A valid ISBN-10 code if valid,
    216     * otherwise <code>null</code>.
    217     */
    218    public String validateISBN10(String code) {
    219        Object result = isbn10Validator.validate(code);
    220        return (result == null ? null : result.toString());
    221    }
    222
    223    /**
    224     * Check the code is a valid ISBN-13 code.
    225     * <p>
    226     * If valid, this method returns the ISBN-13 code with
    227     * formatting characters removed (i.e. space or hyphen).
    228     *
    229     * @param code The code to validate.
    230     * @return A valid ISBN-13 code if valid,
    231     * otherwise <code>null</code>.
    232     */
    233    public String validateISBN13(String code) {
    234        Object result = isbn13Validator.validate(code);
    235        return (result == null ? null : result.toString());
    236    }
    237
    238    /**
    239     * Convert an ISBN-10 code to an ISBN-13 code.
    240     * <p>
    241     * This method requires a valid ISBN-10 with NO formatting
    242     * characters.
    243     *
    244     * @param isbn10 The ISBN-10 code to convert
    245     * @return A converted ISBN-13 code or <code>null</code>
    246     * if the ISBN-10 code is not valid
    247     */
    248    public String convertToISBN13(String isbn10) {
    249
    250        if (isbn10 == null) {
    251            return null;
    252        }
    253
    254        String input = isbn10.trim();
    255        if (input.length() != ISBN_10_LEN) {
    256            throw new IllegalArgumentException("Invalid length " + input.length() + " for '" + input + "'");
    257        }
    258
    259        // Calculate the new ISBN-13 code (drop the original checkdigit)
    260        String isbn13 = "978" + input.substring(0, ISBN_10_LEN - 1);
    261        try {
    262            String checkDigit = isbn13Validator.getCheckDigit().calculate(isbn13);
    263            isbn13 += checkDigit;
    264            return isbn13;
    265        } catch (CheckDigitException e) {
    266            throw new IllegalArgumentException("Check digit error for '" + input + "' - " + e.getMessage());
    267        }
    268
    269    }
    270
    271}