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.checkdigit;
18  
19  import java.io.Serializable;
20  
21  /**
22   * <b>Verhoeff</b> (Dihedral) Check Digit calculation/validation.
23   * <p>
24   * Check digit calculation for numeric codes using a
25   * <a href="http://en.wikipedia.org/wiki/Dihedral_group">Dihedral Group</a>
26   * of order 10.
27   * <p>
28   * See <a href="http://en.wikipedia.org/wiki/Verhoeff_algorithm">Wikipedia
29   *  - Verhoeff algorithm</a> for more details.
30   *
31   * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Do, 05 Jan 2012) $
32   * @since Validator 1.4
33   */
34  public final class VerhoeffCheckDigit implements CheckDigit, Serializable {
35  
36      private static final long serialVersionUID = 4138993995483695178L;
37  
38      /** Singleton Verhoeff Check Digit instance */
39      public static final CheckDigit VERHOEFF_CHECK_DIGIT = new VerhoeffCheckDigit();
40  
41      /** D - multiplication table */
42      private static final int[][] D_TABLE = new int[][] {
43          {0,  1,  2,  3,  4,  5,  6,  7,  8,  9},
44          {1,  2,  3,  4,  0,  6,  7,  8,  9,  5},
45          {2,  3,  4,  0,  1,  7,  8,  9,  5,  6},
46          {3,  4,  0,  1,  2,  8,  9,  5,  6,  7},
47          {4,  0,  1,  2,  3,  9,  5,  6,  7,  8},
48          {5,  9,  8,  7,  6,  0,  4,  3,  2,  1},
49          {6,  5,  9,  8,  7,  1,  0,  4,  3,  2},
50          {7,  6,  5,  9,  8,  2,  1,  0,  4,  3},
51          {8,  7,  6,  5,  9,  3,  2,  1,  0,  4},
52          {9,  8,  7,  6,  5,  4,  3,  2,  1,  0}};
53  
54      /** P - permutation table */
55      private static final int[][] P_TABLE = new int[][] {
56          {0,  1,  2,  3,  4,  5,  6,  7,  8,  9},
57          {1,  5,  7,  6,  2,  8,  3,  0,  9,  4},
58          {5,  8,  0,  3,  7,  9,  6,  1,  4,  2},
59          {8,  9,  1,  6,  0,  4,  3,  5,  2,  7},
60          {9,  4,  5,  3,  1,  2,  6,  8,  7,  0},
61          {4,  2,  8,  6,  5,  7,  3,  9,  0,  1},
62          {2,  7,  9,  3,  8,  0,  6,  4,  1,  5},
63          {7,  0,  4,  6,  9,  1,  3,  2,  5,  8}};
64  
65      /** inv: inverse table */
66      private static final int[] INV_TABLE = new int[]
67          {0,  4,  3,  2,  1,  5,  6,  7,  8,  9};
68  
69  
70      /**
71       * Validate the Verhoeff <i>Check Digit</i> for a code.
72       *
73       * @param code The code to validate
74       * @return <code>true</code> if the check digit is valid,
75       * otherwise <code>false</code>
76       */
77      public boolean isValid(String code) {
78          if (code == null || code.length() == 0) {
79              return false;
80          }
81          try {
82              return (calculateChecksum(code, true) == 0);
83          } catch (CheckDigitException e) {
84              return false;
85          }
86      }
87  
88      /**
89       * Calculate a Verhoeff <i>Check Digit</i> for a code.
90       *
91       * @param code The code to calculate the Check Digit for
92       * @return The calculated Check Digit
93       * @throws CheckDigitException if an error occurs calculating
94       * the check digit for the specified code
95       */
96      public String calculate(String code) throws CheckDigitException {
97          if (code == null || code.length() == 0) {
98              throw new CheckDigitException("Code is missing");
99          }
100         int checksum = calculateChecksum(code, false);
101         return Integer.toString(INV_TABLE[checksum]);
102     }
103 
104     /**
105      * Calculate the checksum.
106      *
107      * @param code The code to calculate the checksum for.
108      * @param includesCheckDigit Whether the code includes the Check Digit or not.
109      * @return The checksum value
110      * @throws CheckDigitException if the code contains an invalid character (i.e. not numeric)
111      */
112     private int calculateChecksum(String code, boolean includesCheckDigit) throws CheckDigitException {
113         int checksum = 0;
114         for (int i = 0; i < code.length(); i++) {
115             int idx = code.length() - (i + 1);
116             int num = Character.getNumericValue(code.charAt(idx));
117             if (num < 0 || num > 9) {
118                 throw new CheckDigitException("Invalid Character[" +
119                         i + "] = '" + ((int)code.charAt(idx)) + "'");
120             }
121             int pos = includesCheckDigit ? i : i + 1;
122             checksum = D_TABLE[checksum][P_TABLE[pos % 8][num]];
123         }
124         return checksum;
125     }
126 
127 }