001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.validator.routines.checkdigit;
018    
019    import java.io.Serializable;
020    
021    /**
022     * <b>Verhoeff</b> (Dihedral) Check Digit calculation/validation.
023     * <p>
024     * Check digit calculation for numeric codes using a
025     * <a href="http://en.wikipedia.org/wiki/Dihedral_group">Dihedral Group</a>
026     * of order 10.
027     * <p>
028     * See <a href="http://en.wikipedia.org/wiki/Verhoeff_algorithm">Wikipedia
029     *  - Verhoeff algorithm</a> for more details.
030     *
031     * @version $Revision: 1227719 $ $Date: 2012-01-05 18:45:51 +0100 (Thu, 05 Jan 2012) $
032     * @since Validator 1.4
033     */
034    public final class VerhoeffCheckDigit implements CheckDigit, Serializable {
035    
036        private static final long serialVersionUID = 4138993995483695178L;
037    
038        /** Singleton Verhoeff Check Digit instance */
039        public static final CheckDigit VERHOEFF_CHECK_DIGIT = new VerhoeffCheckDigit();
040    
041        /** D - multiplication table */
042        private static final int[][] D_TABLE = new int[][] {
043            {0,  1,  2,  3,  4,  5,  6,  7,  8,  9},
044            {1,  2,  3,  4,  0,  6,  7,  8,  9,  5},
045            {2,  3,  4,  0,  1,  7,  8,  9,  5,  6},
046            {3,  4,  0,  1,  2,  8,  9,  5,  6,  7},
047            {4,  0,  1,  2,  3,  9,  5,  6,  7,  8},
048            {5,  9,  8,  7,  6,  0,  4,  3,  2,  1},
049            {6,  5,  9,  8,  7,  1,  0,  4,  3,  2},
050            {7,  6,  5,  9,  8,  2,  1,  0,  4,  3},
051            {8,  7,  6,  5,  9,  3,  2,  1,  0,  4},
052            {9,  8,  7,  6,  5,  4,  3,  2,  1,  0}};
053    
054        /** P - permutation table */
055        private static final int[][] P_TABLE = new int[][] {
056            {0,  1,  2,  3,  4,  5,  6,  7,  8,  9},
057            {1,  5,  7,  6,  2,  8,  3,  0,  9,  4},
058            {5,  8,  0,  3,  7,  9,  6,  1,  4,  2},
059            {8,  9,  1,  6,  0,  4,  3,  5,  2,  7},
060            {9,  4,  5,  3,  1,  2,  6,  8,  7,  0},
061            {4,  2,  8,  6,  5,  7,  3,  9,  0,  1},
062            {2,  7,  9,  3,  8,  0,  6,  4,  1,  5},
063            {7,  0,  4,  6,  9,  1,  3,  2,  5,  8}};
064    
065        /** inv: inverse table */
066        private static final int[] INV_TABLE = new int[]
067            {0,  4,  3,  2,  1,  5,  6,  7,  8,  9};
068    
069    
070        /**
071         * Validate the Verhoeff <i>Check Digit</i> for a code.
072         *
073         * @param code The code to validate
074         * @return <code>true</code> if the check digit is valid,
075         * otherwise <code>false</code>
076         */
077        public boolean isValid(String code) {
078            if (code == null || code.length() == 0) {
079                return false;
080            }
081            try {
082                return (calculateChecksum(code, true) == 0);
083            } catch (CheckDigitException e) {
084                return false;
085            }
086        }
087    
088        /**
089         * Calculate a Verhoeff <i>Check Digit</i> for a code.
090         *
091         * @param code The code to calculate the Check Digit for
092         * @return The calculated Check Digit
093         * @throws CheckDigitException if an error occurs calculating
094         * the check digit for the specified code
095         */
096        public String calculate(String code) throws CheckDigitException {
097            if (code == null || code.length() == 0) {
098                throw new CheckDigitException("Code is missing");
099            }
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    }