Skip to content

Instantly share code, notes, and snippets.

@kymmt90 kymmt90/Isbn.java
Created Jan 31, 2015

Embed
What would you like to do?
package isbn;
import java.util.regex.Pattern;
/**
* ISBN class.
*/
public class Isbn {
/** Number of digits in ISBN. */
public static final int LENGTH = 13;
/** Number of digits in old ISBN. */
public static final int OLD_LENGTH = 10;
/**
* Check whether the number sequence is valid as ISBN.
* Check method is: http://en.wikipedia.org/wiki/International_Standard_Book_Number#Check_digits
* @param numberSequence the number sequence which you want to check. This sequence is allowed to include hyphens
* @return true if the number sequence is valid as ISBN, otherwise false
*/
public static boolean isValid(String numberSequence) {
if (numberSequence == null) throw new NullPointerException();
if (!Pattern.matches("^\\d+(-?\\d+)*$", numberSequence)) return false;
String normalizedSequence = removeHyphen(numberSequence);
if (normalizedSequence.length() == 13) return isValidAsIsbn13(normalizedSequence);
else if (normalizedSequence.length() == 10) return isValidAsIsbn10(normalizedSequence);
else return false;
}
/**
* Check whether the 13-digits number is valid as 13-digits ISBN.
* @param number 13-digits number which you want to check. This must not include hyphens
* @return true if the 13-digits number is valid as ISBN, otherwise false
* @throws IllegalArgumentException number is not 13-digits
*/
static boolean isValidAsIsbn13(String number) {
if (number == null) throw new NullPointerException();
if (!Pattern.matches("^\\d{" + LENGTH + "}$", number)) throw new IllegalArgumentException();
char[] digits = number.toCharArray();
final int myDigit = computeIsbn13CheckDigit(digits);
int checkDigit = digits[LENGTH - 1] - '0';
return myDigit == 10 && checkDigit == 0 || myDigit == checkDigit;
}
/**
* Compute the check digits of 13-digits ISBN.
* Both full 13-digits and check-digit-less 12-digits are allowed as the argument.
* @param digits the array of each digit in ISBN.
* @return check digit
* @throws IllegalArgumentException the length of the argument array is neither 12 nor 13 or the element of digits is negative
*/
static int computeIsbn13CheckDigit(char[] digits) {
if (digits == null) throw new NullPointerException();
if (digits.length != LENGTH && digits.length != LENGTH - 1) throw new IllegalArgumentException();
for (char c : digits) {
if (c < '0' || '9' < c) throw new IllegalArgumentException();
}
int[] weights = {1, 3};
int sum = 0;
for (int i = 0; i < LENGTH - 1; ++i) {
sum += (digits[i] - '0') * weights[i % 2];
}
return 10 - sum % 10;
}
/**
* Check whether the 10-digits number is valid as 10-digits ISBN.
* @param number 10-digits number which you want to check. This must not include hyphens
* @return true if the 10-digits number is valid as ISBN, otherwise false
* @throws IllegalArgumentException number is not 10-digits
*/
static boolean isValidAsIsbn10(String number) {
if (number == null) throw new NullPointerException();
if (!Pattern.matches("^\\d{" + OLD_LENGTH + "}$", number)) throw new IllegalArgumentException();
char[] digits = number.toCharArray();
final int myDigit = computeIsbn10CheckDigit(digits);
if (myDigit == 10) return digits[9] == 'X';
final int checkDigit = digits[9] - '0';
return myDigit == 11 && checkDigit == 0 || myDigit == checkDigit;
}
/**
* Compute the check digits of 10-digits ISBN.
* Both full 10-digits and check-digit-less 9-digits are allowed as the argument.
* @param digits the array of each digit in ISBN.
* @return check digit
* @throws IllegalArgumentException the length of the argument array is neither 9 nor 10 / the element in digits is negative
*/
static int computeIsbn10CheckDigit(char[] digits) {
if (digits == null) throw new NullPointerException();
if (digits.length != OLD_LENGTH && digits.length != OLD_LENGTH - 1) throw new IllegalArgumentException();
for (char c : digits) {
if (c < '0' || '9' < c) throw new IllegalArgumentException();
}
int sum = 0;
for (int i = 0, weight = 10; i < 9; ++i, --weight) {
sum += (digits[i] - '0') * weight;
}
return 11 - sum % 11;
}
/**
* Convert 10-digits ISBN to 13-digits ISBN. Check digit is re-computed.
* @param isbn10 10-digits ISBN. It can include hyphens
* @return 13-digits ISBN
* @throws IllegalArgumentException the number of digits of the argument is not 10
*/
public static String toIsbn13(String isbn10) {
if (isbn10 == null) throw new NullPointerException();
String normalizedNumber = removeHyphen(isbn10);
if (normalizedNumber.length() != OLD_LENGTH) throw new IllegalArgumentException();
// Compute check digit
String isbn13 = "978" + normalizedNumber.substring(0, OLD_LENGTH - 1);
final int checkDigit = computeIsbn13CheckDigit(isbn13.toCharArray());
// Compose 13-digits ISBN from 10-digits ISBN
if (isbn10.contains("-")) {
return "978-" + isbn10.substring(0, isbn10.length() - 2) + "-" + String.valueOf(checkDigit);
} else {
return "978" + isbn10.substring(0, isbn10.length() - 1) + String.valueOf(checkDigit);
}
}
/**
* Remove hyphens in the argument string.
* @param s
* @return string where hyphens are removed
*/
static String removeHyphen(String s) {
if (s == null) throw new NullPointerException();
return s.replace("-", "");
}
/**
* Static factory.
* @param number ISBN which you want to instantiate.
* @return ISBN Object
* @throws IllegalArgumentException if the argument is invalid as ISBN
*/
public static Isbn of(String number) throws IllegalArgumentException {
if (number == null) throw new NullPointerException();
if (!isValid(number)) throw new IllegalArgumentException();
if (removeHyphen(number).length() == OLD_LENGTH) {
return new Isbn(toIsbn13(number));
} else {
return new Isbn(number);
}
}
private final String originalIsbn;
private final String normalizedIsbn; // hyphens are removed
private final String prefix;
private final String group;
private final String publisher;
private final String bookName;
private final String checkDigit;
private Isbn(String originalIsbn) {
assert null != originalIsbn;
this.originalIsbn = originalIsbn;
this.normalizedIsbn = removeHyphen(this.originalIsbn);
String[] numbers = this.originalIsbn.split("-");
if (numbers.length == 5) {
this.prefix = numbers[0];
this.group = numbers[1];
this.publisher = numbers[2];
this.bookName = numbers[3];
this.checkDigit = numbers[4];
} else {
this.prefix = "";
this.group = "";
this.publisher = "";
this.bookName = "";
this.checkDigit = "";
}
}
public String getIsbn() {
return originalIsbn;
}
public String getPrefix() {
return prefix;
}
public String getGroup() {
return group;
}
public String getPublisher() {
return publisher;
}
public String getBookName() {
return bookName;
}
public String getCheckDigit() {
return checkDigit;
}
/**
* @return original description of ISBN. It can include hyphens like 978-4-***-*****-*.
*/
@Override
public String toString() {
return originalIsbn;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Isbn))
return false;
Isbn other = (Isbn)obj;
return other.normalizedIsbn.equals(normalizedIsbn);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + normalizedIsbn.hashCode();
return result;
}
}
@dustindclark

This comment has been minimized.

Copy link

dustindclark commented Apr 6, 2019

Doesn't work. Generates a 14 character ISBN13s:

isbn13 == Isbn.toIsbn13(isbn10)
|      |       |        |
|      |       |        143470713X
|      |       97814347071310
|      false
|      1 difference (92% similarity)
|      978143470713(-)0
|      978143470713(1)0
9781434707130
@dustindclark

This comment has been minimized.

Copy link

dustindclark commented Apr 6, 2019

Bug is on line 65. If the checksum is 10, you should return 0. For example:

int modulo = sum % 10;
return modulo == 0 ? 0 : 10 - modulo;
@dustindclark

This comment has been minimized.

Copy link

dustindclark commented Apr 6, 2019

Same error is on line 103 for ISBN 10 check digits. Another way to account for this is just run modulo again. For example: return (11 - sum % 11) % 11;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.