View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.validator.routines;
18  
19  import java.io.Serializable;
20  
21  import org.apache.commons.validator.routines.checkdigit.CheckDigitException;
22  import org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit;
23  import org.apache.commons.validator.routines.checkdigit.ISBN10CheckDigit;
24  
25  /**
26   * <b>ISBN-10</b> and <b>ISBN-13</b> Code Validation.
27   * <p>
28   * This validator validates the code is either a valid ISBN-10
29   * (using a {@link CodeValidator} with the {@link ISBN10CheckDigit})
30   * or a valid ISBN-13 code (using a {@link CodeValidator} with the
31   * the {@link EAN13CheckDigit} routine).
32   * <p>
33   * The <code>validate()</code> methods return the ISBN code with formatting
34   * characters removed if valid or {@code null} if invalid.
35   * <p>
36   * This validator also provides the facility to convert ISBN-10 codes to
37   * ISBN-13 if the <code>convert</code> property is {@code true}.
38   * <p>
39   * From 1st January 2007 the book industry will start to use a new 13 digit
40   * ISBN number (rather than this 10 digit ISBN number). ISBN-13 codes are
41   * <a href="https://en.wikipedia.org/wiki/European_Article_Number">EAN</a>
42   * codes, for more information see:</p>
43   *
44   * <ul>
45   *   <li><a href="https://en.wikipedia.org/wiki/ISBN">Wikipedia - International
46   *       Standard Book Number (ISBN)</a>.</li>
47   *   <li>EAN - see
48   *       <a href="https://en.wikipedia.org/wiki/European_Article_Number">Wikipedia -
49   *       European Article Number</a>.</li>
50   *   <li><a href="http://www.isbn.org/standards/home/isbn/transition.asp">ISBN-13
51   *       Transition details</a>.</li>
52   * </ul>
53   *
54   * <p>ISBN-13s are either prefixed with 978 or 979. 978 prefixes are only assigned
55   * to the ISBN agency. 979 prefixes may be assigned to ISBNs or ISMNs
56   * (<a href="https://www.ismn-international.org/">International
57   * Standard Music Numbers</a>).
58   * <ul>
59   *     <li>979-0 are assigned to the ISMN agency</li>
60   *     <li>979-10, 979-11, 979-12 are assigned to the ISBN agency</li>
61   * </ul>
62   * All other 979 prefixed EAN-13 numbers have not yet been assigned to an agency. The
63   * validator validates all 13 digit codes with 978 or 979 prefixes.
64   *
65   * @since 1.4
66   */
67  public class ISBNValidator implements Serializable {
68  
69      private static final int ISBN_10_LEN = 10;
70  
71      private static final long serialVersionUID = 4319515687976420405L;
72  
73      private static final String SEP = "(?:\\-|\\s)";
74      private static final String GROUP = "(\\d{1,5})";
75      private static final String PUBLISHER = "(\\d{1,7})";
76      private static final String TITLE = "(\\d{1,6})";
77  
78      /**
79       * ISBN-10 consists of 4 groups of numbers separated by either dashes (-)
80       * or spaces.  The first group is 1-5 characters, second 1-7, third 1-6,
81       * and fourth is 1 digit or an X.
82       */
83      static final String ISBN10_REGEX = "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9X])))$";
84  
85      /**
86       * ISBN-13 consists of 5 groups of numbers separated by either dashes (-)
87       * or spaces.  The first group is 978 or 979, the second group is
88       * 1-5 characters, third 1-7, fourth 1-6, and fifth is 1 digit.
89       */
90      static final String ISBN13_REGEX = "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9])))$";
91  
92      /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
93      private static final ISBNValidator ISBN_VALIDATOR = new ISBNValidator();
94  
95      /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
96      private static final ISBNValidator ISBN_VALIDATOR_NO_CONVERT = new ISBNValidator(false);
97  
98      /**
99       * Gets the 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      * Gets the singleton instance of the ISBN validator specifying
110      * whether ISBN-10 codes should be converted to ISBN-13.
111      *
112      * @param convert {@code true} if valid ISBN-10 codes
113      * should be converted to ISBN-13 codes or {@code false}
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(final boolean convert) {
118         return convert ? ISBN_VALIDATOR : ISBN_VALIDATOR_NO_CONVERT;
119     }
120 
121     /** ISBN-10 Code Validator */
122     private final CodeValidator isbn10Validator = new CodeValidator(ISBN10_REGEX, 10, ISBN10CheckDigit.ISBN10_CHECK_DIGIT);
123 
124     /** ISBN-13 Code Validator */
125     private final CodeValidator isbn13Validator = new CodeValidator(ISBN13_REGEX, 13, EAN13CheckDigit.EAN13_CHECK_DIGIT);
126 
127     private final boolean convert;
128 
129     /**
130      * Constructs an ISBN validator which converts ISBN-10 codes
131      * to ISBN-13.
132      */
133     public ISBNValidator() {
134         this(true);
135     }
136 
137     /**
138      * Constructs an ISBN validator indicating whether
139      * ISBN-10 codes should be converted to ISBN-13.
140      *
141      * @param convert {@code true} if valid ISBN-10 codes
142      * should be converted to ISBN-13 codes or {@code false}
143      * if valid ISBN-10 codes should be returned unchanged.
144      */
145     public ISBNValidator(final boolean convert) {
146         this.convert = convert;
147     }
148 
149     /**
150      * Convert an ISBN-10 code to an ISBN-13 code.
151      * <p>
152      * This method requires a valid ISBN-10 with NO formatting
153      * characters.
154      *
155      * @param isbn10 The ISBN-10 code to convert
156      * @return A converted ISBN-13 code or {@code null}
157      * if the ISBN-10 code is not valid
158      */
159     public String convertToISBN13(final String isbn10) {
160 
161         if (isbn10 == null) {
162             return null;
163         }
164 
165         final String input = isbn10.trim();
166         if (input.length() != ISBN_10_LEN) {
167             throw new IllegalArgumentException("Invalid length " + input.length() + " for '" + input + "'");
168         }
169 
170         // Calculate the new ISBN-13 code (drop the original checkdigit)
171         String isbn13 = "978" + input.substring(0, ISBN_10_LEN - 1);
172         try {
173             final String checkDigit = isbn13Validator.getCheckDigit().calculate(isbn13);
174             isbn13 += checkDigit;
175             return isbn13;
176         } catch (final CheckDigitException e) {
177             throw new IllegalArgumentException("Check digit error for '" + input + "' - " + e.getMessage());
178         }
179 
180     }
181 
182     /**
183      * Check the code is either a valid ISBN-10 or ISBN-13 code.
184      *
185      * @param code The code to validate.
186      * @return {@code true} if a valid ISBN-10 or
187      * ISBN-13 code, otherwise {@code false}.
188      */
189     public boolean isValid(final String code) {
190         return isValidISBN13(code) || isValidISBN10(code);
191     }
192 
193     /**
194      * Check the code is a valid ISBN-10 code.
195      *
196      * @param code The code to validate.
197      * @return {@code true} if a valid ISBN-10
198      * code, otherwise {@code false}.
199      */
200     public boolean isValidISBN10(final String code) {
201         return isbn10Validator.isValid(code);
202     }
203 
204     /**
205      * Check the code is a valid ISBN-13 code.
206      *
207      * @param code The code to validate.
208      * @return {@code true} if a valid ISBN-13
209      * code, otherwise {@code false}.
210      */
211     public boolean isValidISBN13(final String code) {
212         return isbn13Validator.isValid(code);
213     }
214 
215     /**
216      * Check the code is either a valid ISBN-10 or ISBN-13 code.
217      * <p>
218      * If valid, this method returns the ISBN code with
219      * formatting characters removed (i.e. space or hyphen).
220      * <p>
221      * Converts an ISBN-10 codes to ISBN-13 if
222      * <code>convertToISBN13</code> is {@code true}.
223      *
224      * @param code The code to validate.
225      * @return A valid ISBN code if valid, otherwise {@code null}.
226      */
227     public String validate(final String code) {
228         String result = validateISBN13(code);
229         if (result == null) {
230             result = validateISBN10(code);
231             if (result != null && convert) {
232                 result = convertToISBN13(result);
233             }
234         }
235         return result;
236     }
237 
238     /**
239      * Check the code is a valid ISBN-10 code.
240      * <p>
241      * If valid, this method returns the ISBN-10 code with
242      * formatting characters removed (i.e. space or hyphen).
243      *
244      * @param code The code to validate.
245      * @return A valid ISBN-10 code if valid,
246      * otherwise {@code null}.
247      */
248     public String validateISBN10(final String code) {
249         final Object result = isbn10Validator.validate(code);
250         return result == null ? null : result.toString();
251     }
252 
253     /**
254      * Check the code is a valid ISBN-13 code.
255      * <p>
256      * If valid, this method returns the ISBN-13 code with
257      * formatting characters removed (i.e. space or hyphen).
258      *
259      * @param code The code to validate.
260      * @return A valid ISBN-13 code if valid,
261      * otherwise {@code null}.
262      */
263     public String validateISBN13(final String code) {
264         final Object result = isbn13Validator.validate(code);
265         return result == null ? null : result.toString();
266     }
267 
268 }