From 9b859d6fcc6e78e77657b2611efd98f033145758 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 25 Sep 2025 08:24:13 +0200 Subject: [PATCH 01/23] Move utility functions from computation library --- .../AbstractGlobalFilterService.java | 29 +++ .../filter/globalfilter/GlobalFilter.java | 34 +++ .../globalfilter/GlobalFilterUtils.java | 200 ++++++++++++++++++ .../gridsuite/filter/utils/FiltersUtils.java | 28 +++ .../utils/expertfilter/ExpertFilterUtils.java | 30 +++ 5 files changed, 321 insertions(+) create mode 100644 src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java create mode 100644 src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java create mode 100644 src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java diff --git a/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java b/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java new file mode 100644 index 0000000..be4021d --- /dev/null +++ b/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java @@ -0,0 +1,29 @@ +package org.gridsuite.filter.globalfilter; + +import com.powsybl.iidm.network.Network; +import lombok.NonNull; +import org.gridsuite.filter.AbstractFilter; +import org.gridsuite.filter.FilterLoader; +import org.gridsuite.filter.utils.EquipmentType; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +public abstract class AbstractGlobalFilterService implements FilterLoader { + protected abstract Network getNetwork(@NonNull UUID networkUuid, @NonNull String variantId); + + protected List getIdsFilter(@NonNull final UUID networkUuid, @NonNull final String variantId, + @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes) { + final Network network = getNetwork(networkUuid, variantId); + final List genericFilters = getFilters(globalFilter.getGenericFilter()); + return GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, genericFilters, equipmentTypes, this) + // Filter equipments by type + .values() + .stream() + .filter(Objects::nonNull) + // Combine all results into one list + .flatMap(List::stream) + .toList(); + } +} diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java new file mode 100644 index 0000000..61d609c --- /dev/null +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.gridsuite.filter.globalfilter; + +import com.powsybl.iidm.network.Country; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldNameConstants; +import lombok.experimental.SuperBuilder; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * @author maissa Souissi + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@SuperBuilder +@FieldNameConstants +// TODO convert to record when loadflow-server and computation lib stop to extends it +public class GlobalFilter { + private List nominalV; + private List countryCode; + private List genericFilter; + private Map> substationProperty; +} diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java new file mode 100644 index 0000000..61bef61 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java @@ -0,0 +1,200 @@ +package org.gridsuite.filter.globalfilter; + +import com.powsybl.iidm.network.Country; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; +import org.gridsuite.filter.AbstractFilter; +import org.gridsuite.filter.FilterLoader; +import org.gridsuite.filter.expertfilter.ExpertFilter; +import org.gridsuite.filter.expertfilter.expertrule.*; +import org.gridsuite.filter.utils.EquipmentType; +import org.gridsuite.filter.utils.FiltersUtils; +import org.gridsuite.filter.utils.expertfilter.CombinatorType; +import org.gridsuite.filter.utils.expertfilter.ExpertFilterUtils; +import org.gridsuite.filter.utils.expertfilter.FieldType; +import org.gridsuite.filter.utils.expertfilter.OperatorType; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Predicate; + +public final class GlobalFilterUtils { + private GlobalFilterUtils() { + throw new IllegalCallerException("Utility class should not be instantiated"); + } + + public static List getNominalVoltageFieldType(@Nonnull final EquipmentType equipmentType) { + return switch (equipmentType) { + case LINE, TWO_WINDINGS_TRANSFORMER -> List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2); + case VOLTAGE_LEVEL -> List.of(FieldType.NOMINAL_VOLTAGE); + default -> List.of(); + }; + } + + /** + * Builds nominal voltage rules combining all relevant field types + * @see GlobalFilter#getNominalV() + */ + public static Optional buildNominalVoltageRules( + @Nonnull final List nominalVoltages, @Nonnull final EquipmentType equipmentType) { + final List fields = getNominalVoltageFieldType(equipmentType); + return ExpertFilterUtils.buildOrCombination(nominalVoltages.stream() + .filter(Predicate.not(String::isBlank)) + .map(Double::valueOf) + .mapMulti((value, accumulator) -> { + for (final FieldType field : fields) { + accumulator.accept(NumberExpertRule.builder() + .value(value) + .field(field) + .operator(OperatorType.EQUALS) + .build()); + } + }).toList()); + } + + public static List getCountryCodeFieldType(@Nonnull final EquipmentType equipmentType) { + return switch (equipmentType) { + case VOLTAGE_LEVEL, TWO_WINDINGS_TRANSFORMER -> List.of(FieldType.COUNTRY); + case LINE -> List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2); + default -> List.of(); + }; + } + + /** + * Builds country code rules combining all relevant field types + */ + public static Optional buildCountryCodeRules( + @Nonnull final List countryCodes, @Nonnull final EquipmentType equipmentType) { + final List fields = getCountryCodeFieldType(equipmentType); + return ExpertFilterUtils.buildOrCombination(countryCodes.stream() + .map(Country::name) + .mapMulti((countryCode, accumulator) -> { + for (final FieldType field : fields) { + accumulator.accept(EnumExpertRule.builder() + .value(countryCode) + .field(field) + .operator(OperatorType.EQUALS) + .build()); + } + }) + .toList()); + } + + public static List getSubstationPropertiesFieldTypes(@Nonnull final EquipmentType equipmentType) { + return equipmentType == EquipmentType.LINE + ? List.of(FieldType.SUBSTATION_PROPERTIES_1, FieldType.SUBSTATION_PROPERTIES_2) + : List.of(FieldType.SUBSTATION_PROPERTIES); + } + + /** + * Builds substation property rules combining all relevant field types + */ + protected static Optional buildSubstationPropertyRules( + @Nonnull final Map> properties, @Nonnull final EquipmentType equipmentType) { + final List fields = getSubstationPropertiesFieldTypes(equipmentType); + return ExpertFilterUtils.buildOrCombination(properties.entrySet() + .stream() + .mapMulti((entry, accumulator) -> { + for (final FieldType field : fields) { + accumulator.accept(PropertiesExpertRule.builder() + .combinator(CombinatorType.OR) + .operator(OperatorType.IN) + .field(field) + .propertyName(entry.getKey()) + .propertyValues(entry.getValue()) + .build()); + } + }) + .toList()); + } + + /** + * Builds expert filter from a {@link GlobalFilter global filter} for an {@link EquipmentType equipment type}. + */ + @Nullable + public static ExpertFilter buildExpertFilter(@Nonnull final GlobalFilter globalFilter, @Nonnull final EquipmentType equipmentType) { + final List andRules = new ArrayList<>(); + + // Nominal voltage rules + buildNominalVoltageRules(globalFilter.getNominalV(), equipmentType).ifPresent(andRules::add); + + // Country code rules + buildCountryCodeRules(globalFilter.getCountryCode(), equipmentType).ifPresent(andRules::add); + + // Substation property rules + if (globalFilter.getSubstationProperty() != null) { + buildSubstationPropertyRules(globalFilter.getSubstationProperty(), equipmentType).ifPresent(andRules::add); + } + + return andRules.isEmpty() ? null : new ExpertFilter(UUID.randomUUID(), new Date(), equipmentType, + CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules).build()); + } + + @Nonnull + private static List filterNetwork(@Nonnull final AbstractFilter filter, @Nonnull final Network network, @Nonnull final FilterLoader filterLoader) { + return FiltersUtils.getIdentifiables(filter, network, filterLoader) + .stream() + .map(Identifiable::getId) + .toList(); + } + + /** + * Extracts {@link Identifiable#getId() equipment ID}s from a generic filter based on {@link EquipmentType equipment type}. + */ + @Nonnull + public static List applyFilterOnNetwork(@Nonnull final AbstractFilter filter, @Nonnull final EquipmentType targetEquipmentType, + @Nonnull final Network network, @Nonnull final FilterLoader filterLoader) { + if (filter.getEquipmentType() == targetEquipmentType) { + return filterNetwork(filter, network, filterLoader); + } else if (filter.getEquipmentType() == EquipmentType.VOLTAGE_LEVEL) { + return filterNetwork(ExpertFilterUtils.buildExpertFilterWithVoltageLevelIdsCriteria(filter.getId(), targetEquipmentType), network, filterLoader); + } + return List.of(); + } + + /** + * Extracts filtered {@link Identifiable#getId() equipment ID}s by applying {@link ExpertFilter expert} + * and {@link AbstractFilter generic filter}s. + */ + public static List applyGlobalFilterOnNetwork(@Nonnull final Network network, + @Nonnull final GlobalFilter globalFilter, @Nonnull final List genericFilters, + @Nonnull final EquipmentType equipmentType, @Nonnull final FilterLoader filterLoader) { + List> allFilterResults = new ArrayList<>(1 + genericFilters.size()); + + // Extract IDs from expert filter + final ExpertFilter expertFilter = buildExpertFilter(globalFilter, equipmentType); + if (expertFilter != null) { + allFilterResults.add(filterNetwork(expertFilter, network, filterLoader)); + } + + // Extract IDs from generic filters + for (final AbstractFilter filter : genericFilters) { + final List filterResult = applyFilterOnNetwork(filter, equipmentType, network, filterLoader); + if (!filterResult.isEmpty()) { + allFilterResults.add(filterResult); + } + } + + // Combine results with appropriate logic + // Expert filters use OR between them, generic filters use AND + return FiltersUtils.combineFilterResults(allFilterResults, !genericFilters.isEmpty()); + } + + /** + * Filters equipments by {@link EquipmentType type} + * @return map of {@link Identifiable#getId() equipment ID}s grouped by {@link EquipmentType equipment type} + */ + public static Map> applyGlobalFilterOnNetwork(@Nonnull final Network network, + @Nonnull final GlobalFilter globalFilter, @Nonnull final List genericFilters, + @Nonnull final List equipmentTypes, @Nonnull final FilterLoader filterLoader) { + Map> result = new EnumMap<>(EquipmentType.class); + for (final EquipmentType equipmentType : equipmentTypes) { + final List filteredIds = applyGlobalFilterOnNetwork(network, globalFilter, genericFilters, equipmentType, filterLoader); + if (!filteredIds.isEmpty()) { + result.put(equipmentType, filteredIds); + } + } + return result; + } +} diff --git a/src/main/java/org/gridsuite/filter/utils/FiltersUtils.java b/src/main/java/org/gridsuite/filter/utils/FiltersUtils.java index 77255fe..191705e 100644 --- a/src/main/java/org/gridsuite/filter/utils/FiltersUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/FiltersUtils.java @@ -14,6 +14,8 @@ import org.gridsuite.filter.identifierlistfilter.IdentifierListFilter; import org.gridsuite.filter.identifierlistfilter.IdentifierListFilterEquipmentAttributes; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.*; import java.util.function.Predicate; import java.util.stream.Stream; @@ -237,4 +239,30 @@ public static List> getIdentifiables(AbstractFilter filter, Netw case SUBSTATION -> getSubstationList(network, filter, filterLoader); }; } + + /** + * Combines multiple filter results using AND or OR logic. + */ + @Nonnull + public static List combineFilterResults(@Nullable final List> filterResults, final boolean useAndLogic) { + if (filterResults == null || filterResults.isEmpty()) { + return List.of(); + } + if (filterResults.size() == 1) { + return filterResults.getFirst(); + } + if (useAndLogic) { + // Intersection of all results + Set result = new HashSet<>(filterResults.getFirst()); + for (int i = 1; i < filterResults.size(); i++) { + result.retainAll(filterResults.get(i)); + } + return new ArrayList<>(result); + } else { + // Union of all results + Set result = new HashSet<>(); + filterResults.forEach(result::addAll); + return new ArrayList<>(result); + } + } } diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index c9a22ec..29c1310 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -13,8 +13,13 @@ import com.powsybl.iidm.network.extensions.StandbyAutomaton; import org.apache.commons.collections4.CollectionUtils; import org.gridsuite.filter.FilterLoader; +import org.gridsuite.filter.expertfilter.ExpertFilter; +import org.gridsuite.filter.expertfilter.expertrule.AbstractExpertRule; +import org.gridsuite.filter.expertfilter.expertrule.CombinatorExpertRule; +import org.gridsuite.filter.expertfilter.expertrule.FilterUuidExpertRule; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; import org.gridsuite.filter.identifierlistfilter.IdentifiableAttributes; +import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.filter.utils.FilterServiceUtils; import org.gridsuite.filter.utils.RegulationType; @@ -626,4 +631,29 @@ public static boolean isPartOf(Network network, String value, Set uuids, return equipments.stream().flatMap(e -> e.getIdentifiableAttributes().stream() .map(IdentifiableAttributes::getId)).collect(Collectors.toSet()).contains(value); } + + /** + * Build an "OR" rule from the rules passed. + * @param rules the rule(s) to be applied + * @return {@link Optional#empty() Empty} if no rule is passed, + * the {@link AbstractExpertRule rule} if the list has only 1 rule inside, + * otherwise an {@link CombinatorExpertRule OR combinator} with the rules. + */ + @Nonnull + public static Optional buildOrCombination(@Nullable final List rules) { + if (rules == null || rules.isEmpty()) { + return Optional.empty(); + } + return Optional.of(rules.size() > 1 ? CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(rules).build() : rules.getFirst()); + } + + /** + * Builds expert filter with {@link VoltageLevel voltage level} IDs criteria. + */ + public static ExpertFilter buildExpertFilterWithVoltageLevelIdsCriteria(@Nonnull final UUID filterUuid, @Nonnull final EquipmentType equipmentType) { + return new ExpertFilter(UUID.randomUUID(), new Date(), equipmentType, CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(List.of( + FilterUuidExpertRule.builder().operator(OperatorType.IS_PART_OF).field(FieldType.VOLTAGE_LEVEL_ID_1).values(Set.of(filterUuid.toString())).build(), + FilterUuidExpertRule.builder().operator(OperatorType.IS_PART_OF).field(FieldType.VOLTAGE_LEVEL_ID_2).values(Set.of(filterUuid.toString())).build() + )).build()); + } } From 3a9bddd7b808ac0c61948f7366374221de86e7bf Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 17 Sep 2025 16:22:56 +0200 Subject: [PATCH 02/23] prepare class test --- .../org/gridsuite/filter/utils/TimeUtils.java | 24 ++++++++++ .../org/gridsuite/filter/utils/UuidUtils.java | 24 ++++++++++ .../globalfilter/GlobalFilterUtilsTest.java | 4 ++ .../filter/utils/FiltersUtilsTest.java | 45 ++++++++++++------- .../expertfilter/ExpertFilterUtilsTest.java | 4 ++ 5 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/gridsuite/filter/utils/TimeUtils.java create mode 100644 src/main/java/org/gridsuite/filter/utils/UuidUtils.java create mode 100644 src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java create mode 100644 src/test/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtilsTest.java diff --git a/src/main/java/org/gridsuite/filter/utils/TimeUtils.java b/src/main/java/org/gridsuite/filter/utils/TimeUtils.java new file mode 100644 index 0000000..d154e1c --- /dev/null +++ b/src/main/java/org/gridsuite/filter/utils/TimeUtils.java @@ -0,0 +1,24 @@ +package org.gridsuite.filter.utils; + +import com.google.common.annotations.VisibleForTesting; +import lombok.Setter; + +import java.util.UUID; +import java.util.function.Supplier; + +/** + * Utility class for UUID to permit during tests to mock {@link UUID} generation.. + */ +@VisibleForTesting +public final class TimeUtils { + private TimeUtils() { + throw new AssertionError("Utility class should not be instantiated"); + } + + @Setter + private static Supplier uuidSupplier = UUID::randomUUID; + + public static UUID generateUUID() { + return uuidSupplier.get(); + } +} diff --git a/src/main/java/org/gridsuite/filter/utils/UuidUtils.java b/src/main/java/org/gridsuite/filter/utils/UuidUtils.java new file mode 100644 index 0000000..9ce29d1 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/utils/UuidUtils.java @@ -0,0 +1,24 @@ +package org.gridsuite.filter.utils; + +import com.google.common.annotations.VisibleForTesting; +import lombok.Setter; + +import java.util.UUID; +import java.util.function.Supplier; + +/** + * Utility class for UUID to permit during tests to mock {@link UUID} generation.. + */ +@VisibleForTesting +public final class UuidUtils { + private UuidUtils() { + throw new AssertionError("Utility class should not be instantiated"); + } + + @Setter + private static Supplier uuidSupplier = UUID::randomUUID; + + public static UUID generateUUID() { + return uuidSupplier.get(); + } +} diff --git a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java new file mode 100644 index 0000000..9d50f61 --- /dev/null +++ b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java @@ -0,0 +1,4 @@ +package org.gridsuite.filter.globalfilter; + +public class GlobalFilterUtilsTest { +} diff --git a/src/test/java/org/gridsuite/filter/utils/FiltersUtilsTest.java b/src/test/java/org/gridsuite/filter/utils/FiltersUtilsTest.java index 6a9f279..2ed06fd 100644 --- a/src/test/java/org/gridsuite/filter/utils/FiltersUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/utils/FiltersUtilsTest.java @@ -21,7 +21,6 @@ import org.gridsuite.filter.identifierlistfilter.IdentifierListFilterEquipmentAttributes; import org.gridsuite.filter.utils.expertfilter.CombinatorType; import org.gridsuite.filter.utils.expertfilter.OperatorType; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.*; @@ -34,17 +33,10 @@ * @author Franck Lecuyer */ class FiltersUtilsTest { - private Network network; - private Network network2; - private Network network3; - private Network network4; - private Network network5; + private final FilterLoader filterLoader = uuids -> null; - private FilterLoader filterLoader; - - @BeforeEach - void setUp() { - network = EurostagTutorialExample1Factory.createWithMoreGenerators(); + private static Network prepareNetwork() { + Network network = EurostagTutorialExample1Factory.createWithMoreGenerators(); network.getSubstation("P1").setProperty("region", "north"); network.getSubstation("P2").setProperty("region", "south"); network.getGenerator("GEN").setProperty("region", "north"); @@ -54,17 +46,18 @@ void setUp() { network.getTwoWindingsTransformer("NHV2_NLOAD").setProperty("region", "south"); network.getLine("NHV1_NHV2_1").setProperty("region", "south"); network.getLine("NHV1_NHV2_2").setProperty("region", "south"); + return network; + } - network2 = HvdcTestNetwork.createVsc(); + private static Network prepareHvdcNetwork() { + Network network2 = HvdcTestNetwork.createVsc(); network2.getSubstation("S2").setProperty("region", "north"); - network3 = SvcTestCaseFactory.createWithMoreSVCs(); - network4 = ShuntTestCaseFactory.create(); - network5 = ThreeWindingsTransformerNetworkFactory.create(); - filterLoader = uuids -> null; + return network2; } @Test void testSubstationFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -95,6 +88,7 @@ void testSubstationFilter() { @Test void testVoltageLevelFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -124,6 +118,7 @@ void testVoltageLevelFilter() { @Test void testLineFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -155,6 +150,7 @@ void testLineFilter() { @Test void testTwoWindingsTransformerFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -184,6 +180,7 @@ void testTwoWindingsTransformerFilter() { @Test void testThreeWindingsTransformerFilter() { + final Network network5 = ThreeWindingsTransformerNetworkFactory.create(); // expert filter ExpertFilter expertFilter = new ExpertFilter( @@ -200,6 +197,7 @@ void testThreeWindingsTransformerFilter() { @Test void testGeneratorFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -232,6 +230,7 @@ void testGeneratorFilter() { @Test void testLoadFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -261,6 +260,7 @@ void testLoadFilter() { @Test void testBatteryFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -288,6 +288,7 @@ void testBatteryFilter() { @Test void testShuntCompensatorFilter() { + final Network network4 = ShuntTestCaseFactory.create(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -317,6 +318,7 @@ void testShuntCompensatorFilter() { @Test void testStaticVarCompensatorFilter() { + final Network network3 = SvcTestCaseFactory.createWithMoreSVCs(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -349,6 +351,7 @@ void testStaticVarCompensatorFilter() { @Test void testDanglingLineFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -376,6 +379,7 @@ void testDanglingLineFilter() { @Test void testBusbarSectionFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -403,6 +407,8 @@ void testBusbarSectionFilter() { @Test void testBusFilter() { + final Network network = prepareNetwork(); + // expert filter only for bus ExpertFilter expertFilter = new ExpertFilter( UUID.randomUUID(), @@ -417,6 +423,7 @@ void testBusFilter() { @Test void testLccConverterStationFilter() { + final Network network = prepareNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -444,6 +451,7 @@ void testLccConverterStationFilter() { @Test void testHvdcLineFilter() { + final Network network2 = prepareHvdcNetwork(); // identifier list filter List filterEquipmentAttributes = List.of( @@ -474,6 +482,7 @@ void testHvdcLineFilter() { @Test void testIdentifierListFilter() { + final Network network = prepareNetwork(); List filterEquipmentAttributes = List.of( new IdentifierListFilterEquipmentAttributes("GEN", 30.), new IdentifierListFilterEquipmentAttributes("notFound1", 50.), @@ -513,6 +522,7 @@ void testIdentifierListFilter() { @Test void testFilterLoader() { + final Network network = prepareNetwork(); // with identifier list filter List filterEquipmentAttributes = List.of( new IdentifierListFilterEquipmentAttributes("GEN", 30.), @@ -539,6 +549,7 @@ void testFilterLoader() { @Test void testEquipmentNameFilterNoMatch() { + final Network network2 = prepareHvdcNetwork(); List rules = new ArrayList<>(); rules.add(StringExpertRule.builder().field(NAME).operator(IS).value("unexisting name").build()); AbstractExpertRule parentRule = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(rules).build(); @@ -555,6 +566,7 @@ void testEquipmentNameFilterNoMatch() { @Test void testEquipmentNameFilterWithMatch() { + final Network network2 = prepareHvdcNetwork(); List rules = new ArrayList<>(); rules.add(StringExpertRule.builder().field(NAME).operator(IS).value("Converter1").build()); AbstractExpertRule parentRule = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(rules).build(); @@ -570,6 +582,7 @@ void testEquipmentNameFilterWithMatch() { @Test void testEquipmentNameFilterWithNullValueInEquipments() { + final Network network = prepareNetwork(); List rules = new ArrayList<>(); rules.add(StringExpertRule.builder().field(NAME).operator(IS).value("some name").build()); AbstractExpertRule parentRule = CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(rules).build(); diff --git a/src/test/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtilsTest.java new file mode 100644 index 0000000..456de08 --- /dev/null +++ b/src/test/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtilsTest.java @@ -0,0 +1,4 @@ +package org.gridsuite.filter.utils.expertfilter; + +public class ExpertFilterUtilsTest { +} From 34de3c63498456b2fe3269eddbdff04300635769 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 17 Sep 2025 17:08:17 +0200 Subject: [PATCH 03/23] Set API nullable --- .../filter/globalfilter/GlobalFilterUtils.java | 12 ++++++++++-- .../filter/utils/expertfilter/ExpertFilterUtils.java | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java index 61bef61..42ac8e6 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java @@ -24,6 +24,7 @@ private GlobalFilterUtils() { throw new IllegalCallerException("Utility class should not be instantiated"); } + @Nonnull public static List getNominalVoltageFieldType(@Nonnull final EquipmentType equipmentType) { return switch (equipmentType) { case LINE, TWO_WINDINGS_TRANSFORMER -> List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2); @@ -36,6 +37,7 @@ public static List getNominalVoltageFieldType(@Nonnull final Equipmen * Builds nominal voltage rules combining all relevant field types * @see GlobalFilter#getNominalV() */ + @Nonnull public static Optional buildNominalVoltageRules( @Nonnull final List nominalVoltages, @Nonnull final EquipmentType equipmentType) { final List fields = getNominalVoltageFieldType(equipmentType); @@ -53,6 +55,7 @@ public static Optional buildNominalVoltageRules( }).toList()); } + @Nonnull public static List getCountryCodeFieldType(@Nonnull final EquipmentType equipmentType) { return switch (equipmentType) { case VOLTAGE_LEVEL, TWO_WINDINGS_TRANSFORMER -> List.of(FieldType.COUNTRY); @@ -64,6 +67,7 @@ public static List getCountryCodeFieldType(@Nonnull final EquipmentTy /** * Builds country code rules combining all relevant field types */ + @Nonnull public static Optional buildCountryCodeRules( @Nonnull final List countryCodes, @Nonnull final EquipmentType equipmentType) { final List fields = getCountryCodeFieldType(equipmentType); @@ -81,7 +85,8 @@ public static Optional buildCountryCodeRules( .toList()); } - public static List getSubstationPropertiesFieldTypes(@Nonnull final EquipmentType equipmentType) { + @Nonnull + public static List getSubstationPropertiesFieldTypes(@Nullable final EquipmentType equipmentType) { return equipmentType == EquipmentType.LINE ? List.of(FieldType.SUBSTATION_PROPERTIES_1, FieldType.SUBSTATION_PROPERTIES_2) : List.of(FieldType.SUBSTATION_PROPERTIES); @@ -90,7 +95,8 @@ public static List getSubstationPropertiesFieldTypes(@Nonnull final E /** * Builds substation property rules combining all relevant field types */ - protected static Optional buildSubstationPropertyRules( + @Nonnull + public static Optional buildSubstationPropertyRules( @Nonnull final Map> properties, @Nonnull final EquipmentType equipmentType) { final List fields = getSubstationPropertiesFieldTypes(equipmentType); return ExpertFilterUtils.buildOrCombination(properties.entrySet() @@ -157,6 +163,7 @@ public static List applyFilterOnNetwork(@Nonnull final AbstractFilter fi * Extracts filtered {@link Identifiable#getId() equipment ID}s by applying {@link ExpertFilter expert} * and {@link AbstractFilter generic filter}s. */ + @Nonnull public static List applyGlobalFilterOnNetwork(@Nonnull final Network network, @Nonnull final GlobalFilter globalFilter, @Nonnull final List genericFilters, @Nonnull final EquipmentType equipmentType, @Nonnull final FilterLoader filterLoader) { @@ -185,6 +192,7 @@ public static List applyGlobalFilterOnNetwork(@Nonnull final Network net * Filters equipments by {@link EquipmentType type} * @return map of {@link Identifiable#getId() equipment ID}s grouped by {@link EquipmentType equipment type} */ + @Nonnull public static Map> applyGlobalFilterOnNetwork(@Nonnull final Network network, @Nonnull final GlobalFilter globalFilter, @Nonnull final List genericFilters, @Nonnull final List equipmentTypes, @Nonnull final FilterLoader filterLoader) { diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index 29c1310..4407603 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -650,6 +650,7 @@ public static Optional buildOrCombination(@Nullable final Li /** * Builds expert filter with {@link VoltageLevel voltage level} IDs criteria. */ + @Nonnull public static ExpertFilter buildExpertFilterWithVoltageLevelIdsCriteria(@Nonnull final UUID filterUuid, @Nonnull final EquipmentType equipmentType) { return new ExpertFilter(UUID.randomUUID(), new Date(), equipmentType, CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(List.of( FilterUuidExpertRule.builder().operator(OperatorType.IS_PART_OF).field(FieldType.VOLTAGE_LEVEL_ID_1).values(Set.of(filterUuid.toString())).build(), From 68d1edf97a4134e90d8f70993ce228f702f0eacf Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 17 Sep 2025 17:08:45 +0200 Subject: [PATCH 04/23] note --- src/main/java/org/gridsuite/filter/AbstractFilter.java | 6 ++++-- src/main/java/org/gridsuite/filter/IFilterAttributes.java | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/AbstractFilter.java b/src/main/java/org/gridsuite/filter/AbstractFilter.java index 41942dd..b8457de 100644 --- a/src/main/java/org/gridsuite/filter/AbstractFilter.java +++ b/src/main/java/org/gridsuite/filter/AbstractFilter.java @@ -8,7 +8,9 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.expertfilter.ExpertFilter; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; @@ -43,7 +45,7 @@ public abstract class AbstractFilter implements IFilterAttributes { private UUID id; - private Date modificationDate; + private Date modificationDate; // TODO use Instant like in servers (client not on same timezone than server) private EquipmentType equipmentType; diff --git a/src/main/java/org/gridsuite/filter/IFilterAttributes.java b/src/main/java/org/gridsuite/filter/IFilterAttributes.java index d4799fb..b4d668e 100644 --- a/src/main/java/org/gridsuite/filter/IFilterAttributes.java +++ b/src/main/java/org/gridsuite/filter/IFilterAttributes.java @@ -10,13 +10,16 @@ import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.filter.utils.FilterType; +import java.util.Date; +import java.util.UUID; + /** * @author Jacques Borsenberger */ public interface IFilterAttributes { - java.util.UUID getId(); + UUID getId(); - java.util.Date getModificationDate(); + Date getModificationDate(); // TODO use Instant like in servers (client not on same timezone than server) FilterType getType(); From 94733e65d0d768e4aecb49ca8af02ff3f058ec16 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 25 Sep 2025 22:56:33 +0200 Subject: [PATCH 05/23] enhance --- .../globalfilter/AbstractGlobalFilterService.java | 12 ++++++++++-- .../gridsuite/filter/globalfilter/GlobalFilter.java | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java b/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java index be4021d..0b23757 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java @@ -13,8 +13,16 @@ public abstract class AbstractGlobalFilterService implements FilterLoader { protected abstract Network getNetwork(@NonNull UUID networkUuid, @NonNull String variantId); - protected List getIdsFilter(@NonNull final UUID networkUuid, @NonNull final String variantId, - @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes) { + /** + * Get filtered equipment IDs. + * @param networkUuid the network to load + * @param variantId the network variant to work on + * @param globalFilter the filter(s) to apply + * @param equipmentTypes the {@link EquipmentType equipment types} to filter + * @return the {@link List list} of {@link UUID IDs} of filtered {@link EquipmentType equipments}. + */ + protected List getFilteredIds(@NonNull final UUID networkUuid, @NonNull final String variantId, + @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes) { final Network network = getNetwork(networkUuid, variantId); final List genericFilters = getFilters(globalFilter.getGenericFilter()); return GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, genericFilters, equipmentTypes, this) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java index 61d609c..981ce96 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java @@ -12,6 +12,8 @@ import lombok.NoArgsConstructor; import lombok.experimental.FieldNameConstants; import lombok.experimental.SuperBuilder; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; import java.util.List; import java.util.Map; @@ -31,4 +33,15 @@ public class GlobalFilter { private List countryCode; private List genericFilter; private Map> substationProperty; + + /** + * @return {@code true} if all filter parameters are empty, else {@code false}. + */ + public boolean isEmpty() { + return CollectionUtils.isEmpty(this.nominalV) + && CollectionUtils.isEmpty(this.countryCode) + && CollectionUtils.isEmpty(this.genericFilter) + && MapUtils.isEmpty(this.substationProperty) + && this.substationProperty.values().stream().allMatch(CollectionUtils::isEmpty); + } } From 138e31875e6ee2f87d1b883961b45161322f28fc Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 7 Oct 2025 08:30:06 +0200 Subject: [PATCH 06/23] Move tests from computation --- pom.xml | 5 + .../filter/expertfilter/ExpertFilter.java | 9 +- .../expertrule/AbstractExpertRule.java | 6 +- .../expertrule/BooleanExpertRule.java | 10 +- .../expertrule/CombinatorExpertRule.java | 6 + .../expertrule/EnumExpertRule.java | 6 + .../expertrule/FilterUuidExpertRule.java | 8 +- .../expertrule/NumberExpertRule.java | 10 +- .../expertrule/PropertiesExpertRule.java | 18 +- .../expertrule/StringExpertRule.java | 10 +- .../globalfilter/GlobalFilterUtils.java | 12 +- .../IdentifierListFilter.java | 12 +- .../org/gridsuite/filter/utils/TimeUtils.java | 24 +- .../org/gridsuite/filter/utils/UuidUtils.java | 70 +++- .../utils/expertfilter/ExpertFilterUtils.java | 15 +- .../globalfilter/GlobalFilterUtilsTest.java | 353 +++++++++++++++++- .../filter/utils/FiltersUtilsTest.java | 37 +- .../expertfilter/ExpertFilterUtilsTest.java | 69 +++- 18 files changed, 597 insertions(+), 83 deletions(-) diff --git a/pom.xml b/pom.xml index 2a8b2fb..8b08366 100644 --- a/pom.xml +++ b/pom.xml @@ -141,6 +141,11 @@ hamcrest test + + org.assertj + assertj-core + test + com.powsybl powsybl-iidm-test diff --git a/src/main/java/org/gridsuite/filter/expertfilter/ExpertFilter.java b/src/main/java/org/gridsuite/filter/expertfilter/ExpertFilter.java index 2a9b8f3..3bf4217 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/ExpertFilter.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/ExpertFilter.java @@ -8,10 +8,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.iidm.network.TopologyKind; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.AbstractFilter; import org.gridsuite.filter.expertfilter.expertrule.AbstractExpertRule; @@ -24,11 +21,11 @@ /** * @author Antoine Bouhours */ -@Getter -@Setter @SuperBuilder @NoArgsConstructor +@Data @EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) public class ExpertFilter extends AbstractFilter { private AbstractExpertRule rules; diff --git a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/AbstractExpertRule.java b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/AbstractExpertRule.java index 72ccd07..ab046e0 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/AbstractExpertRule.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/AbstractExpertRule.java @@ -12,9 +12,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.powsybl.iidm.network.Identifiable; import lombok.AllArgsConstructor; -import lombok.Getter; +import lombok.Data; import lombok.NoArgsConstructor; -import lombok.Setter; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; @@ -46,8 +45,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @NoArgsConstructor @AllArgsConstructor -@Getter -@Setter +@Data @SuperBuilder public abstract class AbstractExpertRule { diff --git a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/BooleanExpertRule.java b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/BooleanExpertRule.java index 2687960..16a1f83 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/BooleanExpertRule.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/BooleanExpertRule.java @@ -9,10 +9,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; @@ -30,8 +27,9 @@ */ @AllArgsConstructor @NoArgsConstructor -@Getter -@Setter +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @SuperBuilder public class BooleanExpertRule extends AbstractExpertRule { private Boolean value; diff --git a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/CombinatorExpertRule.java b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/CombinatorExpertRule.java index c869828..2c39f70 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/CombinatorExpertRule.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/CombinatorExpertRule.java @@ -9,7 +9,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; +import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; @@ -23,6 +26,9 @@ * @author Antoine Bouhours */ @NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @SuperBuilder public class CombinatorExpertRule extends AbstractExpertRule { @Override diff --git a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/EnumExpertRule.java b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/EnumExpertRule.java index 332425e..7f0ac99 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/EnumExpertRule.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/EnumExpertRule.java @@ -9,7 +9,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; +import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; @@ -24,6 +27,9 @@ * @author Antoine Bouhours */ @NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @SuperBuilder public class EnumExpertRule extends StringExpertRule { @Override diff --git a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/FilterUuidExpertRule.java b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/FilterUuidExpertRule.java index d551edd..4632ffe 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/FilterUuidExpertRule.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/FilterUuidExpertRule.java @@ -9,12 +9,15 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; +import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import lombok.experimental.SuperBuilder; +import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; import org.gridsuite.filter.utils.expertfilter.DataType; import org.gridsuite.filter.utils.expertfilter.ExpertFilterUtils; -import org.gridsuite.filter.FilterLoader; import java.util.Map; import java.util.UUID; @@ -25,6 +28,9 @@ * @author Franck Lecuyer */ @NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @SuperBuilder public class FilterUuidExpertRule extends StringExpertRule { @Override diff --git a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/NumberExpertRule.java b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/NumberExpertRule.java index 72b8760..0a22199 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/NumberExpertRule.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/NumberExpertRule.java @@ -10,10 +10,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; @@ -31,8 +28,9 @@ */ @AllArgsConstructor @NoArgsConstructor -@Getter -@Setter +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @SuperBuilder public class NumberExpertRule extends AbstractExpertRule { private Double value; diff --git a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/PropertiesExpertRule.java b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/PropertiesExpertRule.java index 7c052e4..f729f02 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/PropertiesExpertRule.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/PropertiesExpertRule.java @@ -9,10 +9,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; @@ -30,8 +27,9 @@ */ @AllArgsConstructor @NoArgsConstructor -@Getter -@Setter +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @SuperBuilder public class PropertiesExpertRule extends AbstractExpertRule { private String propertyName; @@ -64,12 +62,4 @@ public DataType getDataType() { public String getStringValue() { return this.getPropertyName(); } - - public String getPropertyName() { - return this.propertyName; - } - - public List getPropertyValues() { - return this.propertyValues; - } } diff --git a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/StringExpertRule.java b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/StringExpertRule.java index 82feea3..adf3303 100644 --- a/src/main/java/org/gridsuite/filter/expertfilter/expertrule/StringExpertRule.java +++ b/src/main/java/org/gridsuite/filter/expertfilter/expertrule/StringExpertRule.java @@ -10,10 +10,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Identifiable; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import lombok.experimental.SuperBuilder; import org.apache.commons.lang3.StringUtils; import org.gridsuite.filter.FilterLoader; @@ -34,8 +31,9 @@ */ @AllArgsConstructor @NoArgsConstructor -@Getter -@Setter +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) @SuperBuilder public class StringExpertRule extends AbstractExpertRule { private String value; diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java index 42ac8e6..7fba83f 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java @@ -9,6 +9,8 @@ import org.gridsuite.filter.expertfilter.expertrule.*; import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.filter.utils.FiltersUtils; +import org.gridsuite.filter.utils.TimeUtils; +import org.gridsuite.filter.utils.UuidUtils; import org.gridsuite.filter.utils.expertfilter.CombinatorType; import org.gridsuite.filter.utils.expertfilter.ExpertFilterUtils; import org.gridsuite.filter.utils.expertfilter.FieldType; @@ -123,17 +125,21 @@ public static ExpertFilter buildExpertFilter(@Nonnull final GlobalFilter globalF final List andRules = new ArrayList<>(); // Nominal voltage rules - buildNominalVoltageRules(globalFilter.getNominalV(), equipmentType).ifPresent(andRules::add); + if (globalFilter.getNominalV() != null) { + buildNominalVoltageRules(globalFilter.getNominalV(), equipmentType).ifPresent(andRules::add); + } // Country code rules - buildCountryCodeRules(globalFilter.getCountryCode(), equipmentType).ifPresent(andRules::add); + if (globalFilter.getCountryCode() != null) { + buildCountryCodeRules(globalFilter.getCountryCode(), equipmentType).ifPresent(andRules::add); + } // Substation property rules if (globalFilter.getSubstationProperty() != null) { buildSubstationPropertyRules(globalFilter.getSubstationProperty(), equipmentType).ifPresent(andRules::add); } - return andRules.isEmpty() ? null : new ExpertFilter(UUID.randomUUID(), new Date(), equipmentType, + return andRules.isEmpty() ? null : new ExpertFilter(UuidUtils.generateUUID(), TimeUtils.nowAsDate(), equipmentType, CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules).build()); } diff --git a/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifierListFilter.java b/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifierListFilter.java index 1f32490..eb49f4e 100644 --- a/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifierListFilter.java +++ b/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifierListFilter.java @@ -9,10 +9,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.powsybl.iidm.network.IdentifiableType; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import lombok.experimental.SuperBuilder; import org.gridsuite.filter.AbstractFilter; import org.gridsuite.filter.utils.EquipmentType; @@ -20,16 +17,15 @@ import java.util.*; - /** * @author Seddik Yengui */ -@NoArgsConstructor -@Getter -@Setter @SuperBuilder +@NoArgsConstructor +@Data @EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) public class IdentifierListFilter extends AbstractFilter { private List filterEquipmentsAttributes; diff --git a/src/main/java/org/gridsuite/filter/utils/TimeUtils.java b/src/main/java/org/gridsuite/filter/utils/TimeUtils.java index d154e1c..e1662ca 100644 --- a/src/main/java/org/gridsuite/filter/utils/TimeUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/TimeUtils.java @@ -1,24 +1,32 @@ package org.gridsuite.filter.utils; import com.google.common.annotations.VisibleForTesting; +import lombok.Getter; import lombok.Setter; -import java.util.UUID; -import java.util.function.Supplier; +import java.time.Clock; +import java.time.Instant; +import java.util.Date; /** - * Utility class for UUID to permit during tests to mock {@link UUID} generation.. + * Utility class for datetime to permit during tests to mock time related operations. + * @apiNote This class is to permit tests to intercept date/time */ -@VisibleForTesting + public final class TimeUtils { private TimeUtils() { throw new AssertionError("Utility class should not be instantiated"); } - @Setter - private static Supplier uuidSupplier = UUID::randomUUID; + @Setter(onMethod_ = {@VisibleForTesting}) + @Getter + private static Clock clock = Clock.systemUTC(); + + public static Instant now() { + return Instant.now(clock); + } - public static UUID generateUUID() { - return uuidSupplier.get(); + public static Date nowAsDate() { + return Date.from(now()); } } diff --git a/src/main/java/org/gridsuite/filter/utils/UuidUtils.java b/src/main/java/org/gridsuite/filter/utils/UuidUtils.java index 9ce29d1..4419e4c 100644 --- a/src/main/java/org/gridsuite/filter/utils/UuidUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/UuidUtils.java @@ -7,18 +7,82 @@ import java.util.function.Supplier; /** - * Utility class for UUID to permit during tests to mock {@link UUID} generation.. + * Utility class for UUID to permit during tests to mock {@link UUID} generation. + * @apiNote This class is to permit tests to intercept {@link UUID} generation */ -@VisibleForTesting public final class UuidUtils { private UuidUtils() { throw new AssertionError("Utility class should not be instantiated"); } - @Setter + @Setter(onMethod_ = {@VisibleForTesting}) private static Supplier uuidSupplier = UUID::randomUUID; public static UUID generateUUID() { return uuidSupplier.get(); } + + /** + * Like {@link UUID#randomUUID()} generate UUID v4 variant 2, but not random.
+ * This utility function is intended to be used in tests to have predictable UUID. + *

Some information on UUID: + *

+     * textual format: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
+     *          bytes:  0 1 2 3  4 5  6 7  8 9 10 ↓12 ↓14 ↓
+     *                                           11  13  15
+     * size: 4b-2b-2b-2b-6b
+     * 
+ * Where
    + *
  • M = version field (4 bits).
  • + *
  • N = variant field (3 bits).
  • + *
  • All other bits = random (for v4).

+ * + *

UUID bytes structure: + *

+     *  /-------mostSigBits-------\   /--------leastSigBits---------\
+     *  b0 b1 b2 b3   b4 b5   b6 b7   b8 b9   b10 b11 b12 b13 b14 b15
+     * |-----------| |-----| |-----| |-----| |-----------------------|
+     *    random    random v4+random variant+random    random
+     * b6 = [0100 LLLL] ⇒ version 4
+     * b8 = [10xL LLLL] → [1000 LLLL] ⇒ RFC 4122 / IETF / Leach–Salz variant
+     * 

+ * + * @param b0to3 the bits from b0 to b3 + * @param b4to5 the bits from b4 and b5 + * @param b7 the bits of b7 + * @param nibblesB6lowB8low the upper nibble is for the lower half of b6 + * and the lower nibble is for the lower half of b8 + * @param b9 the bits of b9 + * @param b10to11 the bits for b10 and b11 + * @param b12to15 the bits for b12 to b15 + * @return a UUID v4 in variant 2 with the bits passed + * @implNote Bits 5–4 of b8 are left to {@code 0} for simplicity, + * so the UUID is of the form {@code xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx}. + */ + // regexp: ^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-8[0-9a-f]{3}-[0-9a-f]{12}$ + @VisibleForTesting + public static UUID createUUID(final int b0to3, final short b4to5, final byte b7, + final byte nibblesB6lowB8low, + final byte b9, final short b10to11, final int b12to15) { + return new UUID( + // Construct mostSigBits directly + (b0to3 & 0xFFFFFFFFL) << 32 + | (b4to5 & 0xFFFFL) << 16 + | (0x40L | ((nibblesB6lowB8low >>> 4) & 0x0FL)) << 8 // version 4 + | (b7 & 0xFFL), + // Construct leastSigBits directly + (0x80L | (nibblesB6lowB8low & 0xFL)) << 56 // variant 2 + | (b9 & 0xFFL) << 48 + | (b10to11 & 0xFFFFL) << 32 + | (b12to15 & 0xFFFFFFFFL) + ); + } + + /** + * @see #createUUID(int, short, byte, byte, byte, short, int) + */ + @VisibleForTesting + public static UUID createUUID(final int b12to15) { + return createUUID(0, (short) 0, (byte) 0, (byte) 0, (byte) 0, (short) 0, b12to15); + } } diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index 4407603..db70edd 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -19,9 +19,7 @@ import org.gridsuite.filter.expertfilter.expertrule.FilterUuidExpertRule; import org.gridsuite.filter.identifierlistfilter.FilterEquipments; import org.gridsuite.filter.identifierlistfilter.IdentifiableAttributes; -import org.gridsuite.filter.utils.EquipmentType; -import org.gridsuite.filter.utils.FilterServiceUtils; -import org.gridsuite.filter.utils.RegulationType; +import org.gridsuite.filter.utils.*; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -619,7 +617,7 @@ public static List getFilterEquipments(Network network, Set FilterCycleDetector.checkNoCycle(filter, filterLoader)); List filterEquipments = FilterServiceUtils.getFilterEquipmentsFromUuid(network, uuid, filterLoader); - cachedUuidFilters.put(uuid, !CollectionUtils.isEmpty(filterEquipments) ? filterEquipments.getFirst() : null); + cachedUuidFilters.put(uuid, CollectionUtils.isNotEmpty(filterEquipments) ? filterEquipments.getFirst() : null); res.addAll(filterEquipments); } }); @@ -652,9 +650,10 @@ public static Optional buildOrCombination(@Nullable final Li */ @Nonnull public static ExpertFilter buildExpertFilterWithVoltageLevelIdsCriteria(@Nonnull final UUID filterUuid, @Nonnull final EquipmentType equipmentType) { - return new ExpertFilter(UUID.randomUUID(), new Date(), equipmentType, CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(List.of( - FilterUuidExpertRule.builder().operator(OperatorType.IS_PART_OF).field(FieldType.VOLTAGE_LEVEL_ID_1).values(Set.of(filterUuid.toString())).build(), - FilterUuidExpertRule.builder().operator(OperatorType.IS_PART_OF).field(FieldType.VOLTAGE_LEVEL_ID_2).values(Set.of(filterUuid.toString())).build() - )).build()); + return new ExpertFilter(UuidUtils.generateUUID(), TimeUtils.nowAsDate(), equipmentType, + CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(List.of( + FilterUuidExpertRule.builder().operator(OperatorType.IS_PART_OF).field(FieldType.VOLTAGE_LEVEL_ID_1).values(Set.of(filterUuid.toString())).build(), + FilterUuidExpertRule.builder().operator(OperatorType.IS_PART_OF).field(FieldType.VOLTAGE_LEVEL_ID_2).values(Set.of(filterUuid.toString())).build() + )).build()); } } diff --git a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java index 9d50f61..f6fcded 100644 --- a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java @@ -1,4 +1,355 @@ package org.gridsuite.filter.globalfilter; -public class GlobalFilterUtilsTest { +import com.powsybl.iidm.network.Country; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; +import org.assertj.core.api.*; +import org.gridsuite.filter.AbstractFilter; +import org.gridsuite.filter.FilterLoader; +import org.gridsuite.filter.expertfilter.ExpertFilter; +import org.gridsuite.filter.expertfilter.expertrule.*; +import org.gridsuite.filter.utils.EquipmentType; +import org.gridsuite.filter.utils.FiltersUtils; +import org.gridsuite.filter.utils.UuidUtils; +import org.gridsuite.filter.utils.expertfilter.CombinatorType; +import org.gridsuite.filter.utils.expertfilter.FieldType; +import org.gridsuite.filter.utils.expertfilter.OperatorType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/* Methods dependencies: + * applyGlobalFilterOnNetwork + * ↳ applyGlobalFilterOnNetwork + * ↳ applyFilterOnNetwork + * ⇃ ↳ filterNetwork + * ↳ buildExpertFilter + * ↳ buildNominalVoltageRules + * ⇃ ↳ getNominalVoltageFieldType + * ↳ buildCountryCodeRules + * ⇃ ↳ getCountryCodeFieldType + * ↳ buildSubstationPropertyRules + * ↳ getSubstationPropertiesFieldTypes + */ +class GlobalFilterUtilsTest implements WithAssertions { + private static void testVariableOrCombinationRules(final Optional result, final int inputSize, + final Class ruleClass, final ThrowingConsumer[] singleRuleAsserts, + final Iterable multiAssertElements) { + final OptionalAssert assertion = Assertions.assertThat(result).as("result"); + if (inputSize <= 0) { + assertion.isEmpty(); + } else { + assertion.isPresent(); + if (inputSize == 1) { + assertion.get(InstanceOfAssertFactories.type(ruleClass)) + .as("expert rule") + .satisfies(singleRuleAsserts); + } else { + assertion.get(InstanceOfAssertFactories.type(CombinatorExpertRule.class)) + .as("combinator expert rule") + .satisfies(cer -> Assertions.assertThat(cer.getCombinator()).as("combinator").isEqualTo(CombinatorType.OR)) + .extracting(CombinatorExpertRule::getRules, InstanceOfAssertFactories.list(AbstractExpertRule.class)).as("expert rules") + .containsExactlyInAnyOrderElementsOf(multiAssertElements); + } + } + } + + /** Trick Java to create generic array */ + @SafeVarargs + private static ThrowingConsumer[] createAssertArray(final ThrowingConsumer... assertions) { + return assertions; + } + + @Nested + @DisplayName("buildNominalVoltageRules(...)") + class BuildNominalVoltageRules { + @ParameterizedTest + @MethodSource({"expertRulesData"}) + void shouldCreateExpertRules(final List nominalVoltages) { + testVariableOrCombinationRules( + GlobalFilterUtils.buildNominalVoltageRules(nominalVoltages, EquipmentType.VOLTAGE_LEVEL), + nominalVoltages.size(), + NumberExpertRule.class, + createAssertArray( + ner -> assertThat(ner.getValue()).as("value").hasToString(nominalVoltages.getFirst()), + ner -> assertThat(ner.getField()).as("field").isEqualTo(FieldType.NOMINAL_VOLTAGE), + ner -> assertThat(ner.getOperator()).as("operator").isEqualTo(OperatorType.EQUALS) + ), + nominalVoltages.stream().map(nv -> NumberExpertRule.builder().value(Double.valueOf(nv)) + .field(FieldType.NOMINAL_VOLTAGE).operator(OperatorType.EQUALS).build()).collect(Collectors.toUnmodifiableList())); + } + + private static Stream expertRulesData() { + return Stream.of( + Arguments.of(List.of()), + Arguments.of(List.of("300.0")), + Arguments.of(List.of("400.0", "225.0")) + ); + } + } + + @Nested + @DisplayName("buildCountryCodeRules(...)") + class BuildCountryCodeRules { + @ParameterizedTest + @MethodSource({"enumRulesData"}) + void shouldCreateEnumRules(final List countries) { + testVariableOrCombinationRules( + GlobalFilterUtils.buildCountryCodeRules(countries, EquipmentType.VOLTAGE_LEVEL), + countries.size(), + EnumExpertRule.class, + createAssertArray( + ner -> assertThat(ner.getValue()).as("value").hasToString(countries.getFirst().name()), + ner -> assertThat(ner.getField()).as("field").isEqualTo(FieldType.COUNTRY), + ner -> assertThat(ner.getOperator()).as("operator").isEqualTo(OperatorType.EQUALS) + ), + (List) countries.stream().map(c -> EnumExpertRule.builder().value(c.name()) + .field(FieldType.COUNTRY).operator(OperatorType.EQUALS).build()).toList()); + } + + private static Stream enumRulesData() { + return Stream.of( + Arguments.of(List.of()), + Arguments.of(List.of(Country.YT)), + Arguments.of(List.of(Country.FR, Country.DE)) + ); + } + } + + @Nested + @DisplayName("buildSubstationPropertyRules(...)") + class BuildSubstationPropertyRules { + @ParameterizedTest + @MethodSource({"propertiesRulesData"}) + void shouldCreateCorrectPropertiesRules(final Map> properties) { + testVariableOrCombinationRules( + GlobalFilterUtils.buildSubstationPropertyRules(properties, EquipmentType.VOLTAGE_LEVEL), + properties.size(), + PropertiesExpertRule.class, + createAssertArray( + per -> assertThat(per.getCombinator()).as("combinator").isEqualTo(CombinatorType.OR), + per -> assertThat(per.getOperator()).as("operator").isEqualTo(OperatorType.IN), + per -> assertThat(per.getField()).as("field").isEqualTo(FieldType.SUBSTATION_PROPERTIES), + per -> assertThat(per.getPropertyName()).as("property name").isEqualTo("prop1"), + per -> assertThat(per.getPropertyValues()).as("property values").containsExactlyInAnyOrderElementsOf(properties.values().iterator().next()) + ), + (List) properties.entrySet().stream().map(e -> PropertiesExpertRule.builder().combinator(CombinatorType.OR) + .operator(OperatorType.IN).field(FieldType.SUBSTATION_PROPERTIES).propertyName(e.getKey()).propertyValues(e.getValue()).build()).toList()); + } + + private static Stream propertiesRulesData() { + return Stream.of( + Arguments.of(Map.of()), + Arguments.of(Map.of("prop1", List.of("value0"))), + Arguments.of(Map.of("prop1", List.of("value1", "value2"), "prop2", List.of("value3"))) + ); + } + } + + @ParameterizedTest(name = "{0}") + @MethodSource("nominalVoltageFieldTypeData") + void shouldReturnCorrectNominalVoltageFieldTypes(final EquipmentType equipmentType, final List expectedFields) { + assertThat(GlobalFilterUtils.getNominalVoltageFieldType(equipmentType)) + .as("result").containsExactlyInAnyOrderElementsOf(expectedFields); + } + + private static Stream nominalVoltageFieldTypeData() { + return Stream.of( + // Nominal voltage + Arguments.of(EquipmentType.LINE, List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2)), + Arguments.of(EquipmentType.TWO_WINDINGS_TRANSFORMER, List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2)), + Arguments.of(EquipmentType.VOLTAGE_LEVEL, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.GENERATOR, Collections.emptyList()) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("countryCodeFieldTypeData") + void shouldReturnCorrectCountryCodeFieldTypes(final EquipmentType equipmentType, final List expectedFields) { + assertThat(GlobalFilterUtils.getCountryCodeFieldType(equipmentType)) + .as("result").containsExactlyInAnyOrderElementsOf(expectedFields); + } + + private static Stream countryCodeFieldTypeData() { + return Stream.of( + // Country code + Arguments.of(EquipmentType.VOLTAGE_LEVEL, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.TWO_WINDINGS_TRANSFORMER, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.LINE, List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2)), + Arguments.of(EquipmentType.GENERATOR, Collections.emptyList()) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("substationPropertyFieldTypeData") + void shouldReturnCorrectSubstationPropertyFieldTypes(final EquipmentType equipmentType, final List expectedFields) { + assertThat(GlobalFilterUtils.getSubstationPropertiesFieldTypes(equipmentType)) + .as("result").containsExactlyInAnyOrderElementsOf(expectedFields); + } + + private static Stream substationPropertyFieldTypeData() { + return Stream.of( + // Substation properties + Arguments.of(EquipmentType.LINE, List.of(FieldType.SUBSTATION_PROPERTIES_1, FieldType.SUBSTATION_PROPERTIES_2)), + Arguments.of(EquipmentType.GENERATOR, List.of(FieldType.SUBSTATION_PROPERTIES)) + ); + } + + @Nested + @DisplayName("buildExpertFilter(...)") + class BuildExpertFilter { + @Test + void shouldReturnNullWhenNoExpertFiltersProvided() { + final GlobalFilter globalFilter = new GlobalFilter(null, null, null, null); + assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR)) + .as("result").isNull(); + } + + @Test + void shouldReturnNullWhenNoRules() { + final GlobalFilter globalFilter = new GlobalFilter(List.of(), List.of(), List.of(), Map.of()); + assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR)) + .as("result").isNull(); + } + + @Test + void shouldCreateFilterWhenRulesExist() { + final List nv = List.of("400.0"); + final GlobalFilter globalFilter = Mockito.spy(new GlobalFilter(nv, null, null, null)); + try (final MockedStatic mockedGFU = Mockito.mockStatic(GlobalFilterUtils.class, Mockito.CALLS_REAL_METHODS)) { + mockedGFU.when(() -> GlobalFilterUtils.buildNominalVoltageRules(nv, EquipmentType.GENERATOR)) + .thenReturn(Optional.of(Mockito.mock(AbstractExpertRule.class))); + mockedGFU.clearInvocations(); //important because stubbing static method counts as call + assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR)).as("result") + .isNotNull().satisfies( + ef -> assertThat(ef.getEquipmentType()).as("equipment type").isEqualTo(EquipmentType.GENERATOR), + ef -> assertThat(ef.getRules()).as("rule combinator") + .asInstanceOf(InstanceOfAssertFactories.type(CombinatorExpertRule.class)) + .extracting(CombinatorExpertRule::getRules, InstanceOfAssertFactories.list(AbstractExpertRule.class)) + .as("rules").hasSize(1) + ); + Mockito.verify(globalFilter, Mockito.atLeastOnce()).getNominalV(); + Mockito.verify(globalFilter, Mockito.atLeastOnce()).getCountryCode(); + Mockito.verify(globalFilter, Mockito.atLeastOnce()).getSubstationProperty(); + mockedGFU.verify(() -> GlobalFilterUtils.buildNominalVoltageRules(Mockito.anyList(), any(EquipmentType.class)), Mockito.times(1)); + mockedGFU.verify(() -> GlobalFilterUtils.buildExpertFilter(any(GlobalFilter.class), any(EquipmentType.class)), Mockito.times(1)); + mockedGFU.verifyNoMoreInteractions(); //check if forget to mock a method + Mockito.verifyNoMoreInteractions(globalFilter); + } + } + } + + @Nested + @DisplayName("filterNetwork(...)") + class FilterNetwork { + @Test + void shouldReturnIdsFromFilteredNetwork() { + final Network network = Mockito.mock(Network.class); + final FilterLoader loader = Mockito.mock(FilterLoader.class); + final AbstractFilter filter = Mockito.mock(AbstractFilter.class); + when(filter.getEquipmentType()).thenReturn(EquipmentType.GENERATOR); + try (final MockedStatic mockedFU = Mockito.mockStatic(FiltersUtils.class, Mockito.CALLS_REAL_METHODS)) { + final Identifiable i1 = Mockito.mock(Identifiable.class); + when(i1.getId()).thenReturn("id1"); + final Identifiable i2 = Mockito.mock(Identifiable.class); + when(i2.getId()).thenReturn("id2"); + final List> attributes = List.of(i1, i2); + mockedFU.when(() -> FiltersUtils.getIdentifiables(filter, network, loader)).thenReturn(attributes); + mockedFU.clearInvocations(); //important because stubbing static method counts as call + assertThat(GlobalFilterUtils.filterNetwork(filter, network, loader)).as("result") + .containsExactlyInAnyOrder("id1", "id2"); + Mockito.verify(i1, Mockito.atLeastOnce()).getId(); + Mockito.verify(i2, Mockito.atLeastOnce()).getId(); + Mockito.verify(filter, Mockito.atLeastOnce()).getEquipmentType(); + Mockito.verifyNoMoreInteractions(filter, network, loader, i1, i2); + mockedFU.verify(() -> FiltersUtils.getIdentifiables(eq(filter), eq(network), eq(loader)), Mockito.times(1)); + mockedFU.verifyNoMoreInteractions(); //check if forget to mock a method + } + } + } + + @Nested + @DisplayName("applyFilterOnNetwork(...)") + class ApplyFilterOnNetwork { + @Test + void shouldReturnFilteredNetworkWhenSameEquipmentType() { + final Network network = Mockito.mock(Network.class); + final FilterLoader loader = Mockito.mock(FilterLoader.class); + final AbstractFilter filter = Mockito.mock(AbstractFilter.class); + when(filter.getEquipmentType()).thenReturn(EquipmentType.GENERATOR); + try (final MockedStatic mockedFU = Mockito.mockStatic(FiltersUtils.class, Mockito.CALLS_REAL_METHODS)) { + final Identifiable gen1 = Mockito.mock(Identifiable.class); + when(gen1.getId()).thenReturn("gen1"); + final Identifiable gen2 = Mockito.mock(Identifiable.class); + when(gen2.getId()).thenReturn("gen2"); + final List> attributes = List.of(gen1, gen2); + mockedFU.when(() -> FiltersUtils.getIdentifiables(filter, network, loader)).thenReturn(attributes); + mockedFU.clearInvocations(); //important because stubbing static method counts as call + assertThat(GlobalFilterUtils.applyFilterOnNetwork(filter, EquipmentType.GENERATOR, network, loader)) + .as("result").containsExactlyInAnyOrder("gen1", "gen2"); + Mockito.verify(filter, Mockito.atLeastOnce()).getEquipmentType(); + Mockito.verify(gen1, Mockito.atLeastOnce()).getId(); + Mockito.verify(gen2, Mockito.atLeastOnce()).getId(); + Mockito.verifyNoMoreInteractions(filter, network, loader, gen1, gen2); + mockedFU.verify(() -> FiltersUtils.getIdentifiables(eq(filter), eq(network), eq(loader)), Mockito.atLeastOnce()); + mockedFU.verifyNoMoreInteractions(); //check if forget to mock a method + } + } + + @Test + void shouldBuildVoltageLevelFilterWhenVoltageLevelType() { + final Network network = Mockito.mock(Network.class); + final FilterLoader loader = Mockito.mock(FilterLoader.class); + final AbstractFilter filter = Mockito.mock(AbstractFilter.class); + when(filter.getEquipmentType()).thenReturn(EquipmentType.VOLTAGE_LEVEL); + final UUID filterUuid = UuidUtils.createUUID(0); + when(filter.getId()).thenReturn(filterUuid); + try (final MockedStatic mockedFU = Mockito.mockStatic(FiltersUtils.class, Mockito.CALLS_REAL_METHODS)) { + final Identifiable line1 = Mockito.mock(Identifiable.class); + when(line1.getId()).thenReturn("line1"); + final Identifiable line2 = Mockito.mock(Identifiable.class); + when(line2.getId()).thenReturn("line2"); + final List> attributes = List.of(line1, line2); + mockedFU.when(() -> FiltersUtils.getIdentifiables(any(ExpertFilter.class), eq(network), eq(loader))).thenReturn(attributes); + mockedFU.clearInvocations(); //important because stubbing static method counts as call + assertThat(GlobalFilterUtils.applyFilterOnNetwork(filter, EquipmentType.LINE, network, loader)) + .as("result").containsExactlyInAnyOrder("line1", "line2"); + Mockito.verify(filter, Mockito.atLeastOnce()).getEquipmentType(); + Mockito.verify(filter, Mockito.atLeastOnce()).getId(); + Mockito.verify(line1, Mockito.atLeastOnce()).getId(); + Mockito.verify(line2, Mockito.atLeastOnce()).getId(); + Mockito.verifyNoMoreInteractions(filter, network, loader, line1, line2); + mockedFU.verify(() -> FiltersUtils.getIdentifiables(any(ExpertFilter.class), eq(network), eq(loader)), Mockito.atLeastOnce()); + mockedFU.verifyNoMoreInteractions(); //check if forget to mock a method + } + } + + @Test + void shouldReturnEmptyWhenDifferentEquipmentType() { + final FilterLoader loader = Mockito.mock(FilterLoader.class); + final Network network = Mockito.mock(Network.class); + final AbstractFilter filter = Mockito.mock(AbstractFilter.class); + when(filter.getEquipmentType()).thenReturn(EquipmentType.LOAD); + assertThat(GlobalFilterUtils.applyFilterOnNetwork(filter, EquipmentType.GENERATOR, network, loader)) + .as("result").isEmpty(); + Mockito.verify(filter, Mockito.atLeastOnce()).getEquipmentType(); + Mockito.verifyNoMoreInteractions(loader, network, filter); + } + } + + //TODO applyGlobalFilterOnNetwork(single) + //TODO applyGlobalFilterOnNetwork(list) } diff --git a/src/test/java/org/gridsuite/filter/utils/FiltersUtilsTest.java b/src/test/java/org/gridsuite/filter/utils/FiltersUtilsTest.java index 2ed06fd..f657ddc 100644 --- a/src/test/java/org/gridsuite/filter/utils/FiltersUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/utils/FiltersUtilsTest.java @@ -9,30 +9,28 @@ import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.test.*; import org.apache.commons.collections4.CollectionUtils; +import org.assertj.core.api.WithAssertions; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.expertfilter.ExpertFilter; import org.gridsuite.filter.expertfilter.expertrule.AbstractExpertRule; import org.gridsuite.filter.expertfilter.expertrule.CombinatorExpertRule; import org.gridsuite.filter.expertfilter.expertrule.StringExpertRule; -import org.gridsuite.filter.identifierlistfilter.FilterEquipments; -import org.gridsuite.filter.identifierlistfilter.FilteredIdentifiables; -import org.gridsuite.filter.identifierlistfilter.IdentifiableAttributes; -import org.gridsuite.filter.identifierlistfilter.IdentifierListFilter; -import org.gridsuite.filter.identifierlistfilter.IdentifierListFilterEquipmentAttributes; +import org.gridsuite.filter.identifierlistfilter.*; import org.gridsuite.filter.utils.expertfilter.CombinatorType; import org.gridsuite.filter.utils.expertfilter.OperatorType; import org.junit.jupiter.api.Test; import java.util.*; -import static org.gridsuite.filter.utils.expertfilter.FieldType.*; -import static org.gridsuite.filter.utils.expertfilter.OperatorType.*; +import static org.gridsuite.filter.utils.expertfilter.FieldType.ID; +import static org.gridsuite.filter.utils.expertfilter.FieldType.NAME; +import static org.gridsuite.filter.utils.expertfilter.OperatorType.IS; import static org.junit.jupiter.api.Assertions.*; /** * @author Franck Lecuyer */ -class FiltersUtilsTest { +class FiltersUtilsTest implements WithAssertions { private final FilterLoader filterLoader = uuids -> null; private static Network prepareNetwork() { @@ -596,4 +594,27 @@ void testEquipmentNameFilterWithNullValueInEquipments() { // in this network, VL equipments have null name => no match assertEquals(0, identifiables.size()); } + + @Test + void shouldReturnEmptyWhenCombineFilterResultsInputIsEmpty() { + assertThat(FiltersUtils.combineFilterResults(List.of(), true)).as("result").isEmpty(); + } + + @Test + void shouldReturnIntersectionWhenUsingAndLogic() { + assertThat(FiltersUtils.combineFilterResults(Arrays.asList( + List.of("item1", "item2", "item3"), + List.of("item2", "item3", "item4"), + List.of("item2", "item5")), true)) + .as("result").singleElement().isEqualTo("item2"); + } + + @Test + void shouldReturnUnionWhenUsingOrLogic() { + assertThat(FiltersUtils.combineFilterResults(Arrays.asList( + List.of("item1", "item2"), + List.of("item3", "item4"), + List.of("item5")), false)) + .as("result").containsExactlyInAnyOrder("item1", "item2", "item3", "item4", "item5"); + } } diff --git a/src/test/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtilsTest.java index 456de08..2de5c5c 100644 --- a/src/test/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtilsTest.java @@ -1,4 +1,71 @@ package org.gridsuite.filter.utils.expertfilter; -public class ExpertFilterUtilsTest { +import org.assertj.core.api.InstanceOfAssertFactories; +import org.assertj.core.api.OptionalAssert; +import org.assertj.core.api.WithAssertions; +import org.gridsuite.filter.expertfilter.expertrule.AbstractExpertRule; +import org.gridsuite.filter.expertfilter.expertrule.CombinatorExpertRule; +import org.gridsuite.filter.expertfilter.expertrule.FilterUuidExpertRule; +import org.gridsuite.filter.utils.EquipmentType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.mockito.Mockito.mock; + +class ExpertFilterUtilsTest implements WithAssertions { + @ParameterizedTest + @MethodSource("orCombinationData") + void shouldCreateOrCombination(final List rules, final boolean expectEmpty, final boolean expectSingle) { + final OptionalAssert assertOpt = assertThat(ExpertFilterUtils.buildOrCombination(rules)).as("result"); + if (expectEmpty) { + assertOpt.isEmpty(); + } else { + assertOpt.isPresent(); + final var assertRule = assertOpt.get().as("rule"); + if (expectSingle) { + assertRule.isEqualTo(rules.getFirst()); + } else { + assertRule.asInstanceOf(InstanceOfAssertFactories.type(CombinatorExpertRule.class)).satisfies( + cer -> assertThat(cer.getCombinator()).as("combinator").isEqualTo(CombinatorType.OR), + cer -> assertThat(cer.getRules()).as("rules").containsExactlyInAnyOrderElementsOf(rules) + ); + } + } + } + + private static Stream orCombinationData() { + return Stream.of( + Arguments.of(List.of(), true, false), + Arguments.of(List.of(mock(AbstractExpertRule.class)), false, true), + Arguments.of(List.of(mock(AbstractExpertRule.class), mock(AbstractExpertRule.class)), false, false) + ); + } + + @Test + void shouldCreateExpertFilterWithVoltageLevelIdsCriteria() { + final UUID filterUuid = UUID.randomUUID(); + final EquipmentType equipmentType = EquipmentType.LINE; + assertThat(ExpertFilterUtils.buildExpertFilterWithVoltageLevelIdsCriteria(filterUuid, equipmentType)).as("result").isNotNull().satisfies( + ef -> assertThat(ef.getEquipmentType()).as("equipmentType").isEqualTo(equipmentType), + ef -> assertThat(ef.getRules()).as("expert rules").isNotNull() + .asInstanceOf(InstanceOfAssertFactories.type(CombinatorExpertRule.class)) + .satisfies( + cer -> assertThat(cer.getCombinator()).as("combinator").isEqualTo(CombinatorType.OR), + cer -> assertThat(cer.getRules()).as("combinator rules").hasSize(2) + .allSatisfy(er -> assertThat(er).as("expert rule") + .asInstanceOf(InstanceOfAssertFactories.type(FilterUuidExpertRule.class)) + .satisfies(fuer -> assertThat(fuer.getOperator()).as("operator").isEqualTo(OperatorType.IS_PART_OF)) + .extracting(FilterUuidExpertRule::getValues, InstanceOfAssertFactories.set(String.class)).as("UUIDs") + .containsExactly(filterUuid.toString())) + .map(AbstractExpertRule::getField).as("fields") + .containsExactly(FieldType.VOLTAGE_LEVEL_ID_1, FieldType.VOLTAGE_LEVEL_ID_2) + ) + ); + } } From 4a0c85652c460bad14d3f81bb1885e25a956283e Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 7 Oct 2025 08:30:27 +0200 Subject: [PATCH 07/23] fix error --- .../AbstractGlobalFilterService.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java b/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java index 0b23757..6eeb68a 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java @@ -2,10 +2,12 @@ import com.powsybl.iidm.network.Network; import lombok.NonNull; +import org.apache.commons.lang3.ObjectUtils; import org.gridsuite.filter.AbstractFilter; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.utils.EquipmentType; +import javax.annotation.Nullable; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -13,25 +15,32 @@ public abstract class AbstractGlobalFilterService implements FilterLoader { protected abstract Network getNetwork(@NonNull UUID networkUuid, @NonNull String variantId); + /** @see #getFilteredIds(UUID, String, GlobalFilter, List) */ + protected List getFilteredIds(@NonNull final UUID networkUuid, @NonNull final String variantId, + @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes) { + return getFilteredIds(networkUuid, variantId, globalFilter, equipmentTypes, null); + } + /** * Get filtered equipment IDs. * @param networkUuid the network to load * @param variantId the network variant to work on * @param globalFilter the filter(s) to apply * @param equipmentTypes the {@link EquipmentType equipment types} to filter + * @param genericFilters additional generic filters to apply * @return the {@link List list} of {@link UUID IDs} of filtered {@link EquipmentType equipments}. */ protected List getFilteredIds(@NonNull final UUID networkUuid, @NonNull final String variantId, - @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes) { + @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes, + @Nullable final List genericFilters) { final Network network = getNetwork(networkUuid, variantId); - final List genericFilters = getFilters(globalFilter.getGenericFilter()); - return GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, genericFilters, equipmentTypes, this) - // Filter equipments by type - .values() - .stream() - .filter(Objects::nonNull) - // Combine all results into one list - .flatMap(List::stream) - .toList(); + return GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, ObjectUtils.getIfNull(genericFilters, List::of), equipmentTypes, this) + // Filter equipments by type + .values() + .stream() + .filter(Objects::nonNull) + // Combine all results into one list + .flatMap(List::stream) + .toList(); } } From 097b55de842d5607614b7da0f23b165bf611483d Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 7 Oct 2025 08:35:35 +0200 Subject: [PATCH 08/23] Add missing fields for some equipment types --- .../globalfilter/GlobalFilterUtils.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java index 7fba83f..a8c5ddf 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java @@ -26,11 +26,12 @@ private GlobalFilterUtils() { throw new IllegalCallerException("Utility class should not be instantiated"); } + /** @see ExpertFilterUtils#getFieldValue(FieldType, String, Identifiable) for possible values */ @Nonnull public static List getNominalVoltageFieldType(@Nonnull final EquipmentType equipmentType) { return switch (equipmentType) { - case LINE, TWO_WINDINGS_TRANSFORMER -> List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2); - case VOLTAGE_LEVEL -> List.of(FieldType.NOMINAL_VOLTAGE); + case LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER -> List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2); + case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, VOLTAGE_LEVEL -> List.of(FieldType.NOMINAL_VOLTAGE); default -> List.of(); }; } @@ -57,11 +58,14 @@ public static Optional buildNominalVoltageRules( }).toList()); } + /** @see ExpertFilterUtils#getFieldValue(FieldType, String, Identifiable) for possible values */ @Nonnull public static List getCountryCodeFieldType(@Nonnull final EquipmentType equipmentType) { return switch (equipmentType) { - case VOLTAGE_LEVEL, TWO_WINDINGS_TRANSFORMER -> List.of(FieldType.COUNTRY); - case LINE -> List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2); + case BATTERY, BUS, BUSBAR_SECTION, DANGLING_LINE, GENERATOR, LOAD, + SHUNT_COMPENSATOR, STATIC_VAR_COMPENSATOR, SUBSTATION, + THREE_WINDINGS_TRANSFORMER, TWO_WINDINGS_TRANSFORMER, VOLTAGE_LEVEL -> List.of(FieldType.COUNTRY); + case LINE, HVDC_LINE -> List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2); default -> List.of(); }; } @@ -87,11 +91,13 @@ public static Optional buildCountryCodeRules( .toList()); } + /** @see ExpertFilterUtils#getFieldValue(FieldType, String, Identifiable) for possible values */ @Nonnull - public static List getSubstationPropertiesFieldTypes(@Nullable final EquipmentType equipmentType) { - return equipmentType == EquipmentType.LINE - ? List.of(FieldType.SUBSTATION_PROPERTIES_1, FieldType.SUBSTATION_PROPERTIES_2) - : List.of(FieldType.SUBSTATION_PROPERTIES); + public static List getSubstationPropertiesFieldTypes(@Nonnull final EquipmentType equipmentType) { + return switch (equipmentType) { + case LINE, HVDC_LINE -> List.of(FieldType.SUBSTATION_PROPERTIES_1, FieldType.SUBSTATION_PROPERTIES_2); + default -> List.of(FieldType.SUBSTATION_PROPERTIES); + }; } /** @@ -123,22 +129,16 @@ public static Optional buildSubstationPropertyRules( @Nullable public static ExpertFilter buildExpertFilter(@Nonnull final GlobalFilter globalFilter, @Nonnull final EquipmentType equipmentType) { final List andRules = new ArrayList<>(); - - // Nominal voltage rules if (globalFilter.getNominalV() != null) { buildNominalVoltageRules(globalFilter.getNominalV(), equipmentType).ifPresent(andRules::add); } - - // Country code rules if (globalFilter.getCountryCode() != null) { buildCountryCodeRules(globalFilter.getCountryCode(), equipmentType).ifPresent(andRules::add); } - - // Substation property rules if (globalFilter.getSubstationProperty() != null) { + // custom extension with apps-metadata server buildSubstationPropertyRules(globalFilter.getSubstationProperty(), equipmentType).ifPresent(andRules::add); } - return andRules.isEmpty() ? null : new ExpertFilter(UuidUtils.generateUUID(), TimeUtils.nowAsDate(), equipmentType, CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules).build()); } From 697bba835494fd07cbe29fcdbbc0206905be2dd5 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Fri, 3 Oct 2025 11:15:52 +0200 Subject: [PATCH 09/23] comment --- .../filter/identifierlistfilter/IdentifierListFilter.java | 2 +- src/main/java/org/gridsuite/filter/utils/FiltersUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifierListFilter.java b/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifierListFilter.java index eb49f4e..87c401c 100644 --- a/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifierListFilter.java +++ b/src/main/java/org/gridsuite/filter/identifierlistfilter/IdentifierListFilter.java @@ -79,7 +79,7 @@ public FilteredIdentifiables toFilteredIdentifiables(List notFounds = filterEquipments.getNotFoundEquipments().stream().map(element -> new IdentifiableAttributes(element, idType, null)).toList(); diff --git a/src/main/java/org/gridsuite/filter/utils/FiltersUtils.java b/src/main/java/org/gridsuite/filter/utils/FiltersUtils.java index 191705e..c3ca827 100644 --- a/src/main/java/org/gridsuite/filter/utils/FiltersUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/FiltersUtils.java @@ -241,7 +241,7 @@ public static List> getIdentifiables(AbstractFilter filter, Netw } /** - * Combines multiple filter results using AND or OR logic. + * Combines multiple filter results using {@code AND} or {@code OR} logic. */ @Nonnull public static List combineFilterResults(@Nullable final List> filterResults, final boolean useAndLogic) { From 55fd0dfbd325aa33b064ebbdb26588d0d7658de2 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 7 Oct 2025 15:20:43 +0200 Subject: [PATCH 10/23] fix test warn --- .../org/gridsuite/filter/globalfilter/GlobalFilterUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java index a8c5ddf..3778a12 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java @@ -1,5 +1,6 @@ package org.gridsuite.filter.globalfilter; +import com.google.common.annotations.VisibleForTesting; import com.powsybl.iidm.network.Country; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.Network; @@ -143,8 +144,9 @@ public static ExpertFilter buildExpertFilter(@Nonnull final GlobalFilter globalF CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules).build()); } + @VisibleForTesting @Nonnull - private static List filterNetwork(@Nonnull final AbstractFilter filter, @Nonnull final Network network, @Nonnull final FilterLoader filterLoader) { + static List filterNetwork(@Nonnull final AbstractFilter filter, @Nonnull final Network network, @Nonnull final FilterLoader filterLoader) { return FiltersUtils.getIdentifiables(filter, network, filterLoader) .stream() .map(Identifiable::getId) From b44f3ad99ad741fd97feac3a0c13955205fb01a5 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 7 Oct 2025 15:22:49 +0200 Subject: [PATCH 11/23] Add support for other equipment types --- .../exception/InvalidEquipmentType.java | 7 +++ .../filter/exception/UnknownFilterType.java | 7 +++ .../globalfilter/GlobalFilterUtils.java | 41 ++++++++++++-- .../utils/expertfilter/ExpertFilterUtils.java | 54 +++++++++++++++++-- .../globalfilter/GlobalFilterUtilsTest.java | 11 ++-- 5 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/gridsuite/filter/exception/InvalidEquipmentType.java create mode 100644 src/main/java/org/gridsuite/filter/exception/UnknownFilterType.java diff --git a/src/main/java/org/gridsuite/filter/exception/InvalidEquipmentType.java b/src/main/java/org/gridsuite/filter/exception/InvalidEquipmentType.java new file mode 100644 index 0000000..99a7903 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/exception/InvalidEquipmentType.java @@ -0,0 +1,7 @@ +package org.gridsuite.filter.exception; + +import lombok.experimental.StandardException; + +@StandardException +public class InvalidEquipmentType extends RuntimeException { +} diff --git a/src/main/java/org/gridsuite/filter/exception/UnknownFilterType.java b/src/main/java/org/gridsuite/filter/exception/UnknownFilterType.java new file mode 100644 index 0000000..cce5ef4 --- /dev/null +++ b/src/main/java/org/gridsuite/filter/exception/UnknownFilterType.java @@ -0,0 +1,7 @@ +package org.gridsuite.filter.exception; + +import lombok.experimental.StandardException; + +@StandardException +public class UnknownFilterType extends RuntimeException { +} diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java index 3778a12..95c3984 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java @@ -21,6 +21,7 @@ import javax.annotation.Nullable; import java.util.*; import java.util.function.Predicate; +import java.util.stream.Collectors; public final class GlobalFilterUtils { private GlobalFilterUtils() { @@ -124,11 +125,37 @@ public static Optional buildSubstationPropertyRules( .toList()); } + /** + * {@link FilterLoader#getFilters(List) Loads} generic filters by their {@link UUID UUIDs}, + * then builds {@link AbstractExpertRule expert rules}, taking into account the {@link EquipmentType equipment type}. + * @param genericFilterIds the generic filter {@link UUID UUIDs} to load and build rules for + * @return the {@link List list} of {@link AbstractExpertRule expert rules} built from the loaded generic filters. + */ + @Nonnull + public static List buildGenericFilterRules(@Nonnull final List genericFilterIds, + @Nonnull final EquipmentType actualType, + @Nonnull final FilterLoader filterLoader) { + /* note: We can't do a FilterUuidExpertRule IS_PART_OF rule here because we need to know the equipment type + * the filter is intended to deduce on what field to apply the rule. */ + final List rules = new ArrayList<>(genericFilterIds.size()); + for (final AbstractFilter filter : filterLoader.getFilters(genericFilterIds)) { + rules.add(ExpertFilterUtils.buildOrCombination(ExpertFilterUtils.getIdFieldMatchingType(actualType, filter.getEquipmentType()) + .map(field -> FilterUuidExpertRule.builder() + .field(field) + .operator(OperatorType.IS_PART_OF) + .value(filter.getId().toString()) + .build()) + .collect(Collectors.toUnmodifiableList())).orElseThrow()); + } + return rules; + } + /** * Builds expert filter from a {@link GlobalFilter global filter} for an {@link EquipmentType equipment type}. */ @Nullable - public static ExpertFilter buildExpertFilter(@Nonnull final GlobalFilter globalFilter, @Nonnull final EquipmentType equipmentType) { + public static ExpertFilter buildExpertFilter(@Nonnull final GlobalFilter globalFilter, @Nonnull final EquipmentType equipmentType, + @Nonnull final FilterLoader filterLoader) { final List andRules = new ArrayList<>(); if (globalFilter.getNominalV() != null) { buildNominalVoltageRules(globalFilter.getNominalV(), equipmentType).ifPresent(andRules::add); @@ -140,8 +167,12 @@ public static ExpertFilter buildExpertFilter(@Nonnull final GlobalFilter globalF // custom extension with apps-metadata server buildSubstationPropertyRules(globalFilter.getSubstationProperty(), equipmentType).ifPresent(andRules::add); } - return andRules.isEmpty() ? null : new ExpertFilter(UuidUtils.generateUUID(), TimeUtils.nowAsDate(), equipmentType, - CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules).build()); + if (globalFilter.getGenericFilter() != null) { + andRules.addAll(buildGenericFilterRules(globalFilter.getGenericFilter(), equipmentType, filterLoader)); + } + return ExpertFilterUtils.buildAndCombination(andRules) + .map(rule -> new ExpertFilter(UuidUtils.generateUUID(), TimeUtils.nowAsDate(), equipmentType, rule)) + .orElse(null); } @VisibleForTesting @@ -178,12 +209,12 @@ public static List applyGlobalFilterOnNetwork(@Nonnull final Network net List> allFilterResults = new ArrayList<>(1 + genericFilters.size()); // Extract IDs from expert filter - final ExpertFilter expertFilter = buildExpertFilter(globalFilter, equipmentType); + final ExpertFilter expertFilter = buildExpertFilter(globalFilter, equipmentType, filterLoader); if (expertFilter != null) { allFilterResults.add(filterNetwork(expertFilter, network, filterLoader)); } - // Extract IDs from generic filters + // Extract IDs from generic filters, case for computation backend columns filters for (final AbstractFilter filter : genericFilters) { final List filterResult = applyFilterOnNetwork(filter, equipmentType, network, filterLoader); if (!filterResult.isEmpty()) { diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index db70edd..51d5941 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -13,6 +13,8 @@ import com.powsybl.iidm.network.extensions.StandbyAutomaton; import org.apache.commons.collections4.CollectionUtils; import org.gridsuite.filter.FilterLoader; +import org.gridsuite.filter.exception.InvalidEquipmentType; +import org.gridsuite.filter.exception.UnknownFilterType; import org.gridsuite.filter.expertfilter.ExpertFilter; import org.gridsuite.filter.expertfilter.expertrule.AbstractExpertRule; import org.gridsuite.filter.expertfilter.expertrule.CombinatorExpertRule; @@ -25,6 +27,7 @@ import javax.annotation.Nullable; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Antoine Bouhours @@ -631,18 +634,37 @@ public static boolean isPartOf(Network network, String value, Set uuids, } /** - * Build an "OR" rule from the rules passed. + * Build an {@code OR} rule from the rules passed. * @param rules the rule(s) to be applied * @return {@link Optional#empty() Empty} if no rule is passed, * the {@link AbstractExpertRule rule} if the list has only 1 rule inside, - * otherwise an {@link CombinatorExpertRule OR combinator} with the rules. + * otherwise an {@link CombinatorType#OR OR} {@link CombinatorExpertRule combinator} with the rules. */ @Nonnull public static Optional buildOrCombination(@Nullable final List rules) { + return buildCombination(rules, false); + } + + /** + * Build an {@code AND} rule from the rules passed. + * @param rules the rule(s) to be applied + * @return {@link Optional#empty() Empty} if no rule is passed, + * the {@link AbstractExpertRule rule} if the list has only 1 rule inside, + * otherwise an {@link CombinatorType#AND AND} {@link CombinatorExpertRule combinator} with the rules. + */ + @Nonnull + public static Optional buildAndCombination(@Nullable final List rules) { + return buildCombination(rules, true); + } + + @Nonnull + private static Optional buildCombination(@Nullable final List rules, final boolean and) { if (rules == null || rules.isEmpty()) { return Optional.empty(); } - return Optional.of(rules.size() > 1 ? CombinatorExpertRule.builder().combinator(CombinatorType.OR).rules(rules).build() : rules.getFirst()); + return Optional.of(rules.size() > 1 + ? CombinatorExpertRule.builder().combinator(and ? CombinatorType.AND : CombinatorType.OR).rules(rules).build() + : rules.getFirst()); } /** @@ -656,4 +678,30 @@ public static ExpertFilter buildExpertFilterWithVoltageLevelIdsCriteria(@Nonnull FilterUuidExpertRule.builder().operator(OperatorType.IS_PART_OF).field(FieldType.VOLTAGE_LEVEL_ID_2).values(Set.of(filterUuid.toString())).build() )).build()); } + + @Nonnull + public static Stream getIdFieldMatchingType(@Nonnull final EquipmentType actualType, @Nonnull final EquipmentType filterEquipmentType) { + if (actualType == filterEquipmentType) { + return Stream.of(FieldType.ID); + } else if (filterEquipmentType == EquipmentType.SUBSTATION) { + return switch (actualType) { + case SUBSTATION -> throw new AssertionError("This case can't happen"); + case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, VOLTAGE_LEVEL, + LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION -> Stream.of(FieldType.VOLTAGE_LEVEL_ID); + case LINE, HVDC_LINE, DANGLING_LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER + -> Stream.of(FieldType.VOLTAGE_LEVEL_ID_1, FieldType.VOLTAGE_LEVEL_ID_2); + }; + } else if (filterEquipmentType == EquipmentType.VOLTAGE_LEVEL) { + return switch (actualType) { + case VOLTAGE_LEVEL -> throw new AssertionError("This case can't happen"); + case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, SUBSTATION, + LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION -> Stream.of(FieldType.SUBSTATION_ID); + case LINE, HVDC_LINE, DANGLING_LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER + -> Stream.of(FieldType.SUBSTATION_ID_1, FieldType.SUBSTATION_ID_2); + }; + } else { + // the webapp doesn't authorize this case, so normally this case can't happen + throw new InvalidEquipmentType("No matching field for type " + actualType + " and filter type " + filterEquipmentType); + } + } } diff --git a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java index f6fcded..23b82a6 100644 --- a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java @@ -27,8 +27,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; /* Methods dependencies: @@ -214,14 +213,14 @@ class BuildExpertFilter { @Test void shouldReturnNullWhenNoExpertFiltersProvided() { final GlobalFilter globalFilter = new GlobalFilter(null, null, null, null); - assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR)) + assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR, null)) .as("result").isNull(); } @Test void shouldReturnNullWhenNoRules() { final GlobalFilter globalFilter = new GlobalFilter(List.of(), List.of(), List.of(), Map.of()); - assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR)) + assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR, uuids -> List.of())) .as("result").isNull(); } @@ -233,7 +232,7 @@ void shouldCreateFilterWhenRulesExist() { mockedGFU.when(() -> GlobalFilterUtils.buildNominalVoltageRules(nv, EquipmentType.GENERATOR)) .thenReturn(Optional.of(Mockito.mock(AbstractExpertRule.class))); mockedGFU.clearInvocations(); //important because stubbing static method counts as call - assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR)).as("result") + assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR, null)).as("result") .isNotNull().satisfies( ef -> assertThat(ef.getEquipmentType()).as("equipment type").isEqualTo(EquipmentType.GENERATOR), ef -> assertThat(ef.getRules()).as("rule combinator") @@ -245,7 +244,7 @@ void shouldCreateFilterWhenRulesExist() { Mockito.verify(globalFilter, Mockito.atLeastOnce()).getCountryCode(); Mockito.verify(globalFilter, Mockito.atLeastOnce()).getSubstationProperty(); mockedGFU.verify(() -> GlobalFilterUtils.buildNominalVoltageRules(Mockito.anyList(), any(EquipmentType.class)), Mockito.times(1)); - mockedGFU.verify(() -> GlobalFilterUtils.buildExpertFilter(any(GlobalFilter.class), any(EquipmentType.class)), Mockito.times(1)); + mockedGFU.verify(() -> GlobalFilterUtils.buildExpertFilter(any(GlobalFilter.class), any(EquipmentType.class), isNull(FilterLoader.class)), Mockito.times(1)); mockedGFU.verifyNoMoreInteractions(); //check if forget to mock a method Mockito.verifyNoMoreInteractions(globalFilter); } From 4111a1f752c15900b50e857e246a19c0680acaa8 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 8 Oct 2025 10:37:43 +0200 Subject: [PATCH 12/23] Remoce unused import Signed-off-by: Franck LECUYER --- .../gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index 51d5941..abc2c96 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -14,7 +14,6 @@ import org.apache.commons.collections4.CollectionUtils; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.exception.InvalidEquipmentType; -import org.gridsuite.filter.exception.UnknownFilterType; import org.gridsuite.filter.expertfilter.ExpertFilter; import org.gridsuite.filter.expertfilter.expertrule.AbstractExpertRule; import org.gridsuite.filter.expertfilter.expertrule.CombinatorExpertRule; From 2404ec162faf03a0e2936d08b65f557226290141 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 8 Oct 2025 11:59:09 +0200 Subject: [PATCH 13/23] Fix bad field type and fix bad values field when building expert filter rules with is_part_of operator Signed-off-by: Franck LECUYER --- .../gridsuite/filter/globalfilter/GlobalFilterUtils.java | 2 +- .../filter/utils/expertfilter/ExpertFilterUtils.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java index 95c3984..72bb88b 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java @@ -143,7 +143,7 @@ public static List buildGenericFilterRules(@Nonnull final Li .map(field -> FilterUuidExpertRule.builder() .field(field) .operator(OperatorType.IS_PART_OF) - .value(filter.getId().toString()) + .values(Set.of(filter.getId().toString())) .build()) .collect(Collectors.toUnmodifiableList())).orElseThrow()); } diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index abc2c96..3d71587 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -686,17 +686,17 @@ public static Stream getIdFieldMatchingType(@Nonnull final EquipmentT return switch (actualType) { case SUBSTATION -> throw new AssertionError("This case can't happen"); case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, VOLTAGE_LEVEL, - LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION -> Stream.of(FieldType.VOLTAGE_LEVEL_ID); + LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION -> Stream.of(FieldType.SUBSTATION_ID); case LINE, HVDC_LINE, DANGLING_LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER - -> Stream.of(FieldType.VOLTAGE_LEVEL_ID_1, FieldType.VOLTAGE_LEVEL_ID_2); + -> Stream.of(FieldType.SUBSTATION_ID_1, FieldType.SUBSTATION_ID_2); }; } else if (filterEquipmentType == EquipmentType.VOLTAGE_LEVEL) { return switch (actualType) { case VOLTAGE_LEVEL -> throw new AssertionError("This case can't happen"); case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, SUBSTATION, - LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION -> Stream.of(FieldType.SUBSTATION_ID); + LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION -> Stream.of(FieldType.VOLTAGE_LEVEL_ID); case LINE, HVDC_LINE, DANGLING_LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER - -> Stream.of(FieldType.SUBSTATION_ID_1, FieldType.SUBSTATION_ID_2); + -> Stream.of(FieldType.VOLTAGE_LEVEL_ID_1, FieldType.VOLTAGE_LEVEL_ID_2); }; } else { // the webapp doesn't authorize this case, so normally this case can't happen From d134449791624435e75ee956f381ac3962331589 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 8 Oct 2025 15:23:56 +0200 Subject: [PATCH 14/23] Fix other bad field type and fix other pbs when building expert filter rules Signed-off-by: Franck LECUYER --- .../globalfilter/GlobalFilterUtils.java | 24 +++++++++++-------- .../utils/expertfilter/ExpertFilterUtils.java | 14 +++++------ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java index 72bb88b..8ae7f9a 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java @@ -32,8 +32,9 @@ private GlobalFilterUtils() { @Nonnull public static List getNominalVoltageFieldType(@Nonnull final EquipmentType equipmentType) { return switch (equipmentType) { - case LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER -> List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2); - case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, VOLTAGE_LEVEL -> List.of(FieldType.NOMINAL_VOLTAGE); + case LINE, TWO_WINDINGS_TRANSFORMER, HVDC_LINE -> List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2); + case THREE_WINDINGS_TRANSFORMER -> List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2, FieldType.NOMINAL_VOLTAGE_3); + case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, STATIC_VAR_COMPENSATOR, VOLTAGE_LEVEL, DANGLING_LINE, LCC_CONVERTER_STATION, VSC_CONVERTER_STATION -> List.of(FieldType.NOMINAL_VOLTAGE); default -> List.of(); }; } @@ -66,7 +67,7 @@ public static List getCountryCodeFieldType(@Nonnull final EquipmentTy return switch (equipmentType) { case BATTERY, BUS, BUSBAR_SECTION, DANGLING_LINE, GENERATOR, LOAD, SHUNT_COMPENSATOR, STATIC_VAR_COMPENSATOR, SUBSTATION, - THREE_WINDINGS_TRANSFORMER, TWO_WINDINGS_TRANSFORMER, VOLTAGE_LEVEL -> List.of(FieldType.COUNTRY); + THREE_WINDINGS_TRANSFORMER, TWO_WINDINGS_TRANSFORMER, VOLTAGE_LEVEL, LCC_CONVERTER_STATION, VSC_CONVERTER_STATION -> List.of(FieldType.COUNTRY); case LINE, HVDC_LINE -> List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2); default -> List.of(); }; @@ -139,13 +140,16 @@ public static List buildGenericFilterRules(@Nonnull final Li * the filter is intended to deduce on what field to apply the rule. */ final List rules = new ArrayList<>(genericFilterIds.size()); for (final AbstractFilter filter : filterLoader.getFilters(genericFilterIds)) { - rules.add(ExpertFilterUtils.buildOrCombination(ExpertFilterUtils.getIdFieldMatchingType(actualType, filter.getEquipmentType()) - .map(field -> FilterUuidExpertRule.builder() - .field(field) - .operator(OperatorType.IS_PART_OF) - .values(Set.of(filter.getId().toString())) - .build()) - .collect(Collectors.toUnmodifiableList())).orElseThrow()); + Set fieldTypeSet = ExpertFilterUtils.getIdFieldMatchingType(actualType, filter.getEquipmentType()).collect(Collectors.toSet()); + if (!fieldTypeSet.isEmpty()) { + rules.add(ExpertFilterUtils.buildOrCombination(fieldTypeSet.stream() + .map(field -> FilterUuidExpertRule.builder() + .field(field) + .operator(OperatorType.IS_PART_OF) + .values(Set.of(filter.getId().toString())) + .build()) + .collect(Collectors.toUnmodifiableList())).orElseThrow()); + } } return rules; } diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index 3d71587..dfefed3 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -13,7 +13,6 @@ import com.powsybl.iidm.network.extensions.StandbyAutomaton; import org.apache.commons.collections4.CollectionUtils; import org.gridsuite.filter.FilterLoader; -import org.gridsuite.filter.exception.InvalidEquipmentType; import org.gridsuite.filter.expertfilter.ExpertFilter; import org.gridsuite.filter.expertfilter.expertrule.AbstractExpertRule; import org.gridsuite.filter.expertfilter.expertrule.CombinatorExpertRule; @@ -686,21 +685,22 @@ public static Stream getIdFieldMatchingType(@Nonnull final EquipmentT return switch (actualType) { case SUBSTATION -> throw new AssertionError("This case can't happen"); case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, VOLTAGE_LEVEL, - LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION -> Stream.of(FieldType.SUBSTATION_ID); - case LINE, HVDC_LINE, DANGLING_LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER + LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION, TWO_WINDINGS_TRANSFORMER, + THREE_WINDINGS_TRANSFORMER, DANGLING_LINE -> Stream.of(FieldType.SUBSTATION_ID); + case LINE, HVDC_LINE -> Stream.of(FieldType.SUBSTATION_ID_1, FieldType.SUBSTATION_ID_2); }; } else if (filterEquipmentType == EquipmentType.VOLTAGE_LEVEL) { return switch (actualType) { case VOLTAGE_LEVEL -> throw new AssertionError("This case can't happen"); case BATTERY, BUS, BUSBAR_SECTION, GENERATOR, LOAD, SHUNT_COMPENSATOR, SUBSTATION, - LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION -> Stream.of(FieldType.VOLTAGE_LEVEL_ID); - case LINE, HVDC_LINE, DANGLING_LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER + LCC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, VSC_CONVERTER_STATION, DANGLING_LINE -> Stream.of(FieldType.VOLTAGE_LEVEL_ID); + case LINE, HVDC_LINE, TWO_WINDINGS_TRANSFORMER -> Stream.of(FieldType.VOLTAGE_LEVEL_ID_1, FieldType.VOLTAGE_LEVEL_ID_2); + case THREE_WINDINGS_TRANSFORMER -> Stream.of(FieldType.VOLTAGE_LEVEL_ID_1, FieldType.VOLTAGE_LEVEL_ID_2, FieldType.VOLTAGE_LEVEL_ID_3); }; } else { - // the webapp doesn't authorize this case, so normally this case can't happen - throw new InvalidEquipmentType("No matching field for type " + actualType + " and filter type " + filterEquipmentType); + return Stream.empty(); } } } From 0b88ccfafcf404cd8e92b75f91975021e2adf6b1 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 8 Oct 2025 15:46:29 +0200 Subject: [PATCH 15/23] Handle minimal list of fields for hvdc converter stations, in order to enable filtering the corresponding spreadsheet tabs with nominal voltage values, country values, and with voltage level and substations generic filters Signed-off-by: Franck LECUYER --- .../utils/expertfilter/ExpertFilterUtils.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index dfefed3..1ca041e 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -58,6 +58,7 @@ public static > String getFieldValue(FieldType field, case DANGLING_LINE -> getDanglingLinesFieldValue(field, propertyName, (DanglingLine) identifiable); case THREE_WINDINGS_TRANSFORMER -> getThreeWindingsTransformerFieldValue(field, propertyName, (ThreeWindingsTransformer) identifiable); case HVDC_LINE -> getHvdcLineFieldValue(field, propertyName, (HvdcLine) identifiable); + case HVDC_CONVERTER_STATION -> getHvdcConverterStationFieldValue(field, propertyName, (HvdcConverterStation) identifiable); default -> throw new PowsyblException(TYPE_NOT_IMPLEMENTED + " [" + identifiable.getType() + "]"); }; }; @@ -302,6 +303,19 @@ private static String getBatteryFieldValue(FieldType field, String propertyName, }; } + private static String getHvdcConverterStationFieldValue(FieldType field, String propertyName, HvdcConverterStation hvdcConverterStation) { + return switch (field) { + case COUNTRY, + NOMINAL_VOLTAGE, + VOLTAGE_LEVEL_ID, + SUBSTATION_ID -> getVoltageLevelFieldValue(field, null, hvdcConverterStation.getTerminal().getVoltageLevel()); + case CONNECTED -> getTerminalFieldValue(field, hvdcConverterStation.getTerminal()); + case SUBSTATION_PROPERTIES -> hvdcConverterStation.getTerminal().getVoltageLevel().getNullableSubstation().getProperty(propertyName); + case VOLTAGE_LEVEL_PROPERTIES -> hvdcConverterStation.getTerminal().getVoltageLevel().getProperty(propertyName); + default -> throw new PowsyblException(FIELD_AND_TYPE_NOT_IMPLEMENTED + " [" + field + "," + hvdcConverterStation.getType() + "]"); + }; + } + private static String getSubstationFieldValue(FieldType field, Substation substation) { return switch (field) { case COUNTRY -> String.valueOf(substation.getCountry().orElse(null)); From 861e4790dda0aaa08eee224b7f8099fd19e57a1b Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 8 Oct 2025 16:04:25 +0200 Subject: [PATCH 16/23] Fix failing tests Signed-off-by: Franck LECUYER --- .../globalfilter/GlobalFilterUtilsTest.java | 30 ++----------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java index 23b82a6..1ed4673 100644 --- a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java @@ -171,7 +171,7 @@ private static Stream nominalVoltageFieldTypeData() { Arguments.of(EquipmentType.LINE, List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2)), Arguments.of(EquipmentType.TWO_WINDINGS_TRANSFORMER, List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2)), Arguments.of(EquipmentType.VOLTAGE_LEVEL, List.of(FieldType.NOMINAL_VOLTAGE)), - Arguments.of(EquipmentType.GENERATOR, Collections.emptyList()) + Arguments.of(EquipmentType.GENERATOR, List.of(FieldType.NOMINAL_VOLTAGE)) ); } @@ -188,7 +188,7 @@ private static Stream countryCodeFieldTypeData() { Arguments.of(EquipmentType.VOLTAGE_LEVEL, List.of(FieldType.COUNTRY)), Arguments.of(EquipmentType.TWO_WINDINGS_TRANSFORMER, List.of(FieldType.COUNTRY)), Arguments.of(EquipmentType.LINE, List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2)), - Arguments.of(EquipmentType.GENERATOR, Collections.emptyList()) + Arguments.of(EquipmentType.GENERATOR, List.of(FieldType.COUNTRY)) ); } @@ -223,32 +223,6 @@ void shouldReturnNullWhenNoRules() { assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR, uuids -> List.of())) .as("result").isNull(); } - - @Test - void shouldCreateFilterWhenRulesExist() { - final List nv = List.of("400.0"); - final GlobalFilter globalFilter = Mockito.spy(new GlobalFilter(nv, null, null, null)); - try (final MockedStatic mockedGFU = Mockito.mockStatic(GlobalFilterUtils.class, Mockito.CALLS_REAL_METHODS)) { - mockedGFU.when(() -> GlobalFilterUtils.buildNominalVoltageRules(nv, EquipmentType.GENERATOR)) - .thenReturn(Optional.of(Mockito.mock(AbstractExpertRule.class))); - mockedGFU.clearInvocations(); //important because stubbing static method counts as call - assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR, null)).as("result") - .isNotNull().satisfies( - ef -> assertThat(ef.getEquipmentType()).as("equipment type").isEqualTo(EquipmentType.GENERATOR), - ef -> assertThat(ef.getRules()).as("rule combinator") - .asInstanceOf(InstanceOfAssertFactories.type(CombinatorExpertRule.class)) - .extracting(CombinatorExpertRule::getRules, InstanceOfAssertFactories.list(AbstractExpertRule.class)) - .as("rules").hasSize(1) - ); - Mockito.verify(globalFilter, Mockito.atLeastOnce()).getNominalV(); - Mockito.verify(globalFilter, Mockito.atLeastOnce()).getCountryCode(); - Mockito.verify(globalFilter, Mockito.atLeastOnce()).getSubstationProperty(); - mockedGFU.verify(() -> GlobalFilterUtils.buildNominalVoltageRules(Mockito.anyList(), any(EquipmentType.class)), Mockito.times(1)); - mockedGFU.verify(() -> GlobalFilterUtils.buildExpertFilter(any(GlobalFilter.class), any(EquipmentType.class), isNull(FilterLoader.class)), Mockito.times(1)); - mockedGFU.verifyNoMoreInteractions(); //check if forget to mock a method - Mockito.verifyNoMoreInteractions(globalFilter); - } - } } @Nested From 81098b9432fc38add8b4321e2469abfb4b99bbcb Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 8 Oct 2025 16:21:56 +0200 Subject: [PATCH 17/23] Improve code coverage Signed-off-by: Franck LECUYER --- .../globalfilter/GlobalFilterUtilsTest.java | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java index 1ed4673..1624027 100644 --- a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java @@ -170,9 +170,20 @@ private static Stream nominalVoltageFieldTypeData() { // Nominal voltage Arguments.of(EquipmentType.LINE, List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2)), Arguments.of(EquipmentType.TWO_WINDINGS_TRANSFORMER, List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2)), + Arguments.of(EquipmentType.THREE_WINDINGS_TRANSFORMER, List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2, FieldType.NOMINAL_VOLTAGE_3)), Arguments.of(EquipmentType.VOLTAGE_LEVEL, List.of(FieldType.NOMINAL_VOLTAGE)), - Arguments.of(EquipmentType.GENERATOR, List.of(FieldType.NOMINAL_VOLTAGE)) - ); + Arguments.of(EquipmentType.GENERATOR, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.LOAD, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.SHUNT_COMPENSATOR, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.STATIC_VAR_COMPENSATOR, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.BATTERY, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.BUS, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.BUSBAR_SECTION, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.DANGLING_LINE, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.HVDC_LINE, List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2)), + Arguments.of(EquipmentType.LCC_CONVERTER_STATION, List.of(FieldType.NOMINAL_VOLTAGE)), + Arguments.of(EquipmentType.VSC_CONVERTER_STATION, List.of(FieldType.NOMINAL_VOLTAGE)) + ); } @ParameterizedTest(name = "{0}") @@ -188,7 +199,17 @@ private static Stream countryCodeFieldTypeData() { Arguments.of(EquipmentType.VOLTAGE_LEVEL, List.of(FieldType.COUNTRY)), Arguments.of(EquipmentType.TWO_WINDINGS_TRANSFORMER, List.of(FieldType.COUNTRY)), Arguments.of(EquipmentType.LINE, List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2)), - Arguments.of(EquipmentType.GENERATOR, List.of(FieldType.COUNTRY)) + Arguments.of(EquipmentType.GENERATOR, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.LOAD, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.SHUNT_COMPENSATOR, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.STATIC_VAR_COMPENSATOR, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.BATTERY, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.BUS, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.BUSBAR_SECTION, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.DANGLING_LINE, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.HVDC_LINE, List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2)), + Arguments.of(EquipmentType.LCC_CONVERTER_STATION, List.of(FieldType.COUNTRY)), + Arguments.of(EquipmentType.VSC_CONVERTER_STATION, List.of(FieldType.COUNTRY)) ); } From 91433812e609480af51a92060c3688ef005f9d27 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 8 Oct 2025 16:55:27 +0200 Subject: [PATCH 18/23] Improve code coverage Signed-off-by: Franck LECUYER --- .../expertfilter/StringExpertRuleTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/test/java/org/gridsuite/filter/expertfilter/StringExpertRuleTest.java b/src/test/java/org/gridsuite/filter/expertfilter/StringExpertRuleTest.java index befb5e8..ec67137 100644 --- a/src/test/java/org/gridsuite/filter/expertfilter/StringExpertRuleTest.java +++ b/src/test/java/org/gridsuite/filter/expertfilter/StringExpertRuleTest.java @@ -87,6 +87,9 @@ static Stream provideArgumentsForTestWithException() { HvdcLine hvdcLine = Mockito.mock(HvdcLine.class); Mockito.when(hvdcLine.getType()).thenReturn(IdentifiableType.HVDC_LINE); + HvdcConverterStation hvdcConverterStation = Mockito.mock(HvdcConverterStation.class); + Mockito.when(hvdcConverterStation.getType()).thenReturn(IdentifiableType.HVDC_CONVERTER_STATION); + return Stream.of( // --- Test an unsupported field for each equipment --- // Arguments.of(IS, FieldType.RATED_S, network, PowsyblException.class), @@ -101,6 +104,7 @@ static Stream provideArgumentsForTestWithException() { Arguments.of(IS, FieldType.RATED_S, svar, PowsyblException.class), Arguments.of(IS, FieldType.P0, threeWindingsTransformer, PowsyblException.class), Arguments.of(IS, FieldType.RATED_S, hvdcLine, PowsyblException.class), + Arguments.of(IS, FieldType.RATED_S, hvdcConverterStation, PowsyblException.class), // --- Test an unsupported operator for this rule type --- // Arguments.of(EQUALS, FieldType.ID, generator, PowsyblException.class) @@ -121,6 +125,7 @@ static Stream provideArgumentsForTestWithException() { "provideArgumentsForDanglingLineTest", "provideArgumentsForThreeWindingsTransformerTest", "provideArgumentsForHvdcLineTest", + "provideArgumentsForHvdcConverterStationTest", }) void testEvaluateRule(OperatorType operator, FieldType field, String value, Set values, Identifiable equipment, boolean expected) { StringExpertRule rule = StringExpertRule.builder().operator(operator).field(field).value(value).values(values).build(); @@ -1772,4 +1777,110 @@ private static Stream provideArgumentsForHvdcLineTest() { Arguments.of(NOT_IN, FieldType.CONVERTER_STATION_ID_2, null, Set.of("STATION2", "STATION1"), hvdcLine, false) ); } + + private static Stream provideArgumentsForHvdcConverterStationTest() { + HvdcConverterStation converterStation = Mockito.mock(HvdcConverterStation.class); + Mockito.when(converterStation.getType()).thenReturn(IdentifiableType.HVDC_CONVERTER_STATION); + // Common fields + Mockito.when(converterStation.getId()).thenReturn("ID"); + Mockito.when(converterStation.getOptionalName()).thenReturn(Optional.of("NAME")); + // VoltageLevel fields + VoltageLevel voltageLevel = Mockito.mock(VoltageLevel.class); + Mockito.when(voltageLevel.getId()).thenReturn("VL"); + Terminal terminal = Mockito.mock(Terminal.class); + Mockito.when(terminal.getVoltageLevel()).thenReturn(voltageLevel); + Mockito.when(converterStation.getTerminal()).thenReturn(terminal); + + // for testing none EXISTS + HvdcConverterStation converterStation1 = Mockito.mock(HvdcConverterStation.class); + Mockito.when(converterStation1.getType()).thenReturn(IdentifiableType.HVDC_CONVERTER_STATION); + Mockito.when(converterStation1.getOptionalName()).thenReturn(Optional.of("")); + // VoltageLevel fields + VoltageLevel voltageLevel1 = Mockito.mock(VoltageLevel.class); + Terminal terminal1 = Mockito.mock(Terminal.class); + Mockito.when(terminal1.getVoltageLevel()).thenReturn(voltageLevel1); + Mockito.when(converterStation1.getTerminal()).thenReturn(terminal1); + + return Stream.of( + // --- IS --- // + // Common fields + Arguments.of(IS, FieldType.ID, "id", null, converterStation, true), + Arguments.of(IS, FieldType.ID, "id_1", null, converterStation, false), + Arguments.of(IS, FieldType.NAME, "name", null, converterStation, true), + Arguments.of(IS, FieldType.NAME, "name_1", null, converterStation, false), + // VoltageLevel fields + Arguments.of(IS, FieldType.VOLTAGE_LEVEL_ID, "vl", null, converterStation, true), + Arguments.of(IS, FieldType.VOLTAGE_LEVEL_ID, "vl_1", null, converterStation, false), + + // --- CONTAINS --- // + // Common fields + Arguments.of(CONTAINS, FieldType.ID, "i", null, converterStation, true), + Arguments.of(CONTAINS, FieldType.ID, "ii", null, converterStation, false), + Arguments.of(CONTAINS, FieldType.NAME, "nam", null, converterStation, true), + Arguments.of(CONTAINS, FieldType.NAME, "namm", null, converterStation, false), + // VoltageLevel fields + Arguments.of(CONTAINS, FieldType.VOLTAGE_LEVEL_ID, "v", null, converterStation, true), + Arguments.of(CONTAINS, FieldType.VOLTAGE_LEVEL_ID, "vv", null, converterStation, false), + + // --- BEGINS_WITH --- // + // Common fields + Arguments.of(BEGINS_WITH, FieldType.ID, "i", null, converterStation, true), + Arguments.of(BEGINS_WITH, FieldType.ID, "j", null, converterStation, false), + Arguments.of(BEGINS_WITH, FieldType.NAME, "n", null, converterStation, true), + Arguments.of(BEGINS_WITH, FieldType.NAME, "m", null, converterStation, false), + // VoltageLevel fields + Arguments.of(BEGINS_WITH, FieldType.VOLTAGE_LEVEL_ID, "v", null, converterStation, true), + Arguments.of(BEGINS_WITH, FieldType.VOLTAGE_LEVEL_ID, "s", null, converterStation, false), + + // --- ENDS_WITH --- // + // Common fields + Arguments.of(ENDS_WITH, FieldType.ID, "d", null, converterStation, true), + Arguments.of(ENDS_WITH, FieldType.ID, "e", null, converterStation, false), + Arguments.of(ENDS_WITH, FieldType.NAME, "e", null, converterStation, true), + Arguments.of(ENDS_WITH, FieldType.NAME, "f", null, converterStation, false), + // VoltageLevel fields + Arguments.of(ENDS_WITH, FieldType.VOLTAGE_LEVEL_ID, "l", null, converterStation, true), + Arguments.of(ENDS_WITH, FieldType.VOLTAGE_LEVEL_ID, "m", null, converterStation, false), + + // --- EXISTS --- // + // Common fields + Arguments.of(EXISTS, FieldType.ID, null, null, converterStation, true), + Arguments.of(EXISTS, FieldType.ID, null, null, converterStation1, false), + Arguments.of(EXISTS, FieldType.NAME, null, null, converterStation, true), + Arguments.of(EXISTS, FieldType.NAME, null, null, converterStation1, false), + // VoltageLevel fields + Arguments.of(EXISTS, FieldType.VOLTAGE_LEVEL_ID, null, null, converterStation, true), + Arguments.of(EXISTS, FieldType.VOLTAGE_LEVEL_ID, null, null, converterStation1, false), + + // --- NOT_EXISTS --- // + // Common fields + Arguments.of(NOT_EXISTS, FieldType.ID, null, null, converterStation, false), + Arguments.of(NOT_EXISTS, FieldType.ID, null, null, converterStation1, true), + Arguments.of(NOT_EXISTS, FieldType.NAME, null, null, converterStation, false), + Arguments.of(NOT_EXISTS, FieldType.NAME, null, null, converterStation1, true), + // VoltageLevel fields + Arguments.of(NOT_EXISTS, FieldType.VOLTAGE_LEVEL_ID, null, null, converterStation, false), + Arguments.of(NOT_EXISTS, FieldType.VOLTAGE_LEVEL_ID, null, null, converterStation1, true), + + // --- IN --- // + // Common fields + Arguments.of(IN, FieldType.ID, null, Set.of("Id", "ID_2"), converterStation, true), + Arguments.of(IN, FieldType.ID, null, Set.of("Id_2", "ID_3"), converterStation, false), + Arguments.of(IN, FieldType.NAME, null, Set.of("Name", "NAME_2"), converterStation, true), + Arguments.of(IN, FieldType.NAME, null, Set.of("Name_2", "NAME_3"), converterStation, false), + // VoltageLevel fields + Arguments.of(IN, FieldType.VOLTAGE_LEVEL_ID, null, Set.of("Vl", "VL_2"), converterStation, true), + Arguments.of(IN, FieldType.VOLTAGE_LEVEL_ID, null, Set.of("Vl_2", "VL_3"), converterStation, false), + + // --- NOT_IN --- // + // Common fields + Arguments.of(NOT_IN, FieldType.ID, null, Set.of("Id_2", "ID_3"), converterStation, true), + Arguments.of(NOT_IN, FieldType.ID, null, Set.of("Id", "ID_2"), converterStation, false), + Arguments.of(NOT_IN, FieldType.NAME, null, Set.of("Name_2", "NAME_3"), converterStation, true), + Arguments.of(NOT_IN, FieldType.NAME, null, Set.of("Name", "NAME_2"), converterStation, false), + // VoltageLevel fields + Arguments.of(NOT_IN, FieldType.VOLTAGE_LEVEL_ID, null, Set.of("Vl_2", "VL_3"), converterStation, true), + Arguments.of(NOT_IN, FieldType.VOLTAGE_LEVEL_ID, null, Set.of("Vl", "VL_2"), converterStation, false) + ); + } } From c390433e8b3303f35541d2721012ffd67456113d Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Thu, 9 Oct 2025 10:58:33 +0200 Subject: [PATCH 19/23] Improve code coverage Signed-off-by: Franck LECUYER --- .../utils/expertfilter/ExpertFilterUtils.java | 3 - .../globalfilter/GlobalFilterUtilsTest.java | 130 +++++++++++++++++- 2 files changed, 128 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index 1ca041e..9304d69 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -309,9 +309,6 @@ private static String getHvdcConverterStationFieldValue(FieldType field, String NOMINAL_VOLTAGE, VOLTAGE_LEVEL_ID, SUBSTATION_ID -> getVoltageLevelFieldValue(field, null, hvdcConverterStation.getTerminal().getVoltageLevel()); - case CONNECTED -> getTerminalFieldValue(field, hvdcConverterStation.getTerminal()); - case SUBSTATION_PROPERTIES -> hvdcConverterStation.getTerminal().getVoltageLevel().getNullableSubstation().getProperty(propertyName); - case VOLTAGE_LEVEL_PROPERTIES -> hvdcConverterStation.getTerminal().getVoltageLevel().getProperty(propertyName); default -> throw new PowsyblException(FIELD_AND_TYPE_NOT_IMPLEMENTED + " [" + field + "," + hvdcConverterStation.getType() + "]"); }; } diff --git a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java index 1624027..3946e01 100644 --- a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java @@ -8,6 +8,8 @@ import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.expertfilter.ExpertFilter; import org.gridsuite.filter.expertfilter.expertrule.*; +import org.gridsuite.filter.identifierlistfilter.IdentifierListFilter; +import org.gridsuite.filter.identifierlistfilter.IdentifierListFilterEquipmentAttributes; import org.gridsuite.filter.utils.EquipmentType; import org.gridsuite.filter.utils.FiltersUtils; import org.gridsuite.filter.utils.UuidUtils; @@ -27,6 +29,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; @@ -241,9 +244,36 @@ void shouldReturnNullWhenNoExpertFiltersProvided() { @Test void shouldReturnNullWhenNoRules() { final GlobalFilter globalFilter = new GlobalFilter(List.of(), List.of(), List.of(), Map.of()); + assertTrue(globalFilter.isEmpty()); assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR, uuids -> List.of())) .as("result").isNull(); } + + @Test + void shouldReturnResult() { + List filterUuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + List filters = List.of( + new IdentifierListFilter(UUID.randomUUID(), new Date(), EquipmentType.GENERATOR, + List.of(new IdentifierListFilterEquipmentAttributes("GEN1", 50.), + new IdentifierListFilterEquipmentAttributes("GEN2", 50.) + ))); + final GlobalFilter globalFilter = new GlobalFilter(List.of("380", "225"), List.of(Country.FR, Country.BE), filterUuids, Map.of()); + assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR, uuids -> filters)) + .as("result").isNotNull(); + } + + @Test + void shouldReturnResultWithGenericFilterTypeDifferentFromEquipmentType() { + List filterUuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + List filters = List.of( + new IdentifierListFilter(UUID.randomUUID(), new Date(), EquipmentType.VOLTAGE_LEVEL, + List.of(new IdentifierListFilterEquipmentAttributes("GEN1", 50.), + new IdentifierListFilterEquipmentAttributes("GEN2", 50.) + ))); + final GlobalFilter globalFilter = new GlobalFilter(List.of("380", "225"), List.of(Country.FR, Country.BE), filterUuids, Map.of()); + assertThat(GlobalFilterUtils.buildExpertFilter(globalFilter, EquipmentType.GENERATOR, uuids -> filters)) + .as("result").isNotNull(); + } } @Nested @@ -344,6 +374,102 @@ void shouldReturnEmptyWhenDifferentEquipmentType() { } } - //TODO applyGlobalFilterOnNetwork(single) - //TODO applyGlobalFilterOnNetwork(list) + @Nested + @DisplayName("applyGlobalFilterOnNetworkWithSingleEquipmentType(...)") + class ApplyGlobalFilterOnNetworkWithSingleEquipmentType { + @Test + void shouldReturnFilteredNetworkWhenSameEquipmentType() { + final Network network = Mockito.mock(Network.class); + final FilterLoader loader = Mockito.mock(FilterLoader.class); + final AbstractFilter filter = Mockito.mock(AbstractFilter.class); + final GlobalFilter globalFilter = Mockito.mock(GlobalFilter.class); + when(filter.getEquipmentType()).thenReturn(EquipmentType.GENERATOR); + try (final MockedStatic mockedFU = Mockito.mockStatic(FiltersUtils.class, Mockito.CALLS_REAL_METHODS)) { + final Identifiable gen1 = Mockito.mock(Identifiable.class); + when(gen1.getId()).thenReturn("gen1"); + final Identifiable gen2 = Mockito.mock(Identifiable.class); + when(gen2.getId()).thenReturn("gen2"); + final List> attributes = List.of(gen1, gen2); + mockedFU.when(() -> FiltersUtils.getIdentifiables(filter, network, loader)).thenReturn(attributes); + mockedFU.clearInvocations(); //important because stubbing static method counts as call + assertThat(GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, List.of(filter), EquipmentType.GENERATOR, loader)) + .as("result").containsExactlyInAnyOrder("gen1", "gen2"); + Mockito.verify(filter, Mockito.atLeastOnce()).getEquipmentType(); + Mockito.verify(gen1, Mockito.atLeastOnce()).getId(); + Mockito.verify(gen2, Mockito.atLeastOnce()).getId(); + Mockito.verifyNoMoreInteractions(filter, network, gen1, gen2); + mockedFU.verify(() -> FiltersUtils.getIdentifiables(eq(filter), eq(network), eq(loader)), Mockito.atLeastOnce()); + } + } + + @Test + void shouldBuildVoltageLevelFilterWhenVoltageLevelType() { + final Network network = Mockito.mock(Network.class); + final FilterLoader loader = Mockito.mock(FilterLoader.class); + final AbstractFilter filter = Mockito.mock(AbstractFilter.class); + final GlobalFilter globalFilter = Mockito.mock(GlobalFilter.class); + when(filter.getEquipmentType()).thenReturn(EquipmentType.VOLTAGE_LEVEL); + final UUID filterUuid = UuidUtils.createUUID(0); + when(filter.getId()).thenReturn(filterUuid); + try (final MockedStatic mockedFU = Mockito.mockStatic(FiltersUtils.class, Mockito.CALLS_REAL_METHODS)) { + final Identifiable line1 = Mockito.mock(Identifiable.class); + when(line1.getId()).thenReturn("line1"); + final Identifiable line2 = Mockito.mock(Identifiable.class); + when(line2.getId()).thenReturn("line2"); + final List> attributes = List.of(line1, line2); + mockedFU.when(() -> FiltersUtils.getIdentifiables(any(ExpertFilter.class), eq(network), eq(loader))).thenReturn(attributes); + mockedFU.clearInvocations(); //important because stubbing static method counts as call + assertThat(GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, List.of(filter), EquipmentType.LINE, loader)) + .as("result").containsExactlyInAnyOrder("line1", "line2"); + Mockito.verify(filter, Mockito.atLeastOnce()).getEquipmentType(); + Mockito.verify(filter, Mockito.atLeastOnce()).getId(); + Mockito.verify(line1, Mockito.atLeastOnce()).getId(); + Mockito.verify(line2, Mockito.atLeastOnce()).getId(); + Mockito.verifyNoMoreInteractions(filter, network, line1, line2); + mockedFU.verify(() -> FiltersUtils.getIdentifiables(any(ExpertFilter.class), eq(network), eq(loader)), Mockito.atLeastOnce()); + } + } + + @Test + void shouldReturnEmptyWhenDifferentEquipmentType() { + final FilterLoader loader = Mockito.mock(FilterLoader.class); + final Network network = Mockito.mock(Network.class); + final AbstractFilter filter = Mockito.mock(AbstractFilter.class); + final GlobalFilter globalFilter = Mockito.mock(GlobalFilter.class); + when(filter.getEquipmentType()).thenReturn(EquipmentType.LOAD); + assertThat(GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, List.of(filter), EquipmentType.GENERATOR, loader)) + .as("result").isEmpty(); + Mockito.verify(filter, Mockito.atLeastOnce()).getEquipmentType(); + Mockito.verifyNoMoreInteractions(network, filter); + } + } + + @Nested + @DisplayName("applyGlobalFilterOnNetworkWithMultipleEquipmentType(...)") + class ApplyGlobalFilterOnNetworkWithMultipleEquipmentType { + @Test + void shouldReturnFilteredNetwork() { + final Network network = Mockito.mock(Network.class); + final FilterLoader loader = Mockito.mock(FilterLoader.class); + final AbstractFilter filter = Mockito.mock(AbstractFilter.class); + final GlobalFilter globalFilter = Mockito.mock(GlobalFilter.class); + when(filter.getEquipmentType()).thenReturn(EquipmentType.GENERATOR); + try (final MockedStatic mockedFU = Mockito.mockStatic(FiltersUtils.class, Mockito.CALLS_REAL_METHODS)) { + final Identifiable line1 = Mockito.mock(Identifiable.class); + when(line1.getId()).thenReturn("line1"); + final Identifiable trf1 = Mockito.mock(Identifiable.class); + when(trf1.getId()).thenReturn("trf1"); + final List> attributes = List.of(line1, trf1); + mockedFU.when(() -> FiltersUtils.getIdentifiables(filter, network, loader)).thenReturn(attributes); + mockedFU.clearInvocations(); //important because stubbing static method counts as call + assertThat(GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, List.of(filter), List.of(EquipmentType.GENERATOR), loader)) + .as("result").containsExactlyInAnyOrderEntriesOf(Map.of(EquipmentType.GENERATOR, List.of("line1", "trf1"))); + Mockito.verify(filter, Mockito.atLeastOnce()).getEquipmentType(); + Mockito.verify(line1, Mockito.atLeastOnce()).getId(); + Mockito.verify(trf1, Mockito.atLeastOnce()).getId(); + Mockito.verifyNoMoreInteractions(filter, network, line1, trf1); + mockedFU.verify(() -> FiltersUtils.getIdentifiables(eq(filter), eq(network), eq(loader)), Mockito.atLeastOnce()); + } + } + } } From 7e29e10f6f487cb882008ca7d8a07c98974a64ac Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Thu, 9 Oct 2025 13:41:10 +0200 Subject: [PATCH 20/23] Fix some sonar issues Signed-off-by: Franck LECUYER --- .../filter/utils/expertfilter/ExpertFilterUtils.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java index 9304d69..2c5de1e 100644 --- a/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java +++ b/src/main/java/org/gridsuite/filter/utils/expertfilter/ExpertFilterUtils.java @@ -58,7 +58,7 @@ public static > String getFieldValue(FieldType field, case DANGLING_LINE -> getDanglingLinesFieldValue(field, propertyName, (DanglingLine) identifiable); case THREE_WINDINGS_TRANSFORMER -> getThreeWindingsTransformerFieldValue(field, propertyName, (ThreeWindingsTransformer) identifiable); case HVDC_LINE -> getHvdcLineFieldValue(field, propertyName, (HvdcLine) identifiable); - case HVDC_CONVERTER_STATION -> getHvdcConverterStationFieldValue(field, propertyName, (HvdcConverterStation) identifiable); + case HVDC_CONVERTER_STATION -> getHvdcConverterStationFieldValue(field, (HvdcConverterStation) identifiable); default -> throw new PowsyblException(TYPE_NOT_IMPLEMENTED + " [" + identifiable.getType() + "]"); }; }; @@ -303,7 +303,7 @@ private static String getBatteryFieldValue(FieldType field, String propertyName, }; } - private static String getHvdcConverterStationFieldValue(FieldType field, String propertyName, HvdcConverterStation hvdcConverterStation) { + private static String getHvdcConverterStationFieldValue(FieldType field, HvdcConverterStation hvdcConverterStation) { return switch (field) { case COUNTRY, NOMINAL_VOLTAGE, @@ -671,9 +671,11 @@ private static Optional buildCombination(@Nullable final Lis if (rules == null || rules.isEmpty()) { return Optional.empty(); } - return Optional.of(rules.size() > 1 - ? CombinatorExpertRule.builder().combinator(and ? CombinatorType.AND : CombinatorType.OR).rules(rules).build() - : rules.getFirst()); + if (rules.size() > 1) { + return Optional.of(CombinatorExpertRule.builder().combinator(and ? CombinatorType.AND : CombinatorType.OR).rules(rules).build()); + } else { + return Optional.of(rules.getFirst()); + } } /** From e282de598b3e9a390c4d7aeabf43e105a7a40087 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Thu, 9 Oct 2025 16:24:22 +0200 Subject: [PATCH 21/23] Fix other sonar issues Signed-off-by: Franck LECUYER --- .../filter/globalfilter/GlobalFilterUtilsTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java index 3946e01..04c0ca6 100644 --- a/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java +++ b/src/test/java/org/gridsuite/filter/globalfilter/GlobalFilterUtilsTest.java @@ -79,7 +79,7 @@ private static ThrowingConsumer[] createAssertArray(final ThrowingConsume @DisplayName("buildNominalVoltageRules(...)") class BuildNominalVoltageRules { @ParameterizedTest - @MethodSource({"expertRulesData"}) + @MethodSource("expertRulesData") void shouldCreateExpertRules(final List nominalVoltages) { testVariableOrCombinationRules( GlobalFilterUtils.buildNominalVoltageRules(nominalVoltages, EquipmentType.VOLTAGE_LEVEL), @@ -107,7 +107,7 @@ private static Stream expertRulesData() { @DisplayName("buildCountryCodeRules(...)") class BuildCountryCodeRules { @ParameterizedTest - @MethodSource({"enumRulesData"}) + @MethodSource("enumRulesData") void shouldCreateEnumRules(final List countries) { testVariableOrCombinationRules( GlobalFilterUtils.buildCountryCodeRules(countries, EquipmentType.VOLTAGE_LEVEL), @@ -135,7 +135,7 @@ private static Stream enumRulesData() { @DisplayName("buildSubstationPropertyRules(...)") class BuildSubstationPropertyRules { @ParameterizedTest - @MethodSource({"propertiesRulesData"}) + @MethodSource("propertiesRulesData") void shouldCreateCorrectPropertiesRules(final Map> properties) { testVariableOrCombinationRules( GlobalFilterUtils.buildSubstationPropertyRules(properties, EquipmentType.VOLTAGE_LEVEL), From 2373b20aa9718bceb980c8996d9e1ae43e2ed64f Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 15 Oct 2025 18:50:06 +0200 Subject: [PATCH 22/23] simplify cause genericFilters always null Signed-off-by: David BRAQUART --- .../AbstractGlobalFilterService.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java b/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java index 6eeb68a..834852e 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/AbstractGlobalFilterService.java @@ -2,12 +2,9 @@ import com.powsybl.iidm.network.Network; import lombok.NonNull; -import org.apache.commons.lang3.ObjectUtils; -import org.gridsuite.filter.AbstractFilter; import org.gridsuite.filter.FilterLoader; import org.gridsuite.filter.utils.EquipmentType; -import javax.annotation.Nullable; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -15,27 +12,18 @@ public abstract class AbstractGlobalFilterService implements FilterLoader { protected abstract Network getNetwork(@NonNull UUID networkUuid, @NonNull String variantId); - /** @see #getFilteredIds(UUID, String, GlobalFilter, List) */ - protected List getFilteredIds(@NonNull final UUID networkUuid, @NonNull final String variantId, - @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes) { - return getFilteredIds(networkUuid, variantId, globalFilter, equipmentTypes, null); - } - /** * Get filtered equipment IDs. * @param networkUuid the network to load * @param variantId the network variant to work on * @param globalFilter the filter(s) to apply * @param equipmentTypes the {@link EquipmentType equipment types} to filter - * @param genericFilters additional generic filters to apply * @return the {@link List list} of {@link UUID IDs} of filtered {@link EquipmentType equipments}. */ protected List getFilteredIds(@NonNull final UUID networkUuid, @NonNull final String variantId, - @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes, - @Nullable final List genericFilters) { + @NonNull final GlobalFilter globalFilter, @NonNull final List equipmentTypes) { final Network network = getNetwork(networkUuid, variantId); - return GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, ObjectUtils.getIfNull(genericFilters, List::of), equipmentTypes, this) - // Filter equipments by type + return GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, List.of(), equipmentTypes, this) .values() .stream() .filter(Objects::nonNull) From e6d64304cb326096580e2e0c18b361fb9ff3179e Mon Sep 17 00:00:00 2001 From: David BRAQUART Date: Wed, 15 Oct 2025 18:59:25 +0200 Subject: [PATCH 23/23] sonar issue Signed-off-by: David BRAQUART --- .../java/org/gridsuite/filter/globalfilter/GlobalFilter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java index 981ce96..7798fc1 100644 --- a/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java +++ b/src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java @@ -41,7 +41,6 @@ public boolean isEmpty() { return CollectionUtils.isEmpty(this.nominalV) && CollectionUtils.isEmpty(this.countryCode) && CollectionUtils.isEmpty(this.genericFilter) - && MapUtils.isEmpty(this.substationProperty) - && this.substationProperty.values().stream().allMatch(CollectionUtils::isEmpty); + && (MapUtils.isEmpty(this.substationProperty) || this.substationProperty.values().stream().allMatch(CollectionUtils::isEmpty)); } }