View Javadoc
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  
19  import java.io.Serializable;
20  import org.apache.commons.validator.routines.checkdigit.EAN13CheckDigit;
21  import org.apache.commons.validator.routines.checkdigit.ISBN10CheckDigit;
22  import org.apache.commons.validator.routines.checkdigit.CheckDigitException;
23  
24  /**
25   * <b>ISBN-10</b> and <b>ISBN-13</b> Code Validation.
26   * <p>
27   * This validator validates the code is either a valid ISBN-10
28   * (using a {@link CodeValidator} with the {@link ISBN10CheckDigit})
29   * or a valid ISBN-13 code (using a {@link CodeValidator} with the
30   * the {@link EAN13CheckDigit} routine).
31   * <p>
32   * The <code>validate()</code> methods return the ISBN code with formatting
33   * characters removed if valid or <code>null</code> if invalid.
34   * <p>
35   * This validator also provides the facility to convert ISBN-10 codes to
36   * ISBN-13 if the <code>convert</code> property is <code>true</code>.
37   * <p>
38   * From 1st January 2007 the book industry will start to use a new 13 digit
39   * ISBN number (rather than this 10 digit ISBN number). ISBN-13 codes are
40   * <a href="http://en.wikipedia.org/wiki/European_Article_Number">EAN</a>
41   * codes, for more information see:</p>
42   *
43   * <ul>
44   *   <li><a href="http://en.wikipedia.org/wiki/ISBN">Wikipedia - International
45   *       Standard Book Number (ISBN)</a>.</li>
46   *   <li>EAN - see
47   *       <a href="http://en.wikipedia.org/wiki/European_Article_Number">Wikipedia -
48   *       European Article Number</a>.</li>
49   *   <li><a href="http://www.isbn.org/standards/home/isbn/transition.asp">ISBN-13
50   *       Transition details</a>.</li>
51   * </ul>
52   *
53   * @version $Revision: 1715435 $
54   * @since Validator 1.4
55   */
56  public class ISBNValidator implements Serializable {
57  
58      private static final int ISBN_10_LEN = 10;
59  
60      private static final long serialVersionUID = 4319515687976420405L;
61  
62      private static final String SEP = "(?:\\-|\\s)";
63      private static final String GROUP = "(\\d{1,5})";
64      private static final String PUBLISHER = "(\\d{1,7})";
65      private static final String TITLE = "(\\d{1,6})";
66  
67      /**
68       * ISBN-10 consists of 4 groups of numbers separated by either dashes (-)
69       * or spaces.  The first group is 1-5 characters, second 1-7, third 1-6,
70       * and fourth is 1 digit or an X.
71       */
72      static final String ISBN10_REGEX     =
73                    "^(?:(\\d{9}[0-9X])|(?:" + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9X])))$";
74  
75      /**
76       * ISBN-13 consists of 5 groups of numbers separated by either dashes (-)
77       * or spaces.  The first group is 978 or 979, the second group is
78       * 1-5 characters, third 1-7, fourth 1-6, and fifth is 1 digit.
79       */
80      static final String ISBN13_REGEX     =
81          "^(978|979)(?:(\\d{10})|(?:" + SEP + GROUP + SEP + PUBLISHER + SEP + TITLE + SEP + "([0-9])))$";
82  
83      /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
84      private static final ISBNValidator ISBN_VALIDATOR = new ISBNValidator();
85  
86      /** ISBN Code Validator (which converts ISBN-10 codes to ISBN-13 */
87      private static final ISBNValidator ISBN_VALIDATOR_NO_CONVERT = new ISBNValidator(false);
88  
89  
90      /** ISBN-10 Code Validator */
91      private final CodeValidator isbn10Validator = new CodeValidator(ISBN10_REGEX, 10, ISBN10CheckDigit.ISBN10_CHECK_DIGIT);
92  
93      /** ISBN-13 Code Validator */
94      private final CodeValidator isbn13Validator = new CodeValidator(ISBN13_REGEX, 13, EAN13CheckDigit.EAN13_CHECK_DIGIT);
95  
96      private final boolean convert;
97  
98      /**
99       * Return a singleton instance of the ISBN validator which
100      * converts ISBN-10 codes to ISBN-13.
101      *
102      * @return A singleton instance of the ISBN validator.
103      */
104     public static ISBNValidator getInstance() {
105         return ISBN_VALIDATOR;
106     }
107 
108     /**
109      * Return a singleton instance of the ISBN validator specifying
110      * whether ISBN-10 codes should be converted to ISBN-13.
111      *
112      * @param convert <code>true</code> if valid ISBN-10 codes
113      * should be converted to ISBN-13 codes or <code>false</code>
114      * if valid ISBN-10 codes should be returned unchanged.
115      * @return A singleton instance of the ISBN validator.
116      */
117     public static ISBNValidator getInstance(boolean convert) {
118         return (convert ? ISBN_VALIDATOR : ISBN_VALIDATOR_NO_CONVERT);
119     }
120 
121     /**
122      * Construct an ISBN validator which converts ISBN-10 codes
123      * to ISBN-13.
124      */
125     public ISBNValidator() {
126         this(true);
127     }
128 
129     /**
130      * Construct an ISBN validator indicating whether
131      * ISBN-10 codes should be converted to ISBN-13.
132      *
133      * @param convert <code>true</code> if valid ISBN-10 codes
134      * should be converted to ISBN-13 codes or <code>false</code>
135      * if valid ISBN-10 codes should be returned unchanged.
136      */
137     public ISBNValidator(boolean convert) {
138         this.convert = convert;
139     }
140 
141     /**
142      * Check the code is either a valid ISBN-10 or ISBN-13 code.
143      *
144      * @param code The code to validate.
145      * @return <code>true</code> if a valid ISBN-10 or
146      * ISBN-13 code, otherwise <code>false</code>.
147      */
148     public boolean isValid(String code) {
149         return (isValidISBN13(code) || isValidISBN10(code));
150     }
151 
152     /**
153      * Check the code is a valid ISBN-10 code.
154      *
155      * @param code The code to validate.
156      * @return <code>true</code> if a valid ISBN-10
157      * code, otherwise <code>false</code>.
158      */
159     public boolean isValidISBN10(String code) {
160         return isbn10Validator.isValid(code);
161     }
162 
163     /**
164      * Check the code is a valid ISBN-13 code.
165      *
166      * @param code The code to validate.
167      * @return <code>true</code> if a valid ISBN-13
168      * code, otherwise <code>false</code>.
169      */
170     public boolean isValidISBN13(String code) {
171         return isbn13Validator.isValid(code);
172     }
173 
174     /**
175      * Check the code is either a valid ISBN-10 or ISBN-13 code.
176      * <p>
177      * If valid, this method returns the ISBN code with
178      * formatting characters removed (i.e. space or hyphen).
179      * <p>
180      * Converts an ISBN-10 codes to ISBN-13 if
181      * <code>convertToISBN13</code> is <code>true</code>.
182      *
183      * @param code The code to validate.
184      * @return A valid ISBN code if valid, otherwise <code>null</code>.
185      */
186     public String validate(String code) {
187         String result = validateISBN13(code);
188         if (result == null) {
189             result = validateISBN10(code);
190             if (result != null && convert) {
191                 result = convertToISBN13(result);
192             }
193         }
194         return result;
195     }
196 
197     /**
198      * Check the code is a valid ISBN-10 code.
199      * <p>
200      * If valid, this method returns the ISBN-10 code with
201      * formatting characters removed (i.e. space or hyphen).
202      *
203      * @param code The code to validate.
204      * @return A valid ISBN-10 code if valid,
205      * otherwise <code>null</code>.
206      */
207     public String validateISBN10(String code) {
208         Object result = isbn10Validator.validate(code);
209         return (result == null ? null : result.toString());
210     }
211 
212     /**
213      * Check the code is a valid ISBN-13 code.
214      * <p>
215      * If valid, this method returns the ISBN-13 code with
216      * formatting characters removed (i.e. space or hyphen).
217      *
218      * @param code The code to validate.
219      * @return A valid ISBN-13 code if valid,
220      * otherwise <code>null</code>.
221      */
222     public String validateISBN13(String code) {
223         Object result = isbn13Validator.validate(code);
224         return (result == null ? null : result.toString());
225     }
226 
227     /**
228      * Convert an ISBN-10 code to an ISBN-13 code.
229      * <p>
230      * This method requires a valid ISBN-10 with NO formatting
231      * characters.
232      *
233      * @param isbn10 The ISBN-10 code to convert
234      * @return A converted ISBN-13 code or <code>null</code>
235      * if the ISBN-10 code is not valid
236      */
237     public String convertToISBN13(String isbn10) {
238 
239         if (isbn10 == null) {
240             return null;
241         }
242 
243         String input = isbn10.trim();
244         if (input.length() != ISBN_10_LEN) {
245             throw new IllegalArgumentException("Invalid length " + input.length() + " for '" + input + "'");
246         }
247 
248         // Calculate the new ISBN-13 code (drop the original checkdigit)
249         String isbn13 = "978" + input.substring(0, ISBN_10_LEN - 1);
250         try {
251             String checkDigit = isbn13Validator.getCheckDigit().calculate(isbn13);
252             isbn13 += checkDigit;
253             return isbn13;
254         } catch (CheckDigitException e) {
255             throw new IllegalArgumentException("Check digit error for '" + input + "' - " + e.getMessage());
256         }
257 
258     }
259 
260 }