Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions aom/src/main/java/com/nedap/archie/aom/utils/AOMUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class AOMUtils {

private static Pattern idCodePattern = Pattern.compile("(id|at|ac)(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*");
private static Pattern adl14CodePattern = Pattern.compile("(id|at|ac)([0-9]+)(\\.(0|[1-9][0-9]*))*");
//zero-padded codes (minimum four digit first segment), as used by at-coded ADL 2.4 (retained from ADL 1.4), e.g. at0000, ac0002
private static Pattern zeroPaddedCodePattern = Pattern.compile("(id|at|ac)[0-9]{4,}(\\.(0|[1-9][0-9]*))*");

public static int getSpecializationDepthFromCode(String code) {
if(code == null) {
Expand All @@ -46,7 +48,10 @@ public static boolean isValueSetCode(String code) {
}

public static boolean isValidValueSetCode(String code) {
return isValueSetCode(code) && isValidCode(code);
// isValidADL14Code (a superset of isValidCode) is used so zero-padded at-coded value set codes (e.g. ac0001,
// as produced by the ADL 1.4 to ADL 2.4 at-coded converter) are accepted too; id-coded value set codes are
// never zero-padded so this does not change their behaviour.
return isValueSetCode(code) && isValidADL14Code(code);
}

public static boolean isValidCode(String code) {
Expand All @@ -56,6 +61,32 @@ public static boolean isValidCode(String code) {
return idCodePattern.matcher(code).matches();
}

/**
* Whether the code is a valid non-zero-padded code: an id/at/ac prefix followed by a number without leading zeros,
* with specialisation segments also without leading zeros (e.g. id1, at2, ac3, id1.1). This is the code format used
* by id-coded ADL 2 archetypes - where the at- and ac-codes are non-zero-padded too - and is identical to
* {@link #isValidCode(String)}.
*/
public static boolean isValidNonZeroPaddedCode(String code) {
return isValidCode(code);
}

/**
* Whether the code is a valid zero-padded code: an id/at/ac prefix followed by a zero-padded (minimum four digit)
* number, with specialisation segments without leading zeros (e.g. at0000, at0001.1, ac0002). This is the code
* format used by at-coded ADL 2.4 archetypes, which retain the zero-padded code style of ADL 1.4.
*
* Note this is stricter than {@link #isValidADL14Code(String)}: the latter accepts any number of digits (including
* non-zero-padded forms like at5) and is used for prefix/structure-agnostic checks, whereas this method enforces
* the zero-padding convention.
*/
public static boolean isValidZeroPaddedCode(String code) {
if(code == null) {
return false;
}
return zeroPaddedCodePattern.matcher(code).matches();
}

/**
* Get the numeric node id from a valid id code without any prefix (like at, ac or id)
* @param nodeId the node id to strip the prefix of.
Expand All @@ -71,7 +102,11 @@ public static String stripPrefix(String nodeId) {
public static String pathAtSpecializationLevel(List<PathSegment> pathSegments, int level) {
//todo: this doesn't clone the original
for(PathSegment segment:pathSegments) {
if(segment.getNodeId() != null && AOMUtils.isValidCode(segment.getNodeId()) && AOMUtils.getSpecializationDepthFromCode(segment.getNodeId()) > level) {
// isValidADL14Code accepts the same id/at/ac prefixes as isValidCode; it only additionally tolerates the
// zero-padded first segment of at-coded ADL 2.4 node ids (e.g. at0000). This check is prefix-agnostic on
// purpose - it just decides whether a segment is a code to reduce - so accepting id codes here does not
// matter; enforcing a single code system is CodeSystemValidation's job, not this helper's.
if(segment.getNodeId() != null && AOMUtils.isValidADL14Code(segment.getNodeId()) && AOMUtils.getSpecializationDepthFromCode(segment.getNodeId()) > level) {
segment.setNodeId(codeAtLevel(segment.getNodeId(), level));
}
}
Expand Down Expand Up @@ -273,7 +308,9 @@ private static Boolean matchesExpression(Expression expression, String archetype
}

public static boolean codesConformant(String childNodeId, String parentNodeId) {
return isValidCode(childNodeId) && childNodeId.startsWith(parentNodeId) &&
// isValidADL14Code (a superset of isValidCode) is used so zero-padded at-coded node ids (e.g. at0000.1)
// are accepted as well; id-coded codes are never zero-padded so this does not change their behaviour.
return isValidADL14Code(childNodeId) && childNodeId.startsWith(parentNodeId) &&
(childNodeId.length() == parentNodeId.length() || (childNodeId.length() > parentNodeId.length() && childNodeId.charAt(parentNodeId.length()) == AdlCodeDefinitions.SPECIALIZATION_SEPARATOR));

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public enum ErrorType implements MessageCode {
OVERLAY_VALIDATION_FAILED(I18n.register("The validation of a template overlay failed")),
PARENT_VALIDATION_FAILED(I18n.register("The validation of the parent archetype failed")),
ADL14_INCOMPATIBLE_NODE_IDS(I18n.register("Node id numbers should be unique without their ac, at or id-prefix, to ensure the possibility of converting the archetype to ADL 1.4")),
DEFAULT_OBJECT_TYPE_VALIDITY(I18n.register("object constraint default value type validity: the type of the default value of an object constraint must conform to the type of the constraint"));
DEFAULT_OBJECT_TYPE_VALIDITY(I18n.register("object constraint default value type validity: the type of the default value of an object constraint must conform to the type of the constraint")),
INVALID_NODE_ID_CODE_SYSTEM(I18n.register("node identifier code system validity: an archetype must consistently use a single node identifier code system, either id-coded (id1, id1.1, ...) or, since ADL 2.4, at-coded (at0000, at0000.1, ...), and that code system must match the one the validator is configured to accept."));

private final String description;

Expand Down
64 changes: 64 additions & 0 deletions aom/src/test/java/com/nedap/archie/aom/utils/AOMUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,73 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class AOMUtilsTest {

@Test
public void codesConformant() {
// id-coded
assertTrue(AOMUtils.codesConformant("id1", "id1"));
assertTrue(AOMUtils.codesConformant("id1.1", "id1"));
assertTrue(AOMUtils.codesConformant("id1.1.1", "id1.1"));
assertFalse(AOMUtils.codesConformant("id2", "id1"));

// at-coded, zero-padded (ADL 2.4) - the case enabled by using isValidADL14Code instead of isValidCode
assertTrue(AOMUtils.codesConformant("at0000", "at0000"));
assertTrue(AOMUtils.codesConformant("at0000.1", "at0000"));
assertTrue(AOMUtils.codesConformant("at0001.0.1", "at0001"));
assertFalse(AOMUtils.codesConformant("at0001", "at0000"));
// shares the parent string as a prefix but is not a specialisation child (no '.' separator)
assertFalse(AOMUtils.codesConformant("at00001", "at0000"));
}

@Test
public void isValidValueSetCode() {
// non-padded value set codes (id-coded archetypes and generated at-coded codes)
assertTrue(AOMUtils.isValidValueSetCode("ac1"));
assertTrue(AOMUtils.isValidValueSetCode("ac9000"));
// zero-padded value set code, as produced for at-coded archetypes by the ADL 1.4 converter
assertTrue(AOMUtils.isValidValueSetCode("ac0001"));
assertTrue(AOMUtils.isValidValueSetCode("ac0001.1"));
// not value set codes
assertFalse(AOMUtils.isValidValueSetCode("at0001"));
assertFalse(AOMUtils.isValidValueSetCode("id1"));
assertFalse(AOMUtils.isValidValueSetCode("acabc"));
}

@Test
public void isValidNonZeroPaddedCode() {
// non-zero-padded (id-coded ADL 2) format: no leading zeros, any id/at/ac prefix
assertTrue(AOMUtils.isValidNonZeroPaddedCode("id1"));
assertTrue(AOMUtils.isValidNonZeroPaddedCode("at2"));
assertTrue(AOMUtils.isValidNonZeroPaddedCode("ac3"));
assertTrue(AOMUtils.isValidNonZeroPaddedCode("id1.1"));
// zero-padded (at-coded) codes are not valid in the non-zero-padded (id-coded) system
assertFalse(AOMUtils.isValidNonZeroPaddedCode("at0000"));
assertFalse(AOMUtils.isValidNonZeroPaddedCode("ac0001"));
assertFalse(AOMUtils.isValidNonZeroPaddedCode(null));
}

@Test
public void isValidZeroPaddedCode() {
// zero-padded (minimum four digit) first segment, specialisation segments without leading zeros
assertTrue(AOMUtils.isValidZeroPaddedCode("at0000"));
assertTrue(AOMUtils.isValidZeroPaddedCode("at0001.1"));
assertTrue(AOMUtils.isValidZeroPaddedCode("ac0002"));
assertTrue(AOMUtils.isValidZeroPaddedCode("ac0001.1"));
assertTrue(AOMUtils.isValidZeroPaddedCode("at9088"));
assertTrue(AOMUtils.isValidZeroPaddedCode("at12345")); // more than four digits is allowed (values > 9999)
// non-zero-padded codes are not valid zero-padded, even though the grammar and isValidADL14Code accept them
assertFalse(AOMUtils.isValidZeroPaddedCode("at5"));
assertFalse(AOMUtils.isValidZeroPaddedCode("at123"));
assertFalse(AOMUtils.isValidZeroPaddedCode("id1"));
// leading zeros are only allowed in the first segment, not in specialisation segments
assertFalse(AOMUtils.isValidZeroPaddedCode("at0000.01"));
assertFalse(AOMUtils.isValidZeroPaddedCode(null));
}

@Test
public void codeAtLevel() {
assertEquals("id1", AOMUtils.codeAtLevel("id1", 0));
Expand Down
Loading