From 1fc3ae031b99a587d93a60387454c9a63108bc8f Mon Sep 17 00:00:00 2001
From: massifben <105049157+massifben@users.noreply.github.com>
Date: Wed, 2 Jul 2025 23:22:16 +0200
Subject: [PATCH] fix(#525): Bug : LNodeTypeAdapter.getDataAttributeRef should
not throw exception Multiple Data Attribute found
Signed-off-by: massifben <105049157+massifben@users.noreply.github.com>
---
.../sct/commons/scl/dtt/LNodeTypeAdapter.java | 51 ++++++------
.../scl/dtt/DataTypeTemplateTestUtils.java | 1 +
.../commons/scl/dtt/LNodeTypeAdapterTest.java | 78 ++++++++++++++-----
.../scd_dtt_do_multiple_da.xml | 21 +++++
4 files changed, 102 insertions(+), 49 deletions(-)
create mode 100644 sct-commons/src/test/resources/dtt-test-schema-conf/scd_dtt_do_multiple_da.xml
diff --git a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java
index 78b312f61..ae43d7a8c 100644
--- a/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java
+++ b/sct-commons/src/main/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapter.java
@@ -6,6 +6,7 @@
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.lfenergy.compas.scl2007b4.model.TDO;
import org.lfenergy.compas.scl2007b4.model.TDOType;
@@ -20,7 +21,6 @@
import org.lfenergy.compas.sct.commons.util.Utils;
import java.util.*;
-import java.util.stream.Collectors;
/**
* A representation of the model object
@@ -41,7 +41,7 @@
*
{@link LNodeTypeAdapter#getId() Returns the value of the id attribute}
* {@link LNodeTypeAdapter#getLNClass Returns the value of the lnClass attribute}
* {@link LNodeTypeAdapter#getDataAttributeRefs(DataAttributeRef)} Returns DataAttributeRef list}
- * {@link LNodeTypeAdapter#getDataAttributeRefs(String)} Returns DataAttributeRef list}
+ * {@link LNodeTypeAdapter#getDataAttributeRef(String)} Returns DataAttributeRef list}
*
* Checklist functions
*
@@ -95,10 +95,8 @@ public boolean hasSameContentAs(TLNodeType tlNodeType) {
return false;
}
- if (Objects.equals(
- currentElem.getLnClass().toArray(new String[0]),
- tlNodeType.getLnClass().toArray(new String[0])
- ) || !Objects.equals(currentElem.getIedType(), tlNodeType.getIedType())) {
+ if (Objects.equals(currentElem.getLnClass(), tlNodeType.getLnClass())
+ || !Objects.equals(currentElem.getIedType(), tlNodeType.getIedType())) {
return false;
}
@@ -112,9 +110,9 @@ public boolean hasSameContentAs(TLNodeType tlNodeType) {
TDO inTDO = inTDOs.get(i);
TDO thisTDO = thisTDOs.get(i);
if (!thisTDO.getType().equals(inTDO.getType())
- || !thisTDO.getName().equals(inTDO.getName())
- || thisTDO.isTransient() != inTDO.isTransient()
- || !Objects.equals(thisTDO.getAccessControl(), inTDO.getAccessControl())) {
+ || !thisTDO.getName().equals(inTDO.getName())
+ || thisTDO.isTransient() != inTDO.isTransient()
+ || !Objects.equals(thisTDO.getAccessControl(), inTDO.getAccessControl())) {
return false;
}
}
@@ -139,7 +137,7 @@ public boolean containsDOWithDOTypeId(String doTypeId) {
*/
public String getLNClass() {
if (!currentElem.getLnClass().isEmpty()) {
- return currentElem.getLnClass().get(0);
+ return currentElem.getLnClass().getFirst();
}
return null;
}
@@ -195,7 +193,7 @@ public List getDataAttributeRefs(@NonNull DataAttributeRef fil
}
/**
- * Return a list of summarized Data Attribute References beginning from given this LNodeType.
+ * Return a DataAttributeRef beginning from this LNodeType.
* The key point in the algorithm is to find where the DO/SDO part ends and the DA/BDA part begins.
* Once it is found, we can use the usual method DOTypeAdapter#getDataAttributeRefs to retrieve the DataAttributeRef
* For example, with input "Do1.da1", we want to find the "da1" which is the DA/BDA part and also the DOTypeAdapter of "Do1" (to call DOTypeAdapter#getDataAttributeRefs)
@@ -209,19 +207,19 @@ public List getDataAttributeRefs(@NonNull DataAttributeRef fil
*
* @param dataRef complete reference of Data Attribute including DO name, SDO names (optional), DA name, BDA names (optional).
* Ex: Do.sdo1.sdo2.da.bda1.bda2
- * @return list of completed Data Attribute References beginning from this LNodeType.
+ * @return the Data Attribute References beginning from this LNodeType.
* @apiNote This method doesn't check relationship between DO/SDO and DA. Check should be done by caller
*/
- public DataAttributeRef getDataAttributeRefs(@NonNull String dataRef) {
+ public Optional getDataAttributeRef(@NonNull String dataRef) {
LinkedList dataRefList = new LinkedList<>(Arrays.asList(dataRef.split("\\.")));
- if (dataRefList.size() < 2) {
- throw new ScdException("Invalid data reference %s. At least DO name and DA name are required".formatted(dataRef));
+ if (dataRefList.size() < 2 || dataRefList.stream().anyMatch(StringUtils::isBlank)) {
+ throw new ScdException("Invalid data reference '%s'. At least DO name and DA name are required".formatted(dataRef));
}
// 1. Get the DO
String doName = dataRefList.remove();
return getDOAdapterByName(doName)
.flatMap(doAdapter -> getDataTypeTemplateAdapter().getDOTypeAdapterById(doAdapter.getType()))
- .map(doTypeAdapter -> {
+ .flatMap(doTypeAdapter -> {
// 2. find the SDOs, if any
List sdoAccumulator = new ArrayList<>();
List daAccumulator = new ArrayList<>();
@@ -251,17 +249,14 @@ public DataAttributeRef getDataAttributeRefs(@NonNull String dataRef) {
rootDataAttributeRef.setDoName(doTypeName);
DataAttributeRef filter = new DataAttributeRef();
filter.setDaName(new DaTypeName(String.join(".", daAccumulator)));
- List dataAttributeRefs = deepestDo.getDataAttributeRefs(rootDataAttributeRef, filter);
- // We want exactly one result
- if (dataAttributeRefs.size() > 1) {
- throw new ScdException("Multiple Data Attribute found for this data reference %s in LNodeType.lnClass=%s, LNodeType.id=%s. Found DA : %s ".formatted(dataRef, getLNClass(), getId(), dataAttributeRefs.stream().map(DataAttributeRef::getDataAttributes).collect(Collectors.joining(", "))));
- }
- if (dataAttributeRefs.isEmpty() || !dataRef.equals(dataAttributeRefs.get(0).getDataAttributes())) {
- return null;
- }
- return dataAttributeRefs.get(0);
- }).orElseThrow(() ->
- new ScdException("No Data Attribute found with this reference %s for LNodeType.lnClass=%s, LNodeType.id=%s ".formatted(dataRef, getLNClass(), getId())));
+ return deepestDo.getDataAttributeRefs(rootDataAttributeRef, filter)
+ .stream()
+ .filter(dataAttributeRef -> dataRef.equals(dataAttributeRef.getDataAttributes()))
+ .reduce((dar1, dar2) -> {
+ // 2 data attribute ref with the same name in the same LNodeType means the SCL is not valid, according to XSD
+ throw new ScdException("Multiple Data Attribute found for this data reference '%s' in LNodeType.lnClass=%s, LNodeType.id=%s. Found DA : '%s' ".formatted(dataRef, getLNClass(), getId(), dar1));
+ });
+ });
}
/**
@@ -353,7 +348,7 @@ public void checkDoAndDaTypeName(@NonNull DoTypeName doTypeName, @NonNull DaType
daTypeName.setFc(daAdapter.getCurrentElem().getFc());
DATypeAdapter daTypeAdapter = parentAdapter.getDATypeAdapterById(daAdapter.getType())
.orElseThrow(() -> new ScdException(String.format("Unknown DAType (%s) referenced by DA(%s)", daAdapter.getType(), daAdapter.getName()))
- );
+ );
daTypeAdapter.check(daTypeName);
}
}
diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateTestUtils.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateTestUtils.java
index 6f68ab934..020e9ae7e 100644
--- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateTestUtils.java
+++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/DataTypeTemplateTestUtils.java
@@ -15,6 +15,7 @@ public class DataTypeTemplateTestUtils {
public static final String SCD_DTT = "/dtt-test-schema-conf/scd_dtt_import_test.xml";
public static final String SCD_DTT_DIFF_CONTENT_SAME_ID = "/dtt-test-schema-conf/scd_dtt_import_sameid-diff-content-test.xml";
public static final String SCD_DTT_DO_SDO_DA_BDA = "/dtt-test-schema-conf/scd_dtt_do_sdo_da_bda.xml";
+ public static final String SCD_DTT_DO_MULTIPLE_DA = "/dtt-test-schema-conf/scd_dtt_do_multiple_da.xml";
public static DataTypeTemplateAdapter initDttAdapterFromFile(String fileName) {
SCL scd = SclTestMarshaller.getSCLFromFile(fileName);
diff --git a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapterTest.java b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapterTest.java
index 319c2ee14..e72bfefd1 100644
--- a/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapterTest.java
+++ b/sct-commons/src/test/java/org/lfenergy/compas/sct/commons/scl/dtt/LNodeTypeAdapterTest.java
@@ -14,6 +14,7 @@
import org.lfenergy.compas.sct.commons.scl.SclRootAdapter;
import java.util.List;
+import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -126,7 +127,7 @@ void containsDOWithDOTypeId_should_check_if_LNodeType_contains_DO_with_specific_
void testGetDataAttributeRefs() {
// Given
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT);
- LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").get());
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
DataAttributeRef rootDataAttributeRef = new DataAttributeRef();
rootDataAttributeRef.setDoName(new DoTypeName("Op"));
DataAttributeRef filter = new DataAttributeRef();
@@ -151,39 +152,43 @@ void testGetDataAttributeRefs() {
@Test
@Tag("issue-321")
- void testGetDataAttributeRefsString() {
+ void getDataAttributeRef_should_succeed() {
// Given
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT);
- LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").get());
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
// When
- var dataAttributeRefs = lNodeTypeAdapter.getDataAttributeRefs("StrVal.origin.origin.ctlVal");
+ Optional dataAttributeRefs = lNodeTypeAdapter.getDataAttributeRef("StrVal.origin.origin.ctlVal");
// Then
- assertThat(dataAttributeRefs).isNotNull();
+ assertThat(dataAttributeRefs).isPresent();
}
@Test
- void getDataAttributeRefs_should_find_DO_SDO_DA_and_BDA() {
+ void getDataAttributeRef_should_find_DO_SDO_DA_and_BDA() {
// Given
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_SDO_DA_BDA);
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
// When
- DataAttributeRef dataAttributeRefs = lNodeTypeAdapter.getDataAttributeRefs("Do1.sdo1.sdo2.da2.bda1.bda2");
+ Optional optDataAttributeRef = lNodeTypeAdapter.getDataAttributeRef("Do1.sdo1.sdo2.da2.bda1.bda2");
// Then
- assertThat(dataAttributeRefs).extracting(DataAttributeRef::getDoRef, DataAttributeRef::getDaRef)
+ assertThat(optDataAttributeRef).isPresent();
+ DataAttributeRef dataAttributeRef = optDataAttributeRef.get();
+ assertThat(dataAttributeRef).extracting(DataAttributeRef::getDoRef, DataAttributeRef::getDaRef)
.containsExactly("Do1.sdo1.sdo2", "da2.bda1.bda2");
- assertThat(dataAttributeRefs.getDoName().getCdc()).isEqualTo(TPredefinedCDCEnum.WYE);
- assertThat(dataAttributeRefs.getDaName()).extracting(DaTypeName::getBType, DaTypeName::getFc)
+ assertThat(dataAttributeRef.getDoName().getCdc()).isEqualTo(TPredefinedCDCEnum.WYE);
+ assertThat(dataAttributeRef.getDaName()).extracting(DaTypeName::getBType, DaTypeName::getFc)
.containsExactly(TPredefinedBasicTypeEnum.ENUM, TFCEnum.ST);
}
@Test
- void getDataAttributeRefs_should_find_DO_and_DA() {
+ void getDataAttributeRef_should_find_DO_and_DA() {
// Given
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_SDO_DA_BDA);
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
// When
- DataAttributeRef dataAttributeRefs = lNodeTypeAdapter.getDataAttributeRefs("Do1.da1");
+ Optional optDataAttributeRefs = lNodeTypeAdapter.getDataAttributeRef("Do1.da1");
// Then
+ assertThat(optDataAttributeRefs).isPresent();
+ DataAttributeRef dataAttributeRefs = optDataAttributeRefs.get();
assertThat(dataAttributeRefs).extracting(DataAttributeRef::getDoRef, DataAttributeRef::getDaRef)
.containsExactly("Do1", "da1");
assertThat(dataAttributeRefs.getDoName().getCdc()).isEqualTo(TPredefinedCDCEnum.WYE);
@@ -191,23 +196,54 @@ void getDataAttributeRefs_should_find_DO_and_DA() {
.containsExactly(TPredefinedBasicTypeEnum.BOOLEAN, TFCEnum.ST);
}
+
+ @Test
+ void getDataAttributeRef_when_same_da_in_multiple_sdo_should_find_the_correct_DO_DA() {
+ // Given
+ DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_MULTIPLE_DA);
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
+ // When
+ Optional optDataAttributeRef = lNodeTypeAdapter.getDataAttributeRef("Do1.da1");
+ // Then
+ assertThat(optDataAttributeRef).isPresent();
+ DataAttributeRef dataAttributeRef = optDataAttributeRef.get();
+ assertThat(dataAttributeRef).extracting(DataAttributeRef::getDoRef, DataAttributeRef::getDaRef)
+ .containsExactly("Do1", "da1");
+ assertThat(dataAttributeRef.getDoName().getCdc()).isEqualTo(TPredefinedCDCEnum.WYE);
+ assertThat(dataAttributeRef.getDaName()).extracting(DaTypeName::getBType, DaTypeName::getFc)
+ .containsExactly(TPredefinedBasicTypeEnum.BOOLEAN, TFCEnum.ST);
+ }
+
+
@ParameterizedTest
- @ValueSource(strings = {"", "malformed", "Do1", "InexistantDo.da1", "Do1.inexistantDa", "Do1.da1.inexistantBda", "Do1.sdo1.inexistantSdo.da2", "Do1.sdo1.sdo2.da2.bda1.inexistantBda"})
- void getDataAttributeRefs_when_dataRef_not_found_should_throw_exception(String dataRef) {
+ @ValueSource(strings = {"", "malformed", "Do1", "Do1.", ".da1"})
+ void getDataAttributeRef_when_dataRef_parameter_malformed_should_throw_exception(String dataRef) {
// Given
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_SDO_DA_BDA);
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
// When & Then
- assertThatThrownBy(() -> lNodeTypeAdapter.getDataAttributeRefs(dataRef))
+ assertThatThrownBy(() -> lNodeTypeAdapter.getDataAttributeRef(dataRef))
.isInstanceOf(ScdException.class);
}
+ @ParameterizedTest
+ @ValueSource(strings = {"InexistantDo.da1", "Do1.inexistantDa", "Do1.da1.inexistantBda", "Do1.sdo1.inexistantSdo.da2", "Do1.sdo1.sdo2.da2.bda1.inexistantBda"})
+ void getDataAttributeRef_when_dataRef_not_found_should_return_empty(String dataRef) {
+ // Given
+ DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_SDO_DA_BDA);
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
+ // When & Then
+ Optional optDataAttributeRef = lNodeTypeAdapter.getDataAttributeRef(dataRef);
+ // Then
+ assertThat(optDataAttributeRef).isEmpty();
+ }
+
@Test
@Tag("issue-321")
void testCheck() {
// Given
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT);
- LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").get());
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
DoTypeName doTypeName1 = new DoTypeName("");
DaTypeName daTypeName1 = new DaTypeName("");
// When Then
@@ -239,7 +275,7 @@ void getDataAttributeRefByDaName_should_return_list_of_DataAttributeRef() {
// Given
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DIFF_CONTENT_SAME_ID);
DaTypeName daTypeName = new DaTypeName("antRef","origin.ctlVal");
- LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").get());
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
// When
List dataAttributeRefs = assertDoesNotThrow(()-> lNodeTypeAdapter.getDataAttributeRefByDaName(daTypeName));
// Then
@@ -271,7 +307,7 @@ void addPrivate_with_type_and_source_should_create_Private() {
void elementXPath_should_return_expected_xpath_value() {
// Given
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT);
- LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() ->dttAdapter.getLNodeTypeAdapterById("LN1").get());
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow());
// When
String result = lNodeTypeAdapter.elementXPath();
// Then
@@ -299,7 +335,7 @@ void findMatchingDOType_shouldFindOneDO() {
dttAdapter.getCurrentElem().getDOType().add(tdoType);
dttAdapter.getCurrentElem().getLNodeType().add(tlNodeType);
- LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").get());
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").orElseThrow());
ExtRefSignalInfo signalInfo = new ExtRefSignalInfo();
signalInfo.setPDO("P_DO12");
@@ -335,7 +371,7 @@ void checkMatchingDOType_whenDOUnknown_shouldThrowException() {
dttAdapter.getCurrentElem().getDOType().add(tdoType);
dttAdapter.getCurrentElem().getLNodeType().add(tlNodeType);
- LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").get());
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").orElseThrow());
//When Then
assertThatThrownBy(() -> lNodeTypeAdapter.findMatchingDOType(signalInfo))
.isInstanceOf(IllegalArgumentException.class)
@@ -368,7 +404,7 @@ void checkMatchingDOType_whenDONotReferenced_shouldThrowException() {
dttAdapter.getCurrentElem().getDOType().add(tdoType);
dttAdapter.getCurrentElem().getLNodeType().add(tlNodeType);
- LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").get());
+ LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").orElseThrow());
//When Then
assertThatThrownBy(() -> lNodeTypeAdapter.findMatchingDOType(signalInfo))
.isInstanceOf(IllegalArgumentException.class)
diff --git a/sct-commons/src/test/resources/dtt-test-schema-conf/scd_dtt_do_multiple_da.xml b/sct-commons/src/test/resources/dtt-test-schema-conf/scd_dtt_do_multiple_da.xml
new file mode 100644
index 000000000..272b73d1c
--- /dev/null
+++ b/sct-commons/src/test/resources/dtt-test-schema-conf/scd_dtt_do_multiple_da.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+