/*
* Licensed under the Apache License, Version 2.0 (the "License");
*
* You may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributions from 2013-2017 where performed either by US government
* employees, or under US Veterans Health Administration contracts.
*
* US Veterans Health Administration contributions by government employees
* are work of the U.S. Government and are not subject to copyright
* protection in the United States. Portions contributed by government
* employees are USGovWork (17USC ยง105). Not subject to copyright.
*
* Contribution by contractors to the US Veterans Health Administration
* during this period are contractually contributed under the
* Apache License, Version 2.0.
*
* See: https://www.usa.gov/government-works
*
* Contributions prior to 2013:
*
* Copyright (C) International Health Terminology Standards Development Organisation.
* Licensed under the Apache License, Version 2.0.
*
*/
package sh.isaac.utility;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.commons.lang3.StringUtils;
//~--- classes ----------------------------------------------------------------
/**
* {@link SctId} contains validation utilities for SNOMED ID (SCT ID). A unique long
* Identifier applied to each SNOMED CT component ( Concept, Description, Relationship, Subset, etc.).
* The SCTID data type is a 64-bit integer, which is subject to the following constraints:
* - Only positive integer values are permitted.
* - The minimum permitted value is 100,000 (6 digits)
* - The maximum permitted value is 999,999,999,999,999,999 (18-digits).
*
* As a result of rules for the partition-identifier and check-digit, many integers within this range are not valid SCTIDs.
*
* Extension SCTID (MSD)Extension Item ID (18-11)Namespace ID(10-4)Partition ID(3-2)Check-digit(1)(LSD)
*
* In java, the SCTID is handled as a long.
*
* @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a>
* @see <a href="http://www.snomed.org/tig?t=trg2main_sctid">IHTSDO Technical Implementation Guide - SCT ID</a>
*/
public class SctId {
/** The Fn F. */
// parts of the SCTID algorithm
private static int[][] FnF = {
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
}, {
1, 5, 7, 6, 2, 8, 3, 0, 9, 4
}, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}
};
/** The Dihedral. */
private static int[][] Dihedral = {
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
}, {
1, 2, 3, 4, 0, 6, 7, 8, 9, 5
}, {
2, 3, 4, 0, 1, 7, 8, 9, 5, 6
}, {
3, 4, 0, 1, 2, 8, 9, 5, 6, 7
}, {
4, 0, 1, 2, 3, 9, 5, 6, 7, 8
}, {
5, 9, 8, 7, 6, 0, 4, 3, 2, 1
}, {
6, 5, 9, 8, 7, 1, 0, 4, 3, 2
}, {
7, 6, 5, 9, 8, 2, 1, 0, 4, 3
}, {
8, 7, 6, 5, 9, 3, 2, 1, 0, 4
}, {
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
}
};
//~--- static initializers -------------------------------------------------
static {
for (int i = 2; i < 8; i++) {
for (int j = 0; j < 10; j++) {
FnF[i][j] = FnF[i - 1][FnF[1][j]];
}
}
}
//~--- enums ---------------------------------------------------------------
/**
* The Enum TYPE listing the possible types of SCT IDs. The second and third
* digits from the right of the string rendering of the SCTID. The value of
* the partition-identifier indicates the type of component that the SCTID
* identifies (e.g. Concept, Description, Relationship, etc) and also
* indicates whether the SCTID contains a namespace identifier.
*
*/
public static enum TYPE {
/**
* Identifies the SCT ID as a concept.
*/
CONCEPT("10"),
/**
* Identifies the SCT ID as a description.
*/
DESCRIPTION("11"),
/**
* Identifies the SCT ID as a relationship.
*/
RELATIONSHIP("12"),
/**
* Identifies the SCT ID as a refset.
*/
SUBSET("13");
/** The digits. */
private final String digits;
//~--- constructors -----------------------------------------------------
/**
* Instantiates a new SCT ID type based on the <code>digits</code>.
*
* @param digits the digits specifying the SCT ID type
*/
private TYPE(String digits) {
this.digits = digits;
}
//~--- get methods ------------------------------------------------------
/**
* Gets the digits specifying the SCT ID type.
*
* @return the digits specifying the SCT ID type
*/
public String getDigits() {
return this.digits;
}
}
//~--- get methods ---------------------------------------------------------
/**
* see {@link #isValidSctId(String)}.
*
* @param sctid the sctid
* @return true, if valid SCTID
*/
public static boolean isValidSCTID(int sctid) {
return isValidSctId(Integer.toString(sctid));
}
/**
* see {@link #isValidSctId(String)}.
*
* @param sctid the sctid
* @return true, if valid sct id
*/
public static boolean isValidSctId(long sctid) {
return isValidSctId(Long.toString(sctid));
}
// TODO validate this code / make clean APIs when it is needed.
// /**
// * Generates an SCT ID based on the given <code>sequence</code>, <code>projectId</code>, <code>namespaceId</code>, and <code>type</code>.
// *
// * @param sequence the sequence to use for the item identifier
// * @param projectId the id of the mapping project
// * @param namespaceId the <code>int</code> representation of the namespace to use
// * @param type the SCT ID type
// * @return a string representation of the generated SCT ID
// */
// public static String generate(long sequence, int projectId, int namespaceId, TYPE type)
// {
//
// if (sequence <= 0)
// {
// throw new RuntimeException("sequence must be > 0");
// }
//
// String mergedid = Long.toString(sequence) + projectId + namespaceId + type.digits;
//
// return mergedid + verhoeffCompute(mergedid);
// }
//
// /**
// * Generates an SCT ID based on the given <code>sequence</code>, <code>namespaceString</code>, and <code>type</code>.
// *
// * @param sequence the sequence to use for the item identifier
// * @param namespaceString the <code>String</code> representation of the namespace to use
// * @param type the SCT ID type
// * @return a string representation of the generated SCT ID
// */
// public static String generate(long sequence, String namespaceString, TYPE type)
// {
// if (sequence <= 0)
// {
// throw new RuntimeException("sequence must be > 0");
// }
// String mergedid = Long.toString(sequence) + namespaceString + type.digits;
// return mergedid + verhoeffCompute(mergedid);
// }
//
// /**
// * Generates an SCT ID based on the given <code>sequence</code>, <code>namespaceId</code>, and <code>type</code>.
// *
// * @param sequence the sequence to use for the item identifier
// * @param namespaceId the <code>int</code> representation of the namespace to use
// * @param type the SCT ID type
// * @return a string representation of the generated SCT ID
// */
// public static String generate(long sequence, int namespaceId, TYPE type)
// {
//
// if (sequence <= 0)
// {
// throw new RuntimeException("sequence must be > 0");
// }
//
// String mergedid = Long.toString(sequence) + namespaceId + type.digits;
//
// return mergedid + verhoeffCompute(mergedid);
// }
// /**
// * Computes the check digit. The SCTID (See Component features - Identifiers) includes a check-digit, which is generated using Verhoeff's
// * dihedral check.
// *
// * @param idAsString a String representation of the SCT ID
// * @return the generated SCT ID
// * @see <a href="http://www.snomed.org/tig?t=trg_app_check_digit">IHTSDO Technical Implementation Guide - Verhoeff</a>
// */
// public static long verhoeffCompute(String idAsString)
// {
// int check = 0;
// for (int i = idAsString.length() - 1; i >= 0; i--)
// {
// check = Dihedral_[check][FnF_[((idAsString.length() - i) % 8)][new Integer(new String(new char[] { idAsString.charAt(i) }))]];
//
// }
// return InverseD5_[check];
// }
/**
* Verifies the check digit of an SCT identifier.
*
* @param idAsString a String representation of the SCT ID
* @return <code>true</code>, if the checksum in the string is correct for an SCTID.
* @see <a href="http://www.snomed.org/tig?t=trg_app_check_digit">IHTSDO Technical Implementation Guide - Verhoeff</a>
*/
public static boolean isValidSctId(String idAsString) {
if (StringUtils.isBlank(idAsString)) {
return false;
}
try {
final long l = Long.parseLong(idAsString);
if ((l < 100000) || (l > 999999999999999999l)) {
return false;
}
} catch (final NumberFormatException e) {
return false;
}
int check = 0;
for (int i = idAsString.length() - 1; i >= 0; i--) {
check =
Dihedral[check][FnF[(idAsString.length() - i - 1) % 8][new Integer(new String(new char[] { idAsString.charAt(i) }))]];
}
if (check != 0) {
return false;
} else {
return true;
}
}
}