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