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