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 }