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="https://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 }