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