From 1e0afe9a72de0b203affd5d405353602857e8444 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 6 Aug 2025 23:27:52 +0200 Subject: [PATCH 1/7] Add tests for `ElementUtils.operationalLimitsGroupToMapDataCurrentLimits` --- .../network/map/utils/ElementUtilsTest.java | 155 ++++++++++++++++++ .../network/map/utils/MockUtils.java | 43 +++++ 2 files changed, 198 insertions(+) create mode 100644 src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java create mode 100644 src/test/java/org/gridsuite/network/map/utils/MockUtils.java diff --git a/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java b/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java new file mode 100644 index 00000000..e86d2b83 --- /dev/null +++ b/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java @@ -0,0 +1,155 @@ +package org.gridsuite.network.map.utils; + +import com.powsybl.iidm.network.OperationalLimitsGroup; +import org.assertj.core.api.WithAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.gridsuite.network.map.dto.common.CurrentLimitsData; +import org.gridsuite.network.map.dto.common.TemporaryLimitData; +import org.gridsuite.network.map.dto.utils.ElementUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@ExtendWith({ SoftAssertionsExtension.class, MockitoExtension.class }) +class ElementUtilsTest implements WithAssertions { + /** Tests for {@link ElementUtils#operationalLimitsGroupToMapDataCurrentLimits(OperationalLimitsGroup)} */ + @Nested + @DisplayName("fn operationalLimitsGroupToMapDataCurrentLimits(…)") + class OperationalLimitsGroupToMapDataCurrentLimits { + private Collection mocks = new ArrayList<>(); + + @AfterEach + void setDown() { + if (!mocks.isEmpty()) { + Mockito.verifyNoMoreInteractions(mocks.toArray(Object[]::new)); + mocks.clear(); + } + } + + /* All cases possibles: + * null -> null + * operationalLimitsGroup(currentLimits=empty, *=any) -> null + * operationalLimitsGroup(currentLimits={PermanentLimit=NaN, TemporaryLimits=null, *=any}, *=any) -> null + * operationalLimitsGroup(currentLimits={PermanentLimit=NaN, TemporaryLimits=[], *=any}, *=any) -> null + * operationalLimitsGroup(currentLimits={permanentLimit=NaN, temporaryLimits=[any], *=any}, *=any) -> (id=*, applicability=null, permanentLimit=null, temporaryLimits=[any]) + * operationalLimitsGroup(currentLimits={PermanentLimit=_non_NaN_, TemporaryLimits=null, *=any}, *=any) -> (id=*, applicability=null, permanentLimit=*, temporaryLimits=null) + * operationalLimitsGroup(currentLimits={PermanentLimit=_non_NaN_, TemporaryLimits=[], *=any}, *=any) -> (id=*, applicability=null, permanentLimit=*, temporaryLimits=null) + * operationalLimitsGroup(currentLimits={PermanentLimit=_non_NaN_, TemporaryLimits=[any], *=any}, *=any) -> (id=*, applicability=null, permanentLimit=*, temporaryLimits=[any]) + */ + + @Test + void nullInput() { + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(null)).isNull(); + } + + @Test + void emptyCurrentLimits() { + final var mock = MockUtils.mockOperationalLimitsGroup("id", null); + mocks.add(mock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(mock)).isNull(); + Mockito.verify(mock).getCurrentLimits(); + } + + @Test + void nanPermLimitAndNullTempLimit() { + final var clMock = MockUtils.mockCurrentLimits(Double.NaN, null); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isNull(); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock).getPermanentLimit(); + Mockito.verify(clMock).getTemporaryLimits(); + } + + @Test + void nanPermLimitAndEmptyTempLimit() { + final var clMock = MockUtils.mockCurrentLimits(Double.NaN, List.of()); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isNull(); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock).getPermanentLimit(); + Mockito.verify(clMock).getTemporaryLimits(); + } + + @Test + void nanPermLimitAndNonEmptyTempLimit() { + final var tlMock = MockUtils.mockTemporaryLimits(123, "testLimit", 456.789); + mocks.add(tlMock); + final var clMock = MockUtils.mockCurrentLimits(Double.NaN, List.of(tlMock)); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() + .id("my id").applicability(null).permanentLimit(null).temporaryLimits(List.of(TemporaryLimitData.builder() + .acceptableDuration(123).name("testLimit").value(456.789).build())).build()); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock).getPermanentLimit(); + Mockito.verify(clMock, Mockito.times(2)).getTemporaryLimits(); + Mockito.verify(tlMock, Mockito.times(2)).getAcceptableDuration(); + Mockito.verify(tlMock).getName(); + Mockito.verify(tlMock, Mockito.times(2)).getValue(); + } + + @Test + void nonNanPermLimitAndNullTempLimit() { + final var clMock = MockUtils.mockCurrentLimits(0.123, null); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() + .id("my id").applicability(null).permanentLimit(0.123).temporaryLimits(null).build()); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); + Mockito.verify(clMock).getTemporaryLimits(); + } + + @Test + void nonNanPermLimitAndEmptyTempLimit() { + final var clMock = MockUtils.mockCurrentLimits(0.123, List.of()); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() + .id("my id").applicability(null).permanentLimit(0.123).temporaryLimits(null).build()); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); + Mockito.verify(clMock).getTemporaryLimits(); + } + + @Test + void nonNanPermLimitAndNonEmptyTempLimit() { + final var tlMock = MockUtils.mockTemporaryLimits(123, "testLimit", 456.789); + mocks.add(tlMock); + final var clMock = MockUtils.mockCurrentLimits(0.0, List.of(tlMock)); + mocks.add(clMock); + final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); + mocks.add(olgMock); + assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() + .id("my id").applicability(null).permanentLimit(0.0).temporaryLimits(List.of(TemporaryLimitData.builder() + .acceptableDuration(123).name("testLimit").value(456.789).build())).build()); + Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); + Mockito.verify(olgMock).getId(); + Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); + Mockito.verify(clMock, Mockito.times(2)).getTemporaryLimits(); + Mockito.verify(tlMock, Mockito.times(2)).getAcceptableDuration(); + Mockito.verify(tlMock).getName(); + Mockito.verify(tlMock, Mockito.times(2)).getValue(); + } + } +} diff --git a/src/test/java/org/gridsuite/network/map/utils/MockUtils.java b/src/test/java/org/gridsuite/network/map/utils/MockUtils.java new file mode 100644 index 00000000..77a4e276 --- /dev/null +++ b/src/test/java/org/gridsuite/network/map/utils/MockUtils.java @@ -0,0 +1,43 @@ +package org.gridsuite.network.map.utils; + +import com.powsybl.iidm.network.CurrentLimits; +import com.powsybl.iidm.network.LoadingLimits.TemporaryLimit; +import com.powsybl.iidm.network.OperationalLimitsGroup; +import org.mockito.Mockito; +import org.mockito.quality.Strictness; + +import java.util.List; +import java.util.Optional; + +public final class MockUtils { + private MockUtils() { + throw new InstantiationError("Utility Class cannot be instantiated."); + } + + public static TemporaryLimit mockTemporaryLimits(final int acceptableDuration, final String name, final double value) { + TemporaryLimit mock = Mockito.mock(TemporaryLimit.class); + Mockito.when(mock.getAcceptableDuration()).thenReturn(acceptableDuration); + Mockito.when(mock.getName()).thenReturn(name); + Mockito.when(mock.getValue()).thenReturn(value); + return mock; + } + + public static CurrentLimits mockCurrentLimits(double permanentLimit, List temporaryLimits) { + CurrentLimits mock = Mockito.mock(CurrentLimits.class); + //Mockito.when(mock.getLimitType()).thenCallRealMethod(); + Mockito.when(mock.getPermanentLimit()).thenReturn(permanentLimit); + Mockito.when(mock.getTemporaryLimits()).thenReturn(temporaryLimits); + return mock; + } + + public static OperationalLimitsGroup mockOperationalLimitsGroup(final String id, double permanentLimit, List temporaryLimits) { + return mockOperationalLimitsGroup(id, mockCurrentLimits(permanentLimit, temporaryLimits)); + } + + public static OperationalLimitsGroup mockOperationalLimitsGroup(final String id, final CurrentLimits cl) { + OperationalLimitsGroup mock = Mockito.mock(OperationalLimitsGroup.class, Mockito.withSettings().strictness(Strictness.LENIENT)); + Mockito.when(mock.getId()).thenReturn(id); + Mockito.when(mock.getCurrentLimits()).thenReturn(Optional.ofNullable(cl)); + return mock; + } +} From 1e5ee8ec46b5a9ea59b31c07c369216884661c93 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 6 Aug 2025 23:35:30 +0200 Subject: [PATCH 2/7] Add test for `ElementUtils.mergeCurrentLimits` --- .../network/map/utils/ElementUtilsTest.java | 141 +++++++++++++++++- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java b/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java index e86d2b83..b7fddbaa 100644 --- a/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java +++ b/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java @@ -1,9 +1,11 @@ package org.gridsuite.network.map.utils; import com.powsybl.iidm.network.OperationalLimitsGroup; +import org.assertj.core.api.SoftAssertions; import org.assertj.core.api.WithAssertions; import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; import org.gridsuite.network.map.dto.common.CurrentLimitsData; +import org.gridsuite.network.map.dto.common.CurrentLimitsData.Applicability; import org.gridsuite.network.map.dto.common.TemporaryLimitData; import org.gridsuite.network.map.dto.utils.ElementUtils; import org.junit.jupiter.api.AfterEach; @@ -11,12 +13,18 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.stream.Stream; @ExtendWith({ SoftAssertionsExtension.class, MockitoExtension.class }) class ElementUtilsTest implements WithAssertions { @@ -93,8 +101,8 @@ void nanPermLimitAndNonEmptyTempLimit() { final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); mocks.add(olgMock); assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() - .id("my id").applicability(null).permanentLimit(null).temporaryLimits(List.of(TemporaryLimitData.builder() - .acceptableDuration(123).name("testLimit").value(456.789).build())).build()); + .id("my id").applicability(null).permanentLimit(null).temporaryLimits(List.of(TemporaryLimitData.builder() + .acceptableDuration(123).name("testLimit").value(456.789).build())).build()); Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); Mockito.verify(olgMock).getId(); Mockito.verify(clMock).getPermanentLimit(); @@ -111,7 +119,7 @@ void nonNanPermLimitAndNullTempLimit() { final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); mocks.add(olgMock); assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() - .id("my id").applicability(null).permanentLimit(0.123).temporaryLimits(null).build()); + .id("my id").applicability(null).permanentLimit(0.123).temporaryLimits(null).build()); Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); Mockito.verify(olgMock).getId(); Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); @@ -125,7 +133,7 @@ void nonNanPermLimitAndEmptyTempLimit() { final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); mocks.add(olgMock); assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() - .id("my id").applicability(null).permanentLimit(0.123).temporaryLimits(null).build()); + .id("my id").applicability(null).permanentLimit(0.123).temporaryLimits(null).build()); Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); Mockito.verify(olgMock).getId(); Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); @@ -141,8 +149,8 @@ void nonNanPermLimitAndNonEmptyTempLimit() { final var olgMock = MockUtils.mockOperationalLimitsGroup("my id", clMock); mocks.add(olgMock); assertThat(ElementUtils.operationalLimitsGroupToMapDataCurrentLimits(olgMock)).isEqualTo(CurrentLimitsData.builder() - .id("my id").applicability(null).permanentLimit(0.0).temporaryLimits(List.of(TemporaryLimitData.builder() - .acceptableDuration(123).name("testLimit").value(456.789).build())).build()); + .id("my id").applicability(null).permanentLimit(0.0).temporaryLimits(List.of(TemporaryLimitData.builder() + .acceptableDuration(123).name("testLimit").value(456.789).build())).build()); Mockito.verify(olgMock, Mockito.times(2)).getCurrentLimits(); Mockito.verify(olgMock).getId(); Mockito.verify(clMock, Mockito.times(2)).getPermanentLimit(); @@ -152,4 +160,125 @@ void nonNanPermLimitAndNonEmptyTempLimit() { Mockito.verify(tlMock, Mockito.times(2)).getValue(); } } + + /** Tests for {@link ElementUtils#mergeCurrentLimits(Collection, Collection, Consumer)} */ + @Nested + @DisplayName("fn mergeCurrentLimits(…, …, …)") + class MergeCurrentLimitsTest { + @ParameterizedTest(name = ParameterizedTest.INDEX_PLACEHOLDER) + @MethodSource("mergeCurrentLimitsTestData") + void shouldNotThrow( + final Collection olg1, + final Collection olg2, + final List expected) { + AtomicReference> results = new AtomicReference<>(); + ElementUtils.mergeCurrentLimits(olg1, olg2, results::set); + assertThat(results.get()).as("Result").isEqualTo(expected); + } + + private static Stream mergeCurrentLimitsTestData() { + return Stream.of( + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.EQUIPMENT).build() + )), + Arguments.of(List.of(), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE2).build() + )), + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of(), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE1).build() + )), + // TODO get two dto but because hasLimit() condition always false + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of()) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE2).build() + )), + // TODO java.lang.NullPointerException: Cannot invoke "org.gridsuite.network.map.dto.common.CurrentLimitsData.getId()" because "limitsData" is null + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of()) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE2).build() + )), + // TODO java.lang.NullPointerException: Cannot invoke "java.lang.Double.doubleValue()" because "this.permanentLimit" is null + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE2).build() + )), + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of()) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE1).build() + )), + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of()) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE1).build() + )), + Arguments.of(List.of( + MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0))) + ), List.of( + CurrentLimitsData.builder().id("group1").permanentLimit(220.0).temporaryLimits(List.of( + TemporaryLimitData.builder().acceptableDuration(100).name("temporary1").value(50.0).build(), + TemporaryLimitData.builder().acceptableDuration(150).name("temporary2").value(70.0).build() + )).applicability(Applicability.SIDE1).build() + )) + ); + } + + @Test + void shouldThrowOnNanLimit(final SoftAssertions softly) { + final var l1 = List.of(MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0)))); + final var l2 = List.of(MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0)))); + AtomicReference> results = new AtomicReference<>(); + softly.assertThatNullPointerException().isThrownBy(() -> ElementUtils.mergeCurrentLimits(l1, l2, results::set)); + softly.assertThatNullPointerException().isThrownBy(() -> ElementUtils.mergeCurrentLimits(l2, l1, results::set)); + } + + // TODO what to do when one side has duplicate ID? + } } From 415c200344719e74b48d79a1eb968853fd8718b5 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 6 Aug 2025 23:44:27 +0200 Subject: [PATCH 3/7] quick refactor --- .../network/map/dto/utils/ElementUtils.java | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java index 0bcdae1d..6e6891b4 100644 --- a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java +++ b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java @@ -102,6 +102,7 @@ private static CurrentLimitsData copyCurrentLimitsData(CurrentLimitsData current } /** + * Combine 2 sides in one list. * @return id of the selected operation limits group 1 and 2 if they have been renamed */ public static void mergeCurrentLimits(Collection operationalLimitsGroups1, @@ -110,23 +111,22 @@ public static void mergeCurrentLimits(Collection operati List mergedLimitsData = new ArrayList<>(); // Build temporary limit from side 1 and 2 - List currentLimitsData1 = operationalLimitsGroups1.stream() + final List currentLimitsData1 = operationalLimitsGroups1.stream() .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits).toList(); - ArrayList currentLimitsData2 = new ArrayList<>(operationalLimitsGroups2.stream() - .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits).toList()); - - // combine 2 sides in one list + final List currentLimitsData2 = operationalLimitsGroups2.stream() + .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) + .collect(Collectors.toCollection(ArrayList::new)); // simple case : one of the arrays are empty if (currentLimitsData2.isEmpty() && !currentLimitsData1.isEmpty()) { - for (CurrentLimitsData currentLimitsData : currentLimitsData1) { + for (final CurrentLimitsData currentLimitsData : currentLimitsData1) { mergedLimitsData.add(copyCurrentLimitsData(currentLimitsData, SIDE1)); } build.accept(mergedLimitsData); return; } if (currentLimitsData1.isEmpty() && !currentLimitsData2.isEmpty()) { - for (CurrentLimitsData currentLimitsData : currentLimitsData2) { + for (final CurrentLimitsData currentLimitsData : currentLimitsData2) { mergedLimitsData.add(copyCurrentLimitsData(currentLimitsData, SIDE2)); } build.accept(mergedLimitsData); @@ -134,31 +134,21 @@ public static void mergeCurrentLimits(Collection operati } // more complex case - for (CurrentLimitsData limitsData : currentLimitsData1) { - Optional l2 = currentLimitsData2.stream().filter(l -> l.getId().equals(limitsData.getId())).findFirst(); - + for (final CurrentLimitsData limitsData : currentLimitsData1) { + final Optional l2 = currentLimitsData2.stream().filter(l -> l.getId().equals(limitsData.getId())).findFirst(); if (l2.isPresent()) { CurrentLimitsData limitsData2 = l2.get(); - // Only side one has limits - if (limitsData.hasLimits() && !limitsData2.hasLimits()) { + if (limitsData.hasLimits() && !limitsData2.hasLimits()) { // Only side one has limits + mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); + } else if (limitsData2.hasLimits() && !limitsData.hasLimits()) { // only side two has limits + mergedLimitsData.add(copyCurrentLimitsData(limitsData2, SIDE2)); + } else if (limitsData.limitsEquals(limitsData2)) { // both sides have limits and limits are equals + mergedLimitsData.add(copyCurrentLimitsData(limitsData, EQUIPMENT)); + } else { // both side have limits and are different : create 2 different limit sets mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); - // only side two has limits - } else if (limitsData2.hasLimits() && !limitsData.hasLimits()) { mergedLimitsData.add(copyCurrentLimitsData(limitsData2, SIDE2)); - } else { - // both sides have limits and limits are equals - if (limitsData.limitsEquals(limitsData2)) { - mergedLimitsData.add(copyCurrentLimitsData(limitsData, EQUIPMENT)); - // both side have limits and they are different : create 2 different limit sets - } else { - // Side 1 - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); - // Side 2 - mergedLimitsData.add(copyCurrentLimitsData(limitsData2, SIDE2)); - } } - // remove processed limits from side 2 - currentLimitsData2.remove(l2.get()); + currentLimitsData2.remove(l2.get()); // remove processed limits from side 2 } else { mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); } From d511ad4f5fb754c552fd497859c9f5e3da6f2b9a Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 6 Aug 2025 23:47:33 +0200 Subject: [PATCH 4/7] replace utility function by lombok ;) --- .../map/dto/common/CurrentLimitsData.java | 2 ++ .../network/map/dto/utils/ElementUtils.java | 26 +++++++------------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java index e7d5e5da..0ea0dd45 100644 --- a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java +++ b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java @@ -10,6 +10,7 @@ import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.With; import org.springframework.util.CollectionUtils; import java.util.List; @@ -20,6 +21,7 @@ * == a powsybl OperationalLimitsGroup == a LimitSet */ @Builder +@With @Getter @EqualsAndHashCode public class CurrentLimitsData { diff --git a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java index 6e6891b4..208c0ce5 100644 --- a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java +++ b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java @@ -93,14 +93,6 @@ public static void buildCurrentLimits(Collection current } } - private static CurrentLimitsData copyCurrentLimitsData(CurrentLimitsData currentLimitsData, CurrentLimitsData.Applicability applicability) { - return CurrentLimitsData.builder() - .id(currentLimitsData.getId()) - .applicability(applicability) - .temporaryLimits(currentLimitsData.getTemporaryLimits()) - .permanentLimit(currentLimitsData.getPermanentLimit()).build(); - } - /** * Combine 2 sides in one list. * @return id of the selected operation limits group 1 and 2 if they have been renamed @@ -120,14 +112,14 @@ public static void mergeCurrentLimits(Collection operati // simple case : one of the arrays are empty if (currentLimitsData2.isEmpty() && !currentLimitsData1.isEmpty()) { for (final CurrentLimitsData currentLimitsData : currentLimitsData1) { - mergedLimitsData.add(copyCurrentLimitsData(currentLimitsData, SIDE1)); + mergedLimitsData.add(currentLimitsData.withApplicability(SIDE1)); } build.accept(mergedLimitsData); return; } if (currentLimitsData1.isEmpty() && !currentLimitsData2.isEmpty()) { for (final CurrentLimitsData currentLimitsData : currentLimitsData2) { - mergedLimitsData.add(copyCurrentLimitsData(currentLimitsData, SIDE2)); + mergedLimitsData.add(currentLimitsData.withApplicability(SIDE2)); } build.accept(mergedLimitsData); return; @@ -139,24 +131,24 @@ public static void mergeCurrentLimits(Collection operati if (l2.isPresent()) { CurrentLimitsData limitsData2 = l2.get(); if (limitsData.hasLimits() && !limitsData2.hasLimits()) { // Only side one has limits - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); + mergedLimitsData.add(limitsData.withApplicability(SIDE1)); } else if (limitsData2.hasLimits() && !limitsData.hasLimits()) { // only side two has limits - mergedLimitsData.add(copyCurrentLimitsData(limitsData2, SIDE2)); + mergedLimitsData.add(limitsData2.withApplicability(SIDE2)); } else if (limitsData.limitsEquals(limitsData2)) { // both sides have limits and limits are equals - mergedLimitsData.add(copyCurrentLimitsData(limitsData, EQUIPMENT)); + mergedLimitsData.add(limitsData.withApplicability(EQUIPMENT)); } else { // both side have limits and are different : create 2 different limit sets - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); - mergedLimitsData.add(copyCurrentLimitsData(limitsData2, SIDE2)); + mergedLimitsData.add(limitsData.withApplicability(SIDE1)); + mergedLimitsData.add(limitsData2.withApplicability(SIDE2)); } currentLimitsData2.remove(l2.get()); // remove processed limits from side 2 } else { - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE1)); + mergedLimitsData.add(limitsData.withApplicability(SIDE1)); } } // add remaining limits from side 2 for (CurrentLimitsData limitsData : currentLimitsData2) { - mergedLimitsData.add(copyCurrentLimitsData(limitsData, SIDE2)); + mergedLimitsData.add(limitsData.withApplicability(SIDE2)); } if (!mergedLimitsData.isEmpty()) { From 2a847dba9ed445ed1ce836755c5a6d0a152dad1d Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 6 Aug 2025 23:59:38 +0200 Subject: [PATCH 5/7] no need for a consumer when we can return --- .../map/dto/common/CurrentLimitsData.java | 4 ++-- .../map/dto/mapper/LineInfosMapper.java | 7 +++--- .../TwoWindingsTransformerInfosMapper.java | 7 +++--- .../network/map/dto/utils/ElementUtils.java | 24 ++++++++----------- .../network/map/utils/ElementUtilsTest.java | 13 ++++------ 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java index 0ea0dd45..b1d1efeb 100644 --- a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java +++ b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java @@ -8,8 +8,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; +import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; import lombok.With; import org.springframework.util.CollectionUtils; @@ -22,7 +22,7 @@ */ @Builder @With -@Getter +@Data @EqualsAndHashCode public class CurrentLimitsData { // may be null in case we just need the selected limit set and don't really need its name/id diff --git a/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java b/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java index b661276f..ea1c7a0c 100644 --- a/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java +++ b/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java @@ -65,11 +65,10 @@ private static LineFormInfos toFormInfos(Identifiable identifiable) { .b1(line.getB1()) .g2(line.getG2()) .b2(line.getB2()) - .properties(getProperties(line)) .selectedOperationalLimitsGroup1(line.getSelectedOperationalLimitsGroupId1().orElse(null)) - .selectedOperationalLimitsGroup2(line.getSelectedOperationalLimitsGroupId2().orElse(null)); - - mergeCurrentLimits(line.getOperationalLimitsGroups1(), line.getOperationalLimitsGroups2(), builder::currentLimits); + .selectedOperationalLimitsGroup2(line.getSelectedOperationalLimitsGroupId2().orElse(null)) + .currentLimits(mergeCurrentLimits(line.getOperationalLimitsGroups1(), line.getOperationalLimitsGroups2())) + .properties(getProperties(line)); buildCurrentLimits(line.getOperationalLimitsGroups1(), builder::currentLimits1); buildCurrentLimits(line.getOperationalLimitsGroups2(), builder::currentLimits2); diff --git a/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java b/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java index 870beffb..a1867bf8 100644 --- a/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java +++ b/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java @@ -66,6 +66,9 @@ private static TwoWindingsTransformerFormInfos toFormInfos(Identifiable ident .g(twoWT.getG()) .ratedU1(twoWT.getRatedU1()) .ratedU2(twoWT.getRatedU2()) + .selectedOperationalLimitsGroup1(twoWT.getSelectedOperationalLimitsGroupId1().orElse(null)) + .selectedOperationalLimitsGroup2(twoWT.getSelectedOperationalLimitsGroupId2().orElse(null)) + .currentLimits(mergeCurrentLimits(twoWT.getOperationalLimitsGroups1(), twoWT.getOperationalLimitsGroups2())) .properties(getProperties(twoWT)); builder.busOrBusbarSectionId1(getBusOrBusbarSection(terminal1)) @@ -77,10 +80,6 @@ private static TwoWindingsTransformerFormInfos toFormInfos(Identifiable ident builder.q2(nullIfNan(terminal2.getQ())); builder.i1(nullIfNan(terminal1.getI())); builder.i2(nullIfNan(terminal2.getI())); - builder.selectedOperationalLimitsGroup1(twoWT.getSelectedOperationalLimitsGroupId1().orElse(null)); - builder.selectedOperationalLimitsGroup2(twoWT.getSelectedOperationalLimitsGroupId2().orElse(null)); - - mergeCurrentLimits(twoWT.getOperationalLimitsGroups1(), twoWT.getOperationalLimitsGroups2(), builder::currentLimits); buildCurrentLimits(twoWT.getOperationalLimitsGroups1(), builder::currentLimits1); buildCurrentLimits(twoWT.getOperationalLimitsGroups2(), builder::currentLimits2); diff --git a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java index 208c0ce5..1155139a 100644 --- a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java +++ b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java @@ -13,6 +13,7 @@ import org.gridsuite.network.map.dto.common.*; import org.gridsuite.network.map.dto.definition.extension.*; import org.gridsuite.network.map.dto.definition.threewindingstransformer.ThreeWindingsTransformerTabInfos; +import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import java.util.ArrayList; @@ -97,11 +98,9 @@ public static void buildCurrentLimits(Collection current * Combine 2 sides in one list. * @return id of the selected operation limits group 1 and 2 if they have been renamed */ - public static void mergeCurrentLimits(Collection operationalLimitsGroups1, - Collection operationalLimitsGroups2, - Consumer> build) { - List mergedLimitsData = new ArrayList<>(); - + @Nullable + public static List mergeCurrentLimits(final Collection operationalLimitsGroups1, + final Collection operationalLimitsGroups2) { // Build temporary limit from side 1 and 2 final List currentLimitsData1 = operationalLimitsGroups1.stream() .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits).toList(); @@ -112,20 +111,19 @@ public static void mergeCurrentLimits(Collection operati // simple case : one of the arrays are empty if (currentLimitsData2.isEmpty() && !currentLimitsData1.isEmpty()) { for (final CurrentLimitsData currentLimitsData : currentLimitsData1) { - mergedLimitsData.add(currentLimitsData.withApplicability(SIDE1)); + currentLimitsData.setApplicability(SIDE1); } - build.accept(mergedLimitsData); - return; + return currentLimitsData1; } if (currentLimitsData1.isEmpty() && !currentLimitsData2.isEmpty()) { for (final CurrentLimitsData currentLimitsData : currentLimitsData2) { - mergedLimitsData.add(currentLimitsData.withApplicability(SIDE2)); + currentLimitsData.setApplicability(SIDE2); } - build.accept(mergedLimitsData); - return; + return currentLimitsData2; } // more complex case + List mergedLimitsData = new ArrayList<>(currentLimitsData1.size() + currentLimitsData2.size()); for (final CurrentLimitsData limitsData : currentLimitsData1) { final Optional l2 = currentLimitsData2.stream().filter(l -> l.getId().equals(limitsData.getId())).findFirst(); if (l2.isPresent()) { @@ -151,9 +149,7 @@ public static void mergeCurrentLimits(Collection operati mergedLimitsData.add(limitsData.withApplicability(SIDE2)); } - if (!mergedLimitsData.isEmpty()) { - build.accept(mergedLimitsData); - } + return mergedLimitsData.isEmpty() ? null : mergedLimitsData; } public static Optional toStandbyAutomaton(StaticVarCompensator staticVarCompensator) { diff --git a/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java b/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java index b7fddbaa..7353b851 100644 --- a/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java +++ b/src/test/java/org/gridsuite/network/map/utils/ElementUtilsTest.java @@ -23,7 +23,6 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; import java.util.stream.Stream; @ExtendWith({ SoftAssertionsExtension.class, MockitoExtension.class }) @@ -161,9 +160,9 @@ void nonNanPermLimitAndNonEmptyTempLimit() { } } - /** Tests for {@link ElementUtils#mergeCurrentLimits(Collection, Collection, Consumer)} */ + /** Tests for {@link ElementUtils#mergeCurrentLimits(Collection, Collection)} */ @Nested - @DisplayName("fn mergeCurrentLimits(…, …, …)") + @DisplayName("fn mergeCurrentLimits(…, …)") class MergeCurrentLimitsTest { @ParameterizedTest(name = ParameterizedTest.INDEX_PLACEHOLDER) @MethodSource("mergeCurrentLimitsTestData") @@ -171,9 +170,7 @@ void shouldNotThrow( final Collection olg1, final Collection olg2, final List expected) { - AtomicReference> results = new AtomicReference<>(); - ElementUtils.mergeCurrentLimits(olg1, olg2, results::set); - assertThat(results.get()).as("Result").isEqualTo(expected); + assertThat(ElementUtils.mergeCurrentLimits(olg1, olg2)).as("Result").isEqualTo(expected); } private static Stream mergeCurrentLimitsTestData() { @@ -275,8 +272,8 @@ void shouldThrowOnNanLimit(final SoftAssertions softly) { final var l1 = List.of(MockUtils.mockOperationalLimitsGroup("group1", Double.NaN, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0)))); final var l2 = List.of(MockUtils.mockOperationalLimitsGroup("group1", 220.0, List.of(MockUtils.mockTemporaryLimits(100, "temporary1", 50.0), MockUtils.mockTemporaryLimits(150, "temporary2", 70.0)))); AtomicReference> results = new AtomicReference<>(); - softly.assertThatNullPointerException().isThrownBy(() -> ElementUtils.mergeCurrentLimits(l1, l2, results::set)); - softly.assertThatNullPointerException().isThrownBy(() -> ElementUtils.mergeCurrentLimits(l2, l1, results::set)); + softly.assertThatNullPointerException().isThrownBy(() -> ElementUtils.mergeCurrentLimits(l1, l2)); + softly.assertThatNullPointerException().isThrownBy(() -> ElementUtils.mergeCurrentLimits(l2, l1)); } // TODO what to do when one side has duplicate ID? From 2ba88f8ad6fb395442b1338981a1deffcd2315d2 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 7 Aug 2025 00:20:00 +0200 Subject: [PATCH 6/7] `SIDE1` and `SIDE2` are default cases --- .../map/dto/common/CurrentLimitsData.java | 6 ++-- .../network/map/dto/utils/ElementUtils.java | 33 +++++++++---------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java index b1d1efeb..15df9149 100644 --- a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java +++ b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java @@ -9,8 +9,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.With; +import lombok.experimental.Accessors; import org.springframework.util.CollectionUtils; import java.util.List; @@ -21,9 +20,8 @@ * == a powsybl OperationalLimitsGroup == a LimitSet */ @Builder -@With @Data -@EqualsAndHashCode +@Accessors(chain = true) public class CurrentLimitsData { // may be null in case we just need the selected limit set and don't really need its name/id @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java index 1155139a..0b7143dc 100644 --- a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java +++ b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -103,22 +104,21 @@ public static List mergeCurrentLimits(final Collection operationalLimitsGroups2) { // Build temporary limit from side 1 and 2 final List currentLimitsData1 = operationalLimitsGroups1.stream() - .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits).toList(); + .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) + .filter(Objects::nonNull) + .map(cld -> cld.setApplicability(SIDE1)) + .toList(); final List currentLimitsData2 = operationalLimitsGroups2.stream() .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) + .filter(Objects::nonNull) + .map(cld -> cld.setApplicability(SIDE2)) .collect(Collectors.toCollection(ArrayList::new)); // simple case : one of the arrays are empty if (currentLimitsData2.isEmpty() && !currentLimitsData1.isEmpty()) { - for (final CurrentLimitsData currentLimitsData : currentLimitsData1) { - currentLimitsData.setApplicability(SIDE1); - } return currentLimitsData1; } if (currentLimitsData1.isEmpty() && !currentLimitsData2.isEmpty()) { - for (final CurrentLimitsData currentLimitsData : currentLimitsData2) { - currentLimitsData.setApplicability(SIDE2); - } return currentLimitsData2; } @@ -129,26 +129,22 @@ public static List mergeCurrentLimits(final Collection Date: Thu, 7 Aug 2025 10:47:35 +0200 Subject: [PATCH 7/7] rule no setter for dto and deduplicate code --- .../map/dto/common/CurrentLimitsData.java | 14 ++--- .../map/dto/mapper/LineInfosMapper.java | 9 ++- .../map/dto/mapper/TieLineInfosMapper.java | 4 +- .../TwoWindingsTransformerInfosMapper.java | 9 ++- .../network/map/dto/utils/ElementUtils.java | 58 +++++++++---------- 5 files changed, 44 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java index 15df9149..15bb978e 100644 --- a/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java +++ b/src/main/java/org/gridsuite/network/map/dto/common/CurrentLimitsData.java @@ -9,7 +9,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Builder; import lombok.Data; -import lombok.experimental.Accessors; +import lombok.With; import org.springframework.util.CollectionUtils; import java.util.List; @@ -17,24 +17,23 @@ /** * @author David Braquart - * == a powsybl OperationalLimitsGroup == a LimitSet */ @Builder @Data -@Accessors(chain = true) +@With // get rule to not have setter in DTOs but mergeCurrentLimits() need to get then set data... public class CurrentLimitsData { // may be null in case we just need the selected limit set and don't really need its name/id @JsonInclude(JsonInclude.Include.NON_NULL) - private String id; + private final String id; @JsonInclude(JsonInclude.Include.NON_NULL) - private Double permanentLimit; + private final Double permanentLimit; @JsonInclude(JsonInclude.Include.NON_NULL) - private List temporaryLimits; + private final List temporaryLimits; @JsonInclude - private Applicability applicability; + private final Applicability applicability; public enum Applicability { EQUIPMENT, // applied to both sides @@ -51,4 +50,3 @@ public boolean limitsEquals(CurrentLimitsData other) { && Objects.equals(temporaryLimits, other.temporaryLimits); } } - diff --git a/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java b/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java index ea1c7a0c..bbd1a2a3 100644 --- a/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java +++ b/src/main/java/org/gridsuite/network/map/dto/mapper/LineInfosMapper.java @@ -67,12 +67,11 @@ private static LineFormInfos toFormInfos(Identifiable identifiable) { .b2(line.getB2()) .selectedOperationalLimitsGroup1(line.getSelectedOperationalLimitsGroupId1().orElse(null)) .selectedOperationalLimitsGroup2(line.getSelectedOperationalLimitsGroupId2().orElse(null)) + .currentLimits1(buildCurrentLimits(line.getOperationalLimitsGroups1())) + .currentLimits2(buildCurrentLimits(line.getOperationalLimitsGroups2())) .currentLimits(mergeCurrentLimits(line.getOperationalLimitsGroups1(), line.getOperationalLimitsGroups2())) .properties(getProperties(line)); - buildCurrentLimits(line.getOperationalLimitsGroups1(), builder::currentLimits1); - buildCurrentLimits(line.getOperationalLimitsGroups2(), builder::currentLimits2); - builder.busOrBusbarSectionId1(getBusOrBusbarSection(terminal1)) .busOrBusbarSectionId2(getBusOrBusbarSection(terminal2)); @@ -206,8 +205,8 @@ private static LineTooltipInfos toTooltipInfos(Identifiable identifiable, Dou .b1(line.getB1()) .b2(line.getB2()); - line.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toMapDataCurrentLimits(limits1))); - line.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toMapDataCurrentLimits(limits2))); + line.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toCurrentLimitsData(limits1))); + line.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toCurrentLimitsData(limits2))); return builder.build(); } diff --git a/src/main/java/org/gridsuite/network/map/dto/mapper/TieLineInfosMapper.java b/src/main/java/org/gridsuite/network/map/dto/mapper/TieLineInfosMapper.java index 572fa1a7..0e3fa176 100644 --- a/src/main/java/org/gridsuite/network/map/dto/mapper/TieLineInfosMapper.java +++ b/src/main/java/org/gridsuite/network/map/dto/mapper/TieLineInfosMapper.java @@ -56,8 +56,8 @@ private static TieLineMapInfos toMapInfos(Identifiable identifiable, Double d .i2(nullIfNan(ElementUtils.computeIntensity(terminal2, dcPowerFactor))) .operatingStatus(toOperatingStatus(tieLine)); - tieLine.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toMapDataCurrentLimits(limits1))); - tieLine.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toMapDataCurrentLimits(limits2))); + tieLine.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toCurrentLimitsData(limits1))); + tieLine.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toCurrentLimitsData(limits2))); return builder.build(); } diff --git a/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java b/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java index a1867bf8..ef1c85c0 100644 --- a/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java +++ b/src/main/java/org/gridsuite/network/map/dto/mapper/TwoWindingsTransformerInfosMapper.java @@ -68,6 +68,8 @@ private static TwoWindingsTransformerFormInfos toFormInfos(Identifiable ident .ratedU2(twoWT.getRatedU2()) .selectedOperationalLimitsGroup1(twoWT.getSelectedOperationalLimitsGroupId1().orElse(null)) .selectedOperationalLimitsGroup2(twoWT.getSelectedOperationalLimitsGroupId2().orElse(null)) + .currentLimits1(buildCurrentLimits(twoWT.getOperationalLimitsGroups1())) + .currentLimits2(buildCurrentLimits(twoWT.getOperationalLimitsGroups2())) .currentLimits(mergeCurrentLimits(twoWT.getOperationalLimitsGroups1(), twoWT.getOperationalLimitsGroups2())) .properties(getProperties(twoWT)); @@ -81,9 +83,6 @@ private static TwoWindingsTransformerFormInfos toFormInfos(Identifiable ident builder.i1(nullIfNan(terminal1.getI())); builder.i2(nullIfNan(terminal2.getI())); - buildCurrentLimits(twoWT.getOperationalLimitsGroups1(), builder::currentLimits1); - buildCurrentLimits(twoWT.getOperationalLimitsGroups2(), builder::currentLimits2); - builder.operatingStatus(toOperatingStatus(twoWT)); builder.connectablePosition1(toMapConnectablePosition(twoWT, 1)) .connectablePosition2(toMapConnectablePosition(twoWT, 2)); @@ -209,8 +208,8 @@ private static TwoWindingsTransformerTooltipInfos toTooltipInfos(Identifiable .x(twoWindingsTransformer.getX()) .b(twoWindingsTransformer.getB()); - twoWindingsTransformer.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toMapDataCurrentLimits(limits1))); - twoWindingsTransformer.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toMapDataCurrentLimits(limits2))); + twoWindingsTransformer.getCurrentLimits1().ifPresent(limits1 -> builder.currentLimits1(toCurrentLimitsData(limits1))); + twoWindingsTransformer.getCurrentLimits2().ifPresent(limits2 -> builder.currentLimits2(toCurrentLimitsData(limits2))); return builder.build(); } diff --git a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java index 0b7143dc..2fc2389a 100644 --- a/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java +++ b/src/main/java/org/gridsuite/network/map/dto/utils/ElementUtils.java @@ -11,8 +11,10 @@ import com.powsybl.iidm.network.extensions.*; import com.powsybl.math.graph.TraversalType; import org.gridsuite.network.map.dto.common.*; +import org.gridsuite.network.map.dto.common.CurrentLimitsData.CurrentLimitsDataBuilder; import org.gridsuite.network.map.dto.definition.extension.*; import org.gridsuite.network.map.dto.definition.threewindingstransformer.ThreeWindingsTransformerTabInfos; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; @@ -23,7 +25,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -86,13 +87,14 @@ public static Optional toHvdcOperatorActivePo .oprFromCS2toCS1(hvdcOperatorActivePowerRange.getOprFromCS2toCS1()).build()); } - public static void buildCurrentLimits(Collection currentLimits, Consumer> build) { - List currentLimitsData = currentLimits.stream() + @Nullable + public static List buildCurrentLimits(Collection currentLimits) { + final List currentLimitsData = currentLimits.stream() .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) + .filter(Objects::nonNull) + .map(CurrentLimitsDataBuilder::build) .toList(); - if (!currentLimitsData.isEmpty()) { - build.accept(currentLimitsData); - } + return currentLimitsData.isEmpty() ? null : currentLimitsData; } /** @@ -106,12 +108,12 @@ public static List mergeCurrentLimits(final Collection currentLimitsData1 = operationalLimitsGroups1.stream() .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) .filter(Objects::nonNull) - .map(cld -> cld.setApplicability(SIDE1)) + .map(cld -> cld.applicability(SIDE1).build()) .toList(); final List currentLimitsData2 = operationalLimitsGroups2.stream() .map(ElementUtils::operationalLimitsGroupToMapDataCurrentLimits) .filter(Objects::nonNull) - .map(cld -> cld.setApplicability(SIDE2)) + .map(cld -> cld.applicability(SIDE2).build()) .collect(Collectors.toCollection(ArrayList::new)); // simple case : one of the arrays are empty @@ -133,7 +135,7 @@ public static List mergeCurrentLimits(final Collection toIdentifiableShortCircuit .ipMax(identifiableShortCircuit.getIpMax()).build()); } - public static CurrentLimitsData toMapDataCurrentLimits(CurrentLimits limits) { - CurrentLimitsData.CurrentLimitsDataBuilder builder = CurrentLimitsData.builder(); + @Nullable + public static CurrentLimitsData toCurrentLimitsData(@NonNull CurrentLimits limits) { + final CurrentLimitsDataBuilder builder = toCurrentLimitsDataBuilder(limits); + return builder == null ? null : builder.build(); + } + + @Nullable + private static CurrentLimitsDataBuilder toCurrentLimitsDataBuilder(@NonNull CurrentLimits limits) { + CurrentLimitsDataBuilder builder = CurrentLimitsData.builder(); boolean empty = true; if (!Double.isNaN(limits.getPermanentLimit())) { builder.permanentLimit(limits.getPermanentLimit()); @@ -232,29 +241,18 @@ public static CurrentLimitsData toMapDataCurrentLimits(CurrentLimits limits) { builder.temporaryLimits(toMapDataTemporaryLimit(limits.getTemporaryLimits())); empty = false; } - return empty ? null : builder.build(); + return empty ? null : builder; } @Nullable - public static CurrentLimitsData operationalLimitsGroupToMapDataCurrentLimits(OperationalLimitsGroup operationalLimitsGroup) { - if (operationalLimitsGroup == null || operationalLimitsGroup.getCurrentLimits().isEmpty()) { + public static CurrentLimitsDataBuilder operationalLimitsGroupToMapDataCurrentLimits(@Nullable final OperationalLimitsGroup operationalLimitsGroup) { + if (operationalLimitsGroup == null) { return null; } - CurrentLimitsData.CurrentLimitsDataBuilder builder = CurrentLimitsData.builder(); - boolean containsLimitsData = false; - - CurrentLimits currentLimits = operationalLimitsGroup.getCurrentLimits().get(); - builder.id(operationalLimitsGroup.getId()); - if (!Double.isNaN(currentLimits.getPermanentLimit())) { - builder.permanentLimit(currentLimits.getPermanentLimit()); - containsLimitsData = true; - } - if (!CollectionUtils.isEmpty(currentLimits.getTemporaryLimits())) { - builder.temporaryLimits(toMapDataTemporaryLimit(currentLimits.getTemporaryLimits())); - containsLimitsData = true; - } - - return containsLimitsData ? builder.build() : null; + return operationalLimitsGroup.getCurrentLimits() + .map(ElementUtils::toCurrentLimitsDataBuilder) + .map(builder -> builder.id(operationalLimitsGroup.getId())) + .orElse(null); } private static List toMapDataTemporaryLimit(Collection limits) { @@ -549,7 +547,7 @@ public static Map buildCurrentLimitsMap(Collection res = new HashMap<>(); if (!CollectionUtils.isEmpty(operationalLimitsGroups)) { operationalLimitsGroups.forEach(operationalLimitsGroup -> operationalLimitsGroup.getCurrentLimits().ifPresent(limits -> { - CurrentLimitsData limitsData = toMapDataCurrentLimits(limits); + CurrentLimitsData limitsData = toCurrentLimitsData(limits); res.put(operationalLimitsGroup.getId(), limitsData); })); }