ISBNValidator.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.validator.routines;

  18. import java.io.Serializable;

  19. import org.apache.commons.validator.routines.checkdigit.CheckDigitException;
  20. import org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit;
  21. import org.apache.commons.validator.routines.checkdigit.ISBN10CheckDigit;

  22. /**
  23.  * <b>ISBN-10</b> and <b>ISBN-13</b> Code Validation.
  24.  * <p>
  25.  * This validator validates the code is either a valid ISBN-10
  26.  * (using a {@link CodeValidator} with the {@link ISBN10CheckDigit})
  27.  * or a valid ISBN-13 code (using a {@link CodeValidator} with the
  28.  * the {@link EAN13CheckDigit} routine).
  29.  * <p>
  30.  * The <code>validate()</code> methods return the ISBN code with formatting
  31.  * characters removed if valid or {@code null} if invalid.
  32.  * <p>
  33.  * This validator also provides the facility to convert ISBN-10 codes to
  34.  * ISBN-13 if the <code>convert</code> property is {@code true}.
  35.  * <p>
  36.  * From 1st January 2007 the book industry will start to use a new 13 digit
  37.  * ISBN number (rather than this 10 digit ISBN number). ISBN-13 codes are
  38.  * <a href="https://en.wikipedia.org/wiki/European_Article_Number">EAN</a>
  39.  * codes, for more information see:</p>
  40.  *
  41.  * <ul>
  42.  *   <li><a href="https://en.wikipedia.org/wiki/ISBN">Wikipedia - International
  43.  *       Standard Book Number (ISBN)</a>.</li>
  44.  *   <li>EAN - see
  45.  *       <a href="https://en.wikipedia.org/wiki/European_Article_Number">Wikipedia -
  46.  *       European Article Number</a>.</li>
  47.  *   <li><a href="http://www.isbn.org/standards/home/isbn/transition.asp">ISBN-13
  48.  *       Transition details</a>.</li>
  49.  * </ul>
  50.  *
  51.  * <p>ISBN-13s are either prefixed with 978 or 979. 978 prefixes are only assigned
  52.  * to the ISBN agency. 979 prefixes may be assigned to ISBNs or ISMNs
  53.  * (<a href="https://www.ismn-international.org/">International
  54.  * Standard Music Numbers</a>).
  55.  * <ul>
  56.  *     <li>979-0 are assigned to the ISMN agency</li>
  57.  *     <li>979-10, 979-11, 979-12 are assigned to the ISBN agency</li>
  58.  * </ul>
  59.  * All other 979 prefixed EAN-13 numbers have not yet been assigned to an agency. The
  60.  * validator validates all 13 digit codes with 978 or 979 prefixes.
  61.  *
  62.  * @since 1.4
  63.  */
  64. public class ISBNValidator implements Serializable {

  65.     private static final int ISBN_10_LEN = 10;

  66.     private static final long serialVersionUID = 4319515687976420405L;

  67.     private static final String SEP = "(?:\\-|\\s)";
  68.     private static final String GROUP = "(\\d{1,5})";
  69.     private static final String PUBLISHER = "(\\d{1,7})";
  70.     private static final String TITLE = "(\\d{1,6})";

  71.     /**
  72.      * ISBN-10 consists of 4 groups of numbers separated by either dashes (-)
  73.      * or spaces.  The first group is 1-5 characters, second 1-7, third 1-6,
  74.      * and fourth is 1 digit or an X.
  75.      */
  76.     static final String ISBN10_REGEX = "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9X])))$";

  77.     /**
  78.      * ISBN-13 consists of 5 groups of numbers separated by either dashes (-)
  79.      * or spaces.  The first group is 978 or 979, the second group is
  80.      * 1-5 characters, third 1-7, fourth 1-6, and fifth is 1 digit.
  81.      */
  82.     static final String ISBN13_REGEX = "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9])))$";

  83.     /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
  84.     private static final ISBNValidator ISBN_VALIDATOR = new ISBNValidator();

  85.     /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
  86.     private static final ISBNValidator ISBN_VALIDATOR_NO_CONVERT = new ISBNValidator(false);

  87.     /**
  88.      * Gets the singleton instance of the ISBN validator which
  89.      * converts ISBN-10 codes to ISBN-13.
  90.      *
  91.      * @return A singleton instance of the ISBN validator.
  92.      */
  93.     public static ISBNValidator getInstance() {
  94.         return ISBN_VALIDATOR;
  95.     }

  96.     /**
  97.      * Gets the singleton instance of the ISBN validator specifying
  98.      * whether ISBN-10 codes should be converted to ISBN-13.
  99.      *
  100.      * @param convert {@code true} if valid ISBN-10 codes
  101.      * should be converted to ISBN-13 codes or {@code false}
  102.      * if valid ISBN-10 codes should be returned unchanged.
  103.      * @return A singleton instance of the ISBN validator.
  104.      */
  105.     public static ISBNValidator getInstance(final boolean convert) {
  106.         return convert ? ISBN_VALIDATOR : ISBN_VALIDATOR_NO_CONVERT;
  107.     }

  108.     /** ISBN-10 Code Validator */
  109.     private final CodeValidator isbn10Validator = new CodeValidator(ISBN10_REGEX, 10, ISBN10CheckDigit.ISBN10_CHECK_DIGIT);

  110.     /** ISBN-13 Code Validator */
  111.     private final CodeValidator isbn13Validator = new CodeValidator(ISBN13_REGEX, 13, EAN13CheckDigit.EAN13_CHECK_DIGIT);

  112.     private final boolean convert;

  113.     /**
  114.      * Constructs an ISBN validator which converts ISBN-10 codes
  115.      * to ISBN-13.
  116.      */
  117.     public ISBNValidator() {
  118.         this(true);
  119.     }

  120.     /**
  121.      * Constructs an ISBN validator indicating whether
  122.      * ISBN-10 codes should be converted to ISBN-13.
  123.      *
  124.      * @param convert {@code true} if valid ISBN-10 codes
  125.      * should be converted to ISBN-13 codes or {@code false}
  126.      * if valid ISBN-10 codes should be returned unchanged.
  127.      */
  128.     public ISBNValidator(final boolean convert) {
  129.         this.convert = convert;
  130.     }

  131.     /**
  132.      * Convert an ISBN-10 code to an ISBN-13 code.
  133.      * <p>
  134.      * This method requires a valid ISBN-10 with NO formatting
  135.      * characters.
  136.      *
  137.      * @param isbn10 The ISBN-10 code to convert
  138.      * @return A converted ISBN-13 code or {@code null}
  139.      * if the ISBN-10 code is not valid
  140.      */
  141.     public String convertToISBN13(final String isbn10) {

  142.         if (isbn10 == null) {
  143.             return null;
  144.         }

  145.         final String input = isbn10.trim();
  146.         if (input.length() != ISBN_10_LEN) {
  147.             throw new IllegalArgumentException("Invalid length " + input.length() + " for '" + input + "'");
  148.         }

  149.         // Calculate the new ISBN-13 code (drop the original checkdigit)
  150.         String isbn13 = "978" + input.substring(0, ISBN_10_LEN - 1);
  151.         try {
  152.             final String checkDigit = isbn13Validator.getCheckDigit().calculate(isbn13);
  153.             isbn13 += checkDigit;
  154.             return isbn13;
  155.         } catch (final CheckDigitException e) {
  156.             throw new IllegalArgumentException("Check digit error for '" + input + "' - " + e.getMessage());
  157.         }

  158.     }

  159.     /**
  160.      * Check the code is either a valid ISBN-10 or ISBN-13 code.
  161.      *
  162.      * @param code The code to validate.
  163.      * @return {@code true} if a valid ISBN-10 or
  164.      * ISBN-13 code, otherwise {@code false}.
  165.      */
  166.     public boolean isValid(final String code) {
  167.         return isValidISBN13(code) || isValidISBN10(code);
  168.     }

  169.     /**
  170.      * Check the code is a valid ISBN-10 code.
  171.      *
  172.      * @param code The code to validate.
  173.      * @return {@code true} if a valid ISBN-10
  174.      * code, otherwise {@code false}.
  175.      */
  176.     public boolean isValidISBN10(final String code) {
  177.         return isbn10Validator.isValid(code);
  178.     }

  179.     /**
  180.      * Check the code is a valid ISBN-13 code.
  181.      *
  182.      * @param code The code to validate.
  183.      * @return {@code true} if a valid ISBN-13
  184.      * code, otherwise {@code false}.
  185.      */
  186.     public boolean isValidISBN13(final String code) {
  187.         return isbn13Validator.isValid(code);
  188.     }

  189.     /**
  190.      * Check the code is either a valid ISBN-10 or ISBN-13 code.
  191.      * <p>
  192.      * If valid, this method returns the ISBN code with
  193.      * formatting characters removed (i.e. space or hyphen).
  194.      * <p>
  195.      * Converts an ISBN-10 codes to ISBN-13 if
  196.      * <code>convertToISBN13</code> is {@code true}.
  197.      *
  198.      * @param code The code to validate.
  199.      * @return A valid ISBN code if valid, otherwise {@code null}.
  200.      */
  201.     public String validate(final String code) {
  202.         String result = validateISBN13(code);
  203.         if (result == null) {
  204.             result = validateISBN10(code);
  205.             if (result != null && convert) {
  206.                 result = convertToISBN13(result);
  207.             }
  208.         }
  209.         return result;
  210.     }

  211.     /**
  212.      * Check the code is a valid ISBN-10 code.
  213.      * <p>
  214.      * If valid, this method returns the ISBN-10 code with
  215.      * formatting characters removed (i.e. space or hyphen).
  216.      *
  217.      * @param code The code to validate.
  218.      * @return A valid ISBN-10 code if valid,
  219.      * otherwise {@code null}.
  220.      */
  221.     public String validateISBN10(final String code) {
  222.         final Object result = isbn10Validator.validate(code);
  223.         return result == null ? null : result.toString();
  224.     }

  225.     /**
  226.      * Check the code is a valid ISBN-13 code.
  227.      * <p>
  228.      * If valid, this method returns the ISBN-13 code with
  229.      * formatting characters removed (i.e. space or hyphen).
  230.      *
  231.      * @param code The code to validate.
  232.      * @return A valid ISBN-13 code if valid,
  233.      * otherwise {@code null}.
  234.      */
  235.     public String validateISBN13(final String code) {
  236.         final Object result = isbn13Validator.validate(code);
  237.         return result == null ? null : result.toString();
  238.     }

  239. }