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 * https://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 org.apache.commons.validator.GenericValidator;
20 import org.apache.commons.validator.routines.CodeValidator;
21
22 /**
23 * Modulus 10 <strong>CAS Registry Number</strong> (or <strong>Chemical Abstracts Service</strong> (CAS RN)) Check Digit
24 * calculation/validation.
25 *
26 * <p>
27 * CAS Numbers are unique identification numbers used
28 * to identify chemical substance described in the open scientific literature.
29 * </p>
30 *
31 * <p>
32 * Check digit calculation is based on <em>modulus 10</em> with digits being weighted
33 * based on their position (from right to left).
34 * </p>
35 *
36 * <p>
37 * The check digit is found by taking the last digit times 1, the preceding digit times 2,
38 * the preceding digit times 3 etc., adding all these up and computing the sum modulo 10.
39 * For example, the CAS number of water is {@code 7732-18-5}:
40 * the checksum 5 is calculated as (8×1 + 1×2 + 2×3 + 3×4 + 7×5 + 7×6) = 105; 105 mod 10 = 5.
41 * </p>
42 *
43 * <p>
44 * For further information see
45 * <a href="https://en.wikipedia.org/wiki/CAS_Registry_Number">Wikipedia - CAS Registry Number</a>.
46 * </p>
47 *
48 * @since 1.9.0
49 */
50 public final class CASNumberCheckDigit extends ModulusCheckDigit {
51
52 private static final long serialVersionUID = -5387334603220786657L;
53
54 /** Singleton Check Digit instance */
55 private static final CASNumberCheckDigit INSTANCE = new CASNumberCheckDigit();
56
57 /**
58 * CAS number consists of 3 groups of numbers separated dashes (-).
59 * First group has 2 to 7 digits.
60 * Example: water is 7732-18-5
61 */
62 private static final String GROUP1 = "(\\d{2,7})";
63
64 private static final String DASH = "(?:\\-)";
65 static final String CAS_REGEX = "^(?:" + GROUP1 + DASH + "(\\d{2})" + DASH + "(\\d))$";
66 private static final int CAS_MIN_LEN = 4; // 9-99-9 LEN without SEP
67
68 /** maximum capacity of 1,000,000,000 == 9999999-99-9*/
69 private static final int CAS_MAX_LEN = 10;
70 static final CodeValidator REGEX_VALIDATOR = new CodeValidator(CAS_REGEX, CAS_MIN_LEN, CAS_MAX_LEN, null);
71 /** Weighting given to digits depending on their right position */
72 private static final int[] POSITION_WEIGHT = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
73
74 /**
75 * Gets the singleton instance of this validator.
76 * @return A singleton instance of the CAS Number validator.
77 */
78 public static CheckDigit getInstance() {
79 return INSTANCE;
80 }
81
82 /**
83 * Constructs a modulus 10 Check Digit routine for CAS Numbers.
84 */
85 private CASNumberCheckDigit() {
86 }
87
88 /**
89 * {@inheritDoc}
90 */
91 @Override
92 public String calculate(final String code) throws CheckDigitException {
93 if (GenericValidator.isBlankOrNull(code)) {
94 throw new CheckDigitException("Code is missing");
95 }
96 final int modulusResult = INSTANCE.calculateModulus(code, false);
97 return toCheckDigit(modulusResult);
98 }
99
100 /**
101 * {@inheritDoc}
102 */
103 @Override
104 public boolean isValid(final String code) {
105 if (GenericValidator.isBlankOrNull(code)) {
106 return false;
107 }
108 final Object cde = REGEX_VALIDATOR.validate(code);
109 if (!(cde instanceof String)) {
110 return false;
111 }
112 try {
113 final int modulusResult = INSTANCE.calculateModulus((String) cde, true);
114 return modulusResult == Character.getNumericValue(code.charAt(code.length() - 1));
115 } catch (final CheckDigitException ex) {
116 return false;
117 }
118 }
119
120 /**
121 * Calculates the <em>weighted</em> value of a character in the code at a specified position.
122 * <p>
123 * CAS numbers are weighted in the following manner:
124 * </p>
125 * <pre>{@code
126 * right position: 1 2 3 4 5 6 7 8 9 10
127 * weight: 1 2 3 4 5 6 7 8 9 0
128 * }</pre>
129 *
130 * @param charValue The numeric value of the character.
131 * @param leftPos The position of the character in the code, counting from left to right
132 * @param rightPos The position of the character in the code, counting from right to left
133 * @return The weighted value of the character.
134 */
135 @Override
136 protected int weightedValue(final int charValue, final int leftPos, final int rightPos) {
137 final int weight = POSITION_WEIGHT[(rightPos - 1) % MODULUS_10];
138 return charValue * weight;
139 }
140
141 }