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