/* * @copyright 2012 Philip Warner * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Book Catalogue is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue.utils; import java.util.Arrays; public class IsbnUtils { private static class IsbnInfo { public int[] digits; public int size; public boolean foundX; public boolean isValid; public IsbnInfo(String isbn) { foundX = false; digits = new int[13]; size = 0; for(int i = 0; i < isbn.length(); i++) { final Character c = isbn.charAt(i); int val; if (Character.isDigit(c)) { // X can only be at end of an ISBN10 if (foundX) { isValid = false; return; } val = Integer.parseInt(c.toString()); } else if (Character.toUpperCase(c) == 'X' && size == 9) { // X can only be at end of an ISBN10 if (foundX) { isValid = false; return; } val = 10; foundX = true; } else { // Invalid character isValid = false; return; } // Check if too long if (size >= 13) { isValid = false; return; } digits[size] = val; size++; } if (size == 10) { isValid = isValidIsbn10(digits); } else if (size == 13) { isValid = isValidIsbn13(digits); } else { isValid = false; } } public boolean equals(IsbnInfo cmp) { // If either is an invalid ISBN, require they match exactly if (!this.isValid || !cmp.isValid) { if (this.size != cmp.size) return false; return digitsMatch(this.size, this.digits, 0, cmp.digits, 0); } // We know the lengths are either 10 or 13 when we get here. So ... compare if (this.size == 10) { if (cmp.size == 10) { return digitsMatch(9, this.digits, 0, cmp.digits, 0); } else { return digitsMatch(9, this.digits, 0, cmp.digits, 3); } } else { if (cmp.size == 13) { return digitsMatch(13, this.digits, 0, cmp.digits, 0); } else { return digitsMatch(9, this.digits, 3, cmp.digits, 0); } } } private static boolean digitsMatch(final int len, final int[] dig1, int pos1, final int[] dig2, int pos2) { for(int i = 0; i < len; i++) { if (dig1[pos1++] != dig2[pos2++]) return false; } return true; } public String getIsbn10() { StringBuilder sb = new StringBuilder(); int[] vals; if (size == 10) { vals = digits; } else { int p = 0; vals = new int[10]; for(int i = 3; i < 12; i++) { vals[p++] = digits[i]; } vals[9] = 0; vals[9] = (11 - getIsbn10Check(vals)) % 11; } for(int i = 0; i < 10; i++) { final int d = vals[i]; if (d == 10) { sb.append('X'); } else { sb.append(d); } } return sb.toString(); } public String getIsbn13() { StringBuilder sb = new StringBuilder(); int[] vals; if (size == 13) { vals = digits; } else { vals = new int[13]; vals[0] = 9; vals[1] = 7; vals[2] = 8; int p = 3; for(int i = 0; i < 9; i++) { vals[p++] = digits[i]; } vals[12] = 0; vals[12] = (10 - getIsbn13Check(vals)) % 10; } for(int d: vals) { if (d == 10) { sb.append('X'); } else { sb.append(d); } } return sb.toString(); } } /** * Validate an ISBN * * @param isbn * @return */ public static boolean isValid(String isbn) { try { IsbnInfo info = new IsbnInfo(isbn); return info.isValid; } catch (Exception e) { return false; } } /** * Validate an ISBN10. * See http://en.wikipedia.org/wiki/International_Standard_Book_Number * * @param digits * @return */ private static int getIsbn10Check(int[] digits) { int mult = 10; int check = 0; for(int i = 0; i < 10; i++) { check += digits[i] * mult; mult--; } return (check % 11); } /** * Validate an ISBN10. * See http://en.wikipedia.org/wiki/International_Standard_Book_Number * * @param digits * @return */ private static boolean isValidIsbn10(int[] digits) { return (getIsbn10Check(digits) == 0); } /** * Validate an ISBN10. * See http://en.wikipedia.org/wiki/International_Standard_Book_Number * * @param digits * @return */ private static int getIsbn13Check(int[] digits) { int check = 0; for (int i = 0; i <= 12; i += 2) { check += digits[i]; } for (int i = 1; i < 12; i += 2) { check += digits[i] * 3; } return (check % 10); } /** * Validate an ISBN10. * See http://en.wikipedia.org/wiki/International_Standard_Book_Number * * @param digits * @return */ private static boolean isValidIsbn13(int[] digits) { // Start with 978 or 979 if (digits[0] != 9 || digits[1] != 7 || (digits[2] != 8 && digits[2] != 9)) return false; return (getIsbn13Check(digits) == 0); } public static boolean matches(String isbn1, String isbn2) { final int l1 = isbn1.length(); final int l2 = isbn2.length(); // Deal with the trivial case if (l1 == l2) return isbn1.equalsIgnoreCase(isbn2); // Different lengths; sanity check...if either is invalid, we consider them different IsbnInfo info1 = new IsbnInfo(isbn1); if (!info1.isValid) return false; IsbnInfo info2 = new IsbnInfo(isbn2); if (!info2.isValid) return false; return info1.equals(info2); } public static String isbn2isbn(String isbn) { IsbnInfo info = new IsbnInfo(isbn); if (!info.isValid) throw new RuntimeException("Unable to convert invalid ISBN"); if (isbn.length() == 10) { return info.getIsbn13(); } else { return info.getIsbn10(); } } }