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 * The {@code validate()} methods return the ISBN code with formatting 035 * characters removed if valid or {@code null} if invalid. 036 * <p> 037 * This validator also provides the facility to convert ISBN-10 codes to 038 * ISBN-13 if the {@code convert} property is {@code true}. 039 * <p> 040 * From 1st January 2007 the book industry will start to use a new 13 digit 041 * ISBN number (rather than this 10 digit ISBN number). ISBN-13 codes are 042 * <a href="https://en.wikipedia.org/wiki/European_Article_Number">EAN</a> 043 * codes, for more information see:</p> 044 * 045 * <ul> 046 * <li><a href="https://en.wikipedia.org/wiki/ISBN">Wikipedia - International 047 * Standard Book Number (ISBN)</a>.</li> 048 * <li>EAN - see 049 * <a href="https://en.wikipedia.org/wiki/European_Article_Number">Wikipedia - 050 * European Article Number</a>.</li> 051 * <li><a href="http://www.isbn.org/standards/home/isbn/transition.asp">ISBN-13 052 * Transition details</a>.</li> 053 * </ul> 054 * 055 * <p>ISBN-13s are either prefixed with 978 or 979. 978 prefixes are only assigned 056 * to the ISBN agency. 979 prefixes may be assigned to ISBNs or ISMNs 057 * (<a href="https://www.ismn-international.org/">International 058 * Standard Music Numbers</a>). 059 * <ul> 060 * <li>979-0 are assigned to the ISMN agency</li> 061 * <li>979-10, 979-11, 979-12 are assigned to the ISBN agency</li> 062 * </ul> 063 * All other 979 prefixed EAN-13 numbers have not yet been assigned to an agency. The 064 * validator validates all 13-digit codes with 978 or 979 prefixes. 065 * 066 * @since 1.4 067 */ 068public class ISBNValidator implements Serializable { 069 070 private static final int ISBN_10_LEN = 10; 071 072 private static final long serialVersionUID = 4319515687976420405L; 073 074 private static final String SEP = "(?:\\-|\\s)"; 075 private static final String GROUP = "(\\d{1,5})"; 076 private static final String PUBLISHER = "(\\d{1,7})"; 077 private static final String TITLE = "(\\d{1,6})"; 078 079 /** 080 * ISBN-10 consists of 4 groups of numbers separated by either dashes (-) 081 * or spaces. The first group is 1-5 characters, second 1-7, third 1-6, 082 * and fourth is 1 digit or an X. 083 */ 084 static final String ISBN10_REGEX = "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9X])))$"; 085 086 /** 087 * ISBN-13 consists of 5 groups of numbers separated by either dashes (-) 088 * or spaces. The first group is 978 or 979, the second group is 089 * 1-5 characters, third 1-7, fourth 1-6, and fifth is 1 digit. 090 */ 091 static final String ISBN13_REGEX = "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9])))$"; 092 093 /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */ 094 private static final ISBNValidator ISBN_VALIDATOR = new ISBNValidator(); 095 096 /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */ 097 private static final ISBNValidator ISBN_VALIDATOR_NO_CONVERT = new ISBNValidator(false); 098 099 /** 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}