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    /**
020     * Abstract <b>Modulus</b> Check digit calculation/validation.
021     * <p>
022     * Provides a <i>base</i> class for building <i>modulus</i> Check
023     * Digit routines.
024     * <p>
025     * This implementation only handles <i>numeric</i> codes, such as
026     * <b>EAN-13</b>. For <i>alphanumeric</i> codes such as <b>EAN-128</b> you
027     * will need to implement/override the <code>toInt()</code> and
028     * <code>toChar()</code> methods.
029     * <p>
030     *
031     * @version $Revision: 594917 $ $Date: 2007-11-14 16:36:40 +0100 (Mi, 14. Nov 2007) $
032     * @since Validator 1.4
033     */
034    public abstract class ModulusCheckDigit implements CheckDigit {
035    
036        private final int modulus;
037    
038        /**
039         * Construct a {@link CheckDigit} routine for a specified modulus.
040         *
041         * @param modulus The modulus value to use for the check digit calculation
042         */
043        public ModulusCheckDigit(int modulus) {
044            this.modulus = modulus;
045        }
046    
047        /**
048         * Return the modulus value this check digit routine is based on.
049         *
050         * @return The modulus value this check digit routine is based on
051         */
052        public int getModulus() {
053            return modulus;
054        }
055    
056        /**
057         * Validate a modulus check digit for a code.
058         *
059         * @param code The code to validate
060         * @return <code>true</code> if the check digit is valid, otherwise
061         * <code>false</code>
062         */
063        public boolean isValid(String code) {
064            if (code == null || code.length() == 0) {
065                return false;
066            }
067            try {       
068                int modulusResult = calculateModulus(code, true);
069                return (modulusResult == 0);
070            } catch (CheckDigitException  ex) {
071                return false;
072            }
073        }
074    
075        /**
076         * Calculate a modulus <i>Check Digit</i> for a code.
077         *
078         * @param code The code to calculate the Check Digit for
079         * @return The calculated Check Digit
080         * @throws CheckDigitException if an error occurs calculating
081         * the check digit for the specified code
082         */
083        public String calculate(String code) throws CheckDigitException {
084            if (code == null || code.length() == 0) {
085                throw new CheckDigitException("Code is missing");
086            }
087            int modulusResult = calculateModulus(code, false);
088            int charValue = (modulus - modulusResult) % modulus;
089            return toCheckDigit(charValue);
090        }
091    
092        /**
093         * Calculate the modulus for a code.
094         *
095         * @param code The code to calculate the modulus for.
096         * @param includesCheckDigit Whether the code includes the Check Digit or not.
097         * @return The modulus value
098         * @throws CheckDigitException if an error occurs calculating the modulus
099         * for the specified code
100         */
101        protected int calculateModulus(String code, boolean includesCheckDigit) throws CheckDigitException {
102            int total = 0;
103            for (int i = 0; i < code.length(); i++) {
104                int lth = code.length() + (includesCheckDigit ? 0 : 1);
105                int leftPos  = i + 1;
106                int rightPos = lth - i;
107                int charValue = toInt(code.charAt(i), leftPos, rightPos);
108                total += weightedValue(charValue, leftPos, rightPos);
109            }
110            if (total == 0) {
111                throw new CheckDigitException("Invalid code, sum is zero");
112            }
113            return (total % modulus);
114        }
115    
116        /**
117         * Calculates the <i>weighted</i> value of a character in the
118         * code at a specified position.
119         * <p>
120         * Some modulus routines weight the value of a character
121         * depending on its position in the code (e.g. ISBN-10), while
122         * others use different weighting factors for odd/even positions
123         * (e.g. EAN or Luhn). Implement the appropriate mechanism
124         * required by overriding this method.
125         *
126         * @param charValue The numeric value of the character
127         * @param leftPos The position of the character in the code, counting from left to right 
128         * @param rightPos The positionof the character in the code, counting from right to left
129         * @return The weighted value of the character
130         * @throws CheckDigitException if an error occurs calculating
131         * the weighted value
132         */
133        protected abstract int weightedValue(int charValue, int leftPos, int rightPos)
134                throws CheckDigitException;
135    
136    
137        /**
138         * Convert a character at a specified position to an integer value.
139         * <p>
140         * <b>Note:</b> this implementation only handlers numeric values
141         * For non-numeric characters, override this method to provide
142         * character-->integer conversion.
143         *
144         * @param character The character to convert
145         * @param leftPos The position of the character in the code, counting from left to right 
146         * @param rightPos The positionof the character in the code, counting from right to left
147         * @return The integer value of the character
148         * @throws CheckDigitException if character is non-numeric
149         */
150        protected int toInt(char character, int leftPos, int rightPos)
151                throws CheckDigitException {
152            if (Character.isDigit(character)) {
153                return Character.getNumericValue(character);
154            } else {
155                throw new CheckDigitException("Invalid Character[" + 
156                        leftPos + "] = '" + character + "'");
157            }
158        }
159    
160        /**
161         * Convert an integer value to a check digit.
162         * <p>
163         * <b>Note:</b> this implementation only handles numeric values
164         * For non-numeric characters, override this method to provide
165         * integer-->character conversion.
166         *
167         * @param charValue The integer value of the character
168         * @return The converted character
169         * @throws CheckDigitException if integer character value
170         * doesn't represent a numeric character
171         */
172        protected String toCheckDigit(int charValue)
173                throws CheckDigitException {
174            if (charValue >= 0 && charValue <= 9) {
175                return Integer.toString(charValue);
176            } else {
177                throw new CheckDigitException("Invalid Check Digit Value =" + 
178                        + charValue);
179            }
180        }
181    
182        /**
183         * Add together the individual digits in a number.
184         *
185         * @param number The number whose digits are to be added
186         * @return The sum of the digits
187         */
188        public static int sumDigits(int number) {
189            int total = 0;
190            int todo = number;
191            while (todo > 0) {
192                total += todo % 10;
193                todo  = todo / 10;
194            }
195            return total;
196        }
197    
198    }