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.util.Arrays; 20 21 import org.apache.commons.validator.routines.CodeValidator; 22 23 /** 24 * General Modulus 10 Check Digit calculation/validation. 25 * 26 * <h2>How it Works</h2> 27 * <p> 28 * This implementation calculates/validates the check digit in the following 29 * way: 30 * <ul> 31 * <li>Converting each character to an integer value using 32 * <code>Character.getNumericValue(char)</code> - negative integer values from 33 * that method are invalid.</li> 34 * <li>Calculating a <i>weighted value</i> by multiplying the character's 35 * integer value by a <i>weighting factor</i>. The <i>weighting factor</i> is 36 * selected from the configured <code>postitionWeight</code> array based on its 37 * position. The <code>postitionWeight</code> values are used either 38 * left-to-right (when <code>useRightPos=false</code>) or right-to-left (when 39 * <code>useRightPos=true</code>).</li> 40 * <li>If <code>sumWeightedDigits=true</code>, the <i>weighted value</i> is 41 * re-calculated by summing its digits.</li> 42 * <li>The <i>weighted values</i> of each character are totalled.</li> 43 * <li>The total modulo 10 will be zero for a code with a valid Check Digit.</li> 44 * </ul> 45 * <h2>Limitations</h2> 46 * <p> 47 * This implementation has the following limitations: 48 * <ul> 49 * <li>It assumes the last character in the code is the Check Digit and 50 * validates that it is a numeric character.</li> 51 * <li>The only limitation on valid characters are those that 52 * <code>Character.getNumericValue(char)</code> returns a positive value. If, 53 * for example, the code should only contain numbers, this implementation does 54 * not check that.</li> 55 * <li>There are no checks on code length.</li> 56 * </ul> 57 * <p> 58 * <b>Note:</b> This implementation can be combined with the 59 * {@link CodeValidator} in order to ensure the length and characters are valid. 60 * 61 * <h2>Example Usage</h2> 62 * <p> 63 * This implementation was added after a number of Modulus 10 routines and these 64 * are shown re-implemented using this routine below: 65 * 66 * <p> 67 * <b>ABA Number</b> Check Digit Routine (equivalent of 68 * {@link ABANumberCheckDigit}). Weighting factors are <code>[1, 7, 3]</code> 69 * applied from right to left. 70 * 71 * <pre> 72 * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 7, 3 }, true); 73 * </pre> 74 * 75 * <p> 76 * <b>CUSIP</b> Check Digit Routine (equivalent of {@link CUSIPCheckDigit}). 77 * Weighting factors are <code>[1, 2]</code> applied from right to left and the 78 * digits of the <i>weighted value</i> are summed. 79 * 80 * <pre> 81 * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 2 }, true, true); 82 * </pre> 83 * 84 * <p> 85 * <b>EAN-13 / UPC</b> Check Digit Routine (equivalent of 86 * {@link EAN13CheckDigit}). Weighting factors are <code>[1, 3]</code> applied 87 * from right to left. 88 * 89 * <pre> 90 * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 3 }, true); 91 * </pre> 92 * 93 * <p> 94 * <b>Luhn</b> Check Digit Routine (equivalent of {@link LuhnCheckDigit}). 95 * Weighting factors are <code>[1, 2]</code> applied from right to left and the 96 * digits of the <i>weighted value</i> are summed. 97 * 98 * <pre> 99 * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 2 }, true, true); 100 * </pre> 101 * 102 * <p> 103 * <b>SEDOL</b> Check Digit Routine (equivalent of {@link SedolCheckDigit}). 104 * Weighting factors are <code>[1, 3, 1, 7, 3, 9, 1]</code> applied from left to 105 * right. 106 * 107 * <pre> 108 * CheckDigit routine = new ModulusTenCheckDigit(new int[] { 1, 3, 1, 7, 3, 9, 1 }); 109 * </pre> 110 * 111 * @since 1.6 112 */ 113 public final class ModulusTenCheckDigit extends ModulusCheckDigit { 114 115 private static final long serialVersionUID = -3752929983453368497L; 116 117 /** 118 * The weighted values to apply based on the character position 119 */ 120 private final int[] postitionWeight; 121 122 /** 123 * {@code true} if use positionWeights from right to left 124 */ 125 private final boolean useRightPos; 126 127 /** 128 * {@code true} if sum the digits of the weighted value 129 */ 130 private final boolean sumWeightedDigits; 131 132 /** 133 * Constructs a modulus 10 Check Digit routine with the specified weighting 134 * from left to right. 135 * 136 * @param postitionWeight the weighted values to apply based on the 137 * character position 138 */ 139 public ModulusTenCheckDigit(final int[] postitionWeight) { 140 this(postitionWeight, false, false); 141 } 142 143 /** 144 * Constructs a modulus 10 Check Digit routine with the specified weighting, 145 * indicating whether its from the left or right. 146 * 147 * @param postitionWeight the weighted values to apply based on the 148 * character position 149 * @param useRightPos {@code true} if use positionWeights from right to 150 * left 151 */ 152 public ModulusTenCheckDigit(final int[] postitionWeight, final boolean useRightPos) { 153 this(postitionWeight, useRightPos, false); 154 } 155 156 /** 157 * Constructs a modulus 10 Check Digit routine with the specified weighting, 158 * indicating whether its from the left or right and whether the weighted 159 * digits should be summed. 160 * 161 * @param postitionWeight the weighted values to apply based on the 162 * character position 163 * @param useRightPos {@code true} if use positionWeights from right to 164 * left 165 * @param sumWeightedDigits {@code true} if sum the digits of the 166 * weighted value 167 */ 168 public ModulusTenCheckDigit(final int[] postitionWeight, final boolean useRightPos, final boolean sumWeightedDigits) { 169 super(); 170 this.postitionWeight = Arrays.copyOf(postitionWeight, postitionWeight.length); 171 this.useRightPos = useRightPos; 172 this.sumWeightedDigits = sumWeightedDigits; 173 } 174 175 /** 176 * Validate a modulus check digit for a code. 177 * <p> 178 * Note: assumes last digit is the check digit 179 * 180 * @param code The code to validate 181 * @return {@code true} if the check digit is valid, otherwise 182 * {@code false} 183 */ 184 @Override 185 public boolean isValid(final String code) { 186 if (code == null || code.isEmpty()) { 187 return false; 188 } 189 if (!Character.isDigit(code.charAt(code.length() - 1))) { 190 return false; 191 } 192 193 return super.isValid(code); 194 } 195 196 /** 197 * Convert a character at a specified position to an integer value. 198 * <p> 199 * <b>Note:</b> this implementation only handlers values that 200 * Character.getNumericValue(char) returns a non-negative number. 201 * 202 * @param character The character to convert 203 * @param leftPos The position of the character in the code, counting from 204 * left to right (for identifying the position in the string) 205 * @param rightPos The position of the character in the code, counting from 206 * right to left (not used here) 207 * @return The integer value of the character 208 * @throws CheckDigitException if Character.getNumericValue(char) returns a 209 * negative number 210 */ 211 @Override 212 protected int toInt(final char character, final int leftPos, final int rightPos) throws CheckDigitException { 213 final int num = Character.getNumericValue(character); 214 if (num < 0) { 215 throw new CheckDigitException("Invalid Character[" + leftPos + "] = '" + character + "'"); 216 } 217 return num; 218 } 219 220 /** 221 * Return a string representation of this implementation. 222 * 223 * @return a string representation 224 */ 225 @Override 226 public String toString() { 227 return getClass().getSimpleName() + "[postitionWeight=" + Arrays.toString(postitionWeight) + ", useRightPos=" 228 + useRightPos + ", sumWeightedDigits=" + sumWeightedDigits + "]"; 229 } 230 231 /** 232 * Calculates the <i>weighted</i> value of a character in the code at a 233 * specified position. 234 * 235 * @param charValue The numeric value of the character. 236 * @param leftPos The position of the character in the code, counting from 237 * left to right 238 * @param rightPos The position of the character in the code, counting from 239 * right to left 240 * @return The weighted value of the character. 241 */ 242 @Override 243 protected int weightedValue(final int charValue, final int leftPos, final int rightPos) { 244 final int pos = useRightPos ? rightPos : leftPos; 245 final int weight = postitionWeight[(pos - 1) % postitionWeight.length]; 246 int weightedValue = charValue * weight; 247 if (sumWeightedDigits) { 248 weightedValue = ModulusCheckDigit.sumDigits(weightedValue); 249 } 250 return weightedValue; 251 } 252 253 }