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: 493905 $ $Date: 2007-01-08 03:11:38 +0100 (Mo, 08. Jan 2007) $
032     * @since Validator 1.4
033     */
034    public final class VerhoeffCheckDigit implements CheckDigit, Serializable {
035    
036        /** Singleton Verhoeff Check Digit instance */
037        public static final CheckDigit INSTANCE = new VerhoeffCheckDigit();
038    
039        /** D - multiplication table */
040        private static final int[][] D_TABLE = new int[][] {
041            {0,  1,  2,  3,  4,  5,  6,  7,  8,  9}, 
042            {1,  2,  3,  4,  0,  6,  7,  8,  9,  5},
043            {2,  3,  4,  0,  1,  7,  8,  9,  5,  6},
044            {3,  4,  0,  1,  2,  8,  9,  5,  6,  7},
045            {4,  0,  1,  2,  3,  9,  5,  6,  7,  8},
046            {5,  9,  8,  7,  6,  0,  4,  3,  2,  1},
047            {6,  5,  9,  8,  7,  1,  0,  4,  3,  2},
048            {7,  6,  5,  9,  8,  2,  1,  0,  4,  3},
049            {8,  7,  6,  5,  9,  3,  2,  1,  0,  4},
050            {9,  8,  7,  6,  5,  4,  3,  2,  1,  0}};
051    
052        /** P - permutation table */
053        private static final int[][] P_TABLE = new int[][] {
054            {0,  1,  2,  3,  4,  5,  6,  7,  8,  9},
055            {1,  5,  7,  6,  2,  8,  3,  0,  9,  4},
056            {5,  8,  0,  3,  7,  9,  6,  1,  4,  2},
057            {8,  9,  1,  6,  0,  4,  3,  5,  2,  7},
058            {9,  4,  5,  3,  1,  2,  6,  8,  7,  0},
059            {4,  2,  8,  6,  5,  7,  3,  9,  0,  1},
060            {2,  7,  9,  3,  8,  0,  6,  4,  1,  5},
061            {7,  0,  4,  6,  9,  1,  3,  2,  5,  8}};
062    
063        /** inv: inverse table */
064        private static final int[] INV_TABLE = new int[]
065            {0,  4,  3,  2,  1,  5,  6,  7,  8,  9};
066    
067    
068        /**
069         * Validate the Verhoeff <i>Check Digit</i> for a code.
070         *
071         * @param code The code to validate
072         * @return <code>true</code> if the check digit is valid,
073         * otherwise <code>false</code>
074         */
075        public boolean isValid(String code) {
076            if (code == null || code.length() == 0) {
077                return false;
078            }
079            try {
080                return (calculateChecksum(code, true) == 0);
081            } catch (CheckDigitException e) {
082                return false;
083            }
084        }
085    
086        /**
087         * Calculate a Verhoeff <i>Check Digit</i> for a code.
088         *
089         * @param code The code to calculate the Check Digit for
090         * @return The calculated Check Digit
091         * @throws CheckDigitException if an error occurs calculating
092         * the check digit for the specified code
093         */
094        public String calculate(String code) throws CheckDigitException {
095            if (code == null || code.length() == 0) {
096                throw new CheckDigitException("Code is missing");
097            }
098            int checksum = calculateChecksum(code, false);
099            return Integer.toString(INV_TABLE[checksum]);
100        }
101    
102        /**
103         * Calculate the checksum.
104         *
105         * @param code The code to calculate the checksum for.
106         * @param includesCheckDigit Whether the code includes the Check Digit or not.
107         * @return The checksum value
108         * @throws CheckDigitException if the code contains an invalid character (i.e. not numeric)
109         */
110        private int calculateChecksum(String code, boolean includesCheckDigit) throws CheckDigitException {
111            int checksum = 0;
112            for (int i = 0; i < code.length(); i++) {
113                int idx = code.length() - (i + 1); 
114                int num = Character.getNumericValue(code.charAt(idx));
115                if (num < 0 || num > 9) {
116                    throw new CheckDigitException("Invalid Character[" + 
117                            i + "] = '" + ((int)code.charAt(idx)) + "'");
118                }
119                int pos = includesCheckDigit ? i : i + 1;
120                checksum = D_TABLE[checksum][P_TABLE[pos % 8][num]];
121            }
122            return checksum;
123        }
124    
125    }