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 * @version $Revision: 1441678 $ $Date: 2013-02-02 02:31:07 +0100 (Sa, 02 Feb 2013) $
054 * @since Validator 1.4
055 */
056public class ISBNValidator implements Serializable {
057
058    private static final long serialVersionUID = 4319515687976420405L;
059
060    private static final String SEP = "(?:\\-|\\s)";
061    private static final String GROUP = "(\\d{1,5})";
062    private static final String PUBLISHER = "(\\d{1,7})";
063    private static final String TITLE = "(\\d{1,6})";
064
065    /**
066     * ISBN-10 consists of 4 groups of numbers separated by either dashes (-)
067     * or spaces.  The first group is 1-5 characters, second 1-7, third 1-6,
068     * and fourth is 1 digit or an X.
069     */
070    static final String ISBN10_REGEX     =
071                  "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9X])))$";
072
073    /**
074     * ISBN-13 consists of 5 groups of numbers separated by either dashes (-)
075     * or spaces.  The first group is 978 or 979, the second group is
076     * 1-5 characters, third 1-7, fourth 1-6, and fifth is 1 digit.
077     */
078    static final String ISBN13_REGEX     =
079        "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9])))$";
080
081    /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
082    private static final ISBNValidator ISBN_VALIDATOR = new ISBNValidator();
083
084    /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
085    private static final ISBNValidator ISBN_VALIDATOR_NO_CONVERT = new ISBNValidator(false);
086
087
088    /** ISBN-10 Code Validator */
089    private final CodeValidator isbn10Validator = new CodeValidator(ISBN10_REGEX, 10, ISBN10CheckDigit.ISBN10_CHECK_DIGIT);
090
091    /** ISBN-13 Code Validator */
092    private final CodeValidator isbn13Validator = new CodeValidator(ISBN13_REGEX, 13, EAN13CheckDigit.EAN13_CHECK_DIGIT);
093
094    private final boolean convert;
095
096    /**
097     * Return a singleton instance of the ISBN validator which
098     * converts ISBN-10 codes to ISBN-13.
099     *
100     * @return A singleton instance of the ISBN validator.
101     */
102    public static ISBNValidator getInstance() {
103        return ISBN_VALIDATOR;
104    }
105
106    /**
107     * Return a singleton instance of the ISBN validator specifying
108     * whether ISBN-10 codes should be converted to ISBN-13.
109     *
110     * @param convert <code>true</code> if valid ISBN-10 codes
111     * should be converted to ISBN-13 codes or <code>false</code>
112     * if valid ISBN-10 codes should be returned unchanged.
113     * @return A singleton instance of the ISBN validator.
114     */
115    public static ISBNValidator getInstance(boolean convert) {
116        return (convert ? ISBN_VALIDATOR : ISBN_VALIDATOR_NO_CONVERT);
117    }
118
119    /**
120     * Construct an ISBN validator which converts ISBN-10 codes
121     * to ISBN-13.
122     */
123    public ISBNValidator() {
124        this(true);
125    }
126
127    /**
128     * Construct an ISBN validator indicating whether
129     * ISBN-10 codes should be converted to ISBN-13.
130     *
131     * @param convert <code>true</code> if valid ISBN-10 codes
132     * should be converted to ISBN-13 codes or <code>false</code>
133     * if valid ISBN-10 codes should be returned unchanged.
134     */
135    public ISBNValidator(boolean convert) {
136        this.convert = convert;
137    }
138
139    /**
140     * Check the code is either a valid ISBN-10 or ISBN-13 code.
141     *
142     * @param code The code to validate.
143     * @return <code>true</code> if a valid ISBN-10 or
144     * ISBN-13 code, otherwise <code>false</code>.
145     */
146    public boolean isValid(String code) {
147        return (isValidISBN13(code) || isValidISBN10(code));
148    }
149
150    /**
151     * Check the code is a valid ISBN-10 code.
152     *
153     * @param code The code to validate.
154     * @return <code>true</code> if a valid ISBN-10
155     * code, otherwise <code>false</code>.
156     */
157    public boolean isValidISBN10(String code) {
158        return isbn10Validator.isValid(code);
159    }
160
161    /**
162     * Check the code is a valid ISBN-13 code.
163     *
164     * @param code The code to validate.
165     * @return <code>true</code> if a valid ISBN-13
166     * code, otherwise <code>false</code>.
167     */
168    public boolean isValidISBN13(String code) {
169        return isbn13Validator.isValid(code);
170    }
171
172    /**
173     * Check the code is either a valid ISBN-10 or ISBN-13 code.
174     * <p>
175     * If valid, this method returns the ISBN code with
176     * formatting characters removed (i.e. space or hyphen).
177     * <p>
178     * Converts an ISBN-10 codes to ISBN-13 if
179     * <code>convertToISBN13</code> is <code>true</code>.
180     *
181     * @param code The code to validate.
182     * @return A valid ISBN code if valid, otherwise <code>null</code>.
183     */
184    public String validate(String code) {
185        String result = validateISBN13(code);
186        if (result == null) {
187            result = validateISBN10(code);
188            if (result != null && convert) {
189                result = convertToISBN13(result);
190            }
191        }
192        return result;
193    }
194
195    /**
196     * Check the code is a valid ISBN-10 code.
197     * <p>
198     * If valid, this method returns the ISBN-10 code with
199     * formatting characters removed (i.e. space or hyphen).
200     *
201     * @param code The code to validate.
202     * @return A valid ISBN-10 code if valid,
203     * otherwise <code>null</code>.
204     */
205    public String validateISBN10(String code) {
206        Object result = isbn10Validator.validate(code);
207        return (result == null ? null : result.toString());
208    }
209
210    /**
211     * Check the code is a valid ISBN-13 code.
212     * <p>
213     * If valid, this method returns the ISBN-13 code with
214     * formatting characters removed (i.e. space or hyphen).
215     *
216     * @param code The code to validate.
217     * @return A valid ISBN-13 code if valid,
218     * otherwise <code>null</code>.
219     */
220    public String validateISBN13(String code) {
221        Object result = isbn13Validator.validate(code);
222        return (result == null ? null : result.toString());
223    }
224
225    /**
226     * Convert an ISBN-10 code to an ISBN-13 code.
227     * <p>
228     * This method requires a valid ISBN-10 with NO formatting
229     * characters.
230     *
231     * @param isbn10 The ISBN-10 code to convert
232     * @return A converted ISBN-13 code or <code>null</code>
233     * if the ISBN-10 code is not valid
234     */
235    public String convertToISBN13(String isbn10) {
236
237        if (isbn10 == null) {
238            return null;
239        }
240
241        String input = isbn10.trim();
242        if (input.length() != 10) {
243            throw new IllegalArgumentException("Invalid length " + input.length() + " for '" + input + "'");
244        }
245
246        // Calculate the new ISBN-13 code
247        String isbn13 = "978" + input.substring(0, 9);
248        try {
249            String checkDigit = isbn13Validator.getCheckDigit().calculate(isbn13);
250            isbn13 += checkDigit;
251            return isbn13;
252        } catch (CheckDigitException e) {
253            throw new IllegalArgumentException("Check digit error for '" + input + "' - " + e.getMessage());
254        }
255
256    }
257
258}