CASNumberCheckDigit.java

  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. import org.apache.commons.validator.GenericValidator;
  19. import org.apache.commons.validator.routines.CodeValidator;

  20. /**
  21.  * Modulus 10 <b>CAS Registry Number</b> (or <b>Chemical Abstracts Service</b> (CAS RN)) Check Digit
  22.  * calculation/validation.
  23.  *
  24.  * <p>
  25.  * CAS Numbers are unique identification numbers used
  26.  * to identify chemical substance described in the open scientific literature.
  27.  * </p>
  28.  *
  29.  * <p>
  30.  * Check digit calculation is based on <i>modulus 10</i> with digits being weighted
  31.  * based on their position (from right to left).
  32.  * </p>
  33.  *
  34.  * <p>
  35.  * The check digit is found by taking the last digit times 1, the preceding digit times 2,
  36.  * the preceding digit times 3 etc., adding all these up and computing the sum modulo 10.
  37.  * For example, the CAS number of water is <code>7732-18-5</code>:
  38.  * the checksum 5 is calculated as (8×1 + 1×2 + 2×3 + 3×4 + 7×5 + 7×6) = 105; 105 mod 10 = 5.
  39.  * </p>
  40.  *
  41.  * <p>
  42.  * For further information see
  43.  *  <a href="https://en.wikipedia.org/wiki/CAS_Registry_Number">Wikipedia - CAS Registry Number</a>.
  44.  * </p>
  45.  *
  46.  * @since 1.9.0
  47.  */
  48. public final class CASNumberCheckDigit extends ModulusCheckDigit {

  49.     private static final long serialVersionUID = -5387334603220786657L;

  50.     /** Singleton Check Digit instance */
  51.     private static final CASNumberCheckDigit INSTANCE = new CASNumberCheckDigit();

  52.     /**
  53.      * Gets the singleton instance of this validator.
  54.      * @return A singleton instance of the CAS Number validator.
  55.      */
  56.     public static CheckDigit getInstance() {
  57.         return INSTANCE;
  58.     }

  59.     /**
  60.      * CAS number consists of 3 groups of numbers separated dashes (-).
  61.      * First group has 2 to 7 digits.
  62.      * Example: water is 7732-18-5
  63.      */
  64.     private static final String GROUP1 = "(\\d{2,7})";
  65.     private static final String DASH = "(?:\\-)";
  66.     static final String CAS_REGEX = "^(?:" + GROUP1 + DASH + "(\\d{2})" + DASH + "(\\d))$";

  67.     private static final int CAS_MIN_LEN = 4; // 9-99-9 LEN without SEP
  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.      * Constructs a modulus 10 Check Digit routine for CAS Numbers.
  75.      */
  76.     private CASNumberCheckDigit() {
  77.     }

  78.     /**
  79.      * Calculates the <i>weighted</i> value of a character in the code at a specified position.
  80.      * <p>
  81.      * CAS numbers are weighted in the following manner:
  82.      * </p>
  83.      * <pre><code>
  84.      *    right position: 1  2  3  4  5  6  7  8  9 10
  85.      *            weight: 1  2  3  4  5  6  7  8  9  0
  86.      * </code></pre>
  87.      *
  88.      * @param charValue The numeric value of the character.
  89.      * @param leftPos The position of the character in the code, counting from left to right
  90.      * @param rightPos The positionof the character in the code, counting from right to left
  91.      * @return The weighted value of the character.
  92.      */
  93.     @Override
  94.     protected int weightedValue(final int charValue, final int leftPos, final int rightPos) {
  95.         final int weight = POSITION_WEIGHT[(rightPos - 1) % MODULUS_10];
  96.         return charValue * weight;
  97.     }

  98.     /**
  99.      * {@inheritDoc}
  100.      */
  101.     @Override
  102.     public String calculate(final String code) throws CheckDigitException {
  103.         if (GenericValidator.isBlankOrNull(code)) {
  104.             throw new CheckDigitException("Code is missing");
  105.         }
  106.         final int modulusResult = INSTANCE.calculateModulus(code, false);
  107.         return toCheckDigit(modulusResult);
  108.     }

  109.     /**
  110.      * {@inheritDoc}
  111.      */
  112.     @Override
  113.     public boolean isValid(final String code) {
  114.         if (GenericValidator.isBlankOrNull(code)) {
  115.             return false;
  116.         }
  117.         final Object cde = REGEX_VALIDATOR.validate(code);
  118.         if (!(cde instanceof String)) {
  119.             return false;
  120.         }
  121.         try {
  122.             final int modulusResult = INSTANCE.calculateModulus((String) cde, true);
  123.             return modulusResult == Character.getNumericValue(code.charAt(code.length() - 1));
  124.         } catch (final CheckDigitException ex) {
  125.             return false;
  126.         }
  127.     }

  128. }