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 * https://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 */ 017package org.apache.commons.validator.routines; 018 019import java.io.Serializable; 020import java.util.Objects; 021 022import org.apache.commons.validator.routines.checkdigit.CheckDigitException; 023import org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit; 024import org.apache.commons.validator.routines.checkdigit.ISBN10CheckDigit; 025 026/** 027 * <strong>ISBN-10</strong> and <strong>ISBN-13</strong> Code Validation. 028 * <p> 029 * This validator validates the code is either a valid ISBN-10 030 * (using a {@link CodeValidator} with the {@link ISBN10CheckDigit}) 031 * or a valid ISBN-13 code (using a {@link CodeValidator} with 032 * the {@link EAN13CheckDigit} routine). 033 * </p> 034 * <p> 035 * The {@code validate()} methods return the ISBN code with formatting 036 * characters removed if valid or {@code null} if invalid. 037 * </p> 038 * <p> 039 * This validator also provides the facility to convert ISBN-10 codes to 040 * ISBN-13 if the {@code convert} property is {@code true}. 041 * </p> 042 * <p> 043 * From 1st January 2007 the book industry will start to use a new 13 digit 044 * ISBN number (rather than this 10 digit ISBN number). ISBN-13 codes are 045 * <a href="https://en.wikipedia.org/wiki/European_Article_Number">EAN</a> 046 * codes, for more information see: 047 * </p> 048 * 049 * <ul> 050 * <li><a href="https://en.wikipedia.org/wiki/ISBN">Wikipedia - International 051 * Standard Book Number (ISBN)</a>.</li> 052 * <li>EAN - see 053 * <a href="https://en.wikipedia.org/wiki/European_Article_Number">Wikipedia - 054 * European Article Number</a>.</li> 055 * <li><a href="https://www.isbn.org/standards/home/isbn/transition.asp">ISBN-13 056 * Transition details</a>.</li> 057 * </ul> 058 * 059 * <p> 060 * ISBN-13s are either prefixed with 978 or 979. 978 prefixes are only assigned 061 * to the ISBN agency. 979 prefixes may be assigned to ISBNs or ISMNs 062 * (<a href="https://www.ismn-international.org/">International 063 * Standard Music Numbers</a>). 064 * </p> 065 * <ul> 066 * <li>979-0 are assigned to the ISMN agency</li> 067 * <li>979-10, 979-11, 979-12 are assigned to the ISBN agency</li> 068 * </ul> 069 * <p> 070 * All other 979 prefixed EAN-13 numbers have not yet been assigned to an agency. The 071 * validator validates all 13-digit codes with 978 or 979 prefixes. 072 * </p> 073 * 074 * @since 1.4 075 */ 076public class ISBNValidator implements Serializable { 077 078 private static final int ISBN_10_LEN = 10; 079 080 private static final long serialVersionUID = 4319515687976420405L; 081 082 private static final String SEP = "(?:\\-|\\s)"; 083 private static final String GROUP = "(\\d{1,5})"; 084 private static final String PUBLISHER = "(\\d{1,7})"; 085 private static final String TITLE = "(\\d{1,6})"; 086 087 /** 088 * ISBN-10 consists of 4 groups of numbers separated by either dashes (-) 089 * or spaces. The first group is 1-5 characters, second 1-7, third 1-6, 090 * and fourth is 1 digit or an X. 091 */ 092 static final String ISBN10_REGEX = "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9X])))$"; 093 094 /** 095 * ISBN-13 consists of 5 groups of numbers separated by either dashes (-) 096 * or spaces. The first group is 978 or 979, the second group is 097 * 1-5 characters, third 1-7, fourth 1-6, and fifth is 1 digit. 098 */ 099 static final String ISBN13_REGEX = "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9])))$"; 100 101 /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */ 102 private static final ISBNValidator ISBN_VALIDATOR = new ISBNValidator(); 103 104 /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */ 105 private static final ISBNValidator ISBN_VALIDATOR_NO_CONVERT = new ISBNValidator(false); 106 107 /** 108 * Gets the singleton instance of the ISBN validator which 109 * converts ISBN-10 codes to ISBN-13. 110 * 111 * @return A singleton instance of the ISBN validator. 112 */ 113 public static ISBNValidator getInstance() { 114 return ISBN_VALIDATOR; 115 } 116 117 /** 118 * Gets the singleton instance of the ISBN validator specifying 119 * whether ISBN-10 codes should be converted to ISBN-13. 120 * 121 * @param convert {@code true} if valid ISBN-10 codes 122 * should be converted to ISBN-13 codes or {@code false} 123 * if valid ISBN-10 codes should be returned unchanged. 124 * @return A singleton instance of the ISBN validator. 125 */ 126 public static ISBNValidator getInstance(final boolean convert) { 127 return convert ? ISBN_VALIDATOR : ISBN_VALIDATOR_NO_CONVERT; 128 } 129 130 /** ISBN-10 Code Validator */ 131 private final CodeValidator isbn10Validator = new CodeValidator(ISBN10_REGEX, 10, ISBN10CheckDigit.ISBN10_CHECK_DIGIT); 132 133 /** ISBN-13 Code Validator */ 134 private final CodeValidator isbn13Validator = new CodeValidator(ISBN13_REGEX, 13, EAN13CheckDigit.EAN13_CHECK_DIGIT); 135 136 /** 137 * Whether validation converts an ISBN-10 codes to ISBN-13. 138 */ 139 private final boolean convert; 140 141 /** 142 * Constructs an ISBN validator which converts ISBN-10 codes 143 * to ISBN-13. 144 */ 145 public ISBNValidator() { 146 this(true); 147 } 148 149 /** 150 * Constructs an ISBN validator indicating whether 151 * ISBN-10 codes should be converted to ISBN-13. 152 * 153 * @param convert {@code true} if valid ISBN-10 codes 154 * should be converted to ISBN-13 codes or {@code false} 155 * if valid ISBN-10 codes should be returned unchanged. 156 */ 157 public ISBNValidator(final boolean convert) { 158 this.convert = convert; 159 } 160 161 /** 162 * Convert an ISBN-10 code to an ISBN-13 code. 163 * <p> 164 * This method requires a valid ISBN-10 with NO formatting 165 * characters. 166 * </p> 167 * 168 * @param isbn10 The ISBN-10 code to convert 169 * @return A converted ISBN-13 code or {@code null} 170 * if the ISBN-10 code is not valid 171 */ 172 public String convertToISBN13(final String isbn10) { 173 174 if (isbn10 == null) { 175 return null; 176 } 177 178 final String input = isbn10.trim(); 179 if (input.length() != ISBN_10_LEN) { 180 throw new IllegalArgumentException("Invalid length " + input.length() + " for '" + input + "'"); 181 } 182 183 // Calculate the new ISBN-13 code (drop the original checkdigit) 184 String isbn13 = "978" + input.substring(0, ISBN_10_LEN - 1); 185 try { 186 final String checkDigit = isbn13Validator.getCheckDigit().calculate(isbn13); 187 isbn13 += checkDigit; 188 return isbn13; 189 } catch (final CheckDigitException e) { 190 throw new IllegalArgumentException("Check digit error for '" + input + "' - " + e.getMessage()); 191 } 192 193 } 194 195 /** 196 * Check the code is either a valid ISBN-10 or ISBN-13 code. 197 * 198 * @param code The code to validate. 199 * @return {@code true} if a valid ISBN-10 or 200 * ISBN-13 code, otherwise {@code false}. 201 */ 202 public boolean isValid(final String code) { 203 return isValidISBN13(code) || isValidISBN10(code); 204 } 205 206 /** 207 * Check the code is a valid ISBN-10 code. 208 * 209 * @param code The code to validate. 210 * @return {@code true} if a valid ISBN-10 211 * code, otherwise {@code false}. 212 */ 213 public boolean isValidISBN10(final String code) { 214 return isbn10Validator.isValid(code); 215 } 216 217 /** 218 * Check the code is a valid ISBN-13 code. 219 * 220 * @param code The code to validate. 221 * @return {@code true} if a valid ISBN-13 222 * code, otherwise {@code false}. 223 */ 224 public boolean isValidISBN13(final String code) { 225 return isbn13Validator.isValid(code); 226 } 227 228 /** 229 * Check the code is either a valid ISBN-10 or ISBN-13 code. 230 * <p> 231 * If valid, this method returns the ISBN code with 232 * formatting characters removed (such as space or hyphen). 233 * </p> 234 * <p> 235 * Converts an ISBN-10 codes to ISBN-13 if 236 * {@code convertToISBN13} is {@code true}. 237 * </p> 238 * 239 * @param code The code to validate. 240 * @return A valid ISBN code if valid, otherwise {@code null}. 241 */ 242 public String validate(final String code) { 243 String result = validateISBN13(code); 244 if (result == null) { 245 result = validateISBN10(code); 246 if (result != null && convert) { 247 result = convertToISBN13(result); 248 } 249 } 250 return result; 251 } 252 253 /** 254 * Check the code is a valid ISBN-10 code. 255 * <p> 256 * If valid, this method returns the ISBN-10 code with 257 * formatting characters removed (such as space or hyphen). 258 * </p> 259 * 260 * @param code The code to validate. 261 * @return A valid ISBN-10 code if valid, 262 * otherwise {@code null}. 263 */ 264 public String validateISBN10(final String code) { 265 return Objects.toString(isbn10Validator.validate(code), null); 266 } 267 268 /** 269 * Check the code is a valid ISBN-13 code. 270 * <p> 271 * If valid, this method returns the ISBN-13 code with 272 * formatting characters removed (such as space or hyphen). 273 * </p> 274 * 275 * @param code The code to validate. 276 * @return A valid ISBN-13 code if valid, 277 * otherwise {@code null}. 278 */ 279 public String validateISBN13(final String code) { 280 return Objects.toString(isbn13Validator.validate(code), null); 281 } 282 283}