From 9b859d6fcc6e78e77657b2611efd98f033145758 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Thu, 25 Sep 2025 08:24:13 +0200 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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(); } }